Index: /branches/wsgi/COPYING
===================================================================
--- /branches/wsgi/COPYING	(revision 571)
+++ /branches/wsgi/COPYING	(revision 571)
@@ -0,0 +1,353 @@
+This software is SIPB Virtual Servers, copyright 2007-2008 its contributors.
+
+SIPB Virtual Servers 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 2 of the
+License, or (at your option) any later version.
+
+SIPB Virtual Servers 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.
+
+-----
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
Index: /branches/wsgi/packages/sipb-xen-base/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/debian/changelog	(revision 571)
@@ -0,0 +1,49 @@
+sipb-xen-base (8) unstable; urgency=low
+
+  * update .k5login to match black-mesa
+
+ -- Greg Price <price@mit.edu>  Sun,  4 May 2008 20:28:28 -0400
+
+sipb-xen-base (7) unstable; urgency=low
+
+  * include backports.org
+  * sources.list.d doesn't actually need a .sources.list, just .list
+
+ -- Greg Price <price@mit.edu>  Thu,  1 May 2008 19:45:50 -0400
+
+sipb-xen-base (6) unstable; urgency=low
+
+  * actually use sources.list.d correctly
+
+ -- Greg Price <price@mit.edu>  Sat, 26 Apr 2008 21:22:12 -0400
+
+sipb-xen-base (5) unstable; urgency=low
+
+  * update sources.list, use sources.list.d
+
+ -- Greg Price <price@mit.edu>  Sat, 26 Apr 2008 21:06:05 -0400
+
+sipb-xen-base (4) unstable; urgency=low
+
+  * sipb-vm-1 becomes sipb-xen-dev
+
+ -- Sam Hartman <hartmans@debian.org>  Tue,  4 Sep 2007 15:48:50 -0400
+
+sipb-xen-base (3) unstable; urgency=low
+
+  * We want security updates too
+
+ -- Sam Hartman <hartmans@debian.org>  Fri, 10 Aug 2007 20:39:14 -0400
+
+sipb-xen-base (2) unstable; urgency=low
+
+  * Update sources.list to include our debian mirror
+
+ -- Sam Hartman <hartmans@debian.org>  Sat,  4 Aug 2007 19:11:18 -0400
+
+sipb-xen-base (1) unstable; urgency=low
+
+  * New upstream version
+
+ -- Sam Hartman <hartmans@debian.org>  Sat,  4 Aug 2007 18:44:21 -0400
+
Index: /branches/wsgi/packages/sipb-xen-base/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-base/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/debian/control	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-base
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-base
+Architecture: all
+Depends: ${misc:Depends}, 
+Description: Base configuration required for all SIPB xen servers
+ This package includes apt configuration, .k5login and other files that
+ should be synchronized among all our servers.
+ Installing this on a non-sipb-xen machine would be very anti-social.
Index: /branches/wsgi/packages/sipb-xen-base/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask tabbott@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-base/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/debian/rules	(revision 571)
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+
+include /usr/share/cdbs/1/rules/debhelper.mk
Index: /branches/wsgi/packages/sipb-xen-base/debian/sipb-xen-base.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/debian/sipb-xen-base.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/debian/sipb-xen-base.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-base/files/etc/apt/sources.list.d/sipb-xen.list
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/files/etc/apt/sources.list.d/sipb-xen.list	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/files/etc/apt/sources.list.d/sipb-xen.list	(revision 571)
@@ -0,0 +1,4 @@
+deb     http://sipb-xen-dev.mit.edu/sipb-xen stable main
+deb-src http://sipb-xen-dev.mit.edu/sipb-xen stable main
+deb     http://www.backports.org/debian etch-backports main
+deb-src http://www.backports.org/debian etch-backports main
Index: /branches/wsgi/packages/sipb-xen-base/files/root/.k5login
===================================================================
--- /branches/wsgi/packages/sipb-xen-base/files/root/.k5login	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-base/files/root/.k5login	(revision 571)
@@ -0,0 +1,20 @@
+hartmans@ATHENA.MIT.EDU
+hartmans/root@ATHENA.MIT.EDU
+jbarnold@ATHENA.MIT.EDU
+jbarnold/root@ATHENA.MIT.EDU
+andersk@ATHENA.MIT.EDU
+andersk/root@ATHENA.MIT.EDU
+nelhage@ATHENA.MIT.EDU
+nelhage/root@ATHENA.MIT.EDU
+tabbott@ATHENA.MIT.EDU
+tabbott/root@ATHENA.MIT.EDU
+quentin/root@ATHENA.MIT.EDU
+ecprice@ATHENA.MIT.EDU
+ecprice/root@ATHENA.MIT.EDU
+price@ATHENA.MIT.EDU
+price/root@ATHENA.MIT.EDU
+broder/root@ATHENA.MIT.EDU
+neboat@ATHENA.MIT.EDU
+geofft/root@ATHENA.MIT.EDU
+zev/root@ATHENA.MIT.EDU
+ccpost/root@ATHENA.MIT.EDU
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/changelog	(revision 571)
@@ -0,0 +1,12 @@
+sipb-xen-chrony-config (1.2) unstable; urgency=low
+
+  * Divert /etc/chrony/chrony.conf, not /etc/chrony.conf.
+  * Switch to current config-packages-dev API.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Sun, 01 Jun 2008 16:48:32 -0400
+
+sipb-xen-chrony-config (1) unstable; urgency=low
+
+  * Initial release.
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 06:23:10 -0400
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/control	(revision 571)
@@ -0,0 +1,15 @@
+Source: sipb-xen-chrony-config
+Section: misc
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 5), config-package-dev (>= 4.5~)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-chrony-config
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, chrony
+Description: Chrony configuration package for sipb-xen VMs
+ This package should be installed on sipb-xen VMs to maintain
+ an accurate clock
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/control.in	(revision 571)
@@ -0,0 +1,15 @@
+Source: sipb-xen-chrony-config
+Section: misc
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-chrony-config
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, chrony
+Description: Chrony configuration package for sipb-xen VMs
+ This package should be installed on sipb-xen VMs to maintain
+ an accurate clock
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/rules	(revision 571)
@@ -0,0 +1,9 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+DEB_DIVERT_EXTENSION = .sipb-xen
+DEB_DIVERT_FILES_sipb-xen-chrony-config += \
+	/etc/chrony/chrony.conf
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/divert.mk
Index: /branches/wsgi/packages/sipb-xen-chrony-config/debian/sipb-xen-chrony-config.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/debian/sipb-xen-chrony-config.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/debian/sipb-xen-chrony-config.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-chrony-config/files/etc/chrony/chrony.conf.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-chrony-config/files/etc/chrony/chrony.conf.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-chrony-config/files/etc/chrony/chrony.conf.sipb-xen	(revision 571)
@@ -0,0 +1,87 @@
+# This the default chrony.conf file for the Debian chrony package.  It is
+# suitable for a system with an intermittent dial-up connection.  After
+# editing this file use the command 'invoke-rc.d chrony restart' to make
+# your changes take effect.  
+# John Hasler <jhasler@debian.org> 3 Dec. 1998
+
+# See www.pool.ntp.org for an explanation of these servers.  Please
+# consider joining the project if possible.  If you can't or don't want to
+# use these servers I suggest that you try your ISP's nameservers.  We mark
+# the servers 'offline' so that chronyd won't try to connect when the link
+# is down.  Scripts in /etc/ppp/ip-up.d and /etc/ppp/ip-down.d use chronyc
+# commands to switch it on when the link comes up and off when it goes
+# down.  If you have an always-on connection such as cable omit the
+# 'offline' directive and chronyd will default to online.
+
+server time.mit.edu
+server tick.mit.edu
+
+# Look here for the admin password needed for chronyc.  The initial
+# password is generated by a random process at install time.  You may
+# change it if you wish.
+
+keyfile /etc/chrony/chrony.keys
+
+# Set runtime command key.  Note that if you change the key (not the
+# password) to anything other than 1 you will need to edit
+# /etc/ppp/ip-up.d/chrony, /etc/ppp/ip-down.d/chrony, and
+# /etc/cron.weekly/chrony as these scripts use it to get the password.
+
+commandkey 1
+
+# I moved the driftfile to /var/lib/chrony to comply with the Debian
+# filesystem standard.
+
+driftfile /var/lib/chrony/chrony.drift
+
+# Comment this line out to turn off logging.
+
+log tracking measurements statistics
+logdir /var/log/chrony
+
+# Stop bad estimates upsetting machine clock.
+
+maxupdateskew 100.0
+
+# Dump measurements when daemon exits.
+
+dumponexit
+
+# Specify directory for dumping measurements.
+
+dumpdir /var/lib/chrony
+
+# Let computer be a server when it is unsynchronised.
+
+local stratum 10
+
+# Allow computers on the unrouted nets 10 and 192.168 to use the server.
+
+allow 10/8
+allow 192.168/16
+allow 172.16/12
+
+# This directive forces `chronyd' to send a message to syslog if it
+# makes a system clock adjustment larger than a threshold value in seconds.
+
+logchange 0.5
+
+# This directive defines an email address to which mail should be sent
+# if chronyd applies a correction exceeding a particular threshold to the
+# system clock.
+
+# mailonchange root@localhost 0.5
+
+# This directive tells chrony to regulate the real-time clock and tells it
+# Where to store related data.  It may not work on some newer motherboards
+# that use the HPET real-time clock.  It requires enhanced real-time
+# support in the kernel.
+
+rtcfile /var/lib/chrony/chrony.rtc
+
+# If the last line of this file reads 'rtconutc' chrony will assume that
+# the CMOS clock is on UTC (GMT).  If it reads '# rtconutc' or is absent
+# chrony will assume local time.  The line (if any) was written by the
+# chrony postinst based on what it found in /etc/default/rcS.  You may
+# change it if necessary. 
+rtconutc
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/changelog	(revision 571)
@@ -0,0 +1,28 @@
+sipb-xen-clvm-config (2.0) unstable; urgency=low
+
+  * Depend on multipath-tools because we're going to want it
+  * Fix multipath-tools' udev script because it's horribly broken
+
+ -- Evan Broder <broder@mit.edu>  Mon,  5 May 2008 03:35:00 -0400
+
+sipb-xen-clvm-config (1.2) unstable; urgency=low
+
+  * I remain an idiot, unfortunately, and didn't notice this last time
+
+ -- Evan Broder <broder@mit.edu>  Sun, 27 Apr 2008 18:20:06 -0400
+
+sipb-xen-clvm-config (1.1) unstable; urgency=low
+
+  [ Evan Broder ]
+  * And...I'm an idiot - but it's fixed now
+
+  [ Quentin Smith ]
+  * I'm an idiot too. Make LVM actually use clvm.
+
+ -- Quentin Smith <quentin@mit.edu>  Sun, 27 Apr 2008 18:02:28 -0400
+
+sipb-xen-clvm-config (1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Evan Broder <broder@mit.edu>  Sun, 27 Apr 2008 12:55:34 -0400
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/control	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-clvm-config
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 5), config-package-dev (>= 4.5~)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-clvm-config
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, clvm, cman, ccs, multipath-tools
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Description: Configure clustering LVM
+ Configure the dom0 servers for clustering LVM
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/control.in	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-clvm-config
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-clvm-config
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, clvm, cman, ccs, multipath-tools
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Description: Configure clustering LVM
+ Configure the dom0 servers for clustering LVM
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/init
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/init	(revision 571)
@@ -0,0 +1,57 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-clvm-config
+# Required-Start:    $ccs $cman
+# Required-Stop:
+# Should-Start:      $network
+# Default-Start:     S
+# Default-Stop:
+# Short-Description: Start clvm daemon
+# Description:       Network file systems are mounted by
+#                    /etc/network/if-up.d/mountnfs in the background
+#                    when interfaces are brought up; this script waits
+#                    for them to be mounted before carrying on.
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/sbin/clvmd
+NAME=sipb-xen-clvm-config
+PIDFILE=/var/run/cluster/clvmd.pid
+DESC="Cluster LVM"
+
+test -x $DAEMON || exit 0
+
+CLVMD_OPTIONS=""
+
+if [ -f /etc/default/sipb-xen-clvm-config ] ; then
+	. /etc/default/sipb-xen-clvm-config
+fi
+
+set -e
+
+case "$1" in
+  start)
+	echo -n "Starting $DESC: "
+	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $CLVMD_OPTIONS
+	echo "$NAME."
+	;;
+  stop)
+	echo -n "Stopping $DESC: "
+	start-stop-daemon --stop --oknodo --quiet --pidfile $PIDFILE --exec $DAEMON
+	echo "$NAME."
+	;;
+  restart|force-reload)
+	echo -n "Restarting $DESC: "
+	$0 stop
+	sleep 1
+	$0 start
+	echo "$NAME."
+	;;
+  *)
+	N=/etc/init.d/$NAME
+	echo "Usage: $N {start|stop|restart|force-reload}" >&2
+	exit 1
+	;;
+esac
+
+exit 0
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/install
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-clvm-config/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/debian/rules	(revision 571)
@@ -0,0 +1,10 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+DEB_DIVERT_EXTENSION = .sipb-xen
+DEB_DIVERT_FILES_sipb-xen-clvm-config += \
+	/etc/lvm/lvm.conf.sipb-xen \
+	/etc/udev/multipath.rules.sipb-xen
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/config-package.mk
Index: /branches/wsgi/packages/sipb-xen-clvm-config/files/etc/lvm/lvm.conf.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/files/etc/lvm/lvm.conf.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/files/etc/lvm/lvm.conf.sipb-xen	(revision 571)
@@ -0,0 +1,357 @@
+# This is an example configuration file for the LVM2 system.
+# It contains the default settings that would be used if there was no
+# /etc/lvm/lvm.conf file.
+#
+# Refer to 'man lvm.conf' for further information including the file layout.
+#
+# To put this file in a different directory and override /etc/lvm set
+# the environment variable LVM_SYSTEM_DIR before running the tools.
+
+
+# This section allows you to configure which block devices should
+# be used by the LVM system.
+devices {
+
+    # Where do you want your volume groups to appear ?
+    dir = "/dev"
+
+    # An array of directories that contain the device nodes you wish
+    # to use with LVM2.
+    scan = [ "/dev" ]
+
+    # A filter that tells LVM2 to only use a restricted set of devices.
+    # The filter consists of an array of regular expressions.  These
+    # expressions can be delimited by a character of your choice, and
+    # prefixed with either an 'a' (for accept) or 'r' (for reject).
+    # The first expression found to match a device name determines if
+    # the device will be accepted or rejected (ignored).  Devices that
+    # don't match any patterns are accepted.
+
+    # Be careful if there there are symbolic links or multiple filesystem 
+    # entries for the same device as each name is checked separately against
+    # the list of patterns.  The effect is that if any name matches any 'a'
+    # pattern, the device is accepted; otherwise if any name matches any 'r'
+    # pattern it is rejected; otherwise it is accepted.
+
+    # Don't have more than one filter line active at once: only one gets used.
+
+    # Run vgscan after you change this parameter to ensure that
+    # the cache file gets regenerated (see below).
+    # If it doesn't do what you expect, check the output of 'vgscan -vvvv'.
+
+    # By default we accept every block device
+    # filter = [ "a/.*/" ]
+
+    # Exclude the cdrom drive
+    filter = [ "r|/dev/cdrom|" ]
+
+    # When testing I like to work with just loopback devices:
+    # filter = [ "a/loop/", "r/.*/" ]
+
+    # Or maybe all loops and ide drives except hdc:
+    # filter =[ "a|loop|", "r|/dev/hdc|", "a|/dev/ide|", "r|.*|" ]
+
+    # Use anchors if you want to be really specific
+    # filter = [ "a|^/dev/hda8$|", "r/.*/" ]
+
+    # The results of the filtering are cached on disk to avoid
+    # rescanning dud devices (which can take a very long time).  By
+    # default this cache file is hidden in the /etc/lvm directory.
+    # It is safe to delete this file: the tools regenerate it.
+    cache = "/etc/lvm/.cache"
+
+    # You can turn off writing this cache file by setting this to 0.
+    write_cache_state = 1
+
+    # Advanced settings.
+
+    # List of pairs of additional acceptable block device types found 
+    # in /proc/devices with maximum (non-zero) number of partitions.
+    # types = [ "fd", 16 ]
+
+    # If sysfs is mounted (2.6 kernels) restrict device scanning to 
+    # the block devices it believes are valid.
+    # 1 enables; 0 disables.
+    sysfs_scan = 1	
+
+    # By default, LVM2 will ignore devices used as components of
+    # software RAID (md) devices by looking for md superblocks.
+    # 1 enables; 0 disables.
+    md_component_detection = 1
+}
+
+# This section that allows you to configure the nature of the
+# information that LVM2 reports.
+log {
+
+    # Controls the messages sent to stdout or stderr.
+    # There are three levels of verbosity, 3 being the most verbose.
+    verbose = 0
+
+    # Should we send log messages through syslog?
+    # 1 is yes; 0 is no.
+    syslog = 1
+
+    # Should we log error and debug messages to a file?
+    # By default there is no log file.
+    #file = "/var/log/lvm2.log"
+
+    # Should we overwrite the log file each time the program is run?
+    # By default we append.
+    overwrite = 0
+
+    # What level of log messages should we send to the log file and/or syslog?
+    # There are 6 syslog-like log levels currently in use - 2 to 7 inclusive.
+    # 7 is the most verbose (LOG_DEBUG).
+    level = 0
+    
+    # Format of output messages
+    # Whether or not (1 or 0) to indent messages according to their severity
+    indent = 1
+
+    # Whether or not (1 or 0) to display the command name on each line output
+    command_names = 0
+
+    # A prefix to use before the message text (but after the command name,
+    # if selected).  Default is two spaces, so you can see/grep the severity
+    # of each message.
+    prefix = "  "
+
+    # To make the messages look similar to the original LVM tools use:
+    #   indent = 0
+    #   command_names = 1
+    #   prefix = " -- "
+
+    # Set this if you want log messages during activation.
+    # Don't use this in low memory situations (can deadlock).
+    # activation = 0
+}
+
+# Configuration of metadata backups and archiving.  In LVM2 when we
+# talk about a 'backup' we mean making a copy of the metadata for the
+# *current* system.  The 'archive' contains old metadata configurations.
+# Backups are stored in a human readeable text format.
+backup {
+
+    # Should we maintain a backup of the current metadata configuration ?
+    # Use 1 for Yes; 0 for No.
+    # Think very hard before turning this off!
+    backup = 1
+
+    # Where shall we keep it ?
+    # Remember to back up this directory regularly!
+    backup_dir = "/etc/lvm/backup"
+
+    # Should we maintain an archive of old metadata configurations.
+    # Use 1 for Yes; 0 for No.
+    # On by default.  Think very hard before turning this off.
+    archive = 1
+
+    # Where should archived files go ?
+    # Remember to back up this directory regularly!
+    archive_dir = "/etc/lvm/archive"
+    
+    # What is the minimum number of archive files you wish to keep ?
+    retain_min = 10
+
+    # What is the minimum time you wish to keep an archive file for ?
+    retain_days = 30
+}
+
+# Settings for the running LVM2 in shell (readline) mode.
+shell {
+
+    # Number of lines of history to store in ~/.lvm_history
+    history_size = 100
+}
+
+
+# Miscellaneous global LVM2 settings
+global {
+    
+    # The file creation mask for any files and directories created.
+    # Interpreted as octal if the first digit is zero.
+    umask = 077
+
+    # Allow other users to read the files
+    #umask = 022
+
+    # Enabling test mode means that no changes to the on disk metadata
+    # will be made.  Equivalent to having the -t option on every
+    # command.  Defaults to off.
+    test = 0
+
+    # Whether or not to communicate with the kernel device-mapper.
+    # Set to 0 if you want to use the tools to manipulate LVM metadata 
+    # without activating any logical volumes.
+    # If the device-mapper kernel driver is not present in your kernel
+    # setting this to 0 should suppress the error messages.
+    activation = 1
+
+    # If we can't communicate with device-mapper, should we try running 
+    # the LVM1 tools?
+    # This option only applies to 2.4 kernels and is provided to help you
+    # switch between device-mapper kernels and LVM1 kernels.
+    # The LVM1 tools need to be installed with .lvm1 suffices
+    # e.g. vgscan.lvm1 and they will stop working after you start using
+    # the new lvm2 on-disk metadata format.
+    # The default value is set when the tools are built.
+    # fallback_to_lvm1 = 0
+
+    # The default metadata format that commands should use - "lvm1" or "lvm2".
+    # The command line override is -M1 or -M2.
+    # Defaults to "lvm1" if compiled in, else "lvm2".
+    # format = "lvm1"
+
+    # Location of proc filesystem
+    proc = "/proc"
+
+    # Type of locking to use. Defaults to file-based locking (1).
+    # Turn locking off by setting to 0 (dangerous: risks metadata corruption
+    # if LVM2 commands get run concurrently).
+    #locking_type = 1
+
+    # Local non-LV directory that holds file-based locks while commands are
+    # in progress.  A directory like /tmp that may get wiped on reboot is OK.
+    locking_dir = "/var/lock/lvm"
+
+    # Other entries can go here to allow you to load shared libraries
+    # e.g. if support for LVM1 metadata was compiled as a shared library use
+    #   format_libraries = "liblvm2format1.so" 
+    # Full pathnames can be given.
+
+    # Search this directory first for shared libraries.
+    #  library_dir = "/lib/lvm2"
+    
+    # Enable these three for cluster LVM when clvmd is running.
+    # Remember to remove the "locking_type = 1" above.
+    #
+    locking_library = "liblvm2clusterlock.so"
+    locking_type = 2
+    library_dir = "/lib/lvm2"
+}
+
+activation {
+    # Device used in place of missing stripes if activating incomplete volume.
+    # For now, you need to set this up yourself first (e.g. with 'dmsetup')
+    # For example, you could make it return I/O errors using the 'error' 
+    # target or make it return zeros.
+    missing_stripe_filler = "/dev/ioerror"
+
+    # How much stack (in KB) to reserve for use while devices suspended
+    reserved_stack = 256
+
+    # How much memory (in KB) to reserve for use while devices suspended
+    reserved_memory = 8192
+
+    # Nice value used while devices suspended
+    process_priority = -18
+
+    # If volume_list is defined, each LV is only activated if there is a
+    # match against the list.
+    #   "vgname" and "vgname/lvname" are matched exactly.
+    #   "@tag" matches any tag set in the LV or VG.
+    #   "@*" matches if any tag defined on the host is also set in the LV or VG
+    #
+    # volume_list = [ "vg1", "vg2/lvol1", "@tag1", "@*" ]
+
+    # Permissions to use for new devices
+    # device_permissions = [ 0, 6, 0660 ]
+
+    # Size (in KB) of each copy operation when mirroring
+    mirror_region_size = 512
+
+    # 'mirror_image_fault_policy' and 'mirror_log_fault_policy' define
+    # how a device failure affecting a mirror is handled.
+    # A mirror is composed of mirror images (copies) and a log.
+    # A disk log ensures that a mirror does not need to be re-synced
+    # (all copies made the same) every time a machine reboots or crashes.
+    #
+    # In the event of a failure, the specified policy will be used to
+    # determine what happens:
+    #
+    # "remove" - Simply remove the faulty device and run without it.  If
+    #            the log device fails, the mirror would convert to using
+    #            an in-memory log.  This means the mirror will not
+    #            remember its sync status across crashes/reboots and
+    #            the entire mirror will be re-synced.  If a
+    #            mirror image fails, the mirror will convert to a
+    #            non-mirrored device if there is only one remaining good
+    #            copy.
+    #
+    # "allocate" - Remove the faulty device and try to allocate space on
+    #            a new device to be a replacement for the failed device.
+    #            Using this policy for the log is fast and maintains the
+    #            ability to remember sync state through crashes/reboots.
+    #            Using this policy for a mirror device is slow, as it
+    #            requires the mirror to resynchronize the devices, but it
+    #            will preserve the mirror characteristic of the device.
+    #            This policy acts like "remove" if no suitable device and
+    #            space can be allocated for the replacement.
+    #            Currently this is not implemented properly and behaves
+    #            similarly to:
+    #
+    # "allocate_anywhere" - Operates like "allocate", but it does not
+    #            require that the new space being allocated be on a
+    #            device is not part of the mirror.  For a log device
+    #            failure, this could mean that the log is allocated on
+    #            the same device as a mirror device.  For a mirror
+    #            device, this could mean that the mirror device is
+    #            allocated on the same device as another mirror device.
+    #            This policy would not be wise for mirror devices
+    #            because it would break the redundant nature of the
+    #            mirror.  This policy acts like "remove" if no suitable
+    #            device and space can be allocated for the replacement.
+
+    mirror_log_fault_policy = "allocate"
+    mirror_device_fault_policy = "remove"
+}
+
+
+####################
+# Advanced section #
+####################
+
+# Metadata settings
+#
+# metadata {
+    # Default number of copies of metadata to hold on each PV.  0, 1 or 2.
+    # You might want to override it from the command line with 0 
+    # when running pvcreate on new PVs which are to be added to large VGs.
+
+    # pvmetadatacopies = 1
+
+    # Approximate default size of on-disk metadata areas in sectors.
+    # You should increase this if you have large volume groups or
+    # you want to retain a large on-disk history of your metadata changes.
+
+    # pvmetadatasize = 255
+
+    # List of directories holding live copies of text format metadata.
+    # These directories must not be on logical volumes!
+    # It's possible to use LVM2 with a couple of directories here,
+    # preferably on different (non-LV) filesystems, and with no other 
+    # on-disk metadata (pvmetadatacopies = 0). Or this can be in
+    # addition to on-disk metadata areas.
+    # The feature was originally added to simplify testing and is not
+    # supported under low memory situations - the machine could lock up.
+    #
+    # Never edit any files in these directories by hand unless you
+    # you are absolutely sure you know what you are doing! Use
+    # the supplied toolset to make changes (e.g. vgcfgrestore).
+
+    # dirs = [ "/etc/lvm/metadata", "/mnt/disk2/lvm/metadata2" ]
+#}
+
+# Event daemon
+#
+# dmeventd {
+    # mirror_library is the library used when monitoring a mirror device.
+    #
+    # "libdevmapper-event-lvm2mirror.so" attempts to recover from failures.
+    # It removes failed devices from a volume group and reconfigures a
+    # mirror as necessary.
+    #
+    # mirror_library = "libdevmapper-event-lvm2mirror.so"
+#}
+
Index: /branches/wsgi/packages/sipb-xen-clvm-config/files/etc/udev/multipath.rules.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-clvm-config/files/etc/udev/multipath.rules.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-clvm-config/files/etc/udev/multipath.rules.sipb-xen	(revision 571)
@@ -0,0 +1,12 @@
+#
+# multipath and multipath partitions nodes are created in /dev/mapper/
+# this file should be installed in /etc/udev/rules.d
+#
+
+# take care of devmap partitioning
+ACTION=="add", SUBSYSTEM=="block", KERNEL=="dm-*", \
+	PROGRAM="/sbin/dmsetup -j %M -m %m status", \
+	RESULT=="*multipath*", \
+	PROGRAM="/sbin/dmsetup -j %M -m %m --noopencount --noheadings -c -o name info", \
+	RUN+="/sbin/kpartx -a /dev/mapper/%c"
+
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/changelog	(revision 571)
@@ -0,0 +1,38 @@
+sipb-xen-console-server (2.02) unstable; urgency=low
+
+  * And...xm isn't in the path, so give a full path
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 04:48:53 -0400
+
+sipb-xen-console-server (2.01) unstable; urgency=low
+
+  * update-conserver script should reload, not restart
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 04:43:12 -0400
+
+sipb-xen-console-server (2) unstable; urgency=low
+
+  * Use a python based update-conserver script that gets the list of
+    consoles from xm list
+  * Run the update-conserver script every 5 minutes to catch VMs that
+    are not started or stopped through the remctl interface
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 04:32:58 -0400
+
+sipb-xen-console-server (1.0.2) unstable; urgency=low
+
+  * Also...make this package actually do something
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 01:41:32 -0400
+
+sipb-xen-console-server (1.0.1) unstable; urgency=low
+
+  * Misnamed a file
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 01:36:29 -0400
+
+sipb-xen-console-server (1) unstable; urgency=low
+
+  * Initial release.
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Wed,  2 Apr 2008 00:27:12 -0400
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/control	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-console-server
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 5), config-package-dev
+Standards-Version: 3.7.2
+
+Package: sipb-xen-console-server
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, conserver-server
+Description: SIPB Xen serial console server server
+  This configures the VMM for the server-side of the console server
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/control.in	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-console-server
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-console-server
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, conserver-server
+Description: SIPB Xen serial console server server
+  This configures the VMM for the server-side of the console server
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/rules	(revision 571)
@@ -0,0 +1,9 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+DEB_DIVERT_EXTENSION = .sipb-xen
+DEB_DIVERT_FILES_sipb-xen-console += \
+	/etc/conserver/conserver.cf
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/replace-files.mk
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/sipb-xen-console-server.cron.d
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/sipb-xen-console-server.cron.d	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/sipb-xen-console-server.cron.d	(revision 571)
@@ -0,0 +1,8 @@
+#
+# cron-jobs for sipb-xen-console-server
+# Refresh the list of active VM consoles
+#
+
+MAILTO=root
+
+*/5 * * * * root python /usr/sbin/sipb-xen-update-conserver
Index: /branches/wsgi/packages/sipb-xen-console-server/debian/sipb-xen-console-server.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/debian/sipb-xen-console-server.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/debian/sipb-xen-console-server.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-console-server/files/etc/conserver/conserver.cf.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/files/etc/conserver/conserver.cf.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/files/etc/conserver/conserver.cf.sipb-xen	(revision 571)
@@ -0,0 +1,36 @@
+# The character '&' in logfile names are substituted with the console
+# name.
+#
+
+config * {
+	sslrequired yes;
+}
+default full {
+	rw *;
+}
+default * {
+	master black-mesa;
+	logfile /var/log/conserver/&.log;
+	timestamp "1lab";
+	include full;
+	type exec;
+	exec xm console d_f;
+	execsubst f=cs;
+}
+
+console s_sipb-xen-dev {
+	type exec;
+	exec xm console s_sipb-xen-dev;
+}
+
+#include /etc/conserver/sipb-xen-consoles.cf
+
+##
+## list of clients we allow
+##
+access * {
+	allowed 127.0.0.1;
+	trusted 18.181.0.134;
+	limited *;
+}
+
Index: /branches/wsgi/packages/sipb-xen-console-server/files/usr/sbin/sipb-xen-update-conserver
===================================================================
--- /branches/wsgi/packages/sipb-xen-console-server/files/usr/sbin/sipb-xen-update-conserver	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console-server/files/usr/sbin/sipb-xen-update-conserver	(revision 571)
@@ -0,0 +1,24 @@
+#!/usr/bin/python
+
+import sipb_xen_database
+import subprocess
+import os
+
+sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
+
+def live_vms():
+    p = subprocess.Popen(['/usr/sbin/xm', 'list'], stdout=subprocess.PIPE)
+    p.wait()
+    output = p.stdout.read()
+    vms = [x.split()[0][2:] for x in output.splitlines() if x.startswith('d_')]
+    return vms
+
+def reload_conserver():
+    p = subprocess.Popen(['/etc/init.d/conserver-server', 'reload'], stdout=subprocess.PIPE)
+    p.wait()
+
+if __name__ == '__main__':
+    f = open('/etc/conserver/sipb-xen-consoles.cf', 'w')
+    f.write('\n'.join('console %s {}' % vm for vm in live_vms()))
+    f.close()
+    reload_conserver()
Index: /branches/wsgi/packages/sipb-xen-console/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/changelog	(revision 571)
@@ -0,0 +1,94 @@
+sipb-xen-console (7.1) unstable; urgency=low
+
+  * Remember to actually divert the conserver config
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 01:48:05 -0400
+
+sipb-xen-console (7) unstable; urgency=low
+
+  * Use conserver instead of ssh to connect to black-mesa
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 00:52:05 -0400
+
+sipb-xen-console (6.2) unstable; urgency=low
+
+  * /etc/modules is no longer managed by this package
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Tue,  1 Apr 2008 22:25:09 -0400
+
+sipb-xen-console (6.1) unstable; urgency=low
+
+  * Don't add the "d_" to the domain name on this side - do it on the
+    black-mesa side
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Tue, 01 Apr 2008 22:20:47 -0400
+
+sipb-xen-console (6) unstable; urgency=low
+
+  * modprobe fuse before attaching consolefs
+  * Revert code to block dropping privileges to user accounts
+  * Add configuration to accept Kerberos config for users and error on
+    non-root users if Kerberos authentication fails
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Tue, 01 Apr 2008 20:03:11 -0400
+
+sipb-xen-console (5.1) unstable; urgency=low
+
+  * Package should create /consolefs so that sipb-xen-consolefs has
+    somewhere to mount to
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Sun, 30 Mar 2008 18:20:02 -0400
+
+sipb-xen-console (5) unstable; urgency=low
+
+  * modprobe fuse at boot
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Sun, 30 Mar 2008 17:57:36 -0400
+
+sipb-xen-console (4.1) unstable; urgency=low
+
+  * It should not be trivial for us to access the serial console of
+    users' machines
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 17:42:04 -0400
+
+sipb-xen-console (4) unstable; urgency=low
+
+  * Added comments to sipb-xen-consolefs
+  * Added support for symlinks in the realpath
+  * Changed sipb-xen-consolefs to use syslog instead of printf debugging
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 14:17:59 -0400
+
+sipb-xen-console (3.2) unstable; urgency=low
+
+  * Fixing a bug in sipb-xen-consolefs ('@' is not re-added to realms
+    in the .k5login
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 06:39:30 -0400
+
+sipb-xen-console (3.1) unstable; urgency=low
+
+  * Clean up the motd a bit
+  * Add dependency on sipb-xen-chrony-config to make sure the clock is
+    staying synced
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 06:33:55 -0400
+
+sipb-xen-console (3) unstable; urgency=low
+
+  * Make the motd useful instead of turning it off
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 06:14:23 -0400
+
+sipb-xen-console (2) unstable; urgency=low
+
+  * Actually functional release.
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 05:07:43 -0400
+
+sipb-xen-console (1) unstable; urgency=low
+
+  * Initial release.
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 01:08:50 -0400
Index: /branches/wsgi/packages/sipb-xen-console/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-console/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/control	(revision 571)
@@ -0,0 +1,15 @@
+Source: sipb-xen-console
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 5), config-package-dev
+Standards-Version: 3.7.2
+
+Package: sipb-xen-console
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, conserver-client, daemon, debathena-kerberos-config, fuse-utils, libnss-pgsql1, nscd, openssh-server, python-fuse, sipb-xen-chrony-config, sipb-xen-database-common
+Description: SIPB Xen serial console server
+ This package  should be installed on sipb-xen-console
+ It makes sure that necessary tools are available.
Index: /branches/wsgi/packages/sipb-xen-console/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/control.in	(revision 571)
@@ -0,0 +1,15 @@
+Source: sipb-xen-console
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-console
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, conserver-client, daemon, debathena-kerberos-config, fuse-utils, libnss-pgsql1, nscd, openssh-server, python-fuse, sipb-xen-chrony-config, sipb-xen-database-common
+Description: SIPB Xen serial console server
+ This package  should be installed on sipb-xen-console
+ It makes sure that necessary tools are available.
Index: /branches/wsgi/packages/sipb-xen-console/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-console/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/rules	(revision 571)
@@ -0,0 +1,15 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+DEB_DIVERT_EXTENSION = .sipb-xen
+DEB_DIVERT_FILES_sipb-xen-console += \
+	/etc/init.d/bootmisc.sh \
+	/etc/conserver/conserver.cf \
+	/etc/motd \
+	/etc/nscd.conf \
+	/etc/nsswitch.conf \
+	/etc/pam.d/ssh \
+	/etc/ssh/sshd_config
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/replace-files.mk
Index: /branches/wsgi/packages/sipb-xen-console/debian/sipb-xen-console.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/sipb-xen-console.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/sipb-xen-console.init	(revision 571)
@@ -0,0 +1,126 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-console
+# Required-Start:    $local_fs $remote_fs
+# Required-Stop:     $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: sipb-xen Console Server homedir filesystem
+# Description:       
+### END INIT INFO
+
+# Author: SIPB Xen Project <sipb-xen@mit.edu>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="The sipb-xen console server homedir filesystem"
+NAME=sipb-xen-console
+DAEMON=/usr/bin/sipb-xen-consolefs
+DAEMON_ARGS="/consolefs"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	modprobe fuse
+	daemon --running -n $NAME && return 1
+	daemon -r -O daemon.info -E daemon.err -n $NAME -U $DAEMON $DAEMON_ARGS || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	daemon --stop -n $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Many daemons don't delete their pidfiles when they exit.
+	rm -f $PIDFILE
+	umount "$DAEMON_ARGS"
+	return "$RETVAL"
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-console/debian/sipb-xen-console.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/debian/sipb-xen-console.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/debian/sipb-xen-console.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/conserver/conserver.cf.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/conserver/conserver.cf.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/conserver/conserver.cf.sipb-xen	(revision 571)
@@ -0,0 +1,6 @@
+# default config for console
+config * {
+	master  black-mesa;
+	port    3109;
+	sslenabled yes;
+}
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/init.d/bootmisc.sh.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/init.d/bootmisc.sh.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/init.d/bootmisc.sh.sipb-xen	(revision 571)
@@ -0,0 +1,92 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          bootmisc
+# Required-Start:    $local_fs hostname $remote_fs
+# Required-Stop:     $local_fs
+# Default-Start:     S
+# Default-Stop:
+# Short-Description: Miscellaneous things to be done during bootup.
+# Description:
+### END INIT INFO
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+[ "$DELAYLOGIN" ] || DELAYLOGIN=yes
+. /lib/init/vars.sh
+
+do_start () {
+	#
+	# If login delaying is enabled then create the flag file
+	# which prevents logins before startup is complete
+	#
+	case "$DELAYLOGIN" in
+	  Y*|y*)
+		echo "System bootup in progress - please wait" > /var/lib/initscripts/nologin
+		;;
+	esac
+
+	# Create /var/run/utmp so we can login.
+	: > /var/run/utmp
+	if grep -q ^utmp: /etc/group
+	then
+		chmod 664 /var/run/utmp
+		chgrp utmp /var/run/utmp
+	fi
+
+	# Set pseudo-terminal access permissions.
+	if [ ! -e /dev/.devfsd ] && [ -c /dev/ttyp0 ]
+	then
+		chmod -f 666 /dev/tty[p-za-e][0-9a-f]
+		chown -f root:tty /dev/tty[p-za-e][0-9a-f]
+	fi
+
+	# Do not update motd
+	#uname -snrvm > /var/run/motd
+	#[ -f /etc/motd.tail ] && cat /etc/motd.tail >> /var/run/motd
+	cp /etc/motd /var/run/motd
+
+	# Save kernel messages in /var/log/dmesg
+	if which dmesg >/dev/null 2>&1
+	then
+		savelog -q -p -c 5 /var/log/dmesg
+		dmesg -s 524288 > /var/log/dmesg
+		chgrp adm /var/log/dmesg || :
+	elif [ -c /dev/klog ]
+	then
+		savelog -q -p -c 5 /var/log/dmesg
+		dd if=/dev/klog of=/var/log/dmesg &
+		sleep 1
+		kill $!
+		[ -f /var/log/dmesg ] && { chgrp adm /var/log/dmesg || : ; }
+	fi
+
+	#
+	#	Save udev log in /var/log/udev
+	#
+	if [ -e /dev/.udev.log ]
+	then
+		mv -f /dev/.udev.log /var/log/udev
+	fi
+
+	# Remove bootclean's flag files.
+	# Don't run bootclean again after this!
+	rm -f /tmp/.clean /var/run/.clean /var/lock/.clean
+}
+
+case "$1" in
+  start|"")
+	do_start
+	;;
+  restart|reload|force-reload)
+	echo "Error: argument '$1' not supported" >&2
+	exit 3
+	;;
+  stop)
+	# No-op
+	;;
+  *)
+	echo "Usage: bootmisc.sh [start|stop]" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/issue.net.no_tkt
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/issue.net.no_tkt	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/issue.net.no_tkt	(revision 571)
@@ -0,0 +1,2 @@
+You must login to the sipb-xen console server using Kerberos tickets, but your
+ssh client did not pass a valid ticket to the console server.
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/issue.net.no_user
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/issue.net.no_user	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/issue.net.no_user	(revision 571)
@@ -0,0 +1,2 @@
+The VM you are attempting to access does not appear to exist.
+
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/motd.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/motd.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/motd.sipb-xen	(revision 571)
@@ -0,0 +1,3 @@
+
+Type Ctrl-e, then c, then . to escape from the console
+
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/nscd.conf.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/nscd.conf.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/nscd.conf.sipb-xen	(revision 571)
@@ -0,0 +1,67 @@
+#
+# /etc/nscd.conf
+#
+# An example Name Service Cache config file.  This file is needed by nscd.
+#
+# Legal entries are:
+#
+#	logfile			<file>
+#	debug-level		<level>
+#	threads			<initial #threads to use>
+#	max-threads		<maximum #threads to use>
+#	server-user             <user to run server as instead of root>
+#		server-user is ignored if nscd is started with -S parameters
+#       stat-user               <user who is allowed to request statistics>
+#	reload-count		unlimited|<number>
+#	paranoia		<yes|no>
+#	restart-interval	<time in seconds>
+#
+#       enable-cache		<service> <yes|no>
+#	positive-time-to-live	<service> <time in seconds>
+#	negative-time-to-live   <service> <time in seconds>
+#       suggested-size		<service> <prime number>
+#	check-files		<service> <yes|no>
+#	persistent		<service> <yes|no>
+#	shared			<service> <yes|no>
+#
+# Currently supported cache names (services): passwd, group, hosts
+#
+
+
+#	logfile			/var/log/nscd.log
+#	threads			6
+#	max-threads		128
+#	server-user		nobody
+#	stat-user		somebody
+	debug-level		0
+#	reload-count		5
+	paranoia		no
+#	restart-interval	3600
+
+	enable-cache		passwd		yes
+	positive-time-to-live	passwd		600
+#	negative-time-to-live	passwd		20
+	negative-time-to-live	passwd		3
+	suggested-size		passwd		211
+	check-files		passwd		yes
+#	persistent		passwd		yes
+	persistent		passwd		no
+	shared			passwd		yes
+
+	enable-cache		group		yes
+	positive-time-to-live	group		3600
+#	negative-time-to-live	group		60
+	negative-time-to-live	group		3
+	suggested-size		group		211
+	check-files		group		yes
+#	persistent		group		yes
+	persistent		group		no
+	shared			group		yes
+
+	enable-cache		hosts		yes
+	positive-time-to-live	hosts		3600
+	negative-time-to-live	hosts		20
+	suggested-size		hosts		211
+	check-files		hosts		yes
+	persistent		hosts		yes
+	shared			hosts		yes
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/nss-pgsql.conf
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/nss-pgsql.conf	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/nss-pgsql.conf	(revision 571)
@@ -0,0 +1,18 @@
+# example configfile for PostgreSQL NSS module
+
+# these are entries as used with the BOFHMS tool (sf.net/projects/bofhms)
+host		= sipb-xen-dev
+port		= 5432
+database	= sipb_xen
+login		= sipb-xen
+
+querypasswd = SELECT name, NULL, machine_id + 1000 as uid, machine_id + 1000 as gid, '', '/consolefs/'|| name, '/usr/bin/sipb-xen-consolesh' FROM machines
+querygroup = SELECT name, NULL, machine_id + 1000 as gid FROM machines
+querymembers = SELECT name FROM machines WHERE 1000 + machine_id = %d
+queryids = SELECT 1000 + machine_id AS gid FROM machines LIMIT 0
+
+passwd_name = name
+passwd_uid = 1000 + machine_id
+
+group_name = name
+group_gid = 1000 + machine_id
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/nsswitch.conf.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/nsswitch.conf.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/nsswitch.conf.sipb-xen	(revision 571)
@@ -0,0 +1,19 @@
+# /etc/nsswitch.conf
+#
+# Example configuration of GNU Name Service Switch functionality.
+# If you have the `glibc-doc-reference' and `info' packages installed, try:
+# `info libc "Name Service Switch"' for information about this file.
+
+passwd:         compat pgsql
+group:          compat pgsql
+shadow:         compat
+
+hosts:          files dns
+networks:       files
+
+protocols:      db files
+services:       db files
+ethers:         db files
+rpc:            db files
+
+netgroup:       nis
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/pam.d/ssh.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/pam.d/ssh.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/pam.d/ssh.sipb-xen	(revision 571)
@@ -0,0 +1,48 @@
+# PAM configuration for the Secure Shell service
+
+# If they're not root, but their user exists (success),
+auth    [success=ignore ignore=ignore default=1 module_unknown=die]        pam_succeed_if.so uid > 0
+# print the "You don't have tickets" error:
+auth    [success=die ignore=reset default=die module_unknown=die]  pam_echo.so file=/etc/issue.net.no_tkt
+# If !(they are root),
+auth    [success=1 ignore=ignore default=ignore module_unknown=die]        pam_succeed_if.so uid eq 0
+# print the "your account doesn't exist" error:
+auth    [success=die ignore=reset default=die module_unknown=die]  pam_echo.so file=/etc/issue.net.no_user
+
+# Read environment variables from /etc/environment and
+# /etc/security/pam_env.conf.
+auth       required     pam_env.so # [1]
+# In Debian 4.0 (etch), locale-related environment variables were moved to
+# /etc/default/locale, so read that as well.
+auth       required     pam_env.so envfile=/etc/default/locale
+
+# Standard Un*x authentication.
+@include common-auth
+
+# Disallow non-root logins when /etc/nologin exists.
+account    required     pam_nologin.so
+
+# Uncomment and edit /etc/security/access.conf if you need to set complex
+# access limits that are hard to express in sshd_config.
+# account  required     pam_access.so
+
+# Standard Un*x authorization.
+@include common-account
+
+# Standard Un*x session setup and teardown.
+@include common-session
+
+# Print the message of the day upon successful login.
+session    optional     pam_motd.so # [1]
+
+# Print the status of the user's mailbox upon successful login.
+session    optional     pam_mail.so standard noenv # [1]
+
+# Set up user limits from /etc/security/limits.conf.
+session    required     pam_limits.so
+
+# Set up SELinux capabilities (need modified pam)
+# session  required     pam_selinux.so multiple
+
+# Standard Un*x password updating.
+@include common-password
Index: /branches/wsgi/packages/sipb-xen-console/files/etc/ssh/sshd_config.sipb-xen
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/etc/ssh/sshd_config.sipb-xen	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/etc/ssh/sshd_config.sipb-xen	(revision 571)
@@ -0,0 +1,59 @@
+Port 22
+Protocol 2
+# HostKeys for protocol version 2
+HostKey /etc/ssh/ssh_host_rsa_key
+HostKey /etc/ssh/ssh_host_dsa_key
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin yes
+StrictModes yes
+
+RSAAuthentication yes
+PubkeyAuthentication yes
+#AuthorizedKeysFile	%h/.ssh/authorized_keys
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts yes
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication no
+# similar for protocol version 2
+HostbasedAuthentication no
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords no
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication yes
+
+# Change to no to disable tunnelled clear text passwords
+PasswordAuthentication no
+
+# GSSAPI options
+GSSAPIAuthentication yes
+GSSAPICleanupCredentials yes
+GSSAPIKeyExchange yes
+
+X11Forwarding yes
+X11DisplayOffset 10
+PrintMotd no
+PrintLastLog yes
+TCPKeepAlive yes
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+UsePAM yes
Index: /branches/wsgi/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolefs
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolefs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolefs	(revision 571)
@@ -0,0 +1,248 @@
+#!/usr/bin/python
+
+import fuse
+from fuse import Fuse
+
+from time import time
+
+import stat	# for file properties
+import os	  # for filesystem modes (O_RDONLY, etc)
+import errno   # for error number codes (ENOENT, etc)
+			   # - note: these must be returned as negatives
+
+from syslog import *
+
+import sipb_xen_database
+
+fuse.fuse_python_api = (0, 2)
+
+realpath = "/home/machines/"
+
+def getDepth(path):
+	"""
+	Return the depth of a given path, zero-based from root ('/')
+	"""
+	if path == '/':
+		return 0
+	else:
+		return path.count('/')
+
+def getParts(path):
+	"""
+	Return the slash-separated parts of a given path as a list
+	"""
+	if path == '/':
+		return ['/']
+	else:
+		# [1:] because otherwise you get an empty list element from the
+		# initial '/'
+		return path[1:].split('/')
+
+class MyStat:
+	def __init__(self):
+		self.st_mode = 0
+		self.st_ino = 0
+		self.st_dev = 0
+		self.st_nlink = 0
+		self.st_uid = 0
+		self.st_gid = 0
+		self.st_size = 0
+		self.st_atime = 0
+		self.st_mtime = 0
+		self.st_ctime = 0
+	
+	def toTuple(self):
+		return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink, self.st_uid, self.st_gid, self.st_size, self.st_atime, self.st_mtime, self.st_ctime)
+
+class ConsoleFS(Fuse):
+	"""
+	ConsoleFS creates a series of subdirectories each mirroring the same real
+	directory, except for a single file - the .k5login - which is dynamically
+	generated for each subdirectory
+	
+	This filesystem only implements the getattr, getdir, read, and readlink
+	calls, beacuse this is a read-only filesystem
+	"""
+	
+	def __init__(self, *args, **kw):
+		"""Initialize the filesystem and set it to allow_other access besides
+		the user who mounts the filesystem (i.e. root)
+		"""
+		Fuse.__init__(self, *args, **kw)
+		self.lasttime = time()
+		self.allow_other = 1
+		
+		openlog('sipb-xen-consolefs ', LOG_PID, LOG_DAEMON)
+		
+		syslog(LOG_DEBUG, 'Init complete.')
+	
+	def mirrorPath(self, path):
+		"""Translate a virtual path to its real path counterpart"""
+		return realpath + "/".join(getParts(path)[1:])
+	
+	def getMachines(self):
+		"""Get the list of VMs in the database, clearing the cache if it's 
+		older than 15 seconds"""
+		if time() - self.lasttime > 15:
+			self.lasttime = time()
+			sipb_xen_database.clear_cache()
+		return [machine.name for machine in sipb_xen_database.Machine.select()]
+	
+	def getUid(self, machine_name):
+		"""Calculate the UID of a machine-account, which is just machine_id+1000
+		"""
+		return sipb_xen_database.Machine.get_by(name=machine_name).machine_id + 1000
+	
+	def getK5login(self, machine_name):
+		"""Build the ACL for a machine and turn it into a .k5login file
+		"""
+		machine = sipb_xen_database.Machine.get_by(name=machine_name)
+		users = [acl.user for acl in machine.acl]
+		return "\n".join(map(self.userToPrinc, users) + [''])
+	
+	def userToPrinc(self, user):
+		"""Convert Kerberos v4-style names to v5-style and append a default
+		realm if none is specified
+		"""
+		if '@' in user:
+			(princ, realm) = user.split('@')
+		else:
+			princ = user
+			realm = "ATHENA.MIT.EDU"
+		
+		return princ.replace('.', '/') + '@' + realm
+	
+	def getattr(self, path):
+		"""
+		- st_mode (protection bits)
+		- st_ino (inode number)
+		- st_dev (device)
+		- st_nlink (number of hard links)
+		- st_uid (user ID of owner)
+		- st_gid (group ID of owner)
+		- st_size (size of file, in bytes)
+		- st_atime (time of most recent access)
+		- st_mtime (time of most recent content modification)
+		- st_ctime (platform dependent; time of most recent metadata change on Unix,
+					or the time of creation on Windows).
+		"""
+		
+		syslog(LOG_DEBUG, "*** getattr: " + path)
+		
+		depth = getDepth(path)
+		parts = getParts(path)
+		
+		st = MyStat()
+		# / is a directory
+		if path == '/':
+			st.st_mode = stat.S_IFDIR | 0755
+			st.st_nlink = 2
+		# /foo is a directory if foo is a machine - otherwise it doesn't exist
+		elif depth == 1:
+			if parts[-1] in self.getMachines():
+				st.st_mode = stat.S_IFDIR | 0755
+				st.st_nlink = 2
+				# Homedirs should be owned by the user whose homedir it is
+				st.st_uid = st.st_gid = self.getUid(parts[0])
+			else:
+				return -errno.ENOENT
+		# Catch the .k5login file, because it's a special case
+		elif depth == 2 and parts[-1] == '.k5login':
+			st.st_mode = stat.S_IFREG | 0444
+			st.st_nlink = 1
+			st.st_size = len(self.getK5login(parts[0]))
+			# The .k5login file should be owned by the user whose homedir it is
+			st.st_uid = st.st_gid = self.getUid(parts[0])
+		# For anything else, we get the mirror path and call out to the OS
+		else:
+			stats = list(os.lstat(self.mirrorPath(path)))
+			# Shadow the UID and GID from the original homedir
+			stats[4:6] = [self.getUid(parts[0])] * 2
+			return tuple(stats)
+		return st.toTuple()
+	
+	# This call isn't actually used in the version of Fuse on console, but we
+	# wanted to leave it implemented to ease the transition in the future
+	def readdir(self, path, offset):
+		"""Return a generator with the listing for a directory
+		"""
+		syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
+		for (value, zero) in self.getdir(path):
+			yield fuse.Direntry(value)
+	
+	def getdir(self, path):
+		"""Return a list of tuples of the form (item, 0) with the contents of
+		the directory path
+		
+		Fuse doesn't add '.' or '..' on its own, so we have to
+		"""
+		syslog(LOG_DEBUG, '*** getdir %s' % path)
+		
+		# '/' contains a directory for each machine
+		if path == '/':
+			contents = self.getMachines()
+		# The directory for each machine contains the same files as the realpath
+		# but also the .k5login
+		#
+		# The list is converted to a set so that we can handle the case where 
+		# there is already a .k5login in the realpath gracefully
+		elif getDepth(path) == 1:
+			contents = set(os.listdir(self.mirrorPath(path)) + ['.k5login'])
+		# If it's not the root of the homedir, just pass the call onto the OS
+		# for realpath
+		else:
+			contents = os.listdir(self.mirrorPath(path))
+		# Format the list the way that Fuse wants it - and don't forget to add
+		# '.' and '..'
+		return [(i, 0) for i in (list(contents) + ['.', '..'])]
+	
+	def read(self, path, length, offset):
+		"""Read length bytes starting at offset of path. In most cases, this
+		just gets passed on to the OS
+		"""
+		syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
+		
+		parts = getParts(path)
+		
+		# If the depth is less than 2, then either it's a directory or the file
+		# doesn't exist
+		# (realistically this doesn't appear to ever happen)
+		if getDepth(path) < 2:
+			return -errno.ENOENT
+		# If we're asking for a real .k5login file, then create it and return
+		# the snippet requested
+		elif parts[1:] == ['.k5login']:
+			if parts[0] not in self.getMachines():
+				return -errno.ENOENT
+			else:
+				return self.getK5login(parts[0])[offset:length + offset]
+		# Otherwise, pass the call onto the OS
+		# (note that the file will get closed when this call returns and the
+		# file descriptor goes out of scope)
+		else:
+			fname = self.mirrorPath(path)
+			if not os.path.isfile(fname):
+				return -errno.ENOENT
+			else:
+				f = open(fname)
+				f.seek(offset)
+				return f.read(length)
+	
+	def readlink(self, path):
+		syslog(LOG_DEBUG, '*** readlink %s' % path)
+		
+		# There aren't any symlinks here
+		if getDepth(path) < 2:
+			return -errno.ENOENT
+		# But there might be here
+		else:
+			return os.readlink(self.mirrorPath(path))
+
+if __name__ == '__main__':
+	sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
+	usage="""
+ConsoleFS [mount_path]
+"""
+	server = ConsoleFS()
+	server.flags = 0
+	server.main()
Index: /branches/wsgi/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolesh
===================================================================
--- /branches/wsgi/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolesh	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolesh	(revision 571)
@@ -0,0 +1,2 @@
+#!/bin/bash
+exec /usr/bin/console "$USER"
Index: /branches/wsgi/packages/sipb-xen-database/client/etc/xen/sipb-database
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/client/etc/xen/sipb-database	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/client/etc/xen/sipb-database	(revision 571)
@@ -0,0 +1,92 @@
+# -*- mode: python; -*-
+import sipb_xen_database.models as models
+from sipb_xen_database import connect
+import re
+import tempfile
+from subprocess import call
+
+connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
+prefix = "d_"
+
+# 'machine_name', and optionally 'cdrom_image', should get passed in
+# from the xm create call
+
+def check(b):
+    if not b:
+        import sys
+        sys.exit(1)
+
+machine = models.Machine.get_by(name=machine_name)
+check(machine is not None)
+machine_type = models.Type.get_by(type_id=machine.type_id)
+
+memory = machine.memory
+maxmem = memory
+check(re.match('^[A-Za-z0-9][A-Za-z0-9._-]*$', machine.name))
+name = prefix + machine.name
+check(re.match('^[0-9a-f-]+$', machine.uuid))
+uuid = machine.uuid
+
+vcpus = machine.cpus
+
+diskioemu = ""
+viftype = ""
+
+if machine_type.hvm:
+    ioemu = "ioemu:"
+    viftype = "type=ioemu, "
+
+    kernel = 'hvmloader'
+    builder = 'hvm'
+    vnc = 1
+    vncpasswd = 'moocow'
+    device_model = '/usr/sbin/qemu-dm-sipb'
+    serial = 'pty'
+else:
+    kernel  = '/boot/vmlinuz-2.6.18-5-xen-amd64'
+    ramdisk = '/boot/initrd.img-2.6.18-5-xen-amd64'
+    builder = 'linux'
+
+pae = machine_type.pae
+acpi = machine_type.acpi
+apic = machine_type.apic
+
+vif = []
+
+for n in machine.nics:
+    check(re.match('^[0-9a-fA-F:]+$', n.mac_addr) and re.match('^[0-9.]*$', n.ip))
+    d = '%smac=%s, ip=%s, bridge=xenbr0, script=vif-sipbroute' % (viftype, n.mac_addr, n.ip)
+    vif.append(d)
+
+disk = []
+
+for d in machine.disks:
+    check(re.match('^[A-Za-z0-9]+$', d.guest_device_name))
+    device = '/dev/xenvg/' + prefix + machine.name + '_' + d.guest_device_name
+    dspec = 'phy:%s,%s%s,w' % (device, diskioemu, d.guest_device_name)
+    disk.append(dspec)
+
+if 'installer' in locals():
+    check(re.match('^[A-Za-z0-9][A-Za-z0-9_.-]*$', installer))
+    tmptree = tempfile.mkdtemp('', 'auto-install.', '/tmp')
+    call(['/usr/sbin/sipb-xen-make-iso', installer, tmptree]
+         + installer_options.split(' '))
+    disk.append('file:'+tmptree+'/install.iso,hdc:cdrom,r')
+    boot = 'd'
+
+elif 'disks' in locals(): # for the copying installer's use
+    disk = disks.split(' ')
+    boot = 'c'
+
+elif 'cdrom_image' in locals():
+    check(re.match('^[A-Za-z0-9][A-Za-z0-9_.-]*$', cdrom_image))
+    disk.append('file:/srv/images/' + cdrom_image + '.iso,hdc:cdrom,r')
+    boot = 'd'
+
+usbdevice = 'tablet'
+
+on_poweroff = 'destroy'
+on_reboot = 'restart'
+on_crash = 'destroy'
+if machine.autorestart:
+    on_crash = 'restart'
Index: /branches/wsgi/packages/sipb-xen-database/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/changelog	(revision 571)
@@ -0,0 +1,149 @@
+sipb-xen-database (10.9) unstable; urgency=low
+
+  * Eager loading of relations for better performance.
+
+ -- Eric Price <ecprice@pseudomyrmex.mit.edu>  Mon, 02 Jun 2008 01:39:11 -0400
+
+sipb-xen-database (10.8) unstable; urgency=low
+
+  * Configure serial console on boot
+
+ -- Quentin Smith <quentin@sipb-xen-dev.mit.edu>  Thu,  1 May 2008 20:21:25 -0400
+
+sipb-xen-database (10.7) unstable; urgency=low
+
+  * Depend on psql client in -client package.
+
+ -- Greg Price <price@mit.edu>  Sat, 26 Apr 2008 21:06:41 -0400
+
+sipb-xen-database (10.6) unstable; urgency=low
+
+  * Support autoinstalls table.
+
+ -- Eric Price <ecprice@pseudomyrmex.mit.edu>  Mon, 21 Apr 2008 22:34:46 -0400
+
+sipb-xen-database (10.5) unstable; urgency=low
+
+  * Enable USB tablet device in guests.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Mon, 21 Apr 2008 17:39:59 -0400
+
+sipb-xen-database (10.4.1) unstable; urgency=low
+
+  * Actually export the cache clearing function.
+
+ -- Eric Price <ecprice@pseudomyrmex.mit.edu>  Fri, 28 Mar 2008 21:18:01 -0400
+
+sipb-xen-database (10.4) unstable; urgency=low
+
+  * Ubuntu's dch sucks.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Fri, 28 Mar 2008 20:43:42 -0400
+
+sipb-xen-database (10.3ubuntu1) gutsy; urgency=low
+
+  * Expose a function for clearing the cache.
+
+ -- Eric Price <ecprice@pseudomyrmex.mit.edu>  Fri, 28 Mar 2008 20:31:06 -0400
+
+sipb-xen-database (10.3) unstable; urgency=low
+
+  * Use 'acl', not 'users', for the reference to the machine_access table.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Thu, 24 Jan 2008 17:23:33 -0500
+
+sipb-xen-database (10.2) unstable; urgency=low
+
+  * Add MachineAccess to __all__ so it can be imported
+  * Remove unnecessary constructors for database objects
+
+ -- Quentin Smith <quentin@sipb-xen-dev.mit.edu>  Mon, 12 Nov 2007 04:53:47 -0500
+
+sipb-xen-database (10.1) unstable; urgency=low
+
+  * Make sipb-xen-database-tables safer (don't default to drop_all)
+
+ -- Quentin Smith <quentin@sipb-xen-dev.mit.edu>  Mon, 12 Nov 2007 04:39:59 -0500
+
+sipb-xen-database (10) unstable; urgency=low
+
+  * Added a machine_access table for cached ACLs
+
+ -- Quentin Smith <quentin@sipb-xen-dev.mit.edu>  Mon, 12 Nov 2007 04:24:35 -0500
+
+sipb-xen-database (9.0) unstable; urgency=low
+
+  * No changes.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Wed, 10 Oct 2007 00:17:20 -0400
+
+sipb-xen-database (9) unstable; urgency=low
+
+  * Add administrator column and backrefs to machine.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Wed, 10 Oct 2007 00:11:47 -0400
+
+sipb-xen-database (8) gutsy; urgency=low
+
+  * Prepend d_ to database VMs.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Tue,  9 Oct 2007 02:35:51 -0400
+
+sipb-xen-database (7.00002) gutsy; urgency=low
+
+  * Actually import re.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Sun, 07 Oct 2007 04:34:07 -0400
+
+sipb-xen-database (7.00001) gutsy; urgency=low
+
+  * import re.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Sun, 07 Oct 2007 03:40:25 -0400
+
+sipb-xen-database (7) unstable; urgency=low
+
+  * nics primary key should be mac address, not hostname.
+  * Make ip unique, and constructor for nics.
+  * Add a table for boot CDs.
+  * Add cdrom_image parameter.
+  * Regex sanity checking.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Sun, 07 Oct 2007 03:18:30 -0400
+
+sipb-xen-database (6) unstable; urgency=low
+
+  * Adding a script to create lvm volumes
+
+ -- Nelson Elhage <nelhage@mit.edu>  Sat, 22 Sep 2007 13:59:36 -0400
+
+sipb-xen-database (5) unstable; urgency=low
+
+  * Change the host from sipb-vm-1 to sipb-xen-dev
+
+ -- Nelson Elhage <nelhage@mit.edu>  Sat,  8 Sep 2007 16:16:07 -0400
+
+sipb-xen-database (4) unstable; urgency=low
+
+  * Add script=vif-sipbroute to our vif lines
+
+ -- Nelson Elhage <nelhage@mit.edu>  Fri, 24 Aug 2007 22:07:18 -0400
+
+sipb-xen-database (3) unstable; urgency=low
+
+  * Add a connect() line to sipb-database
+  * Fix some other bugs in sipb-database
+
+ -- Nelson Elhage <nelhage@mit.edu>  Sun, 19 Aug 2007 19:40:08 -0400
+
+sipb-xen-database (2) unstable; urgency=low
+
+  * Fix the postinit and prerm scripts to create a sipb-xen user
+
+ -- Nelson Elhage <nelhage@mit.edu>  Thu, 16 Aug 2007 16:27:36 -0400
+
+sipb-xen-database (1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Nelson Elhage <nelhage@mit.edu>  Sun, 15 Jul 2007 16:01:16 -0400
Index: /branches/wsgi/packages/sipb-xen-database/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-database/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/control	(revision 571)
@@ -0,0 +1,26 @@
+Source: sipb-xen-database
+Section: net
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.2.0), debhelper (>= 5.0.37.2), cdbs (>= 0.4.43), python-dev (>= 2.3.5-11), python-support (>= 0.3.2), python-support (>= 0.5.3)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-database-common
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}, python-sqlalchemy, python-psycopg2
+Provides: ${python:Provides}
+Description: Installs the SIPB Xen database schema files
+ This contains the python modules to access the SIPB Xen database
+
+Package: sipb-xen-database-server
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}, postgresql-8.1, python-sqlalchemy, python-psycopg2, sipb-xen-database-common, adduser
+Description: Installs the SIPB Xen database server
+ This tracks all the user VMs and is accessed from the VM host
+
+Package: sipb-xen-database-client
+Architecture: all
+Depends: ${misc:Depends}, python-sqlalchemy, python-psycopg2, sipb-xen-database-common, python
+Description: Installs the SIPB Xen database configuration file
+ This is a python xen configuration script that talks to the database
+ to dynamically load xen domU configuration information
Index: /branches/wsgi/packages/sipb-xen-database/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/control.in	(revision 571)
@@ -0,0 +1,26 @@
+Source: sipb-xen-database
+Section: net
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: @cdbs@, python-support (>= 0.5.3)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-database-common
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}, python-sqlalchemy, python-psycopg2
+Provides: ${python:Provides}
+Description: Installs the SIPB Xen database schema files
+ This contains the python modules to access the SIPB Xen database
+
+Package: sipb-xen-database-server
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}, postgresql-8.1, python-sqlalchemy, python-psycopg2, sipb-xen-database-common, adduser
+Description: Installs the SIPB Xen database server
+ This tracks all the user VMs and is accessed from the VM host
+
+Package: sipb-xen-database-client
+Architecture: all
+Depends: ${misc:Depends}, postgresql-client-8.1, python-sqlalchemy, python-psycopg2, sipb-xen-database-common, python
+Description: Installs the SIPB Xen database configuration file
+ This is a python xen configuration script that talks to the database
+ to dynamically load xen domU configuration information
Index: /branches/wsgi/packages/sipb-xen-database/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask tabbott@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-database/debian/pycompat
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/pycompat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/pycompat	(revision 571)
@@ -0,0 +1,1 @@
+2
Index: /branches/wsgi/packages/sipb-xen-database/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/rules	(revision 571)
@@ -0,0 +1,8 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+
+DEB_PYTHON_SYSTEM=pysupport
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/class/python-distutils.mk
Index: /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-client.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-client.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-client.install	(revision 571)
@@ -0,0 +1,1 @@
+client/* .
Index: /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-common.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-common.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-common.install	(revision 571)
@@ -0,0 +1,2 @@
+common/* .
+debian/tmp/usr/lib/python* usr/lib/
Index: /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.install	(revision 571)
@@ -0,0 +1,1 @@
+sipb-xen-database-tables usr/bin
Index: /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.postinst
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.postinst	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.postinst	(revision 571)
@@ -0,0 +1,49 @@
+#!/bin/sh
+# postinst script for #PACKAGE#
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+        #Don't create users on upgrade
+        if [ -z "$2" ]; then
+            # Don't fail if the user/database already exists
+            su postgres -c 'createuser sipb-xen -S -d -R'    || true
+            su postgres -c 'createdb sipb_xen -O sipb-xen'   || true
+            adduser --system sipb-xen
+        fi
+        su sipb-xen -s /bin/sh -c 'sipb-xen-database-tables create'
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
Index: /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.prerm
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.prerm	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/debian/sipb-xen-database-server.prerm	(revision 571)
@@ -0,0 +1,45 @@
+#!/bin/sh
+# prerm script for #PACKAGE#
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <prerm> `remove'
+#        * <old-prerm> `upgrade' <new-version>
+#        * <new-prerm> `failed-upgrade' <old-version>
+#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+#        * <deconfigured's-prerm> `deconfigure' `in-favour'
+#          <package-being-installed> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    remove|deconfigure)
+
+        # This will destroy data -- do we want to do this?
+        # su postgres -c 'dropdb sipb_xen'
+        # su postgres -c 'dropuser sipb-xen'
+        deluser sipb-xen
+    ;;
+
+    upgrade|failed-upgrade)
+    ;;
+
+    *)
+        echo "prerm called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
Index: /branches/wsgi/packages/sipb-xen-database/setup.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/setup.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/setup.py	(revision 571)
@@ -0,0 +1,5 @@
+from distutils.core import setup
+setup(name='sipb_xen_database',
+      version='0.1',
+      packages=['sipb_xen_database'],
+      )
Index: /branches/wsgi/packages/sipb-xen-database/sipb-xen-database-tables
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/sipb-xen-database-tables	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/sipb-xen-database-tables	(revision 571)
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+from sipb_xen_database import *
+import sys
+
+def usage():
+    print >>sys.stderr, "Usage: %s [create|drop]" %(sys.argv[0],)
+    sys.exit(-1)
+
+if len(sys.argv) == 1:
+    usage()
+
+connect('postgres://sipb-xen@/sipb_xen')
+
+if sys.argv[1] == "create":
+    meta.create_all()
+elif sys.argv[1] == "drop":
+    meta.drop_all()
+else:
+    usage()
Index: /branches/wsgi/packages/sipb-xen-database/sipb_xen_database/__init__.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/sipb_xen_database/__init__.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/sipb_xen_database/__init__.py	(revision 571)
@@ -0,0 +1,5 @@
+from models import *
+
+def connect(uri):
+    """ Connect to a given database URI"""
+    meta.connect(uri)
Index: /branches/wsgi/packages/sipb-xen-database/sipb_xen_database/models.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-database/sipb_xen_database/models.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-database/sipb_xen_database/models.py	(revision 571)
@@ -0,0 +1,120 @@
+from sqlalchemy import *
+
+from sqlalchemy.ext.sessioncontext import SessionContext
+from sqlalchemy.ext.assignmapper import assign_mapper
+
+__all__ = ['meta',
+           'ctx',
+           'clear_cache',
+           'machine_table',
+           'machine_access_table',
+           'nic_table',
+           'disk_table',
+           'types_table',
+           'cdroms_table',
+           'autoinstalls_table',
+           'Machine',
+           'MachineAccess',
+           'NIC',
+           'Disk',
+           'Type',
+           'CDROM',
+           'Autoinstall',
+           ]
+
+meta = DynamicMetaData()
+ctx = SessionContext(create_session)
+
+machine_table = Table('machines', meta,
+       Column('machine_id', Integer, primary_key=True, nullable=False),
+       Column('name', String, nullable=False),
+       Column('memory', Integer, nullable=False),
+       Column('owner', String, nullable=False),
+       Column('contact', String, nullable=False),
+       Column('uuid', String, nullable=False),
+       Column('administrator', String, nullable=False, default=False),
+       Column('type_id', String, ForeignKey('types.type_id'), nullable=False),
+       Column('autorestart', Boolean, nullable=False, default=False),
+       Column('cpus', Integer, nullable=False, default=1))
+
+nic_table = Table('nics', meta,
+       Column('machine_id', Integer, ForeignKey('machines.machine_id'), nullable=True),
+       Column('mac_addr', String, nullable=False, primary_key=True),
+       Column('ip', String, nullable=False, unique=True),
+       Column('hostname', String, nullable=True))
+
+disk_table = Table('disks', meta,
+       Column('machine_id', Integer, ForeignKey('machines.machine_id'), nullable=False),
+       Column('guest_device_name', String, nullable=False),
+       Column('size', Integer, nullable=False),
+       PrimaryKeyConstraint('machine_id', 'guest_device_name'))
+
+types_table = Table('types', meta,
+       Column('type_id', String, primary_key=True, nullable=False),
+       Column('description', String, nullable=False),
+       Column('hvm', Boolean, nullable=False),
+       Column('apic', Boolean, nullable=False),
+       Column('acpi', Boolean, nullable=False),
+       Column('pae', Boolean, nullable=False))
+
+cdroms_table = Table('cdroms', meta,
+       Column('cdrom_id', String, primary_key=True, nullable=False),
+       Column('description', String, nullable=False))
+
+autoinstalls_table = Table('autoinstalls', meta,
+       Column('autoinstall_id', String, primary_key=True, nullable=False),
+       Column('description', String, nullable=False),
+       Column('type_id', String, ForeignKey('types.type_id'), nullable=False))
+
+machine_access_table = Table('machine_access', meta,
+       Column('machine_id', Integer, ForeignKey('machines.machine_id'), nullable=False, index=True),
+       Column('user', String, nullable=False, index=True),
+       PrimaryKeyConstraint('machine_id', 'user'))
+
+class Machine(object):
+    def __repr__(self):
+        return "<Machine %s: name='%s' owner='%s'>" % (self.machine_id, self.name, self.owner)
+
+class MachineAccess(object):
+    def __repr__(self):
+        return "<MachineAccess machine='%s' user='%s'>" % (self.machine, self.user)
+
+class NIC(object):
+    def __repr__(self):
+        return "<NIC: mac='%s' machine='%s' ip='%s' hostname='%s'>" % (self.mac_addr, self.machine_id, self.ip, self.hostname)
+
+class Disk(object):
+    def __repr__(self):
+        return "<Disk: machine=%s device=%s size=%s>" % (self.machine_id, self.guest_device_name, self.size)
+
+class Type(object):
+    def __repr__(self):
+        return "<Type %s: %s>" % (self.type_id, self.description)
+
+class CDROM(object):
+    def __repr__(self):
+        return "<CDROM %s: %s>" % (self.cdrom_id, self.description)
+
+class Autoinstall(object):
+    def __repr__(self):
+        return "<Autoinstall %s: %s (%s)>" % (self.autoinstall_id, self.description, self.type.type_id)
+
+assign_mapper(ctx, Machine, machine_table,
+              properties={'nics': relation(NIC, backref="machine", lazy=False),
+                          'disks': relation(Disk, backref="machine", lazy=False),
+                          'type': relation(Type, lazy=False),
+                          'acl': relation(MachineAccess, backref="machine", lazy=False)});
+assign_mapper(ctx, MachineAccess, machine_access_table)
+assign_mapper(ctx, NIC, nic_table)
+assign_mapper(ctx, Disk, disk_table)
+assign_mapper(ctx, Type, types_table)
+assign_mapper(ctx, CDROM, cdroms_table)
+assign_mapper(ctx, Autoinstall, autoinstalls_table)
+
+def clear_cache():
+    """Clear sqlalchemy's cache.
+
+    This _seems_ to be the way; it works, but the docs don't mention
+    it.  Why is this so obscure?"""
+
+    ctx.registry.clear()
Index: /branches/wsgi/packages/sipb-xen-dev/debian/README
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/README	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/README	(revision 571)
@@ -0,0 +1,6 @@
+The Debian Package sipb-xen-dev
+----------------------------
+
+Comments regarding the Package
+
+ -- unknown <hartmans@debian.org>  Fri,  6 Jul 2007 00:52:28 -0400
Index: /branches/wsgi/packages/sipb-xen-dev/debian/README.Debian
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/README.Debian	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/README.Debian	(revision 571)
@@ -0,0 +1,6 @@
+sipb-xen-dev for Debian
+-----------------------
+
+<possible notes regarding this package - if none, delete this file>
+
+ -- unknown <hartmans@debian.org>  Fri,  6 Jul 2007 00:52:28 -0400
Index: /branches/wsgi/packages/sipb-xen-dev/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/changelog	(revision 571)
@@ -0,0 +1,112 @@
+sipb-xen-dev (17) unstable; urgency=low
+
+  * use reprepro more correctly (and include in both stable and unstable)
+
+ -- Greg Price <price@mit.edu>  Sat, 26 Apr 2008 23:47:55 -0400
+
+sipb-xen-dev (16) unstable; urgency=low
+
+  * usage as "sx-build-release <package-name>", no "trunk/packages"
+  * clobber the build-release/<package-name> directory automatically
+
+ -- Greg Price <price@mit.edu>  Sat, 26 Apr 2008 23:12:03 -0400
+
+sipb-xen-dev (15) unstable; urgency=low
+
+  * Stop requiring the current directory to be in the checkout.
+  * Don't do unnecessary write operations
+    (hence prompt less often for a password.)
+
+ -- Greg Price <price@mit.edu>  Sat, 26 Apr 2008 22:45:42 -0400
+
+sipb-xen-dev (14) unstable; urgency=low
+
+  * Use the shallower directory structure.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Sat, 29 Mar 2008 17:25:20 -0400
+
+sipb-xen-dev (13) unstable; urgency=low
+
+  * Update the svn uri automatically.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Fri, 28 Mar 2008 21:38:37 -0400
+
+sipb-xen-dev (12) unstable; urgency=low
+
+  * Update the svn uri to point to the new repository
+
+ -- Nelson Elhage <nelhage@mit.edu>  Wed, 15 Aug 2007 22:58:49 -0400
+
+sipb-xen-dev (11) unstable; urgency=low
+
+  * Use svn export not co to avoid installing .svn directories
+
+ -- Sam Hartman <hartmans@debian.org>  Sat,  4 Aug 2007 19:15:58 -0400
+
+sipb-xen-dev (10) unstable; urgency=low
+
+  * Use svn export not svn co so we don't get .svn directories installed
+
+ -- Sam Hartman <hartmans@debian.org>  Sat,  4 Aug 2007 19:14:51 -0400
+
+sipb-xen-dev (9) unstable; urgency=low
+
+  * Add /debian to apache config
+
+ -- Sam Hartman <hartmans@debian.org>  Sat,  4 Aug 2007 18:54:43 -0400
+
+sipb-xen-dev (8) unstable; urgency=low
+
+  * Can't use ..
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 17:29:54 -0400
+
+sipb-xen-dev (7) unstable; urgency=low
+
+  * Can't use ..
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 17:27:59 -0400
+
+sipb-xen-dev (6) unstable; urgency=low
+
+  * Can't use ..
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 17:26:17 -0400
+
+sipb-xen-dev (5) unstable; urgency=low
+
+  * Bug fix
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 17:25:00 -0400
+
+sipb-xen-dev (4) unstable; urgency=low
+
+  * Blah
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 17:21:28 -0400
+
+sipb-xen-dev (3) unstable; urgency=low
+
+  * Working sx-build-release
+  
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 16:58:46 -0400
+
+sipb-xen-dev (2) unstable; urgency=low
+
+  * Add sipb-xen-repository apache config
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 14:04:03 -0400
+
+sipb-xen-dev (1) unstable; urgency=low
+
+  * Add repository config
+  * update dependencies
+
+ --  Sam Hartman <hartmans@debian.org>  Sun, 15 Jul 2007 13:43:08 -0400
+
+sipb-xen-dev (0) unstable; urgency=low
+
+  * Initial Release.
+
+ -- unknown <hartmans@debian.org>  Fri,  6 Jul 2007 00:52:28 -0400
Index: /branches/wsgi/packages/sipb-xen-dev/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-dev/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/control	(revision 571)
@@ -0,0 +1,13 @@
+Source: sipb-xen-dev
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: debhelper (>= 5)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-dev
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, dpkg-dev-el, emacs21, reprepro, apache2, postfix, screen, dh-make, screen, , fakeroot, pbuilder
+Description: SIPB Xen development server 
+ This package  should be installed on sipb-xen-dev
+ It makes sure that necessary tools are available.
Index: /branches/wsgi/packages/sipb-xen-dev/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/copyright	(revision 571)
@@ -0,0 +1,25 @@
+This is sipb-xen-dev, written and maintained by sipb-xen@mit.edu
+on Fri,  6 Jul 2007 00:52:28 -0400.
+
+The original source can always be found at:
+
+Copyright Holder:  unknown
+
+License:
+
+  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 2 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 package; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+
+On Debian systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
Index: /branches/wsgi/packages/sipb-xen-dev/debian/dirs
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/dirs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/dirs	(revision 571)
@@ -0,0 +1,2 @@
+usr/bin
+usr/sbin
Index: /branches/wsgi/packages/sipb-xen-dev/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/rules	(revision 571)
@@ -0,0 +1,97 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+	CFLAGS += -O0
+else
+	CFLAGS += -O2
+endif
+
+configure: configure-stamp
+configure-stamp:
+	dh_testdir
+	# Add here commands to configure the package.
+
+	touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp 
+	dh_testdir
+
+	# Add here commands to compile the package.
+
+
+	touch $@
+
+clean:
+	dh_testdir
+	dh_testroot
+	rm -f build-stamp configure-stamp
+
+	# Add here commands to clean up after the build process.
+
+
+	dh_clean 
+
+install: build
+	dh_testdir
+	dh_testroot
+	dh_clean -k 
+	dh_installdirs
+
+	# Add here commands to install the package into debian/sipb-xen-dev.
+
+
+
+# Build architecture-independent files here.
+binary-arch: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+	dh_installchangelogs 
+	dh_installdocs
+	dh_installexamples
+	dh_install
+#	dh_installmenu
+#	dh_installdebconf	
+#	dh_installlogrotate
+#	dh_installemacsen
+#	dh_installpam
+#	dh_installmime
+#	dh_python
+#	dh_installinit
+#	dh_installcron
+#	dh_installinfo
+	dh_installman
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+#	dh_perl
+#	dh_makeshlibs
+	dh_installdeb
+	dh_shlibdeps
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
Index: /branches/wsgi/packages/sipb-xen-dev/debian/sipb-xen-dev.dirs
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/sipb-xen-dev.dirs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/sipb-xen-dev.dirs	(revision 571)
@@ -0,0 +1,1 @@
+srv/repository/conf
Index: /branches/wsgi/packages/sipb-xen-dev/debian/sipb-xen-dev.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/debian/sipb-xen-dev.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/debian/sipb-xen-dev.install	(revision 571)
@@ -0,0 +1,4 @@
+reprepro-env /usr/bin
+repository-config/* srv/repository/conf
+sipb-xen-repository etc/apache2/conf.d
+sx-build-release /usr/bin
Index: /branches/wsgi/packages/sipb-xen-dev/repository-config/distributions
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/repository-config/distributions	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/repository-config/distributions	(revision 571)
@@ -0,0 +1,15 @@
+CodeName: unstable
+Components: main
+Uploaders: unsigned
+Architectures: amd64 source
+Origin: sipb
+Notautomatic: yes
+Description: Unreleased sipb-xen software
+
+
+Codename: stable
+Components: main
+Architectures: amd64 source 
+Origin: sipb
+Description: Released sipb-xen  code
+Contents: 1
Index: /branches/wsgi/packages/sipb-xen-dev/repository-config/unsigned
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/repository-config/unsigned	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/repository-config/unsigned	(revision 571)
@@ -0,0 +1,2 @@
+allow  * by unsigned
+
Index: /branches/wsgi/packages/sipb-xen-dev/reprepro-env
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/reprepro-env	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/reprepro-env	(revision 571)
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+export HOME=/home/repository
+exec sudo -u repository  /usr/bin/reprepro -b /srv/repository "$@"
+
Index: /branches/wsgi/packages/sipb-xen-dev/sipb-xen-repository
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/sipb-xen-repository	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/sipb-xen-repository	(revision 571)
@@ -0,0 +1,17 @@
+Alias /sipb-xen /srv/repository
+
+Alias /debian/ /debian/
+
+<Directory /srv/repository>
+		Options Indexes FollowSymLinks MultiViews
+		AllowOverride None
+		Order allow,deny
+		allow from all
+</Directory>
+
+<Directory /debian>
+		Options Indexes FollowSymLinks MultiViews
+		AllowOverride None
+		Order allow,deny
+		allow from all
+</Directory>
Index: /branches/wsgi/packages/sipb-xen-dev/sx-build-release
===================================================================
--- /branches/wsgi/packages/sipb-xen-dev/sx-build-release	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dev/sx-build-release	(revision 571)
@@ -0,0 +1,40 @@
+#!/bin/bash
+svnuri=`svn info /srv/checkout | sed -n 's/^Repository Root: //p'`
+
+set -e
+if  [ $# -ne 1 ] ; then
+	echo " usage: sx-build-release package_name"
+	exit 1
+fi
+
+package=$1
+rm -rf build-release/$package
+svn export $svnuri/trunk/packages/$package build-release/$package
+cd build-release/$package
+
+eval  `perl '-F:\s+' -lane 'print  $F[0]."=".$F[1] if /^Version|^Source|^Distribution/' \
+    <(dpkg-parsechangelog)`
+
+dpkg-buildpackage -us -uc -rfakeroot
+
+if ! svn ls $svnuri/package_tags/$Source >/dev/null 2>&1; then
+  svn mkdir $svnuri/package_tags/$Source \
+      -m "Create package tags directory"
+fi
+if ! svn ls $svnuri/package_tags/$Source/$Version >/dev/null 2>&1; then
+  svn cp $svnuri/trunk/packages/$package $svnuri/package_tags/$Source/$Version \
+      -m "Tag $Version of $Source"
+else
+  echo "$(basename $0): tag already exists, not making again"
+fi
+
+cd ..
+[ $Distribution = 'unstable' ] \
+ || echo "$(basename $0): warning: Distribution is ${Distribution}, script expects unstable"
+changesfile=`pwd`/${Source}_*${Version}*.changes
+reprepro-env include unstable $changesfile
+reprepro-env copy stable unstable ${Source} \
+  $(perl '-F:\s+' -lane 'print $F[1]." " if /^Binary/' <$changesfile)
+
+cd ..
+rm -rf build-release
Index: /branches/wsgi/packages/sipb-xen-dhcp/code/dhcpserver.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/code/dhcpserver.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/code/dhcpserver.py	(revision 571)
@@ -0,0 +1,280 @@
+#!/usr/bin/python
+import sys
+import pydhcplib
+import pydhcplib.dhcp_network
+from pydhcplib.dhcp_packet import *
+from pydhcplib.type_hw_addr import hwmac
+from pydhcplib.type_ipv4 import ipv4
+from pydhcplib.type_strlist import strlist
+import socket
+import IN
+
+import event_logger
+if '__main__' == __name__:
+    event_logger.init("stdout", 'DEBUG', {})
+from event_logger import Log
+
+import psycopg2
+import time
+import sipb_xen_database
+from sqlalchemy import create_engine
+
+dhcp_options = {'subnet_mask': '255.255.0.0',
+                'router': '18.181.0.1',
+                'domain_name_server': '18.70.0.160,18.71.0.151,18.72.0.3',
+                'ip_address_lease_time': 60*60*24}
+
+class DhcpBackend:
+    def __init__(self, database=None):
+        if database is not None:
+            self.database = database
+            sipb_xen_database.connect(create_engine(database))
+    def findNIC(self, mac):
+        sipb_xen_database.clear_cache()
+        for i in range(3):
+            try:
+                value = sipb_xen_database.NIC.get_by(mac_addr=mac)
+            except psycopg2.OperationalError:
+                time.sleep(0.5)
+                if i == 2:  #Try twice to reconnect.
+                    raise
+                #Sigh.  SQLAlchemy should do this itself.
+                sipb_xen_database.connect(create_engine(self.database))
+            else:
+                break
+        return value
+    def find_interface(self, packet):
+        chaddr = hwmac(packet.GetHardwareAddress())
+        nic = self.findNIC(str(chaddr))
+        if nic is None or nic.ip is None:
+            return ("18.181.0.60", None)
+        ipstr = ''.join(reversed(['%02X' % i for i in ipv4(nic.ip).list()]))
+        for line in open('/proc/net/route'):
+            parts = line.split()
+            if parts[1] == ipstr:
+                Log.Output(Log.debug, "find_interface found "+str(nic.ip)+" on "+parts[0])
+                return ("18.181.0.60", parts[0])
+        return ("18.181.0.60", None)
+                            
+    def getParameters(self, **extra):
+        all_options=dict(dhcp_options)
+        all_options.update(extra)
+        options = {}
+        for parameter, value in all_options.iteritems():
+            if value is None:
+                continue
+            option_type = DhcpOptionsTypes[DhcpOptions[parameter]]
+
+            if option_type == "ipv4" :
+                # this is a single ip address
+                options[parameter] = map(int,value.split("."))
+            elif option_type == "ipv4+" :
+                # this is multiple ip address
+                iplist = value.split(",")
+                opt = []
+                for single in iplist :
+                    opt.extend(ipv4(single).list())
+                options[parameter] = opt
+            elif option_type == "32-bits" :
+                # This is probably a number...
+                digit = int(value)
+                options[parameter] = [digit>>24&0xFF,(digit>>16)&0xFF,(digit>>8)&0xFF,digit&0xFF]
+            elif option_type == "16-bits" :
+                digit = int(value)
+                options[parameter] = [(digit>>8)&0xFF,digit&0xFF]
+
+            elif option_type == "char" :
+                digit = int(value)
+                options[parameter] = [digit&0xFF]
+
+            elif option_type == "bool" :
+                if value=="False" or value=="false" or value==0 :
+                    options[parameter] = [0]
+                else : options[parameter] = [1]
+                    
+            elif option_type == "string" :
+                options[parameter] = strlist(value).list()
+            
+            elif option_type == "RFC3397" :
+                parsed_value = ""
+                for item in value:
+                    components = item.split('.')
+                    item_fmt = "".join(chr(len(elt)) + elt for elt in components) + "\x00"
+                    parsed_value += item_fmt
+                
+                options[parameter] = strlist(parsed_value).list()
+            
+            else :
+                options[parameter] = strlist(value).list()
+        return options
+
+    def Discover(self, packet):
+        Log.Output(Log.debug,"dhcp_backend : Discover ")
+        chaddr = hwmac(packet.GetHardwareAddress())
+        nic = self.findNIC(str(chaddr))
+        if nic is None or nic.machine is None:
+            return False
+        ip = nic.ip
+        if ip is None:  #Deactivated?
+            return False
+
+        options = {}
+        if nic.hostname and '.' in nic.hostname:
+            options['host_name'], options['domain_name'] = nic.hostname.split('.', 1)
+        elif nic.machine.name:
+            options['host_name'] = nic.machine.name
+            options['domain_name'] = 'xvm.mit.edu'
+        else:
+            hostname = None
+        if DhcpOptions['domain_search'] in packet.GetOption('parameter_request_list'):
+            options['host_name'] += '.' + options['domain_name']
+            del options['domain_name']
+            options['domain_search'] = ['mit.edu']
+        if ip is not None:
+            ip = ipv4(ip)
+            Log.Output(Log.debug,"dhcp_backend : Discover result = "+str(ip))
+            packet_parameters = self.getParameters(**options)
+
+            # FIXME: Other offer parameters go here
+            packet_parameters["yiaddr"] = ip.list()
+            
+            packet.SetMultipleOptions(packet_parameters)
+            return True
+        return False
+        
+    def Request(self, packet):
+        Log.Output(Log.debug, "dhcp_backend : Request")
+        
+        discover = self.Discover(packet)
+        
+        chaddr = hwmac(packet.GetHardwareAddress())
+        request = packet.GetOption("request_ip_address")
+        if not request:
+            request = packet.GetOption("ciaddr")
+        yiaddr = packet.GetOption("yiaddr")
+
+        if not discover:
+            Log.Output(Log.info,"Unknown MAC address: "+str(chaddr))
+            return False
+        
+        if yiaddr!="0.0.0.0" and yiaddr == request :
+            Log.Output(Log.info,"Ack ip "+str(yiaddr)+" for "+str(chaddr))
+            return True
+        else:
+            Log.Output(Log.info,"Requested ip "+str(request)+" not available for "+str(chaddr))
+        return False
+
+    def Decline(self, packet):
+        pass
+    def Release(self, packet):
+        pass
+    
+
+class DhcpServer(pydhcplib.dhcp_network.DhcpServer):
+    def __init__(self, backend, options = {'client_listenport':68,'server_listenport':67}):
+        pydhcplib.dhcp_network.DhcpServer.__init__(self,"0.0.0.0",options["client_listen_port"],options["server_listen_port"],)
+        self.backend = backend
+        Log.Output(Log.debug, "__init__ DhcpServer")
+
+    def SendDhcpPacketTo(self, To, packet):
+        (ip, intf) = self.backend.find_interface(packet)
+        if intf:
+            out_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            out_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1)
+            out_socket.setsockopt(socket.SOL_SOCKET, IN.SO_BINDTODEVICE, intf)
+            #out_socket.bind((ip, self.listen_port))
+            ret = out_socket.sendto(packet.EncodePacket(), (To,self.emit_port))
+            out_socket.close()
+            return ret
+        else:
+            return self.dhcp_socket.sendto(packet.EncodePacket(),(To,self.emit_port))
+
+    def SendPacket(self, packet):
+        """Encode and send the packet."""
+        
+        giaddr = packet.GetOption('giaddr')
+
+        # in all case, if giaddr is set, send packet to relay_agent
+        # network address defines by giaddr
+        if giaddr!=[0,0,0,0] :
+            agent_ip = ".".join(map(str,giaddr))
+            self.SendDhcpPacketTo(agent_ip,packet)
+            Log.Output(Log.debug, "SendPacket to agent : "+agent_ip)
+
+        # FIXME: This shouldn't broadcast if it has an IP address to send
+        # it to instead. See RFC2131 part 4.1 for full details
+        else :
+            Log.Output(Log.debug, "No agent, broadcast packet.")
+            self.SendDhcpPacketTo("255.255.255.255",packet)
+            
+
+    def HandleDhcpDiscover(self, packet):
+        """Build and send DHCPOFFER packet in response to DHCPDISCOVER
+        packet."""
+
+        logmsg = "Get DHCPDISCOVER packet from " + hwmac(packet.GetHardwareAddress()).str()
+
+        Log.Output(Log.info, logmsg)
+        offer = DhcpPacket()
+        offer.CreateDhcpOfferPacketFrom(packet)
+        
+        if self.backend.Discover(offer):
+            self.SendPacket(offer)
+        # FIXME : what if false ?
+
+
+    def HandleDhcpRequest(self, packet):
+        """Build and send DHCPACK or DHCPNACK packet in response to
+        DHCPREQUEST packet. 4 types of DHCPREQUEST exists."""
+
+        ip = packet.GetOption("request_ip_address")
+        sid = packet.GetOption("server_identifier")
+        ciaddr = packet.GetOption("ciaddr")
+        #packet.PrintHeaders()
+        #packet.PrintOptions()
+
+        if sid != [0,0,0,0] and ciaddr == [0,0,0,0] :
+            Log.Output(Log.info, "Get DHCPREQUEST_SELECTING_STATE packet")
+
+        elif sid == [0,0,0,0] and ciaddr == [0,0,0,0] and ip :
+            Log.Output(Log.info, "Get DHCPREQUEST_INITREBOOT_STATE packet")
+
+        elif sid == [0,0,0,0] and ciaddr != [0,0,0,0] and not ip :
+            Log.Output(Log.info,"Get DHCPREQUEST_INITREBOOT_STATE packet")
+
+        else : Log.Output(Log.info,"Get DHCPREQUEST_UNKNOWN_STATE packet : not implemented")
+
+        if self.backend.Request(packet) : packet.TransformToDhcpAckPacket()
+        else : packet.TransformToDhcpNackPacket()
+
+        self.SendPacket(packet)
+
+
+
+    # FIXME: These are not yet implemented.
+    def HandleDhcpDecline(self, packet):
+        Log.Output(Log.info, "Get DHCPDECLINE packet")
+        self.backend.Decline(packet)
+        
+    def HandleDhcpRelease(self, packet):
+        Log.Output(Log.info,"Get DHCPRELEASE packet")
+        self.backend.Release(packet)
+        
+    def HandleDhcpInform(self, packet):
+        Log.Output(Log.info, "Get DHCPINFORM packet")
+
+        if self.backend.Request(packet) :
+            packet.TransformToDhcpAckPacket()
+            # FIXME : Remove lease_time from options
+            self.SendPacket(packet)
+
+        # FIXME : what if false ?
+
+if '__main__' == __name__:
+    options = { "server_listen_port":67,
+                "client_listen_port":68,
+                "listen_address":"0.0.0.0"}
+    backend = DhcpBackend('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
+    server = DhcpServer(backend, options)
+
+    while True : server.GetNextDhcpPacket()
Index: /branches/wsgi/packages/sipb-xen-dhcp/code/event_logger.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/code/event_logger.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/code/event_logger.py	(revision 571)
@@ -0,0 +1,63 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+import sys
+from logging import *
+
+
+
+class EventLogger:
+    def __init__(self,logtype="stdout",loglevel=WARNING,option={}):
+        self.loglevel = loglevel
+        self.logtype = logtype
+
+        self.info = INFO
+        self.debug = DEBUG
+        self.warn = WARN
+        self.error = ERROR
+        self.critical = CRITICAL
+
+
+        self.logger = getLogger('SipbXenDhcpServer')
+
+        if logtype == "file" :
+            # into file logger
+            handler = FileHandler(option["log_file"])
+            
+        elif logtype == "syslog" :
+            handler = SysLogHandler((option["log_host"],option["log_port"]))
+
+        elif logtype == "http" :
+            handler = HTTPHandler(option["log_host"],option["log_url"],option["log_method"])
+
+        else : # logtype == "stdout" :
+            handler = StreamHandler()
+
+
+
+        handler.setFormatter(Formatter('%(asctime)s %(levelname)s %(message)s'))
+        self.logger.addHandler(handler) 
+        self.logger.setLevel(loglevel)
+
+    def Output(self,level,infostring) :
+        self.logger.log(level,infostring)
+
+def init(logtype,level,path):
+    global Log
+    
+    Log = EventLogger(logtype,eval(level),path)
+    Log.Output(INFO,"EventLogger : Started.")
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/changelog	(revision 571)
@@ -0,0 +1,19 @@
+sipb-xen-dhcp (2) unstable; urgency=low
+
+  * DHCP server should send information to set correct information
+    (fixes #44)
+
+ -- SIPB Xen Projet <sipb-xen@mit.edu>  Mon, 31 Mar 2008 19:43:00 -0400
+
+sipb-xen-dhcp (1.1) unstable; urgency=low
+
+  * Split off pydhcplib into its own package and add that as a dependency
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 16:59:46 -0400
+
+sipb-xen-dhcp (1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 25 Feb 2008 00:05:12 -0500
+
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/control	(revision 571)
@@ -0,0 +1,11 @@
+Source: sipb-xen-dhcp
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), subversion
+Standards-Version: 3.7.2
+
+Package: sipb-xen-dhcp
+Architecture: all
+Depends: ${misc:Depends}, daemon, sipb-xen-database-common, sipb-xen-python-pydhcplib (>= 0.3.2-2)
+Description: Install and enable the DHCP server
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/rules	(revision 571)
@@ -0,0 +1,6 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+
+binary-fixup/sipb-xen-dhcp::
+	svn co https://sipb-xen-dev.mit.edu:1111/trunk/packages/sipb-xen-dhcp/code/ $(DEB_DESTDIR)/usr/local/lib/sipb-xen-dhcp
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/sipb-xen-dhcp.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/sipb-xen-dhcp.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/sipb-xen-dhcp.init	(revision 571)
@@ -0,0 +1,124 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-dhcp
+# Required-Start:    $local_fs $remote_fs
+# Required-Stop:     $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: sipb-xen DHCP server
+# Description:       
+### END INIT INFO
+
+# Author: SIPB Xen Project <sipb-xen@mit.edu>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="The sipb-xen DHCP server"
+NAME=sipb-xen-dhcp
+DAEMON=/usr/local/lib/sipb-xen-dhcp/dhcpserver.py
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	daemon --running -n $NAME && return 1
+	daemon -r -D "$(dirname $DAEMON)" -O daemon.info -E daemon.err -n $NAME -U $DAEMON $DAEMON_ARGS || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	daemon --stop -n $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Many daemons don't delete their pidfiles when they exit.
+	rm -f $PIDFILE
+	return "$RETVAL"
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-dhcp/debian/sipb-xen-dhcp.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-dhcp/debian/sipb-xen-dhcp.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dhcp/debian/sipb-xen-dhcp.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-dns/code/dnsserver.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/code/dnsserver.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/code/dnsserver.py	(revision 571)
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+from twisted.internet import reactor
+from twisted.names import server
+from twisted.names import dns
+from twisted.names import common
+from twisted.internet import defer
+from twisted.python import failure
+
+import sipb_xen_database
+import psycopg2
+import sqlalchemy
+import time
+
+class DatabaseAuthority(common.ResolverBase):
+    """An Authority that is loaded from a file."""
+
+    soa = None
+
+    def __init__(self, domains, database=None):
+        common.ResolverBase.__init__(self)
+        if database is not None:
+            sipb_xen_database.connect(database)
+        self.domains = domains
+        self.soa = dns.Record_SOA(mname='sipb-xen-dev.mit.edu', 
+                                  rname='sipb-xen.mit.edu',
+                                  serial=1, refresh=3600, retry=900,
+                                  expire=3600000, minimum=21600, ttl=3600)
+    def _lookup(self, name, cls, type, timeout = None):
+        for i in range(3):
+            try:
+                value = self._lookup_unsafe(name, cls, type, timeout = None)
+            except (psycopg2.OperationalError, sqlalchemy.exceptions.SQLError):
+                if i == 2:
+                    raise
+                print "Reloading database"
+                time.sleep(0.5)
+                continue
+            else:
+                return value
+
+    def _lookup_unsafe(self, name, cls, type, timeout):
+        sipb_xen_database.clear_cache()
+        if name.lower() in self.domains:
+            domain = name.lower()
+        else:
+            for domain in self.domains:
+                if name.lower().endswith('.'+domain):
+                    break
+            else: #Not us
+                return defer.fail(failure.Failure(dns.DomainError(name)))
+        results = []
+        if cls == dns.IN and type in (dns.A, dns.ALL_RECORDS):
+            host = name[:-len(domain)-1]
+            if not host:
+                ttl = 900
+                record = dns.Record_CNAME('sipb-xen-dev.mit.edu', ttl)
+                results.append(dns.RRHeader(name, dns.CNAME, dns.IN, 
+                                            ttl, record, auth=True))
+            else:
+                value = sipb_xen_database.Machine.get_by(name=host)
+                if value is None or not value.nics:
+                    return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
+                ip = value.nics[0].ip
+                if ip is None:  #Deactivated?
+                    return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
+                ttl = 900
+                record = dns.Record_A(ip, ttl)
+                results.append(dns.RRHeader(name, dns.A, dns.IN, 
+                                            ttl, record, auth=True))
+        authority = []
+        auth_record = dns.Record_NS(name='ns1.xvm.mit.edu', ttl=3600)
+        authority.append(dns.RRHeader(domain, dns.NS, dns.IN,
+                                      3600, auth_record, auth=True))
+        return defer.succeed((results, authority, []))
+        #Doesn't exist
+        return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
+
+if '__main__' == __name__:
+    resolver = DatabaseAuthority(['servers.csail.mit.edu',
+                                  'xvm.mit.edu'],
+                                 'postgres://sipb-xen@sipb-xen-dev/sipb_xen')
+
+    verbosity = 0
+    f = server.DNSServerFactory(authorities=[resolver], verbose=verbosity)
+    p = dns.DNSDatagramProtocol(f)
+    f.noisy = p.noisy = verbosity
+    
+    reactor.listenUDP(53, p)
+    reactor.listenTCP(53, f)
+    reactor.run()
Index: /branches/wsgi/packages/sipb-xen-dns/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/changelog	(revision 571)
@@ -0,0 +1,5 @@
+sipb-xen-dns (1) unstable; urgency=low
+
+  * Initial Release.
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 24 Feb 2008 20:45:28 -0500
+
Index: /branches/wsgi/packages/sipb-xen-dns/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-dns/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/control	(revision 571)
@@ -0,0 +1,11 @@
+Source: sipb-xen-dns
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), subversion
+Standards-Version: 3.7.2
+
+Package: sipb-xen-dns
+Architecture: all
+Depends: ${misc:Depends}, daemon
+Description: Install and enable the DNS server
Index: /branches/wsgi/packages/sipb-xen-dns/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-dns/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/rules	(revision 571)
@@ -0,0 +1,6 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+
+binary-fixup/sipb-xen-dns::
+	svn co https://sipb-xen-dev.mit.edu:1111/trunk/packages/sipb-xen-dns/code/ $(DEB_DESTDIR)/usr/local/lib/sipb-xen-dns
Index: /branches/wsgi/packages/sipb-xen-dns/debian/sipb-xen-dns.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/sipb-xen-dns.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/sipb-xen-dns.init	(revision 571)
@@ -0,0 +1,124 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-dns
+# Required-Start:    $local_fs $remote_fs
+# Required-Stop:     $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: sipb-xen DNS server
+# Description:       
+### END INIT INFO
+
+# Author: SIPB Xen Project <sipb-xen@mit.edu>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="The sipb-xen DNS server"
+NAME=sipb-xen-dns
+DAEMON=/usr/local/lib/sipb-xen-dns/dnsserver.py
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	daemon --running -n $NAME && return 1
+	daemon -r -O daemon.info -E daemon.err -n $NAME -U $DAEMON $DAEMON_ARGS || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	daemon --stop -n $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Many daemons don't delete their pidfiles when they exit.
+	rm -f $PIDFILE
+	return "$RETVAL"
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-dns/debian/sipb-xen-dns.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-dns/debian/sipb-xen-dns.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dns/debian/sipb-xen-dns.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/changelog	(revision 571)
@@ -0,0 +1,56 @@
+sipb-xen-dom0 (2.5) unstable; urgency=low
+
+  * Don't rely on exported ROOT.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Fri, 02 May 2008 02:41:43 -0400
+
+sipb-xen-dom0 (2.4) unstable; urgency=low
+
+  * Add sipb-xen-losetup, a half-sane wrapper around the insane losetup.
+
+ -- Greg Price <price@mit.edu>  Thu,  1 May 2008 01:13:28 -0400
+
+sipb-xen-dom0 (2.3) unstable; urgency=low
+
+  * Depend on generic metapackages/provides instead of particular 
+    versions.
+
+ -- Anders Kaseorg <andersk@sipb-xen.mit.edu>  Sat, 27 Oct 2007 21:23:00 -0400
+
+sipb-xen-dom0 (2.2) unstable; urgency=low
+
+  * Add qemu-ifup script that depends on the device model script and
+    invokes vif-sipbroute.    As a consequence, we don't really support
+    bridged hvms any more.  It's not clear how to get enough information
+    out of qemu to do this.
+  * Call arpspoof with 18.181.0.1's address.  This is unfortunate in two
+    ways.  First, if we renumber we'll need to update the script.  Second,
+    it is possible that someone on 18.181 besides the router may have an
+    address cached.  Unfortunately, it doesn't work right if we don't
+    send the spoofed arp directly to the router.
+
+ -- Sam Hartman <hartmans@debian.org>  Sun, 26 Aug 2007 18:57:17 -0400
+
+sipb-xen-dom0 (2.1) unstable; urgency=low
+
+  * Fix typo in init script 
+  * depend  on dsniff for arpspoof
+
+ -- Sam Hartman <hartmans@debian.org>  Fri, 24 Aug 2007 17:22:20 -0400
+
+sipb-xen-dom0 (2) unstable; urgency=low
+
+  * Add qemu-dm-sipb written by andersk to get us the  domain ID in qemu-ifup 
+  * Add vif-sipbroute, a version of vif-route that does better netwwork
+    isolation and has initial but useless ipv6 support.  This version also
+    uses arpspoof to take over an address for domain migrations.   
+  * Add init script to enable rp_filter, proxy_arp and forwarding for the network config.
+
+ -- Sam Hartman <hartmans@debian.org>  Fri, 24 Aug 2007 17:17:23 -0400
+
+sipb-xen-dom0 (1) unstable; urgency=low
+
+  *  First version
+
+ -- Sam Hartman <hartmans@debian.org>  Sat,  4 Aug 2007 20:20:32 -0400
+
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/control	(revision 571)
@@ -0,0 +1,13 @@
+Source: sipb-xen-dom0
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-dom0
+Architecture: all
+Depends: ${misc:Depends}, emacs, nullmailer, dsniff, xen-hypervisor, xen-utils, lvm2, grub, linux-image-xen-amd64
+Description: SIPB Xen domain 0 configuration
+ This package includes the dependencies and configuration for 
+ a domain 0 sipb-xen server.
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask tabbott@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/rules	(revision 571)
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+
+include /usr/share/cdbs/1/rules/debhelper.mk
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.dirs
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.dirs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.dirs	(revision 571)
@@ -0,0 +1,1 @@
+usr/share/sipb-xen-dom0
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.init	(revision 571)
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -e
+
+case $1 in
+    start)
+    echo 1 >/proc/sys/net/ipv4/ip_forward
+    for foo in all default; do
+    echo 1 >/proc/sys/net/ipv4/conf/$foo/rp_filter 
+    echo 1 >/proc/sys/net/ipv4/conf/$foo/proxy_arp
+    done
+    ;;
+    esac
Index: /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/debian/sipb-xen-dom0.install	(revision 571)
@@ -0,0 +1,2 @@
+files/* .
+xend-config.sxp usr/share/sipb-xen-dom0
Index: /branches/wsgi/packages/sipb-xen-dom0/files/etc/modprobe.d/loop-maxloop
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/files/etc/modprobe.d/loop-maxloop	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/files/etc/modprobe.d/loop-maxloop	(revision 571)
@@ -0,0 +1,1 @@
+options loop max_loop=64
Index: /branches/wsgi/packages/sipb-xen-dom0/files/etc/xen/scripts/qemu-ifup
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/files/etc/xen/scripts/qemu-ifup	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/files/etc/xen/scripts/qemu-ifup	(revision 571)
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+echo "config qemu network with xen bridge for $@"
+
+if [ -z "$domid" ]; then
+    echo "domid is not set!!!" >&2
+    exit 1
+fi
+
+dev=$1
+qemu_online=yes
+XENBUS_PATH=/local/domain/0/backend/vif/${domid}/0
+vif=vif${domid}.0
+export vif qemu_online XENBUS_PATH dev 
+
+exec /etc/xen/scripts/vif-sipbroute online
Index: /branches/wsgi/packages/sipb-xen-dom0/files/etc/xen/scripts/vif-sipbroute
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/files/etc/xen/scripts/vif-sipbroute	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/files/etc/xen/scripts/vif-sipbroute	(revision 571)
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+
+
+
+
+#============================================================================
+# /etc/xen/vif-route
+#
+# Script for configuring a vif in routed mode.
+# The hotplugging system will call this script if it is specified either in
+# the device configuration given to Xend, or the default Xend configuration
+# in /etc/xen/xend-config.sxp.  If the script is specified in neither of those
+# places, then vif-bridge is the default.
+#
+# Usage:
+# vif-route (add|remove|online|offline)
+#
+# Environment vars:
+# vif         vif interface name (required).
+# XENBUS_PATH path to this device's details in the XenStore (required).
+# Read from the store:
+# ip      list of IP networks for the vif, space-separated (default given in
+#         this script).
+# V6PREFIX  prefix of v6 address to use
+# Note that the v6 support is kind of broken because there's not really a way to populate the v6 prefix
+# This script will set up proxy arp  for any ip addresses that are being routed
+# type read to determine if the device is ioemu
+
+#============================================================================
+
+dir=$(dirname "$0")
+. "$dir/vif-common.sh"
+
+main_ip=$(dom0_ip)
+dev=${dev:-${vif}}
+
+case "$command" in
+    online)
+        ifconfig ${dev} ${main_ip} netmask 255.255.255.255 up
+        echo 1 >/proc/sys/net/ipv4/conf/${dev}/proxy_arp
+	echo 1 >/proc/sys/net/ipv4/conf/${dev}/rp_filter 
+        ipcmd='add'
+        cmdprefix=''
+        ;;
+    offline)
+        do_without_error ifdown ${vif}
+	if [ -f /var/run/radvd/radvd.pid.${vif} ] ; then
+	    do_without_error kill `cat /var/run/radvd/radvd.pid.${vif}`
+	    fi
+        ipcmd='del'
+        cmdprefix='do_without_error'
+        ;;
+esac
+
+v6prefix=${v6prefix:-}
+v6prefix=$(xenstore_read_default "$XENBUS_PATH/v6prefix" "$v6prefix")
+vif_type=$(xenstore_read_default "$XENBUS_PATH/type" "viffront")
+if [  ${vif_type} != "ioemu"  -o  x${qemu_online} = xyes ] ; then
+    if [ "${ip}" ] ; then
+    # If we've been given a list of IP addresses, then add routes from dom0 to
+    # the guest using those addresses.
+	for addr in ${ip} ; do
+	    ${cmdprefix} ip route ${ipcmd} ${addr} dev ${dev} src ${main_ip}
+	    arpspoof -i eth0 -t 18.181.0.1 ${addr}&
+	    sleep 5
+	    kill %arpspoof
+	done 
+    fi
+
+    if [ x${v6prefix} != x ] ; then
+	sed -e "s/@interface@/${dev}/" -e "s+@prefix@+${v6prefix}+" /etc/xen/radvd.conf.template >/var/run/radvd.conf.${vif}
+	${cmdprefix} ip -6 addr  ${ipcmd}  fe80::/64 scope link  dev ${dev} 
+	if [ $1 = online ] ; then
+	    radvd  -u radvd -C /var/run/radvd.conf.${vif} -p /var/run/radvd/radvd.pid.${vif}
+	fi
+	${cmdprefix} ip -6 route ${ipcmd} ${v6prefix} dev ${dev} 
+    fi
+fi
+
+handle_iptable
+
+log debug "Successful vif-route $command for $vif."
+if [ "$command" == "online" ]
+then
+  success
+fi
Index: /branches/wsgi/packages/sipb-xen-dom0/files/usr/sbin/qemu-dm-sipb
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/files/usr/sbin/qemu-dm-sipb	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/files/usr/sbin/qemu-dm-sipb	(revision 571)
@@ -0,0 +1,16 @@
+#!/bin/sh
+domid=
+for i; do
+    if [ "$domid" = "_NEXT_" ]; then
+	domid=$i
+	break
+    elif [ "$i" = "-d" ]; then
+	domid=_NEXT_
+    fi
+done
+export domid
+echo 'BEGIN qemu-dm-sipb LOG' >> /tmp/log
+echo "$0 $@" >> /tmp/log
+env >> /tmp/log
+echo 'END qemu-dm-sipb LOG' >> /tmp/log
+exec "/usr/lib/xen-$(/usr/lib/xen-common/bin/xen-utils-version -q)/bin/qemu-dm" "$@"
Index: /branches/wsgi/packages/sipb-xen-dom0/files/usr/sbin/sipb-xen-losetup
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/files/usr/sbin/sipb-xen-losetup	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/files/usr/sbin/sipb-xen-losetup	(revision 571)
@@ -0,0 +1,31 @@
+#!/usr/bin/env python2.5
+
+import sys
+import os
+from subprocess import call, Popen, PIPE
+
+def losetup(source, offset=0):
+  lockfilename = '/tmp/losetup.lock'
+  os.close(os.open(lockfilename, os.O_CREAT+os.O_EXCL)) #lock
+  try:
+    loopdevice = Popen(['losetup', '-f'], stdout=PIPE).communicate()[0].rstrip()
+    if loopdevice == '':
+      raise RuntimeError('out of loop devices for copying VM image: too many at once?')
+    if call(['losetup', '-o', str(offset), loopdevice, source]) != 0:
+      raise RuntimeError('losetup failed')
+  finally:
+    os.unlink(lockfilename) #unlock
+  return loopdevice
+
+def main(*argv):
+  args = argv[1:]
+  os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
+  if not (1 <= len(args) <= 2):
+    print >>sys.stderr, 'usage: %s sourcedevice [offset]' % argv[0]
+    print >>sys.stderr, 'prints resulting loopback device; don\'t forget to losetup -d'
+    return 2
+  print losetup(*args)
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main(*sys.argv))
Index: /branches/wsgi/packages/sipb-xen-dom0/xend-config.sxp
===================================================================
--- /branches/wsgi/packages/sipb-xen-dom0/xend-config.sxp	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-dom0/xend-config.sxp	(revision 571)
@@ -0,0 +1,135 @@
+# -*- sh -*-
+
+#
+# Xend configuration file.
+#
+
+# This example configuration is appropriate for an installation that 
+# utilizes a bridged network configuration. Access to xend via http
+# is disabled.  
+
+# Commented out entries show the default for that entry, unless otherwise
+# specified.
+
+#(logfile /var/log/xen/xend.log)
+#(loglevel DEBUG)
+
+#(xend-http-server no)
+#(xend-unix-server no)
+#(xend-tcp-xmlrpc-server no)
+#(xend-unix-xmlrpc-server yes)
+#(xend-relocation-server no)
+
+#(xend-unix-path /var/lib/xend/xend-socket)
+
+# Port xend should use for the HTTP interface, if xend-http-server is set.
+#(xend-port            8000)
+
+# Port xend should use for the relocation interface, if xend-relocation-server
+# is set.
+#(xend-relocation-port 8002)
+
+# Address xend should listen on for HTTP connections, if xend-http-server is
+# set.
+# Specifying 'localhost' prevents remote connections.
+# Specifying the empty string '' (the default) allows all connections.
+#(xend-address '')
+#(xend-address localhost)
+
+# Address xend should listen on for relocation-socket connections, if
+# xend-relocation-server is set.
+# Meaning and default as for xend-address above.
+#(xend-relocation-address '')
+
+# The hosts allowed to talk to the relocation port.  If this is empty (the
+# default), then all connections are allowed (assuming that the connection
+# arrives on a port and interface on which we are listening; see
+# xend-relocation-port and xend-relocation-address above).  Otherwise, this
+# should be a space-separated sequence of regular expressions.  Any host with
+# a fully-qualified domain name or an IP address that matches one of these
+# regular expressions will be accepted.
+#
+# For example:
+#  (xend-relocation-hosts-allow '^localhost$ ^.*\.example\.org$')
+#
+#(xend-relocation-hosts-allow '')
+
+# The limit (in kilobytes) on the size of the console buffer
+#(console-limit 1024)
+
+##
+# To bridge network traffic, like this:
+#
+# dom0: fake eth0 -> vif0.0 -+
+#                            |
+#                          bridge -> real eth0 -> the network
+#                            |
+# domU: fake eth0 -> vifN.0 -+
+#
+# use
+#
+# (network-script network-bridge)
+#
+# Your default ethernet device is used as the outgoing interface, by default. 
+# To use a different one (e.g. eth1) use
+#
+# (network-script 'network-bridge netdev=eth1')
+#
+# The bridge is named xenbr0, by default.  To rename the bridge, use
+#
+# (network-script 'network-bridge bridge=<name>')
+#
+# It is possible to use the network-bridge script in more complicated
+# scenarios, such as having two outgoing interfaces, with two bridges, and
+# two fake interfaces per guest domain.  To do things like this, write
+# yourself a wrapper script, and call network-bridge from it, as appropriate.
+#
+(network-script network-bridge)
+
+# The script used to control virtual interfaces.  This can be overridden on a
+# per-vif basis when creating a domain or a configuring a new vif.  The
+# vif-bridge script is designed for use with the network-bridge script, or
+# similar configurations.
+#
+# If you have overridden the bridge name using
+# (network-script 'network-bridge bridge=<name>') then you may wish to do the
+# same here.  The bridge name can also be set when creating a domain or
+# configuring a new vif, but a value specified here would act as a default.
+#
+# If you are using only one bridge, the vif-bridge script will discover that,
+# so there is no need to specify it explicitly.
+#
+(vif-script vif-bridge)
+
+
+## Use the following if network traffic is routed, as an alternative to the
+# settings for bridged networking given above.
+#(network-script network-route)
+#(vif-script     vif-route)
+
+
+## Use the following if network traffic is routed with NAT, as an alternative
+# to the settings for bridged networking given above.
+#(network-script network-nat)
+#(vif-script     vif-nat)
+
+
+# Dom0 will balloon out when needed to free memory for domU.
+# dom0-min-mem is the lowest memory level (in MB) dom0 will get down to.
+# If dom0-min-mem=0, dom0 will never balloon out.
+(dom0-min-mem 196)
+
+# In SMP system, dom0 will use dom0-cpus # of CPUS
+# If dom0-cpus = 0, dom0 will take all cpus available
+(dom0-cpus 0)
+
+# Whether to enable core-dumps when domains crash.
+#(enable-dump no)
+
+# The tool used for initiating virtual TPM migration
+#(external-migration-tool '')
+
+# The interface for VNC servers to listen on. Defaults
+# to 127.0.0.1  To restore old 'listen everywhere' behaviour
+# set this to 0.0.0.0
+#(vnc-listen '127.0.0.1')
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/changelog	(revision 571)
@@ -0,0 +1,26 @@
+sipb-xen-guest-installer (1.3) unstable; urgency=low
+
+  * Change sipb-xen-lvcopy so that it fails if you try to clone
+    anything but ice3
+
+ -- Evan Broder <broder@mit.edu>  Sat, 31 May 2008 13:38:37 -0700
+
+sipb-xen-guest-installer (1.2) unstable; urgency=low
+
+  * Work around bug in Python subprocess.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Thu, 15 May 2008 18:46:57 -0400
+
+sipb-xen-guest-installer (1.1) unstable; urgency=low
+
+  * All the changes since last October.  In particular:
+  * This system is in "production", using sx-lvcopy to replicate
+    a golden image on user request and frob it as necessary.
+
+ -- Greg Price <price@mit.edu>  Thu,  1 May 2008 01:09:11 -0400
+
+sipb-xen-guest-installer (1.0) unstable; urgency=low
+
+  * Initial release.
+
+ -- Greg Price <price@mega-man.mit.edu>  Tue, 16 Oct 2007 03:01:08 -0400
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/control	(revision 571)
@@ -0,0 +1,12 @@
+Source: sipb-xen-remctl-auto
+Section: net
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-remctl-auto
+Architecture: all
+Depends: ${misc:Depends}, remctl-server
+Description: Installs the SIPB Xen automatic remctl management system
+ This is our automatic remctl configuration management system.
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/control.in	(revision 571)
@@ -0,0 +1,12 @@
+Source: sipb-xen-guest-installer
+Section: net
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-guest-installer
+Architecture: all
+Depends: ${misc:Depends}
+Description: SIPB Xen automatic guest-image installer system
+ This is our automatic guest-image installer system.
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask tabbott@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/rules	(revision 571)
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+include /usr/share/cdbs/1/rules/debhelper.mk
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/sipb-xen-guest-installer.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/sipb-xen-guest-installer.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/sipb-xen-guest-installer.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-guest-installer/debian/sipb-xen-guest-installer.postinst
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/debian/sipb-xen-guest-installer.postinst	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/debian/sipb-xen-guest-installer.postinst	(revision 571)
@@ -0,0 +1,53 @@
+#!/bin/sh
+# postinst script for sipb-xen-guest-installer
+#
+# see: dh_installdeb(1)
+
+set -e
+set -x
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+	for d in /srv/guest-installer/*; do
+	    # download the actual iso, unpack it, patch it.
+	    wget -nc -i $d/iso.url -O $d/cdrom.orig.iso
+	    MOUNTDIR=$(mktemp -d /mnt/sipb-xen-guest-installer.cdrom.XXXXXX)
+	    mount -t iso9660 -o loop $d/cdrom.orig.iso $MOUNTDIR
+	    rm -rf $d/cdrom.orig; cp -a $MOUNTDIR $d/cdrom.orig
+	    umount $MOUNTDIR; rmdir $MOUNTDIR
+	    rm -rf $d/cdrom; cp -a $d/cdrom.orig $d/cdrom
+	    patch -d $d/cdrom -p1 <$d/cdrom.patch
+	    tar cf - -C $d/cdrom.supplement . | tar xf - -C $d/cdrom
+	done
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
Index: /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/cdrom.supplement/isolinux/isolinux.cfg
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/cdrom.supplement/isolinux/isolinux.cfg	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/cdrom.supplement/isolinux/isolinux.cfg	(revision 571)
@@ -0,0 +1,6 @@
+PROMPT 1
+TIMEOUT 0
+DEFAULT sipb-xen-install
+LABEL sipb-xen-install
+       kernel /install.amd/vmlinuz
+       append auto=true preseed/file=/cdrom/preseed.cfg priority=critical vga=normal fb=false initrd=/install.amd/initrd.gz --
Index: /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/iso.url
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/iso.url	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/iso.url	(revision 571)
@@ -0,0 +1,1 @@
+http://cdimage.debian.org/debian-cd/4.0_r1/amd64/iso-cd/debian-40r1-amd64-businesscard.iso
Index: /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/preseed.cfg
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/preseed.cfg	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/files/srv/guest-installer/etch/preseed.cfg	(revision 571)
@@ -0,0 +1,224 @@
+#### Contents of the preconfiguration file
+d-i debian-installer/locale string en_US
+d-i console-keymaps-at/keymap select us
+
+### Network configuration
+# netcfg will choose an interface that has link if possible. This makes it
+# skip displaying a list if there is more than one interface.
+d-i netcfg/choose_interface select auto
+
+#d-i netcfg/dhcp_timeout string 1
+d-i netcfg/use_dhcp boolean true
+
+# Any hostname and domain names assigned from dhcp take precedence over
+# values set here. However, setting the values still prevents the questions
+# from being shown, even if values come from dhcp.
+# at bottom  #d-i netcfg/get_hostname string fillme
+d-i netcfg/get_domain string mit.edu
+
+# Disable that annoying WEP key dialog.
+d-i netcfg/wireless_wep string
+
+### Mirror settings
+# If you select ftp, the mirror/country string does not need to be set.
+#d-i mirror/protocol string ftp
+d-i mirror/country string enter information manually
+d-i mirror/http/hostname string debian.lcs.mit.edu
+d-i mirror/http/directory string /debian
+d-i mirror/http/proxy string
+
+# Suite to install.
+#d-i mirror/suite string testing
+# Suite to use for loading installer components (optional).
+#d-i mirror/udeb/suite string testing
+
+### Partitioning
+# If the system has free space you can choose to only partition that space.
+# Note: this must be preseeded with a localized (translated) value.
+#d-i partman-auto/init_automatically_partition \
+#      select Guided - use the largest continuous free space
+
+# Alternatively, you can specify a disk to partition. The device name
+# can be given in either devfs or traditional non-devfs format.
+# For example, to use the first disk:
+d-i partman-auto/disk string /dev/discs/disc0/disc
+# In addition, you'll need to specify the method to use.
+# The presently available methods are: "regular", "lvm" and "crypto"
+d-i partman-auto/method string regular
+
+# If one of the disks that are going to be automatically partitioned
+# contains an old LVM configuration, the user will normally receive a
+# warning. This can be preseeded away...
+d-i partman-auto/purge_lvm_from_device boolean true
+# And the same goes for the confirmation to write the lvm partitions.
+#d-i partman-lvm/confirm boolean true
+
+# You can choose from any of the predefined partitioning recipes.
+# Note: this must be preseeded with a localized (translated) value.
+d-i partman-auto/choose_recipe \
+       select All files in one partition (recommended for new users)
+#d-i partman-auto/choose_recipe \
+#       select Separate /home partition
+#d-i partman-auto/choose_recipe \
+#       select Separate /home, /usr, /var, and /tmp partitions
+
+# Or provide a recipe of your own...
+# The recipe format is documented in the file devel/partman-auto-recipe.txt.
+# If you have a way to get a recipe file into the d-i environment, you can
+# just point at it.
+#d-i partman-auto/expert_recipe_file string /hd-media/recipe
+
+# If not, you can put an entire recipe into the preconfiguration file in one
+# (logical) line. This example creates a small /boot partition, suitable
+# swap, and uses the rest of the space for the root partition:
+#d-i partman-auto/expert_recipe string                         \
+#      boot-root ::                                            \
+#              40 50 100 ext3                                  \
+#                      $primary{ } $bootable{ }                \
+#                      method{ format } format{ }              \
+#                      use_filesystem{ } filesystem{ ext3 }    \
+#                      mountpoint{ /boot }                     \
+#              .                                               \
+#              500 10000 1000000000 ext3                       \
+#                      method{ format } format{ }              \
+#                      use_filesystem{ } filesystem{ ext3 }    \
+#                      mountpoint{ / }                         \
+#              .                                               \
+#              64 512 300% linux-swap                          \
+#                      method{ swap } format{ }                \
+#              .
+
+# This makes partman automatically partition without confirmation.
+d-i partman/confirm_write_new_label boolean true
+d-i partman/choose_partition \
+       select Finish partitioning and write changes to disk
+d-i partman/confirm boolean true
+
+### Clock and time zone setup
+# Controls whether or not the hardware clock is set to UTC.
+d-i clock-setup/utc boolean true
+
+# You may set this to any valid setting for $TZ; see the contents of
+# /usr/share/zoneinfo/ for valid values.
+d-i time/zone string US/Eastern
+
+### Apt setup
+# You can choose to install non-free and contrib software.
+#d-i apt-setup/non-free boolean true
+#d-i apt-setup/contrib boolean true
+# Uncomment this if you don't want to use a network mirror.
+#d-i apt-setup/use_mirror boolean false
+# Uncomment this to avoid adding security sources, or
+# add a hostname to use a different server than security.debian.org.
+#d-i apt-setup/security_host string
+
+# Additional repositories, local[0-9] available
+#d-i apt-setup/local0/repository string \
+#       deb http://local.server/debian stable main
+#d-i apt-setup/local0/comment string local server
+# Enable deb-src lines
+#d-i apt-setup/local0/source boolean true
+# URL to the public key of the local repository; you must provide a key or
+# apt will complain about the unauthenticated repository and so the
+# sources.list line will be left commented out
+#d-i apt-setup/local0/key string http://local.server/key
+
+# By default the installer requires that repositories be authenticated
+# using a known gpg key. This setting can be used to disable that
+# authentication. Warning: Insecure, not recommended.
+#d-i debian-installer/allow_unauthenticated string true
+
+### Account setup
+# Skip creation of a root account (normal user account will be able to
+# use sudo).
+#d-i passwd/root-login boolean false
+# Alternatively, to skip creation of a normal user account.
+d-i passwd/make-user boolean false
+
+# Root password, either in clear text
+#d-i passwd/root-password password r00tme
+#d-i passwd/root-password-again password r00tme
+# or encrypted using an MD5 hash.
+# at bottom  #d-i passwd/root-password-crypted password [MD5 hash]
+
+# To create a normal user account.
+#d-i passwd/user-fullname string Debian User
+#d-i passwd/username string debian
+# Normal user's password, either in clear text
+#d-i passwd/user-password password insecure
+#d-i passwd/user-password-again password insecure
+# or encrypted using an MD5 hash.
+#d-i passwd/user-password-crypted password [MD5 hash]
+
+### Base system installation
+# Select the initramfs generator used to generate the initrd for 2.6 kernels.
+#d-i base-installer/kernel/linux/initramfs-generators string yaird
+
+### Boot loader installation
+# Grub is the default boot loader (for x86). If you want lilo installed
+# instead, uncomment this:
+#d-i grub-installer/skip boolean true
+
+# This is fairly safe to set, it makes grub install automatically to the MBR
+# if no other operating system is detected on the machine.
+d-i grub-installer/only_debian boolean true
+
+# This one makes grub-installer install to the MBR if it also finds some other
+# OS, which is less safe as it might not be able to boot that other OS.
+d-i grub-installer/with_other_os boolean true
+
+# Alternatively, if you want to install to a location other than the mbr,
+# uncomment and edit these lines:
+#d-i grub-installer/only_debian boolean false
+#d-i grub-installer/with_other_os boolean false
+#d-i grub-installer/bootdev  string (hd0,0)
+# To install grub to multiple disks:
+#d-i grub-installer/bootdev  string (hd0,0) (hd1,0) (hd2,0)
+
+### Package selection
+tasksel tasksel/first multiselect standard
+
+# Individual additional packages to install
+#d-i pkgsel/include string openssh-server build-essential
+
+### Finishing up the first stage install
+# Avoid that last message about the install being complete.
+d-i finish-install/reboot_in_progress note
+
+# This will prevent the installer from ejecting the CD during the reboot,
+# which is useful in some situations.
+#d-i cdrom-detect/eject boolean false
+
+### Preseeding other packages
+# Depending on what software you choose to install, or if things go wrong
+# during the installation process, it's possible that other questions may
+# be asked. You can preseed those too, of course. To get a list of every
+# possible question that could be asked during an install, do an
+# installation, and then run these commands:
+#   debconf-get-selections --installer > file
+#   debconf-get-selections >> file
+
+
+#### Advanced options
+### Running custom commands during the installation
+# d-i preseeding is inherently not secure. Nothing in the installer checks
+# for attempts at buffer overflows or other exploits of the values of a
+# preconfiguration file like this one. Only use preconfiguration files from
+# trusted locations! To drive that home, and because it's generally useful,
+# here's a way to run any shell command you'd like inside the installer,
+# automatically.
+
+# This first command is run as early as possible, just after
+# preseeding is read.
+#d-i preseed/early_command string anna-install some-udeb
+
+# This command is run just before the install finishes, but when there is
+# still a usable /target directory. You can chroot to /target and use it
+# directly, or use the apt-install and in-target commands to easily install
+# packages and run commands in the target system.
+#d-i preseed/late_command string apt-install zsh; in-target chsh -s /bin/zsh
+
+
+
+# sipb-xen: automatically filled-in values get appended here. 
+# at bottom  #d-i passwd/root-password-crypted password [MD5 hash]
Index: /branches/wsgi/packages/sipb-xen-guest-installer/files/usr/sbin/sipb-xen-lvcopy
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/files/usr/sbin/sipb-xen-lvcopy	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/files/usr/sbin/sipb-xen-lvcopy	(revision 571)
@@ -0,0 +1,90 @@
+#!/usr/bin/env python2.5
+
+import sys
+import os
+import shutil
+import tempfile
+import time
+from subprocess import call, check_call, Popen, PIPE
+
+# Make sure to use up fd 0 to avoid a bug in subprocess in Python <= 2.5.1
+# NB we need to do this on every Python remctl script.
+# See r53624 in Python svn.
+sys.stdin = open('/dev/null')
+
+def losetup(source, offset=0):
+  p = Popen(['sipb-xen-losetup', source, str(offset)], stdout=PIPE)
+  return p.communicate()[0].strip()
+
+def frob_copy_in_vm(target, *args):
+  '''UNUSED: maybe we'll use this someday; it does isolate the frobber.'''
+  # 1. prepare arguments volume
+  args_volume = prefix+target+'_args'
+  args_device = '/dev/xenvg/' + args_volume
+  check_call(['/sbin/lvcreate', 'xenvg', '--name', args_volume, '--size', '4K'])
+  file(args_device, 'w').write('\n'.join(args) + '\n')
+
+  # 2. invoke frobber vm
+  copier_device = '/dev/xenvg/d_wert_hda'
+  check_call(['/usr/sbin/xm', 'create', 'sipb-database',
+              'machine_name='+target,
+              'disks=' + ' '.join(['phy:'+copier_device+',hda,w',
+                                   'phy:'+target_device+',hdc,w',
+                                   'phy:'+args_device+',hdd,w'])])
+
+  # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume])
+
+def frob_copy(target, hostname, rootpw):
+  """target should be an LV device filename"""
+  # 1: mount filesystem
+  fs = losetup(target, 32256)
+  mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp')
+  call(['mount', '-t', 'ext3', fs, mntdir])
+  # 2: do frobbing
+  call(['/usr/sbin/chroot', mntdir, '/post-copy', hostname, rootpw])
+  # 3: clean up
+  call(['umount', mntdir])
+  os.rmdir(mntdir)
+  call(['losetup', '-d', fs])
+
+def duplicate_by_vm(source, target, rootpw, nodd=False, nofrob=False):
+  if source != "ice3":
+     print "Error: can't clone that VM"
+     sys.exit(1)
+  # source, target should be machine names
+  prefix = 'd_'
+  # 1. copy (by dd) source to target
+  source_device = '/dev/xenvg/' + prefix + source + '_hda'
+  target_device = '/dev/xenvg/' + prefix + target + '_hda'
+  if not nodd:
+    check_call(['/bin/dd', 'bs=1M', 'conv=nocreat',
+                'if='+source_device, 'of='+target_device])
+  # 2. frob target
+  if not nofrob:
+    frob_copy(target_device, target, rootpw)
+
+def main(*argv):
+  subcommand = argv[1]
+  args = argv[2:]
+  os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
+  if subcommand == 'lvcopy':
+    kwargs = {}
+    while True:
+      if args[0].startswith('--'):
+        kwargs[args[0][2:]] = True
+        args = args[1:]
+        continue
+      if len(args) != 3:
+        print >>sys.stderr, argv[0]+': bad argument list'
+        return 2
+      break
+    duplicate_by_vm(*args, **kwargs)
+  elif subcommand == 'test':
+    pass
+  else:
+    print >>sys.stderr, argv[0]+": unknown subcommand: "+subcommand
+    return 2
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main(*sys.argv))
Index: /branches/wsgi/packages/sipb-xen-guest-installer/files/usr/sbin/sipb-xen-make-iso
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/files/usr/sbin/sipb-xen-make-iso	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/files/usr/sbin/sipb-xen-make-iso	(revision 571)
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import shutil
+import tempfile
+from subprocess import call
+
+data_dir = '/srv/guest-installer'
+
+def make_debian_cd(name, tmptree, passhashfile):
+  basetree = os.path.join(data_dir, name)
+  cdtree = os.path.join(tmptree, 'cdrom')
+  shutil.copytree(os.path.join(basetree, 'cdrom'), cdtree, symlinks=True)
+  new_preseed = file(os.path.join(cdtree, 'preseed.cfg'), 'w')
+  old_preseed = file(os.path.join(basetree, 'preseed.cfg'))
+  new_preseed.write(old_preseed.read())
+  passhash = file(passhashfile).read()
+  new_preseed.write('d-i passwd/root-password-crypted password '+passhash+'\n')
+  new_preseed.close()
+  output_iso = os.path.join(tmptree, 'install.iso')
+  call('''mkisofs -r -V "SIPB-Xen_Custom_Install_CD" -cache-inodes -J -l
+           -b isolinux/isolinux.bin -c isolinux/boot.cat
+           -no-emul-boot -boot-load-size 4 -boot-info-table'''.split()
+       + ['-o', output_iso, cdtree])
+  return output_iso
+
+if __name__ == '__main__':
+  print make_debian_cd(*sys.argv[1:])
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/copying-installer.sh
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/copying-installer.sh	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/copying-installer.sh	(revision 571)
@@ -0,0 +1,15 @@
+#!/bin/bash
+# hda is the copying-installer image itself
+# hdb is unused (was the source image)
+# hdc is the target image
+# hdd is the arguments image
+
+exec 3</dev/hdd
+read -r -u 3 HOSTNAME
+read -r -u 3 ROOTPW
+exec 3<&-
+
+mount /dev/hdc1 /mnt/new
+/mnt/new/post-copy /mnt/new "$HOSTNAME" "$ROOTPW"
+umount /mnt/new
+sync
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap-commands
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap-commands	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap-commands	(revision 571)
@@ -0,0 +1,90 @@
+#!/bin/sh
+#http://www.debian.org/releases/stable/i386/apds03.html.en
+
+#SRC=/root/debootstrap.d
+#SRCHOSTNAME=moo17
+#HOSTNAME=debootstrap-test
+#pick a hostname!
+
+HOSTNAME=moo19
+DIST=etch
+IP=18.181.0.186
+
+PARTITIONS=/root/neboat/testpart
+
+VOLUME=/dev/mapper/xenvg-d_${HOSTNAME}_hda
+
+###
+###  Create the device for SIPB-Xen purposes
+###
+sfdisk -H 255 -S 63 $VOLUME <$PARTITIONS
+kpartx -a $VOLUME
+#SWAPDEV=$(sipb-xen-losetup $VOLUME $(( 7903980 * 512 )))
+#IMAGEDEV=$(sipb-xen-losetup $VOLUME $(( 63 * 512 )))
+# mkfs.ext3 -b 1024 $IMAGEDEV $(( 7903917 / 2 ))
+
+#CREATE_IMAGE_CONFIG=./paravm-create.conf
+
+#xen-create-image --image-dev $IMAGEDEV --swap-dev $SWAPDEV --fs ext3 --config $CREATE_IMAGE_CONFIG \
+#	--dist $DIST --hostname $HOSTNAME
+
+# xen-create-image --partitions sipb-xen --memory=256MB --ide --arch amd64 --dist etch --hostname moo19 --mirror http://debian.lcs.mit.edu/debian --ip=18.181.0.186 --netmask=255.255.0.0 --gateway=18.181.0.1 --cache
+
+xen-create-image --image-dev ${VOLUME}1 --swap-dev ${VOLUME}2 --fs ext3 --ide --memory 256M --arch amd64 --dist etch --hostname $HOSTNAME --mirror http://debian.lcs.mit.edu/debian --ip=$IP --netmask=255.255.0.0 --gateway=18.181.0.1 --cache
+
+kpartx -d $VOLUME
+#losetup -d $SWAPDEV
+#losetup -d $IMAGEDEV
+
+### This should be the end of the script
+### The material past this point is solely for reference purposes
+
+
+# time debootstrap --arch amd64 etch "$DEST" http://debian.lcs.mit.edu/debian
+
+# cp -a $SRC/fstab $DEST/etc/fstab
+# cp -a $SRC/interfaces $DEST/etc/network/interfaces
+# cp -a $SRC/sources.list $DEST/etc/apt/sources.list
+# cp -a $SRC/update-grub.diff $DEST/root/update-grub.diff
+# echo $HOSTNAME > $DEST/etc/hostname
+# sed s/$SRCHOSTNAME/$HOSTNAME/g $SRC/hosts > $DEST/etc/hosts
+
+# mount -t proc proc $DEST/proc
+
+# LANG=C chroot "$DEST" <<ENDCHROOT
+
+# export TERM=xterm-color
+# mount -a
+
+# cd /media
+# mkdir cdrom0
+# ln -s cdrom0 cdrom
+# cd /
+# ln -s media/cdrom
+
+# echo $'y\n3\nEastern' | tzconfig
+# export DEBIAN_FRONTEND=noninteractive
+
+# echo 'locales locales/locales_to_be_generated multiselect     en_US ISO-8859-1' | debconf-set-selections
+
+
+# aptitude update
+# aptitude -y install locales linux-image-xen-amd64
+# aptitude --without-recommends -y install ~pstandard ~prequired ~pimportant
+# #strace tasksel install standard 2>&1 | less
+# aptitude clean
+
+# mkdir /boot/grub
+# aptitude install grub
+# patch /usr/sbin/update-grub </root/update-grub.diff
+# update-grub
+# patch /boot/grub/menu.lst </root/menu.lst.diff
+# patch /etc/inittab </root/inittab.diff
+
+# rm /root/update-grub.diff
+# ENDCHROOT
+
+# ## kill atd and rpc.statd that are using $DEST
+# umount $DEST/proc
+# umount $DEST
+# losetup -d $LODEV
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/fstab
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/fstab	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/fstab	(revision 571)
@@ -0,0 +1,7 @@
+# /etc/fstab: static file system information.
+#
+# <file system> <mount point>   <type>  <options>       <dump>  <pass>
+proc            /proc           proc    defaults        0       0
+/dev/hda1       /               ext3    defaults,errors=remount-ro 0       1
+/dev/hda5       none            swap    sw              0       0
+/dev/hdc        /media/cdrom0   udf,iso9660 user,noauto     0       0
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/hosts
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/hosts	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/hosts	(revision 571)
@@ -0,0 +1,10 @@
+127.0.0.1       localhost
+127.0.1.1       moo17.servers.csail.mit.edu     moo17
+
+# The following lines are desirable for IPv6 capable hosts
+::1     ip6-localhost ip6-loopback
+fe00::0 ip6-localnet
+ff00::0 ip6-mcastprefix
+ff02::1 ip6-allnodes
+ff02::2 ip6-allrouters
+ff02::3 ip6-allhosts
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/inittab.diff
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/inittab.diff	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/inittab.diff	(revision 571)
@@ -0,0 +1,11 @@
+--- inittab.orig	2008-04-22 05:42:50.000000000 -0400
++++ inittab	2008-04-22 05:43:04.000000000 -0400
+@@ -60,7 +60,7 @@
+ 
+ # Example how to put a getty on a serial line (for a terminal)
+ #
+-#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
++T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
+ #T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100
+ 
+ # Example how to put a getty on a modem line.
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/interfaces
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/interfaces	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/interfaces	(revision 571)
@@ -0,0 +1,10 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+# The primary network interface
+allow-hotplug eth0
+iface eth0 inet dhcp
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/menu.lst.diff
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/menu.lst.diff	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/menu.lst.diff	(revision 571)
@@ -0,0 +1,11 @@
+--- menu.lst.orig	2008-04-22 05:41:32.000000000 -0400
++++ menu.lst	2008-04-22 05:42:15.000000000 -0400
+@@ -59,7 +59,7 @@
+ ## e.g. kopt=root=/dev/hda1 ro
+ ##      kopt_2_6_8=root=/dev/hdc1 ro
+ ##      kopt_2_6_8_2_686=root=/dev/hdc2 ro
+-# kopt=root=/dev/hda1 ro
++# kopt=root=/dev/hda1 ro console=ttyS0,9600,8n1
+ 
+ ## default grub root device
+ ## e.g. groot=(hd0,0)
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/sources.list
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/sources.list	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/sources.list	(revision 571)
@@ -0,0 +1,6 @@
+
+deb http://debian.lcs.mit.edu/debian/ etch main
+deb-src http://debian.lcs.mit.edu/debian/ etch main
+
+deb http://security.debian.org/ etch/updates main
+deb-src http://security.debian.org/ etch/updates main
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/update-grub.diff
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/update-grub.diff	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/debootstrap.d/update-grub.diff	(revision 571)
@@ -0,0 +1,15 @@
+--- update-grub.orig	2008-04-22 01:13:34.000000000 -0400
++++ update-grub	2008-04-22 01:13:13.000000000 -0400
+@@ -1036,10 +1036,10 @@
+ 
+ 	if [ ! "$in_domU" ] && [ "$is_xen" ]; then
+ 	  # skip xen kernels
+-          continue
++	  :
+         elif [ "$in_domU" ] && ! [ "$is_xen" ]; then
+ 	  # skip non-xen kernels
+-	  continue
++	  :
+         fi
+         kern="/boot/$kern"
+ 	newerKernels=""
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/post-copy
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/post-copy	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/post-copy	(revision 571)
@@ -0,0 +1,6 @@
+#!/bin/bash
+TARGET="$1"
+HOSTNAME="$2"
+ROOTPW="$3"
+(printf "%s\n" "$ROOTPW"; sleep .2; printf "%s\n" "$ROOTPW") \
+ | /usr/sbin/chroot "$TARGET" /usr/bin/passwd root
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/testpart
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/testpart	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/testpart	(revision 571)
@@ -0,0 +1,4 @@
+,491,L,*
+,31,S
+;
+;
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/90-make-fstab
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/90-make-fstab	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/90-make-fstab	(revision 571)
@@ -0,0 +1,95 @@
+#!/bin/sh
+#
+#  This script is responsible for setting up /etc/fstab upon the
+# new instance.
+#
+#  This should be a simple job, but it is complicated by some of the
+# differences between filesystems - some root filesystems will require
+# the installation of new packages, and we have to handle that here.
+#
+# Steve
+# --
+# http://www.steve.org.uk/
+
+prefix=$1
+
+#
+#  Source our common functions
+#
+if [ -e /usr/lib/xen-tools/common.sh ]; then
+    . /usr/lib/xen-tools/common.sh
+else
+    . ./hooks/common.sh
+fi
+
+
+#
+# Log our start
+#
+logMessage Script $0 starting
+
+
+#
+# Make sure we use ide style device names if required
+#
+device=sda
+if [ "${ide}" ]; then
+    device=hda
+fi
+
+#
+#  Now we have the options we can create the fstab.
+#
+has_xfs=0
+has_reiserfs=0
+cat <<E_O_FSTAB > ${prefix}/etc/fstab
+# /etc/fstab: static file system information.
+#
+# <file system> <mount point>   <type>  <options>       <dump>  <pass>
+proc            /proc           proc    defaults        0       0
+E_O_FSTAB
+for part in `seq 1 ${NUMPARTITIONS}`; do
+    eval "PARTITION=\"\${PARTITION${part}}\""
+    OLDIFS="${IFS}"
+    IFS=:
+    x=0
+    for partdata in ${PARTITION}; do
+        eval "partdata${x}=\"${partdata}\""
+        x=$(( $x+1 ))
+    done
+    IFS="${OLDIFS}"
+    
+    case "${partdata2}" in
+        xfs)
+            has_xfs=1
+            ;;
+        reiserfs)
+            has_reiserfs=1
+            ;;
+    esac
+    
+    # This assumes a particular partition table for our ParaVM's (SIPB)
+    if [ "${partdata2}" = "swap" ]; then
+        echo "/dev/${device}2 none swap sw 0 0" >> ${prefix}/etc/fstab
+    else
+        echo "/dev/${device}1 ${partdata3} ${partdata2} ${partdata4} 0 1" >> ${prefix}/etc/fstab
+    fi
+done
+
+
+#
+#  Finally we can install any required packages for the given root
+# filesystem
+#
+if [ $has_xfs -eq 1 ]; then
+    installDebianPackage ${prefix} xfsprogs
+fi
+if [ $has_reiserfs -eq 1 ]; then
+    installDebianPackage ${prefix} reiserfsprogs
+fi
+
+
+#
+#  Log our finish
+#
+logMessage Script $0 finished
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/97-setup-grub
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/97-setup-grub	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/97-setup-grub	(revision 571)
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+#  This script sets up the ParaVM to use pygrub
+#
+
+
+prefix=$1
+
+
+#
+#  Source our common functions
+#
+if [ -e /usr/lib/xen-tools/common.sh ]; then
+    . /usr/lib/xen-tools/common.sh
+else
+    . ./hooks/common.sh
+fi
+
+
+#
+# Log our start
+#
+logMessage Script $0 starting
+
+#
+# Install the xen kernel
+#
+installDebianPackage ${prefix} linux-image-xen-amd64
+
+#
+# Install grub package
+#
+installDebianPackage ${prefix} grub
+
+#
+# Make the /boot/grub directory
+#
+mkdir -p ${prefix}/boot/grub
+
+#
+# Patch update-grub to see xen kernels
+#
+patch -l ${prefix}/usr/sbin/update-grub </usr/lib/xen-tools/etch.d/patches/update-grub.patch
+
+#
+# Update Grub
+#
+chroot ${prefix} /usr/sbin/update-grub -y
+
+#
+# Patch Grub menu
+#
+patch -l ${prefix}/boot/grub/menu.lst </usr/lib/xen-tools/etch.d/patches/menu.lst.patch
+# --- menu.lst.orig2008-04-22 05:41:32.000000000 -0400
+# +++ menu.lst2008-04-22 05:42:15.000000000 -0400
+# @@ -59,7 +59,7 @@
+#  ## e.g. kopt=root=/dev/hda1 ro
+#  ##      kopt_2_6_8=root=/dev/hdc1 ro
+#  ##      kopt_2_6_8_2_686=root=/dev/hdc2 ro
+# -# kopt=root=/dev/hda1 ro
+# +# kopt=root=/dev/hda1 ro console=ttyS0,9600,8n1
+ 
+#  ## default grub root device
+#  ## e.g. groot=(hd0,0)
+# EOF
+
+#
+# Patch inittab
+#
+patch -l ${prefix}/etc/inittab </usr/lib/xen-tools/etch.d/patches/inittab.patch
+# --- inittab.orig2008-04-22 05:42:50.000000000 -0400
+# +++ inittab2008-04-22 05:43:04.000000000 -0400
+# @@ -60,7 +60,7 @@
+ 
+#  # Example how to put a getty on a serial line (for a terminal)
+#  #
+# -#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
+# +T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
+#  #T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100
+ 
+#  # Example how to put a getty on a modem line.
+# EOF
+
+#
+# Log our finish
+#
+logMessage Script $0 finished
+
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/inittab.patch
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/inittab.patch	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/inittab.patch	(revision 571)
@@ -0,0 +1,11 @@
+--- temp/etc/inittab	2008-05-04 02:48:09.000000000 -0400
++++ inittab	2008-05-04 02:52:09.000000000 -0400
+@@ -60,7 +60,7 @@
+ 
+ # Example how to put a getty on a serial line (for a terminal)
+ #
+-#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
++T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
+ #T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100
+ 
+ # Example how to put a getty on a modem line.
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/menu.lst.patch
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/menu.lst.patch	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/menu.lst.patch	(revision 571)
@@ -0,0 +1,11 @@
+--- temp/boot/grub/menu.lst	2008-05-04 03:17:31.000000000 -0400
++++ menu.lst	2008-05-04 03:19:09.000000000 -0400
+@@ -59,7 +59,7 @@
+ ## e.g. kopt=root=/dev/hda1 ro
+ ##      kopt_2_6_8=root=/dev/hdc1 ro
+ ##      kopt_2_6_8_2_686=root=/dev/hdc2 ro
+-# kopt=root=/dev/hda1 ro
++# kopt=root=/dev/hda1 ro console=ttyS0,9600,8n1
+ 
+ ## default grub root device
+ ## e.g. groot=(hd0,0)
Index: /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/update-grub.patch
===================================================================
--- /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/update-grub.patch	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-guest-installer/partial/xen-create-image-mods/patches/update-grub.patch	(revision 571)
@@ -0,0 +1,16 @@
+--- update-grub.orig	2008-05-03 23:30:37.000000000 -0400
++++ update-grub	2008-05-03 23:30:55.000000000 -0400
+@@ -911,13 +911,6 @@
+ 		is_xen=
+ 	fi
+ 
+-	if [ ! "$in_domU" ] && [ "$is_xen" ]; then
+-	  # skip xen kernels
+-          continue
+-        elif [ "$in_domU" ] && ! [ "$is_xen" ]; then
+-	  # skip non-xen kernels
+-	  continue
+-        fi
+         kern="/boot/$kern"
+ 	newerKernels=""
+ 	for i in $sortedKernels ; do
Index: /branches/wsgi/packages/sipb-xen-host-master/notes
===================================================================
--- /branches/wsgi/packages/sipb-xen-host-master/notes	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-host-master/notes	(revision 571)
@@ -0,0 +1,43 @@
+
+### for kerberos access; need keytab ###
+aptitude install krb5-user ntpdate
+cat >>/etc/ssh/sshd_config <<EOF
+# accept Kerberos
+GSSAPIAuthentication yes
+GSSAPICleanupCredentials yes
+GSSAPIKeyExchange yes
+EOF
+/etc/init.d/ssh reload
+# Set default realm to ATHENA.MIT.EDU
+printf "krb5-config\tkrb5-config/default_realm\tstring\tATHENA.MIT.EDU" | debconf-set-selections
+dpkg-reconfigure -fnoninteractive krb5-config
+# in an ideal world, all of the above is just
+#   aptitude install debathena-{ssh-server,kerberos}-config
+
+# install keytab
+# install /root/.k5login
+ntpdate
+
+### for collaboration ###
+aptitude install screen
+
+### the main event ###
+scp black-mesa:/etc/apt/sources.list.d/* /etc/apt/sources.list.d
+apt-get update
+aptitude install sipb-xen-host-master
+# or scp sx-blade-1:*.deb . && dpkg -i *.deb && apt-get -f install
+
+### clustering... ###
+## should become own package once we figure it out.
+scp OLD-HOST:/etc/cluster/cluster.conf /etc/cluster/cluster.conf
+
+aptitude install clvm cman redhat-cluster-modules-2.6-xen-amd64
+cat >>/etc/modules <<EOF
+cman
+dlm
+EOF
+
+
+# to take up the new Xen-hypervisor kernel
+# shutdown -r now
+
Index: /branches/wsgi/packages/sipb-xen-host-master/sipb-xen-host-master.equivs
===================================================================
--- /branches/wsgi/packages/sipb-xen-host-master/sipb-xen-host-master.equivs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-host-master/sipb-xen-host-master.equivs	(revision 571)
@@ -0,0 +1,21 @@
+Section: base
+Priority: extra
+Standards-Version: 3.6.2
+
+Package: sipb-xen-host-master
+Version: 0.1
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Architecture: all
+Description: Master package for sipb-xen hosts
+ This package depends on everything a sipb-xen host machine needs.
+Depends: sipb-xen-base,
+ sipb-xen-clvm-config,
+ sipb-xen-console-server,
+ sipb-xen-database-client,
+ sipb-xen-database-common,
+ sipb-xen-dhcp,
+ sipb-xen-dom0,
+ sipb-xen-guest-installer,
+ sipb-xen-python-pydhcplib,
+ sipb-xen-remctl-auto,
+ sipb-xen-vnc-server,
Index: /branches/wsgi/packages/sipb-xen-iptables/code/iptables.rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/code/iptables.rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/code/iptables.rules	(revision 571)
@@ -0,0 +1,17 @@
+# Generated by iptables-save v1.3.6 on Mon Oct  8 01:59:16 2007
+*filter
+:INPUT ACCEPT [366:44912]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [292:53151]
+-A FORWARD -d 18.181.0.60 -i eth0 -o eth0 -p tcp -m tcp --dport 10003 -j ACCEPT 
+COMMIT
+# Completed on Mon Oct  8 01:59:16 2007
+# Generated by iptables-save v1.3.6 on Mon Oct  8 01:59:16 2007
+*nat
+:PREROUTING ACCEPT [5:300]
+:POSTROUTING ACCEPT [8:674]
+:OUTPUT ACCEPT [8:674]
+-A PREROUTING -s ! 18.181.0.60 -i eth0 -p tcp -m tcp --dport 10003 -j DNAT --to-destination 18.181.0.60:10003 
+-A POSTROUTING -d 18.181.0.60 -o eth0 -p tcp -m tcp --dport 10003 -j SNAT --to-source 18.181.0.62 
+COMMIT
+# Completed on Mon Oct  8 01:59:16 2007
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/changelog	(revision 571)
@@ -0,0 +1,5 @@
+sipb-xen-iptables (1) unstable; urgency=low
+
+  * Initial Release.
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Fri, 28 Mar 2008 21:22:12 -0500
+
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/control	(revision 571)
@@ -0,0 +1,11 @@
+Source: sipb-xen-iptables
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), subversion
+Standards-Version: 3.7.2
+
+Package: sipb-xen-iptables
+Architecture: all
+Depends: ${misc:Depends}, iptables
+Description: Configure at boot the iptables rules for the VNC proxy client
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/rules	(revision 571)
@@ -0,0 +1,6 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+
+binary-fixup/sipb-xen-iptables::
+	svn co https://sipb-xen-dev.mit.edu:1111/trunk/packages/sipb-xen-iptables/code/ $(DEB_DESTDIR)/usr/local/share/sipb-xen-iptables
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/sipb-xen-iptables.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/sipb-xen-iptables.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/sipb-xen-iptables.init	(revision 571)
@@ -0,0 +1,112 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-iptables
+# Required-Start:    $local_fs $remote_fs
+# Required-Stop:     $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: sipb-xen iptables rules
+# Description:       
+### END INIT INFO
+
+# Author: SIPB Xen Project <sipb-xen@mit.edu>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Load the sipb-xen iptables rules"
+NAME=sipb-xen-iptables
+RULES=/usr/local/share/sipb-xen-iptables/iptables.rules
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	/sbin/iptables-restore < $RULES
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	return 0
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-iptables/debian/sipb-xen-iptables.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-iptables/debian/sipb-xen-iptables.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-iptables/debian/sipb-xen-iptables.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/COPYING
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/COPYING	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/COPYING	(revision 571)
@@ -0,0 +1,284 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/README
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/README	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/README	(revision 571)
@@ -0,0 +1,20 @@
+Pydhcplib is a python library to read/write and encode/decode dhcp
+packet on network. 
+
+
+
+Installation :
+--------------
+On Debian Sarge, simply run "./setup.py install". Python modules will be
+installed in /usr/lib/python2.X/site-packages/pydhcplib/. 
+
+If you want to install it on a different location, use the --prefix on 
+the setup.py command line like this : 
+./setup.py install --prefix=/rootpath/to/your/location/
+
+
+How to use pydhcplib :
+----------------------
+Look in the examples directory to learn how to use the modules.
+man pydhcp
+man pydhcplib
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/changelog	(revision 571)
@@ -0,0 +1,12 @@
+sipb-xen-python-pydhcplib (0.3.2-2) unstable; urgency=low
+
+  * Add a patch to allow DHCP option 119 (domain-search) to work
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Mon, 31 Mar 2008 19:33:13 -0400
+
+sipb-xen-python-pydhcplib (0.3.2-1) unstable; urgency=low
+
+  * Initial release.
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Sun, 30 Mar 2008 16:03:05 -0400
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/control	(revision 571)
@@ -0,0 +1,12 @@
+Source: sipb-xen-python-pydhcplib
+Section: python
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 5), quilt, patchutils (>= 0.2.25), cdbs (>= 0.4.27-1), debhelper (>= 5.0.37.2), cdbs (>= 0.4.43), python-all-dev (>= 2.3.5-11), python-support (>= 0.3.2)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-python-pydhcplib
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}
+Provides: ${python:Provides}
+Description: A pure Python library for handling DHCP packets and requests
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/control.in	(revision 571)
@@ -0,0 +1,12 @@
+Source: sipb-xen-python-pydhcplib
+Section: python
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-python-pydhcplib
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}
+Provides: ${python:Provides}
+Description: A pure Python library for handling DHCP packets and requests
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/copyright	(revision 571)
@@ -0,0 +1,31 @@
+This package was debianized by for the SIPB Xen Project <sipb-xen@mit.edu>
+on Sun, 30 Mar 2008 16:40:21 -0400.
+
+It was downloaded from http://pydhcplib.tuxfamily.org/download/
+
+Upstream Author(s): 
+
+    Mathieu Ignacio <mignacio@april.org>
+
+Copyright: 
+
+    Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+
+License:
+
+    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 2 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 package; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+
+On Debian systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/docs
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/docs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/docs	(revision 571)
@@ -0,0 +1,1 @@
+README
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/patches/non-standard-options.patch
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/patches/non-standard-options.patch	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/patches/non-standard-options.patch	(revision 571)
@@ -0,0 +1,16 @@
+Index: pydhcplib/dhcp_basic_packet.py
+===================================================================
+--- sipb-xen-python-pydhcp/pydhcplib/dhcp_basic_packet.py
++++ sipb-xen-python-pydhcp/pydhcplib/dhcp_basic_packet.py
+@@ -99,9 +99,9 @@
+                              "char":[1,0,1], "16-bits":[2,0,1],
+                              "32-bits":[4,0,1], "identifier":[0,2,1]}
+             
+-            specs = fields_specs[DhcpOptionsTypes[DhcpOptions[name]]]
++            specs = fields_specs.get(DhcpOptionsTypes[DhcpOptions[name]], [])
+             length = len(value)
+-            if (specs[0]!=0 and specs==length) or (specs[1]<=length and length%specs[2]==0):
++            if specs == [] or (specs[0]!=0 and specs==length) or (specs[1]<=length and length%specs[2]==0):
+                 self.options_data[name] = value
+                 return True
+             else :
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/patches/series
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/patches/series	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/patches/series	(revision 571)
@@ -0,0 +1,1 @@
+non-standard-options.patch
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/pycompat
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/pycompat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/pycompat	(revision 571)
@@ -0,0 +1,1 @@
+2
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/pyversions
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/pyversions	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/pyversions	(revision 571)
@@ -0,0 +1,1 @@
+2.4-
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/debian/rules	(revision 571)
@@ -0,0 +1,8 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL=1
+DEB_PYTHON_SYSTEM=pysupport
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/patchsys-quilt.mk
+include /usr/share/cdbs/1/class/python-distutils.mk
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/client_example.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/client_example.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/client_example.py	(revision 571)
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+#
+# pydhcplib
+# Copyright (C) 2005,2006 Mathieu Ignacio -- mignacio@april.org
+#
+# This file is part of pydhcplib.
+# Pydhcplib 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+from pydhcplib.dhcp_packet import *
+from pydhcplib.dhcp_network import *
+
+
+netopt = {'client_listen_port':68,
+           'server_listen_port':67,
+           'listen_address':"0.0.0.0"}
+
+class Client(DhcpClient):
+    def __init__(self, options):
+        DhcpClient.__init__(self,options["listen_address"],
+                            options["client_listen_port"],
+                            options["server_listen_port"])
+        
+    def HandleDhcpOffer(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+        
+    def HandleDhcpAck(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+
+    def HandleDhcpNack(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+        
+
+client = Client(netopt)
+
+while True :
+    client.GetNextDhcpPacket()
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/gen_packet_example.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/gen_packet_example.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/gen_packet_example.py	(revision 571)
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+
+from pydhcplib.dhcp_packet import DhcpPacket
+from pydhcplib.type_strlist import strlist
+from pydhcplib.type_ipv4 import ipv4
+
+
+packet = DhcpPacket()
+
+packet.SetOption("domain_name",strlist("anemon.org").list())
+packet.SetOption("router",ipv4("192.168.0.1").list()+[6,4,2,1])
+packet.SetOption("time_server",[100,100,100,7,6,4,2,1])
+packet.SetOption("yiaddr",[192,168,0,18])
+
+packet.PrintHeaders()
+packet.PrintOptions()
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/hwaddr_example.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/hwaddr_example.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/hwaddr_example.py	(revision 571)
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+
+from pydhcplib.type_hw_addr import hwmac
+
+
+address = hwmac()
+print "a0 : ",address
+
+address1 = hwmac("ff:11:22:33:44:55")
+print "a1 : ",address1
+
+address2 = hwmac("f6.16.26.36.46.56")
+print "a2 : ",address2
+
+address3 = hwmac("ff.11-22:33-44.55")
+print "a3 : ",address3
+
+
+
+if address1 == address2 : print "test 1 : ",address1, "==",address2
+else : print "test 1 : " ,address1, "!=",address2
+
+if address1 == address3 : print "test 2 : ", address1, "==",address3
+else : print "test 2 : ", address1, "!=",address3
+
+
+
+address4 = hwmac([186, 45, 67, 176, 6, 11])
+address5 = hwmac("ba:2d:43:b0:06:0c")
+    
+print "b0 : ", address4,address4.list()
+print "b1 : ", address5,address5.list()
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/ipv4_example.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/ipv4_example.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/ipv4_example.py	(revision 571)
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+
+
+from pydhcplib.type_ipv4 import ipv4
+
+
+address = ipv4()
+print "a0 : ",address
+
+address1 = ipv4("192.168.0.1")
+print "a1 : ",address1
+
+address2 = ipv4("10.0.0.1")
+print "a2 : ",address2
+
+address3 = ipv4([192,168,0,1])
+print "a3 : ",address3
+
+
+
+if address1 == address2 : print "test 1 : ",address1, "==",address2
+else : print "test 1 : " ,address1, "!=",address2
+
+if address1 == address3 : print "test 2 : ", address1, "==",address3
+else : print "test 2 : ", address1, "!=",address3
+
+
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/server_example.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/server_example.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/server_example.py	(revision 571)
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# pydhcplib
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+from pydhcplib.dhcp_packet import *
+from pydhcplib.dhcp_network import *
+
+
+netopt = {'client_listen_port':"68",
+           'server_listen_port':"67",
+           'listen_address':"0.0.0.0"}
+
+class Server(DhcpServer):
+    def __init__(self, options):
+        DhcpServer.__init__(self,options["listen_address"],
+                            options["client_listen_port"],
+                            options["server_listen_port"])
+        
+    def HandleDhcpDiscover(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+        
+    def HandleDhcpRequest(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+
+    def HandleDhcpDecline(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+        
+    def HandleDhcpRelease(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+        
+    def HandleDhcpInform(self, packet):
+        packet.PrintHeaders()
+        packet.PrintOptions()
+
+
+
+server = Server(netopt)
+
+while True :
+    server.GetNextDhcpPacket()
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/strlist_example.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/strlist_example.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/examples/strlist_example.py	(revision 571)
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+from pydhcplib.type_strlist import strlist
+
+
+word = strlist()
+print "a0 : ",word
+
+word1 = strlist("azerty")
+print "a1 : ",word1
+
+word2 = strlist("qwerty")
+print "a2 : ",word2
+
+word3 = strlist([97, 122, 101, 114, 116, 121])
+print "a3 : ",word3
+
+if word1 == word2 : print "test 1 : ",word1, "==",word2
+else : print "test 1 : " ,word1, "!=",word2
+
+if word1 == word3 : print "test 2 : ", word1, "==",word3
+else : print "test 2 : ", word1, "!=",word3
+
+
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/__init__.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/__init__.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/__init__.py	(revision 571)
@@ -0,0 +1,1 @@
+__version__ = '0.2'
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_basic_packet.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_basic_packet.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_basic_packet.py	(revision 571)
@@ -0,0 +1,188 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+import operator
+from struct import unpack
+from struct import pack
+from dhcp_constants import *
+
+# DhcpPacket : base class to encode/decode dhcp packets.
+
+
+class DhcpBasicPacket:
+    def __init__(self):
+        self.packet_data = [0]*240
+        self.options_data = {}
+        self.packet_data[236:240] = MagicCookie
+
+    def IsDhcpPacket(self):
+        if self.packet_data[236:240] != MagicCookie : return False
+        return True
+
+    # Check if variable is a list with int between 0 and 255
+    def CheckType(self,variable):
+        if type(variable) == list :
+            for each in variable :
+                if (type(each) != int)  or (each < 0) or (each > 255) :
+                    return False
+            return True
+        else : return False
+        
+
+    def DeleteOption(self,name):
+        # if name is a standard dhcp field
+        # Set field to 0
+        if DhcpFields.has_key(name) :
+            begin = DhcpFields[name][0]
+            end = DhcpFields[name][0]+DhcpFields[name][1]
+            self.packet_data[begin:end] = [0]*DhcpFields[name][1]
+            return True
+
+        # if name is a dhcp option
+        # delete option from self.option_data
+        elif self.options_data.has_key(name) :
+            # forget how to remove a key... try delete
+            self.options_data.__delitem__(name)
+            return True
+
+        return False
+
+    def GetOption(self,name):
+        if DhcpFields.has_key(name) :
+            option_info = DhcpFields[name]
+            return self.packet_data[option_info[0]:option_info[0]+option_info[1]]
+
+        elif self.options_data.has_key(name) :
+            return self.options_data[name]
+
+        return []
+        
+
+    def SetOption(self,name,value):
+
+        # Basic vlue checking :
+        # has value list a correct length
+        
+        # if name is a standard dhcp field
+        if DhcpFields.has_key(name) :
+            if len(value) != DhcpFields[name][1] :
+                print "Error, bad option length (a): ", name
+                return False
+            begin = DhcpFields[name][0]
+            end = DhcpFields[name][0]+DhcpFields[name][1]
+            self.packet_data[begin:end] = value
+            return True
+
+        # if name is a dhcp option
+        elif DhcpOptions.has_key(name) :
+
+            # fields_specs : {'option_code',fixed_length,minimum_length,multiple}
+            # if fixed_length == 0 : minimum_length and multiple apply
+            # else : forget minimum_length and multiple 
+            # multiple : length MUST be a multiple of 'multiple'
+            fields_specs = { "ipv4":[4,0,1], "ipv4+":[0,4,4],
+                             "string":[0,0,1], "bool":[1,0,1],
+                             "char":[1,0,1], "16-bits":[2,0,1],
+                             "32-bits":[4,0,1], "identifier":[0,2,1]}
+            
+            specs = fields_specs[DhcpOptionsTypes[DhcpOptions[name]]]
+            length = len(value)
+            if (specs[0]!=0 and specs==length) or (specs[1]<=length and length%specs[2]==0):
+                self.options_data[name] = value
+                return True
+            else :
+                return False
+
+        print "Error, unknown option : ", name, value
+        return False
+
+
+
+    def IsOption(self,name):
+        if self.options_data.has_key(name) : return True
+        elif DhcpFields.has_key(name) : return True
+        else : return False
+
+
+    # Encode Packet and return it
+    def EncodePacket(self):
+        options = []
+        
+        
+        for each in self.options_data.keys() :
+            options.append(DhcpOptions[each])
+            options.append(len(self.options_data[each]))
+            options += self.options_data[each]
+
+        packet = self.packet_data[:240] + options
+        packet.append(255) # add end option
+        pack_fmt = str(len(packet))+"c"
+
+        packet = map(chr,packet)
+        
+        return pack(pack_fmt,*packet)
+
+
+    # Insert packet in the object
+    def DecodePacket(self,data,debug=False):
+        self.packet_data = []
+        self.options_data = {}
+
+        if (not data) : return False
+        # we transform all data to int list
+        unpack_fmt = str(len(data)) + "c"
+        for i in unpack(unpack_fmt,data):
+            self.packet_data.append(ord(i))
+        if ( debug ) : print "Packet length : ",len(self.packet_data)
+
+
+        # Some servers or clients don't place magic cookie immediately
+        # after headers and begin options fields only after magic.
+        # These 4 lines search magic cookie and begin iterator after.
+        iterator = 236
+        end_iterator = len(self.packet_data)
+        while ( self.packet_data[iterator:iterator+4] != MagicCookie and iterator < end_iterator) :
+            iterator += 1
+        iterator += 4
+        
+        # parse extended options
+        if ( debug ) : print "Debug : ", self.packet_data[iterator:-1]
+
+
+        while iterator < end_iterator :
+            if ( debug ) :
+                print "Debug Option : ", iterator , self.packet_data[iterator]," : ",DhcpOptionsList[self.packet_data[iterator]]
+            if self.packet_data[iterator] == 0 : # pad option
+                opt_first = iterator+1
+                iterator += 1
+
+            elif self.packet_data[iterator] == 255 :
+                self.packet_data = self.packet_data[:240] # base packet length without magic cookie
+                return
+                
+            elif DhcpOptionsTypes.has_key(self.packet_data[iterator]) and self.packet_data[iterator]!= 255:
+                opt_len = self.packet_data[iterator+1]
+                opt_first = iterator+1
+                self.options_data[DhcpOptionsList[self.packet_data[iterator]]] = self.packet_data[opt_first+1:opt_len+opt_first+1]
+                iterator += self.packet_data[opt_first] + 2
+            else :
+                opt_first = iterator+1
+                iterator += self.packet_data[opt_first] + 2
+
+        # cut packet_data to remove options
+        
+        self.packet_data = self.packet_data[:240] # base packet length with magic cookie
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_constants.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_constants.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_constants.py	(revision 571)
@@ -0,0 +1,404 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+
+MagicCookie = [99,130,83,99]
+
+
+# DhcpBaseOptions = '{fieldname':[location,length]}
+DhcpFields = {'op':[0,1],
+                     'htype':[1,1],
+                     'hlen':[2,1],
+                     'hops':[3,1],
+                     'xid':[4,4],
+                     'secs':[8,2],
+                     'flags':[10,2],
+                     'ciaddr':[12,4],
+                     'yiaddr':[16,4],
+                     'siaddr':[20,4],
+                     'giaddr':[24,4],
+                     'chaddr':[28,16],
+                     'sname':[44,64],
+                     'file':[108,128]
+                     }
+DhcpFieldsTypes = {'op':"int",
+                     'htype':"int",
+                     'hlen':"int",
+                     'hops':"int",
+                     'xid':"int4",
+                     'secs':"int2",
+                     'flags':"int2",
+                     'ciaddr':"ipv4",
+                     'yiaddr':"ipv4",
+                     'siaddr':"ipv4",
+                     'giaddr':"ipv4",
+                     'chaddr':"hwmac",
+                     'sname':"str",
+                     'file':"str"
+                     }
+
+# DhcpOptions = 'option_name':option_code
+DhcpOptions = {'pad':0,
+
+               # Vendor Extension
+               'subnet_mask':1,'time_offset':2,
+               'router':3,'time_server':4,'name_server':5,
+               'domain_name_server':6,'log_server':7,
+               'cookie_server':8,'lpr_server':9,
+               'impress_server':10,'resource_location_server':11,
+               'host_name':12,'boot_file':13,'merit_dump_file':14,
+               'domain_name':15,'swap_server':16,'root_path':17,'extensions_path':18,
+
+               # IP layer parameters per host
+               'ip_forwarding':19,'nonlocal_source_rooting':20,
+               'policy_filter':21,'maximum_datagram_reassembly_size':22,
+               'default_ip_time-to-live':23,'path_mtu_aging_timeout':24,
+               'path_mtu_table':25,
+
+               # IP layer parameters per interface
+               'interface_mtu':26,'all_subnets_are_local':27,
+               'broadcast_address':28,'perform_mask_discovery':29,
+               'mask_supplier':30,'perform_router_discovery':31,
+               'routeur_solicitation_address':32,'static_route':33,
+
+               # link layer parameters per interface
+               'trailer_encapsulation':34,'arp_cache_timeout':35,
+               'ethernet_encapsulation':36,
+
+               # TCP parameters
+               'tcp_default_ttl':37,'tcp_keepalive_interval':38,
+               'tcp_keepalive_garbage':39,
+
+               # Applications and service parameters
+               'nis_domain':40,
+               'nis_servers':41,
+               'ntp_servers':42,
+               'vendor_specific_information':43,
+               'nbns':44,
+               'nbdd':45,'node_type':46,
+               'netbios_scope':47,'x_window_system_font_server':48,
+               'x_window_system_display_manager':49,
+
+               # DHCP extensions
+               'request_ip_address':50,
+               'ip_address_lease_time':51,
+               'overload':52,
+               'dhcp_message_type':53,
+               'server_identifier':54,
+               'parameter_request_list':55,
+               'message':56,
+               'maximum_dhcp_message_size':57,
+               'renewal_time_value':58,
+               'rebinding_time_value':59,
+               'class_identifier':60,
+               'client_identifier':61,
+
+               # Add from RFC 2132 
+               'netware_ip_domain_name':62,
+               'netware_ip_sub_options':63,
+               
+               'nis+_domain':64,
+               'nis+_servers':65,
+               'tftp_server_name':66,
+               'bootfile_name':67,
+               'mobile_ip_home_agent':68,
+               'smtp_servers':69,
+               'pop_servers':70,
+               'nntp_servers':71,
+               'default_world_wide_web_server':72,
+               'default_finger_server':73,
+               'default_internet_relay_chat_server':74,
+               'streettalk_server':75,
+               'streettalk_directory_assistance_server':76,
+
+               'user_class':77,
+               'directory_agent':78,
+               'service_scope':79,
+               'rapid_commit':80,
+
+               'client_fqdn':81,
+               'relay_agent':82,
+               'internet_storage_name_service':83,
+               '84':84,
+               'nds_server':85,
+               'nds_tree_name':86,
+               'nds_context':87,
+               '88':88,
+               '89':89,
+               'authentication':90,
+               'client_last_transaction_time':91,
+               'associated_ip':92,
+               'client_system':93,
+               'client_ndi':94,
+               'ldap':95,
+               'unassigned':96,
+               'uuid_guid':97,
+               'open_group_user_auth':98,
+               'unassigned':99,
+               'unassigned':100,
+               'unassigned':101,
+               'unassigned':102,
+               'unassigned':103,
+               'unassigned':104,
+               'unassigned':105,
+               'unassigned':106,
+               'unassigned':107,
+               'unassigned':108,
+               'unassigned':109,
+               'unassigned':110,
+               'unassigned':111,
+               'netinfo_address':112,
+               'netinfo_tag':113,
+               'url':114,
+               'unassigned':115,
+               'auto_config':116,
+               'name_service_search':117,
+               'subnet_selection':118,
+               'domain_search':119,
+               'sip_servers':120,
+               'classless_static_route':121,
+               'cablelabs_client_configuration':122,
+               'geoconf':123,
+               'vendor_class':124,
+               'vendor_specific':125,
+               '126':126,'127':127,'128':128,'129':129,
+               '130':130,'131':131,'132':132,'133':133,
+               '134':134,'135':135,'136':136,'137':137,
+               '138':138,'139':139,'140':140,'141':141,
+               '142':142,'143':143,'144':144,'145':145,
+               '146':146,'147':147,'148':148,'149':149,
+               '150':150,'151':151,'152':152,'153':153,
+               '154':154,'155':155,'156':156,'157':157,
+               '158':158,'159':159,'160':160,'161':161,
+               '162':162,'163':163,'164':164,'165':165,
+               '166':166,'167':167,'168':168,'169':169,
+               '170':170,'171':171,'172':172,'173':173,
+               '174':174,'175':175,'176':176,'177':177,
+               '178':178,'179':179,'180':180,'181':181,
+               '182':182,'183':183,'184':184,'185':185,
+               '186':186,'187':187,'188':188,'189':189,
+               '190':190,'191':191,'192':192,'193':193,
+               '194':194,'195':195,'196':196,'197':197,
+               '198':198,'199':199,'200':200,'201':201,
+               '202':202,'203':203,'204':204,'205':205,
+               '206':206,'207':207,'208':208,'209':209,
+               '210':210,'211':211,'212':212,'213':213,
+               '214':214,'215':215,'216':216,'217':217,
+               '218':218,'219':219,'220':220,'221':221,
+               '222':222,'223':223,'224':224,'225':225,
+               '226':226,'227':227,'228':228,'229':229,
+               '230':230,'231':231,'232':232,'233':233,
+               '234':234,'235':235,'236':236,'237':237,
+               '238':238,'239':239,'240':240,'241':241,
+               '242':242,'243':243,'244':244,'245':245,
+               '246':246,'247':247,'248':248,'249':249,
+               '250':250,'251':251,'252':252,'253':253,
+               '254':254,'end':255
+
+               }
+
+# DhcpOptionsList : reverse of DhcpOptions
+DhcpOptionsList = ['pad',
+
+                   # Vendor Extension
+                   'subnet_mask','time_offset',
+                   'router','time_server','name_server',
+                   'domain_name_server','log_server',
+                   'cookie_server','lpr_server',
+                   'impress_server','resource_location_server',
+                   'host_name','boot_file','merit_dump_file',
+                   'domain_name','swap_server','root_path','extensions_path',
+                   
+                   # IP layer parameters per host
+                   'ip_forwarding','nonlocal_source_rooting',
+                   'policy_filter','maximum_datagram_reassembly_size',
+                   'default_ip_time-to-live','path_mtu_aging_timeout',
+                   'path_mtu_table',
+                   
+                   # IP layer parameters per interface
+                   'interface_mtu','all_subnets_are_local',
+                   'broadcast_address','perform_mask_discovery',
+                   'mask_supplier','perform_router_discovery',
+                   'routeur_solicitation_address','static_route',
+                   
+                   # link layer parameters per interface
+                   'trailer_encapsulation','arp_cache_timeout',
+                   'ethernet_encapsulation',
+                   
+                   # TCP parameters
+                   'tcp_default_ttl','tcp_keepalive_interval',
+                   'tcp_keepalive_garbage',
+                   
+                   # Applications and service parameters
+                   'nis_domain',
+                   'nis_servers',
+                   'ntp_servers',
+                   'vendor_specific_information','nbns',
+                   'nbdd','node_type',
+                   'netbios_scope','x_window_system_font_server',
+                   'x_window_system_display_manager',
+
+                   # DHCP extensions
+                   'request_ip_address',
+                   'ip_address_lease_time',
+                   'overload',
+                   'dhcp_message_type',
+                   'server_identifier',
+                   'parameter_request_list',
+                   'message',
+                   'maximum_dhcp_message_size',
+                   'renewal_time_value',
+                   'rebinding_time_value',
+                   'class_identifier',
+                   'client_identifier',
+                   
+
+                   # adds from RFC 2132,2242
+                   'netware_ip_domain_name',
+                   'netware_ip_sub_options',
+                   'nis+_domain',
+                   'nis+_servers',
+                   'tftp_server_name',
+                   'bootfile_name',
+                   'mobile_ip_home_agent',
+                   'smtp_servers',
+                   'pop_servers',
+                   'nntp_servers',
+                   'default_world_wide_web_server',
+                   'default_finger_server',
+                   'default_internet_relay_chat_server',
+                   'streettalk_server',
+                   'streettalk_directory_assistance_server',
+                   'user_class','directory_agent','service_scope',
+
+                   # 80
+                   'rapid_commit','client_fqdn','relay_agent',
+                   'internet_storage_name_service',
+                   '84',
+                   'nds_server','nds_tree_name','nds_context',
+                   '88','89',
+
+                   #90
+                   'authentication',
+                   'client_last_transaction_time','associated_ip', #RFC 4388
+                   'client_system', 'client_ndi', #RFC 3679
+                   'ldap','unassigned','uuid_guid', #RFC 3679
+                   'open_group_user_auth', #RFC 2485
+
+                   # 99->115 RFC3679 
+                   'unassigned','unassigned','unassigned',
+                   'unassigned','unassigned','unassigned',
+                   'unassigned','unassigned','unassigned',
+                   'unassigned','unassigned','unassigned',
+                   'unassigned','netinfo_address','netinfo_tag',
+                   'url','unassigned',
+
+                   #116
+                   'auto_config','name_service_search','subnet_selection',
+                   'domain_search','sip_servers','classless_static_route',
+                   'cablelabs_client_configuration','geoconf',
+
+                   #124
+                   'vendor_class', 'vendor_specific',
+
+                   '126','127','128','129',
+                   '130','131','132','133','134','135','136','137','138','139',
+                   '140','141','142','143','144','145','146','147','148','149',
+                   '150','151','152','153','154','155','156','157','158','159',
+                   '160','161','162','163','164','165','166','167','168','169',
+                   '170','171','172','173','174','175','176','177','178','179',
+                   '180','181','182','183','184','185','186','187','188','189',
+                   '190','191','192','193','194','195','196','197','198','199',
+                   '200','201','202','203','204','205','206','207','208','209',
+                   '210','211','212','213','214','215','216','217','218','219',
+                   '220','221','222','223','224','225','226','227','228','229',
+                   '230','231','232','233','234','235','236','237','238','239',
+                   '240','241','242','243','244','245','246','247','248','249',
+                   '250','251','252','253','254',
+
+                   'end'
+                   ] 
+
+
+# See http://www.iana.org/assignments/bootp-dhcp-parameters
+# FIXME : verify all ipv4+ options, somes are 32 bits...
+
+DhcpOptionsTypes = {0:"none", 1:"ipv4", 2:"ipv4", 3:"ipv4+", 
+                    4:"ipv4+", 5:"ipv4+", 6:"ipv4+", 7:"ipv4+", 
+                    8:"ipv4+", 9:"ipv4+", 10:"ipv4+", 11:"ipv4+", 
+                    12:"string", 13:"16-bits", 14:"string", 15:"string", 
+                    16:"ipv4", 17:"string", 18:"string", 19:"bool", 
+                    20:"bool", 21:"ipv4+", 22:"16-bits", 23:"char", 
+                    24:"ipv4", 25:"16-bits", 26:"16-bits", 27:"bool", 
+                    28:"ipv4", 29:"bool", 30:"bool", 31:"bool", 
+                    32:"ipv4", 33:"ipv4+", 34:"bool", 35:"32-bits", 
+                    36:"bool", 37:"char", 38:"32-bits", 39:"bool", 
+                    40:"string", 41:"ipv4+", 42:"ipv4+", 43:"string", 
+                    44:"ipv4+", 45:"ipv4+", 46:"char", 47:"string", 
+                    48:"ipv4+", 49:"ipv4+", 50:"ipv4", 51:"32-bits", 
+                    52:"char", 53:"char", 54:"ipv4", 55:"none", 
+                    56:"string", 57:"16-bits", 58:"32-bits", 59:"32-bits", 
+                    60:"string", 61:"identifier", 62:"string", 63:"RFC2242", 
+                    64:"string", 65:"ipv4+", 66:"string", 67:"string", 
+                    68:"ipv4", 69:"ipv4+", 70:"ipv4+", 71:"ipv4+",                     
+                    72:"ipv4+", 73:"ipv4+", 74:"ipv4+", 75:"ipv4+", 
+                    76:"ipv4+", 77:"RFC3004", 78:"RFC2610", 79:"RFC2610", 
+                    80:"null", 81:"string", 82:"RFC3046", 83:"RFC4174", 
+                    84:"Unassigned", 85:"ipv4+", 86:"RFC2241", 87:"RFC2241", 
+                    88:"Unassigned", 89:"Unassigned", 90:"RFC3118", 91:"RFC4388", 
+                    92:"ipv4+", 93:"Unassigned", 94:"Unassigned", 95:"Unassigned", 
+                    96:"Unassigned", 97:"Unassigned", 98:"string", 99:"Unassigned", 
+                    100:"Unassigned", 101:"Unassigned", 102:"Unassigned", 103:"Unassigned", 
+                    104:"Unassigned", 105:"Unassigned", 106:"Unassigned", 107:"Unassigned", 
+                    108:"Unassigned", 109:"Unassigned", 110:"Unassigned", 111:"Unassigned", 
+                    112:"Unassigned", 113:"Unassigned", 114:"Unassigned", 115:"Unassigned", 
+                    116:"char", 117:"RFC2937", 118:"ipv4", 119:"RFC3397", 
+                    120:"RFC3361",
+
+                    #TODO
+                    121:"Unassigned", 122:"Unassigned", 123:"Unassigned", 
+                    124:"Unassigned", 125:"Unassigned", 126:"Unassigned", 127:"Unassigned", 
+                    128:"Unassigned", 129:"Unassigned", 130:"Unassigned", 131:"Unassigned", 
+                    132:"Unassigned", 133:"Unassigned", 134:"Unassigned", 135:"Unassigned", 
+                    136:"Unassigned", 137:"Unassigned", 138:"Unassigned", 139:"Unassigned", 
+                    140:"Unassigned", 141:"Unassigned", 142:"Unassigned", 143:"Unassigned", 
+                    144:"Unassigned", 145:"Unassigned", 146:"Unassigned", 147:"Unassigned", 
+                    148:"Unassigned", 149:"Unassigned", 150:"Unassigned", 151:"Unassigned", 
+                    152:"Unassigned", 153:"Unassigned", 154:"Unassigned", 155:"Unassigned", 
+                    156:"Unassigned", 157:"Unassigned", 158:"Unassigned", 159:"Unassigned", 
+                    160:"Unassigned", 161:"Unassigned", 162:"Unassigned", 163:"Unassigned", 
+                    164:"Unassigned", 165:"Unassigned", 166:"Unassigned", 167:"Unassigned", 
+                    168:"Unassigned", 169:"Unassigned", 170:"Unassigned", 171:"Unassigned", 
+                    172:"Unassigned", 173:"Unassigned", 174:"Unassigned", 175:"Unassigned", 
+                    176:"Unassigned", 177:"Unassigned", 178:"Unassigned", 179:"Unassigned", 
+                    180:"Unassigned", 181:"Unassigned", 182:"Unassigned", 183:"Unassigned", 
+                    184:"Unassigned", 185:"Unassigned", 186:"Unassigned", 187:"Unassigned", 
+                    188:"Unassigned", 189:"Unassigned", 190:"Unassigned", 191:"Unassigned", 
+                    192:"Unassigned", 193:"Unassigned", 194:"Unassigned", 195:"Unassigned", 
+                    196:"Unassigned", 197:"Unassigned", 198:"Unassigned", 199:"Unassigned", 
+                    200:"Unassigned", 201:"Unassigned", 202:"Unassigned", 203:"Unassigned", 
+                    204:"Unassigned", 205:"Unassigned", 206:"Unassigned", 207:"Unassigned", 
+                    208:"Unassigned", 209:"Unassigned", 210:"Unassigned", 211:"Unassigned", 
+                    212:"Unassigned", 213:"Unassigned", 214:"Unassigned", 215:"Unassigned", 
+                    216:"Unassigned", 217:"Unassigned", 218:"Unassigned", 219:"Unassigned", 
+                    220:"Unassigned", 221:"Unassigned", 222:"Unassigned", 223:"Unassigned", 
+                    224:"Unassigned", 225:"Unassigned", 226:"Unassigned", 227:"Unassigned", 
+                    228:"Unassigned", 229:"Unassigned", 230:"Unassigned", 231:"Unassigned", 
+                    232:"Unassigned", 233:"Unassigned", 234:"Unassigned", 235:"Unassigned", 
+                    236:"Unassigned", 237:"Unassigned", 238:"Unassigned", 239:"Unassigned", 
+                    240:"Unassigned", 241:"Unassigned", 242:"Unassigned", 243:"Unassigned", 
+                    244:"Unassigned", 245:"Un"}
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_network.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_network.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_network.py	(revision 571)
@@ -0,0 +1,134 @@
+# pydhcplib
+# Copyright (C) 2005,2006 Mathieu Ignacio -- mignacio@april.org
+#
+# This file is part of pydhcplib.
+# Pydhcplib 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+import sys
+import socket
+import dhcp_packet
+
+
+class DhcpNetwork:
+    def __init__(self, listen_address="0.0.0.0", listen_port=67, emit_port=68):
+
+        self.listen_port = int(listen_port)
+        self.emit_port = int(emit_port)
+        self.listen_address = listen_address
+        
+    def GetNextDhcpPacket(self):
+        data =""
+
+        while data == "" :
+            data = self.dhcp_socket.recv(1024)
+            if data != "" :
+                packet = dhcp_packet.DhcpPacket()
+                packet.DecodePacket(data)
+
+                self.HandleDhcpAll(packet)
+                
+                if packet.IsDhcpDiscoverPacket():
+                    self.HandleDhcpDiscover(packet)
+                elif packet.IsDhcpRequestPacket():
+                    self.HandleDhcpRequest(packet)
+                elif packet.IsDhcpDeclinePacket():
+                    self.HandleDhcpDecline(packet)
+                elif packet.IsDhcpReleasePacket():
+                    self.HandleDhcpRelease(packet)
+                elif packet.IsDhcpInformPacket():
+                    self.HandleDhcpInform(packet)
+                elif packet.IsDhcpOfferPacket():
+                    self.HandleDhcpOffer(packet)
+                elif packet.IsDhcpAckPacket():
+                    self.HandleDhcpAck(packet)
+                elif packet.IsDhcpNackPacket():
+                    self.HandleDhcpNack(packet)
+                else: self.HandleDhcpUnknown(packet)
+
+                return packet
+
+
+    def SendDhcpPacketTo(self, To, packet):
+        return self.dhcp_socket.sendto(packet.EncodePacket(),(To,self.emit_port))
+
+
+    # Server side Handle methods
+    def HandleDhcpDiscover(self, packet):
+        print "HandleDhcpRequest : method not implemented"
+
+    def HandleDhcpRequest(self, packet):
+        print "HandleDhcpRequest : method not implemented"
+
+    def HandleDhcpDecline(self, packet):
+        print "HandleDhcpDecline : method not implemented"
+
+    def HandleDhcpRelease(self, packet):
+        print "HandleDhcpRelease : method not implemented"
+
+    def HandleDhcpInform(self, packet):
+        print "HandleDhcpInform : method not implemented"
+
+
+    # client-side Handle methods
+    def HandleDhcpOffer(self, packet):
+        print "HandleDhcpOffer : method not implemented"
+        
+    def HandleDhcpAck(self, packet):
+        print "HandleDhcpAckhandling : method not implemented"
+
+    def HandleDhcpNack(self, packet):
+        print "HandleDhcpNack : method not implemented"
+
+
+    # Handle unknown options or all options
+    def HandleDhcpUnknown(self, packet):
+        print "HandleDhcpUnknown : method not implemented"
+
+    def HandleDhcpAll(self, packet):
+        pass
+
+
+class DhcpServer(DhcpNetwork) :
+    def __init__(self, listen_address="0.0.0.0", client_listen_port=67,server_listen_port=68) :
+        
+        DhcpNetwork.__init__(self,listen_address,server_listen_port,client_listen_port)
+        
+        self.dhcp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        self.dhcp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
+        self.dhcp_socket.bind((self.listen_address, self.listen_port))
+
+
+class DhcpClient(DhcpNetwork) :
+    def __init__(self, listen_address="0.0.0.0",client_listen_port=67,server_listen_port=68) :
+
+        DhcpNetwork.__init__(self,listen_address,client_listen_port,server_listen_port)
+
+        self.dhcp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        self.dhcp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
+        self.dhcp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
+        self.dhcp_socket.bind((self.listen_address, self.listen_port))
+
+# Raw client permit to listen on network even if there is
+# no interface set. Probably useful... :-)
+class DhcpRawClient(DhcpNetwork) :
+    def __init__(self, interface="eth0", client_listen_port=67,server_listen_port=68) :
+
+        DhcpNetwork.__init__(self,interface,client_listen_port,server_listen_port)
+        print interface
+                # 0x800 : ETH_P_IP, 0x003 : ETH_P_ALL
+        # See Linux/if_ether.h 
+        self.dhcp_socket = socket.socket(socket.AF_PACKET, socket.SOCK_DGRAM,socket.ntohs(0x0800))
+
+            
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_packet.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_packet.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/dhcp_packet.py	(revision 571)
@@ -0,0 +1,221 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+import operator
+from struct import unpack
+from struct import pack
+from dhcp_basic_packet import *
+from dhcp_constants import *
+from type_ipv4 import ipv4
+from type_strlist import strlist
+class DhcpPacket(DhcpBasicPacket):
+
+
+    # Useful function for debugging
+    def PrintHeaders(self):
+        print "# Header fields\n"
+        print "readable_dhcp_headers = {"
+        for opt in  ['op','htype','hlen','hops','xid','secs','flags',
+                     'ciaddr','yiaddr','siaddr','giaddr','chaddr','sname','file'] :
+            begin = DhcpFields[opt][0]
+            end = DhcpFields[opt][0]+DhcpFields[opt][1]
+            data = self.packet_data[begin:end]
+            if DhcpFieldsTypes[opt] == "int" : result = str(data[0])
+            if DhcpFieldsTypes[opt] == "int2" : result = str(data[0]*256+data[0])
+            if DhcpFieldsTypes[opt] == "int4" : result = str(ipv4(data).int())
+            if DhcpFieldsTypes[opt] == "str" : result = strlist(data).str()
+            if DhcpFieldsTypes[opt] == "ipv4" : result = ipv4(data).str()
+            if DhcpFieldsTypes[opt] == "hwmac" : result = "".join(map(chr,data))
+
+            line = "\t'"+opt+"':"+str(data)+",\t# "+result
+            print line
+        print "\t'end':'true'}"
+
+    # Useful function for debugging
+    def PrintOptions(self):
+        print "# Options fields"
+        print "readable_dhcp_options = {"
+        for opt in self.options_data.keys():
+            data = self.options_data[opt]
+            result = ""
+            optnum  = DhcpOptions[opt]
+            if DhcpOptionsTypes[optnum] == "char" : result = str(data[0])
+            if DhcpOptionsTypes[optnum] == "16-bits" : result = str(data[0]*256+data[0])
+            if DhcpOptionsTypes[optnum] == "32bits" : result = str(ipv4(data).int())
+            if DhcpOptionsTypes[optnum] == "string" : result = strlist(data).str()
+            if DhcpOptionsTypes[optnum] == "ipv4" : result = ipv4(data).str()
+            if DhcpOptionsTypes[optnum] == "ipv4+" :
+                for i in range(0,len(data),4) :
+                    if len(data[i:i+4]) == 4 :
+                        result += ipv4(data[i:i+4]).str() + " - "
+            line = "\t'"+opt+"':"+str(data)+",\t# "+result
+            print line
+        print "\t'end':'true'}"
+        
+
+            
+    # FIXME: This is called from IsDhcpSomethingPacket, but is this really
+    # needed?  Or maybe this testing should be done in
+    # DhcpBasicPacket.DecodePacket().
+
+    # Test Packet Type
+    def IsDhcpSomethingPacket(self,type):
+        if self.IsDhcpPacket() == False : return False
+        if self.IsOption("dhcp_message_type") == False : return False
+        if self.GetOption("dhcp_message_type") != type : return False
+        return True
+    
+    def IsDhcpDiscoverPacket(self):
+        return self.IsDhcpSomethingPacket([1])
+
+    def IsDhcpOfferPacket(self):
+        return self.IsDhcpSomethingPacket([2])
+
+    def IsDhcpRequestPacket(self):
+        return self.IsDhcpSomethingPacket([3])
+
+    def IsDhcpDeclinePacket(self):
+        return self.IsDhcpSomethingPacket([4])
+
+    def IsDhcpAckPacket(self):
+        return self.IsDhcpSomethingPacket([5])
+
+    def IsDhcpNackPacket(self):
+        return self.IsDhcpSomethingPacket([6])
+
+    def IsDhcpReleasePacket(self):
+        return self.IsDhcpSomethingPacket([7])
+
+    def IsDhcpInformPacket(self):
+        return self.IsDhcpSomethingPacket([8])
+
+
+    def GetMultipleOptions(self,options=()):
+        result = {}
+        for each in options:
+            result[each] = self.GetOption(each)
+        return result
+
+    def SetMultipleOptions(self,options={}):
+        for each in options.keys():
+            self.SetOption(each,options[each])
+
+
+
+
+
+
+    # Creating Response Packet
+
+    # Server-side functions
+    # From RFC 2132 page 28/29
+    def CreateDhcpOfferPacketFrom(self,src): # src = discover packet
+        self.SetOption("htype",src.GetOption("htype"))
+        self.SetOption("xid",src.GetOption("xid"))
+        self.SetOption("flags",src.GetOption("flags"))
+        self.SetOption("giaddr",src.GetOption("giaddr"))
+        self.SetOption("chaddr",src.GetOption("chaddr"))
+        self.SetOption("ip_address_lease_time",src.GetOption("ip_address_lease_time"))
+        self.TransformToDhcpOfferPacket()
+
+    def TransformToDhcpOfferPacket(self):
+        self.SetOption("dhcp_message_type",[2])
+        self.SetOption("op",[2])
+        self.SetOption("hlen",[6]) 
+
+        self.DeleteOption("secs")
+        self.DeleteOption("ciaddr")
+        self.DeleteOption("request_ip_address")
+        self.DeleteOption("parameter_request_list")
+        self.DeleteOption("client_identifier")
+        self.DeleteOption("maximum_message_size")
+
+
+
+
+
+    """ Dhcp ACK packet creation """
+    def CreateDhcpAckPacketFrom(self,src): # src = request or inform packet
+        self.SetOption("htype",src.GetOption("htype"))
+        self.SetOption("xid",src.GetOption("xid"))
+        self.SetOption("ciaddr",src.GetOption("ciaddr"))
+        self.SetOption("flags",src.GetOption("flags"))
+        self.SetOption("giaddr",src.GetOption("giaddr"))
+        self.SetOption("chaddr",src.GetOption("chaddr"))
+        self.SetOption("ip_address_lease_time_option",src.GetOption("ip_address_lease_time_option"))
+        self.TransformToDhcpAckPacket()
+
+    def TransformToDhcpAckPacket(self): # src = request or inform packet
+        self.SetOption("op",[2])
+        self.SetOption("hlen",[6]) 
+        self.SetOption("dhcp_message_type",[5])
+
+        self.DeleteOption("secs")
+        self.DeleteOption("request_ip_address")
+        self.DeleteOption("parameter_request_list")
+        self.DeleteOption("client_identifier")
+        self.DeleteOption("maximum_message_size")
+
+
+    """ Dhcp NACK packet creation """
+    def CreateDhcpNackPacketFrom(self,src): # src = request or inform packet
+        
+        self.SetOption("htype",src.GetOption("htype"))
+        self.SetOption("xid",src.GetOption("xid"))
+        self.SetOption("flags",src.GetOption("flags"))
+        self.SetOption("giaddr",src.GetOption("giaddr"))
+        self.SetOption("chaddr",src.GetOption("chaddr"))
+        self.TransformToDhcpNackPacket()
+
+    def TransformToDhcpNackPacket(self):
+        self.SetOption("op",[2])
+        self.SetOption("hlen",[6]) 
+        self.DeleteOption("secs")
+        self.DeleteOption("ciaddr")
+        self.DeleteOption("yiaddr")
+        self.DeleteOption("siaddr")
+        self.DeleteOption("sname")
+        self.DeleteOption("file")
+        self.DeleteOption("request_ip_address")
+        self.DeleteOption("ip_address_lease_time_option")
+        self.DeleteOption("parameter_request_list")
+        self.DeleteOption("client_identifier")
+        self.DeleteOption("maximum_message_size")
+        self.SetOption("dhcp_message_type",[6])
+
+
+
+
+
+
+
+    """ GetClientIdentifier """
+
+    def GetClientIdentifier(self) :
+        if self.IsOption("client_identifier") :
+            return self.GetOption("client_identifier")
+        return []
+
+    def GetGiaddr(self) :
+        return self.GetOption("giaddr")
+
+    def GetHardwareAddress(self) :
+        length = self.GetOption("hlen")[0]
+        full_hw = self.GetOption("chaddr")
+        if length!=[] and length<len(full_hw) : return full_hw[0:length]
+        return full_hw
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_hw_addr.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_hw_addr.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_hw_addr.py	(revision 571)
@@ -0,0 +1,85 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+
+from binascii import unhexlify,hexlify
+
+# Check and convert hardware/nic/mac address type
+class hwmac:
+    def __init__(self,value="00:00:00:00:00:00") :
+        self._hw_numlist = []
+        self._hw_string = ""
+        hw_type = type(value)
+        if hw_type == str :
+            self._hw_string = value
+            self._StringToNumlist(value)
+            self._CheckNumList()
+        elif hw_type == list :
+            self._hw_numlist = value
+            self._CheckNumList()
+            self._NumlistToString()
+        else : raise TypeError , 'hwmac init : Valid types are  tr and list'
+
+
+
+    # Check if _hw_numlist is valid and raise error if not.
+    def _CheckNumList(self) :
+        if len(self._hw_numlist) != 6 : raise ValueError , "hwmac : wrong list length."
+        for part in self._hw_numlist :
+            if type (part) != int : raise TypeError , "hwmac : each element of list must be int"
+            if part < 0 or part > 255 : raise ValueError , "hwmac : need numbers between 0 and 255."
+        return True
+
+
+    def _StringToNumlist(self,value):
+        self._hw_string = self._hw_string.replace("-",":").replace(".",":")
+        self._hw_string = self._hw_string.lower()
+
+
+        for twochar in self._hw_string.split(":"):
+            self._hw_numlist.append(ord(unhexlify(twochar)))
+            
+    # Convert NumList type ip to String type ip
+    def _NumlistToString(self) :
+        self._hw_string = ":".join(map(hexlify,map(chr,self._hw_numlist)))
+
+    # Convert String type ip to NumList type ip
+    # return ip string
+    def str(self) :
+        return self._hw_string
+
+    # return ip list (useful for DhcpPacket class)
+    def list(self) :
+        return self._hw_numlist
+
+    def __hash__(self) :
+        return self._hw_string.__hash__()
+
+    def __repr__(self) :
+        return self._hw_string
+
+    def __cmp__(self,other) :
+        if self._hw_string == other : return 0
+        return 1
+
+    def __nonzero__(self) :
+        if self._hw_string != "00:00:00:00:00:00" : return 1
+        return 0
+
+
+
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_ipv4.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_ipv4.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_ipv4.py	(revision 571)
@@ -0,0 +1,116 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+
+# Check and convert ipv4 address type
+class ipv4:
+    def __init__(self,value="0.0.0.0") :
+        ip_type = type(value)
+        if ip_type == str :
+            if not self.CheckString(value) : raise ValueError, "ipv4 string argument is not an valid ip "
+            self._ip_string = value
+            self._StringToNumlist()
+            self._StringToLong()
+            self._NumlistToString()
+        elif ip_type == list :
+            if not self.CheckNumList(value) : raise ValueError, "ipv4 list argument is not an valid ip "
+            self._ip_numlist = value
+            self._NumlistToString()
+            self._StringToLong()
+        elif ip_type == int or ip_type == long:
+            self._ip_long = value
+            self._LongToNumlist()
+            self._NumlistToString()
+        elif ip_type == bool :
+            self._ip_long = 0
+            self._LongToNumlist()
+            self._NumlistToString()
+            
+        else : raise TypeError , 'ipv4 init : Valid types are  str, list, int or long'
+
+
+
+    # Convert Long type ip to numlist ip
+    def _LongToNumlist(self) :
+        self._ip_numlist = [self._ip_long >> 24 & 0xFF]
+        self._ip_numlist.append(self._ip_long >> 16 & 0xFF)
+        self._ip_numlist.append(self._ip_long >> 8 & 0xFF)
+        self._ip_numlist.append(self._ip_long & 0xFF)
+        if not self.CheckNumList(self._ip_numlist) : raise ValueError, "ipv4 list argument is not an valid ip "
+    # Convert String type ip to Long type ip
+    def _StringToLong(self) :
+        ip_numlist = map(int,self._ip_string.split('.'))
+        self._ip_long = ip_numlist[3] + ip_numlist[2]*256 + ip_numlist[1]*256*256 + ip_numlist[0]*256*256*256
+        if not self.CheckNumList(self._ip_numlist) : raise ValueError, "ipv4 list argument is not an valid ip "
+    # Convert NumList type ip to String type ip
+    def _NumlistToString(self) :
+        self._ip_string = ".".join(map(str,self._ip_numlist))
+        if not self.CheckNumList(self._ip_numlist) : raise ValueError, "ipv4 list argument is not an valid ip "
+    # Convert String type ip to NumList type ip
+    def _StringToNumlist(self) :
+        self._ip_numlist = map(int,self._ip_string.split('.'))
+        if not self.CheckNumList(self._ip_numlist) : raise ValueError, "ipv4 list argument is not an valid ip "
+
+    """ Public methods """
+    # Check if _ip_numlist is valid and raise error if not.
+    # self._ip_numlist
+    def CheckNumList(self,value) :
+        if len(value) != 4 : return False
+        for part in value :
+            if part < 0 or part > 255 : return False
+        return True
+
+    # Check if _ip_numlist is valid and raise error if not.
+    def CheckString(self,value) :
+        tmp = value.split('.')
+        if len(tmp) != 4 :  return False
+        for each in tmp : 
+            if not each.isdigit() : return False
+        return True
+    
+    # return ip string
+    def str(self) :
+        return self._ip_string
+
+    # return ip list (useful for DhcpPacket class)
+    def list(self) :
+        return self._ip_numlist
+
+    # return Long ip type (useful for SQL ip address backend)
+    def int(self) :
+        return self._ip_long
+
+
+
+
+    """ Useful function for native python operations """
+
+    def __hash__(self) :
+        return self._ip_long.__hash__()
+
+    def __repr__(self) :
+        return self._ip_string
+
+    def __cmp__(self,other) :
+        return cmp(self._ip_long, other._ip_long);
+
+    def __nonzero__(self) :
+        if self._ip_long != 0 : return 1
+        return 0
+
+
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_strlist.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_strlist.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/pydhcplib/type_strlist.py	(revision 571)
@@ -0,0 +1,65 @@
+# Anemon Dhcp
+# Copyright (C) 2005 Mathieu Ignacio -- mignacio@april.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+class strlist :
+    def __init__(self,data="") :
+        str_type = type(data)
+        self._str = ""
+        self._list = []
+        
+        if str_type == str :
+            self._str = data
+            for each in range(len(self._str)) :
+                self._list.append(ord(self._str[each]))
+        elif str_type == list :
+            self._list = data
+            self._str = "".join(map(chr,self._list))
+        else : raise TypeError , 'strlist init : Valid types are str and  list of int'
+
+    # return string
+    def str(self) :
+        return self._str
+
+    # return list (useful for DhcpPacket class)
+    def list(self) :
+        return self._list
+
+    # return int
+    # FIXME
+    def int(self) :
+        return 0
+
+
+
+    """ Useful function for native python operations """
+
+    def __hash__(self) :
+        return self._str.__hash__()
+
+    def __repr__(self) :
+        return self._str
+
+    def __nonzero__(self) :
+        if self._str != "" : return 1
+        return 0
+
+    def __cmp__(self,other) :
+        if self._str == other : return 0
+        return 1
+		    
+
+
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/scripts/pydhcp
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/scripts/pydhcp	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/scripts/pydhcp	(revision 571)
@@ -0,0 +1,122 @@
+#!/usr/bin/python
+
+
+from pydhcplib import dhcp_constants
+from pydhcplib import dhcp_packet
+from pydhcplib import dhcp_network
+
+from pydhcplib import type_hw_addr
+from pydhcplib import type_ipv4
+from pydhcplib import type_strlist
+
+import sys
+
+from optparse import OptionParser
+
+
+parser = OptionParser()
+
+""" Action options """
+parser.add_option("-L", "--listen", action="store_true",dest="listen", help="",default=False)
+parser.add_option("-E", "--emit",  action="store_true",dest="emit", help="",  default=False)
+parser.add_option("-R", "--readable-conversion",  action="store_true",dest="readable", help="", default=False)
+parser.add_option("-B", "--binary-conversion",  action="store_true",dest="binary", help="", default=False)
+parser.add_option("-s", "--source-file", action="store",dest="source", help="", default=False, type="string")
+parser.add_option("-d", "--destination-file", action="store",dest="destination", help="", default=False, type="string")
+parser.add_option("-p", "--port", action="store",dest="port", help="", default="67", type="int")
+parser.add_option("-a", "--address", action="store",dest="address", help="", default="0.0.0.0", type="string")
+parser.add_option("-r", "--raw", action="store",dest="raw", help="", default=False,type="string")
+parser.add_option("-n", "--number", action="store",dest="number", help="", default="0", type="int")
+
+
+
+(options, args) = parser.parse_args()
+
+print options
+
+
+
+def main() :
+    ActionSum = 0
+    for Action in (options.listen,options.emit,options.readable,options.binary) :
+        if Action == True : ActionSum += 1
+    if ActionSum > 1 :
+        print "Command line error : [-L -E -R -B] Only one of these actions can be taken."
+        sys.exit(0)
+
+    if options.readable == True : r_conversion()
+
+    if options.binary == True : b_conversion()
+
+    if options.listen == True :
+        if options.raw == False:
+            listen_address(options.address,options.port,int(options.number))
+        else  :
+            print "Listen RAW : ",options.raw
+            listen_packet(options.raw,options.number)
+
+    if options.emit == True : emit(options.address,options.port)
+
+
+def listen_address(address,port,number) :
+    listener = dhcp_network.DhcpClient(address,port,port)
+
+    if  (number == 0 ) :
+        while True :
+            packet = listener.GetNextDhcpPacket()
+            packet.PrintHeaders()
+            packet.PrintOptions()
+
+    else :
+        while number > 0 :
+            packet = listener.GetNextDhcpPacket()
+            packet.PrintHeaders()
+            packet.Print()
+
+            number -= 1
+
+def listen_packet(interface,number) :
+    listener = dhcp_network.DhcpRawClient(mysocket)
+
+    if  (number == 0 ) :
+        while True :
+            packet = dhcp_packet.DhcpPacket()
+            
+            packet.DecodePacket(listener.Receive(1024))
+            packet.PrintHeaders()
+            packet.PrintOptions()
+
+    else :
+        while number > 0 :
+            packet = dhcp_packet.DhcpPacket()
+            
+            packet.DecodePacket(listener.Receive(1024))
+            packet.PrintHeaders()
+            packet.PrintOptions()
+
+            number -= 1
+    
+
+def emit(address,port) :
+    pass
+
+def r_conversion() :
+    rawdata = sys.stdin.read()
+    while ( len(rawdata)>0 ) :
+        readdata = dhcp_packet.DhcpPacket()
+        readdata.DecodePacket(rawdata)
+        readdata.PrintHeaders()
+        readdata.PrintOptions()
+        rawdata = sys.stdin.read()
+
+def b_conversion() :
+    """
+    pythondata =  sys.stdin.read()
+    while ( len(pythondata)>0 ) :
+        data = dhcp_packet.DhcpPacket()
+        data.DecodePacket(rawdata)
+        
+        pythondata = sys.stdin.read()
+    """
+
+main()
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/scripts/pydhcp.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/scripts/pydhcp.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/scripts/pydhcp.py	(revision 571)
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+
+import dhcp_constants
+import dhcp_packet
+import dhcp_network
+
+import type_hw_addr
+import type_ipv4
+import type_strlist
+
+import sys
+
+from optparse import OptionParser
+
+
+parser = OptionParser()
+
+""" Action options """
+parser.add_option("-L", "--listen", action="store_true",dest="listen", help="",default="False")
+parser.add_option("-E", "--emit",  action="store_true",dest="emit", help="",  default="False")
+parser.add_option("-R", "--readable-conversion",  action="store_true",dest="readable", help="", default="False")
+parser.add_option("-B", "--binary-conversion",  action="store_true",dest="binary", help="", default="False")
+
+
+parser.add_option("-s", "--source-file", action="store",dest="source", help="", default="False", type="string")
+parser.add_option("-d", "--destination-file", action="store",dest="destination", help="", default="False", type="string")
+
+
+
+(options, args) = parser.parse_args()
+
+print options
+
+
+
+def main() :
+    ActionSum = 0
+    for Action in (options.listen,options.emit,options.readable,options.binary) :
+        if Action == True : ActionSum += 1
+    if ActionSum > 1 :
+        print "Command line error : [-L -E -R -B] Only one of these actions can be taken."
+        sys.exit(0)
+
+    if options.readable == True : r_conversion()
+
+
+
+def listen(port) :
+    pass
+
+def emit(address,port) :
+    pass
+
+def r_conversion() :
+    rawdata = rawdata = sys.stdin.read()
+    while ( len(rawdata)>0 ) :
+        readdata = dhcp_packet.DhcpPacket()
+        readdata.DecodePacket(rawdata)
+        readdata.PrintHeaders()
+        readdata.PrintOptions()
+        rawdata = rawdata = sys.stdin.read()
+
+def b_conversion() :
+    pass
+
+main()
Index: /branches/wsgi/packages/sipb-xen-python-pydhcplib/setup.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-python-pydhcplib/setup.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-python-pydhcplib/setup.py	(revision 571)
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+
+fr8_manpages=['man/fr/man8/pydhcp.8.gz']
+fr3_manpages=['man/fr/man3/pydhcplib.3.gz',
+              'man/fr/man3/pydhcplib.DhcpBasicPacket.3.gz',
+              'man/fr/man3/pydhcplib.DhcpPacket.3.gz',
+              'man/fr/man3/pydhcplib.hwmac.3.gz',
+              'man/fr/man3/pydhcplib.ipv4.3.gz',
+              'man/fr/man3/pydhcplib.strlist.3.gz']
+en8_manpages=['man/man8/pydhcp.8.gz']
+
+setup(name='pydhcplib',
+      version="0.3.2",
+      license='GPL v2',
+      description='Dhcp client/server library',
+      author='Mathieu Ignacio',
+      author_email='tamtam@anemon.org',
+      url='http://pydhcplib.tuxfamily.org/',
+      packages=['pydhcplib'],
+      scripts=['scripts/pydhcp'],
+      data_files=[("share/man/man8",en8_manpages),
+                  ("share/man/fr/man8",fr8_manpages),
+                  ("share/man/fr/man3",fr3_manpages)
+                  ])
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/changelog	(revision 571)
@@ -0,0 +1,102 @@
+sipb-xen-remctl-auto (1.0.15) unstable; urgency=low
+
+  * Compute time differences on server to avoid drift
+
+ -- Quentin Smith <quentin@mit.edu>  Thu, 15 May 2008 20:58:04 -0400
+
+sipb-xen-remctl-auto (1.0.14) unstable; urgency=low
+
+  * Use more efficient python API to get VM information, and return it
+    in a Python dict structure.
+
+ -- Quentin Smith <quentin@mit.edu>  Thu, 15 May 2008 20:29:03 -0400
+
+sipb-xen-remctl-auto (1.0.13) unstable; urgency=low
+
+  * Switch to just using the remote server.
+
+ -- Greg Price <price@mit.edu>  Sat, 10 May 2008 21:26:42 -0400
+
+sipb-xen-remctl-auto (1.0.12) unstable; urgency=low
+
+  * Remove an obsolete line from the remctl config.
+
+ -- Greg Price <price@mit.edu>  Sun,  4 May 2008 22:36:07 -0400
+
+sipb-xen-remctl-auto (1.0.11) unstable; urgency=low
+
+  * Close file descriptors for update-conserver on destroy/shutdown.
+
+ -- Eric Price <ecprice@pseudomyrmex.mit.edu>  Mon, 21 Apr 2008 21:28:12 -0400
+
+sipb-xen-remctl-auto (1.0.10) unstable; urgency=low
+
+  * Delay running sipb-xen-update-conserver
+
+ -- Evan Broder <broder@mit.edu>  Mon, 21 Apr 2008 20:26:28 -0400
+
+sipb-xen-remctl-auto (1.0.9) unstable; urgency=low
+
+  * Change the way that conserver is updated
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 04:36:04 -0400
+
+sipb-xen-remctl-auto (1.0.8) unstable; urgency=low
+
+  * Add support for updating conserver when VMs are turned on and off
+
+ -- Evan Broder <broder@sipb-xen-dev.mit.edu>  Wed,  2 Apr 2008 00:40:23 -0400
+
+sipb-xen-remctl-auto (1.0.7) unstable; urgency=low
+
+  * changes for installer system
+
+ -- Tim Abbott <tabbott@sipb-xen-dev.mit.edu>  Sun, 28 Oct 2007 22:47:59 -0400
+
+sipb-xen-remctl-auto (1.0.6) unstable; urgency=low
+
+  * get rid of old "vmboot" call
+  * make "create" not destroy running machines.
+
+ -- Tim Abbott <tabbott@sipb-xen-dev.mit.edu>  Sat, 27 Oct 2007 16:54:26 -0400
+
+sipb-xen-remctl-auto (1.0.5) unstable; urgency=low
+
+  * move to common /usr/sbin/sipb-xen-* naming
+  * get rid of dispatch.sh hack
+  * fix "reboot" to "destroy then create" always, since that's what users will expect
+  * change remctl calls interface in various ways
+
+ -- Tim Abbott <tabbott@mit.edu>  Tue, 16 Oct 2007 00:28:36 -0400
+
+sipb-xen-remctl-auto (1.0.4) unstable; urgency=low
+
+  * Fix typos.
+
+ -- Anders Kaseorg <andersk@sipb-xen-dev.mit.edu>  Tue,  9 Oct 2007 04:47:37 -0400
+
+sipb-xen-remctl-auto (1.0.3) unstable; urgency=low
+
+  * Prepend d_ to database VMs.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Tue,  9 Oct 2007 02:34:56 -0400
+
+sipb-xen-remctl-auto (1.0.2) unstable; urgency=low
+
+  * Added web interface support code.
+  * Move things from /etc/remctl/sipb-xen-auto/bin to /usr/sbin
+  * Quoted lots of variables
+
+ -- Tim Abbott <tabbott@sipb-xen-dev.mit.edu>  Sun,  7 Oct 2007 17:23:40 -0400
+
+sipb-xen-remctl-auto (1.0.1) unstable; urgency=low
+
+  * Stop scaring me with unquoted variables.
+
+ -- Anders Kaseorg <andersk@mit.edu>  Sun, 05 Aug 2007 22:11:02 -0400
+
+sipb-xen-remctl-auto (1.0) unstable; urgency=low
+
+  * Initial release.
+
+ -- Tim Abbott <tabbott@mit.edu>  Sun, 15 Jul 2007 19:37:05 -0400
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/control	(revision 571)
@@ -0,0 +1,12 @@
+Source: sipb-xen-remctl-auto
+Section: net
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.2.0)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-remctl-auto
+Architecture: all
+Depends: ${misc:Depends}, remctl-server, sipb-xen-console-server
+Description: Installs the SIPB Xen automatic remctl management system
+ This is our automatic remctl configuration management system.
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/control.in	(revision 571)
@@ -0,0 +1,12 @@
+Source: sipb-xen-remctl-auto
+Section: net
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-remctl-auto
+Architecture: all
+Depends: ${misc:Depends}, remctl-server, sipb-xen-console-server, python-cjson, python-yaml
+Description: Installs the SIPB Xen automatic remctl management system
+ This is our automatic remctl configuration management system.
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask tabbott@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/rules	(revision 571)
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+include /usr/share/cdbs/1/rules/debhelper.mk
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/debian/sipb-xen-remctl-auto.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/debian/sipb-xen-remctl-auto.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/debian/sipb-xen-remctl-auto.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/cron.d/sipb-xen-remctl-auto
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/cron.d/sipb-xen-remctl-auto	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/cron.d/sipb-xen-remctl-auto	(revision 571)
@@ -0,0 +1,10 @@
+# /etc/cron.d/sipb-xen-remctl-auto: crontab entries for the sipb-xen-remctl-auto package
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+MAILTO=root
+UPDATE=/usr/sbin/remctl-update.sh
+
+@reboot         root    if [ -x "$UPDATE" ]; then nice -n10 "$UPDATE" all; fi
+* * * * */15       root    if [ -x "$UPDATE" ]; then nice -n10 "$UPDATE" all; fi
+
+# EOF
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/remctl/acl/remote
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/remctl/acl/remote	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/remctl/acl/remote	(revision 571)
@@ -0,0 +1,1 @@
+host/remote.mit.edu@ATHENA.MIT.EDU
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/remctl/conf.d/sipb-xen-remote
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/remctl/conf.d/sipb-xen-remote	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/etc/remctl/conf.d/sipb-xen-remote	(revision 571)
@@ -0,0 +1,2 @@
+remote web     /usr/sbin/sipb-xen-remote /etc/remctl/acl/remote
+remote control /usr/sbin/sipb-xen-remote /etc/remctl/acl/remote
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-boot
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-boot	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-boot	(revision 571)
@@ -0,0 +1,9 @@
+#!/bin/sh
+machine="$2"
+cdrom="$3"
+
+if [ -n "$cdrom" ]; then
+        xm create sipb-database machine_name="$machine" cdrom_image="$cdrom"
+else
+        xm create sipb-database machine_name="$machine"
+fi
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-listvms
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-listvms	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-listvms	(revision 571)
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+
+import sys
+import time
+sys.path.insert(0, '/usr/lib/xen-3.1-1/lib/python')
+from xen.lowlevel.xs import xs
+
+trans = xs()
+
+def live_vms():
+    domids = set(trans.ls('', '/local/domain'))
+    domids.discard('0')
+
+    vms = dict()
+
+    for domid in domids:
+        name, data = get_dom(int(domid))
+        if name.startswith('d_'):
+            name = name[2:]
+            vms[name] = data
+    return vms
+
+def get_dom(domid):
+    name = trans.read('', '/local/domain/%d/name' % domid)
+    data = dict()
+    data['domid'] = domid
+    # presence of a graphical console
+    data['console'] = trans.read('', '/local/domain/%d/device/vfb/0/state' % domid)
+    # uptime
+    data['vm'] = trans.read('', '/local/domain/%d/vm' % domid)
+    data['start_time'] = float(trans.read('', '%s/start_time' % data['vm']))
+    data['uptime'] = time.time()-data['start_time']
+    
+    return name, data
+
+if __name__ == '__main__':
+    vms = live_vms()
+    if '--json' in sys.argv[1:]:
+        import cjson
+        print cjson.encode(vms)
+    elif '--pickle' in sys.argv[1:]:
+        import cPickle
+        cPickle.dump(vms, sys.stdout, cPickle.HIGHEST_PROTOCOL)
+    else:
+        import yaml
+        print yaml.dump(vms, Dumper=yaml.CSafeDumper, default_flow_style=False)
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-lvm
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-lvm	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-lvm	(revision 571)
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+import sys
+import os.path
+from subprocess import call, PIPE, Popen
+
+def check(b):
+    if not b:
+        exit(1)
+
+vg = "xenvg"
+prefix = "d_"
+
+subcommand = sys.argv[1]
+
+def ensureoff(machine):
+    # Make sure the machine is off, but we don't care about errors if it is already off.
+    rv = call(["/usr/sbin/xm", "destroy", prefix + machine],
+              stderr=PIPE)
+
+if subcommand == "lvcreate-all":
+    from sipb_xen_database import *
+    import re
+    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
+    for d in Disk.select():
+        check(re.match('^[A-Za-z0-9]+$', d.guest_device_name))
+        machine = Machine.get(d.machine_id)
+        check(re.match('^[A-Za-z0-9][A-Za-z0-9._-]*$', machine.name))
+        lvname = prefix + machine.name + "_" + d.guest_device_name
+        if not os.path.exists("/dev/%s/%s" % (vg, lvname)):
+            # LV doesn't exist
+            print >>sys.stderr, "Creating LV %s..." % (lvname,)
+            rv = call(["/sbin/lvcreate", "-L", str(d.size) + "M", "-n", lvname, vg])
+            if rv != 0:
+                print >>sys.stderr, "Error creating LV %s\n" %(lvname,)
+                sys.exit(1)
+else:
+    machine = sys.argv[2]
+    disk = sys.argv[3]
+    lvname = prefix + machine + "_" + disk
+    lvpath = "/dev/" + vg + "/" + lvname
+if subcommand == "lvremove":
+    rv = call(["/sbin/lvremove", "--force", lvpath])
+    ensureoff(machine)
+    if rv != 0:
+        print >>sys.stderr, "Error removing LV %s\n" %(lvname,)
+        sys.exit(1)
+elif subcommand == "lvresize":
+    size = sys.argv[4]
+    ensureoff(machine)
+    p = Popen(["/sbin/lvresize", "-L", size + "M", lvpath],
+              stdin=PIPE, stderr=PIPE)
+    print >> p.stdin, 'y'
+    err = p.stderr.read()
+    if p.wait() != 0 and 'matches existing size' not in err:
+        print >> sys.stderr, "Error resizing LV %s:\n" %(lvname,)
+        print >> sys.stderr, err
+        sys.exit(1)
+    print >> sys.stderr, err
+elif subcommand == "lvrename":
+    newmachine = sys.argv[4]
+    newlvname = prefix + newmachine + "_" + disk
+    ensureoff(machine)
+    ensureoff(newmachine)    
+    rv = call(["/sbin/lvrename", vg, lvname, newlvname])
+    if rv != 0:
+        print >>sys.stderr, "Error renaming LV %s\n" %(lvname,)
+        sys.exit(1)
+elif subcommand == "lvcreate":
+    size = sys.argv[4]
+    rv = call(["/sbin/lvcreate", "-L", size + "M", "-n", lvname, vg])
+    if rv != 0:
+        print >>sys.stderr, "Error creating LV %s\n" %(lvname,)
+        sys.exit(1)
+    
+
Index: /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-vmcontrol
===================================================================
--- /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-vmcontrol	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remctl-auto/files/usr/sbin/sipb-xen-vmcontrol	(revision 571)
@@ -0,0 +1,50 @@
+#!/bin/sh
+# sipb-xen-vmcontrol MACHINE ACTION
+#
+# remctl should have already verified that the user is authorized to
+# control the machine.  So, we just need to execute the action requested.
+#
+# $0 and $1 come from the trusted remctl source.
+#
+# $2 and so on are user-provided, and thus sketchy.  I don't think we
+# need them for this script.
+
+ORIGMACHINE="$1"
+ACTION="$2"
+MACHINE="d_$ORIGMACHINE"
+
+case "$ACTION" in
+    list|vcpu-list|uptime)
+        xm "$ACTION" "$MACHINE"
+	;;
+    destroy|shutdown)
+	xm "$ACTION" "$MACHINE"
+	/usr/sbin/sipb-xen-update-conserver </dev/null >/dev/null 2>&1 &
+	;;
+    install|create|reboot)
+	ARG="$3"
+	shift; shift; shift; MOREARGS="$*"
+	if [ "$ACTION" = "reboot" ]; then
+	    xm destroy "$MACHINE" 2>/dev/null
+	else
+	    xm list "$MACHINE" >/dev/null 2>/dev/null && echo "$MACHINE already exists" && exit 1
+	fi
+	if [ "$ACTION" = "install" ]; then
+	    xm create sipb-database machine_name="$ORIGMACHINE" installer="$ARG" installer_options="$MOREARGS"
+#	elif [ "$ACTION" = "copy" ]; then
+#           sipb-xen-duplicate ...etc...
+	elif [ -n "$ARG" ]; then
+	    xm create sipb-database machine_name="$ORIGMACHINE" cdrom_image="$ARG"
+	else
+	    xm create sipb-database machine_name="$ORIGMACHINE"
+	fi
+	(sleep 4; /usr/sbin/sipb-xen-update-conserver) </dev/null >/dev/null 2>&1 &
+	;;
+    list-long)
+	xm list --long "$MACHINE"
+	;;
+    *)
+        echo "ERROR: Invalid Command"
+        exit 1
+        ;;
+esac
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/changelog	(revision 571)
@@ -0,0 +1,13 @@
+sipb-xen-remote-server (0.2) unstable; urgency=low
+
+  * FUSE filesystem for remctl configuration.
+  * Remove *register calls, since the FUSE keeps things up to date.
+  * An init script to keep the FUSE fs up.
+
+ -- Greg Price <price@mit.edu>  Sat, 10 May 2008 22:04:32 -0400
+
+sipb-xen-remote-server (0.1) unstable; urgency=low
+
+  * First draft.
+
+ -- Greg Price <price@mit.edu>  Sun, 30 Mar 2008 01:08:50 -0400
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+5
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/control	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-remote-server
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 5)
+Standards-Version: 3.7.2
+
+Package: sipb-xen-remote-server
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, debathena-kerberos-config, fuse-utils, openssh-server, python-fuse, sipb-xen-chrony-config, sipb-xen-database-common, remctl-server, remctl-client
+Description: SIPB Xen remote-control server
+ This package should be installed to set up the remote-control server.
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/control.in
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/control.in	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/control.in	(revision 571)
@@ -0,0 +1,14 @@
+Source: sipb-xen-remote-server
+Section: servers
+Priority: important
+Maintainer: sipb-xen@mit.edu
+Build-Depends: @cdbs@
+Standards-Version: 3.7.2
+
+Package: sipb-xen-remote-server
+Architecture: all
+Provides: ${diverted-files}
+Conflicts: ${diverted-files}
+Depends: ${shlibs:Depends}, ${misc:Depends}, daemon, debathena-kerberos-config, fuse-utils, openssh-server, python-fuse, sipb-xen-chrony-config, sipb-xen-database-common, remctl-server, remctl-client
+Description: SIPB Xen remote-control server
+ This package should be installed to set up the remote-control server.
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/files
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/files	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/files	(revision 571)
@@ -0,0 +1,1 @@
+sipb-xen-remote-server_0.1_all.deb servers important
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/rules	(revision 571)
@@ -0,0 +1,11 @@
+#!/usr/bin/make -f
+
+DEB_AUTO_UPDATE_DEBIAN_CONTROL = 1
+DEB_DIVERT_EXTENSION = .sipb-xen
+#DEB_DIVERT_FILES_sipb-xen-remote-server += \
+#	/etc/init.d/bootmisc.sh \
+#	/etc/nscd.conf \
+#	/etc/nsswitch.conf \
+#	/etc/ssh/sshd_config
+
+include /usr/share/cdbs/1/rules/debhelper.mk
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/sipb-xen-remote-server.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/sipb-xen-remote-server.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/sipb-xen-remote-server.init	(revision 571)
@@ -0,0 +1,126 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-remote-server
+# Required-Start:    $local_fs $remote_fs
+# Required-Stop:     $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: sipb-xen remctl configuration filesystem
+# Description:       
+### END INIT INFO
+
+# Author: SIPB Xen Project <sipb-xen@mit.edu>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="The sipb-xen remctl configuration filesystem"
+NAME=sipb-xen-remconffs
+DAEMON=/usr/sbin/sipb-xen-remconffs
+DAEMON_ARGS="/etc/remctl/remconffs"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	modprobe fuse
+	daemon --running -n $NAME && return 1
+	daemon -r -O daemon.info -E daemon.err -n $NAME -U $DAEMON $DAEMON_ARGS || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	daemon --stop -n $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Many daemons don't delete their pidfiles when they exit.
+	rm -f $PIDFILE
+	umount "$DAEMON_ARGS"
+	return "$RETVAL"
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-remote-server/debian/sipb-xen-remote-server.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/debian/sipb-xen-remote-server.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/debian/sipb-xen-remote-server.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/acl/web
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/acl/web	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/acl/web	(revision 571)
@@ -0,0 +1,2 @@
+price/root@ATHENA.MIT.EDU
+daemon/sipb-xen.mit.edu@ATHENA.MIT.EDU
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/conf.d/remconffs
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/conf.d/remconffs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/conf.d/remconffs	(revision 571)
@@ -0,0 +1,1 @@
+include /etc/remctl/remconffs/conf
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/conf.d/sipb-xen-web
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/conf.d/sipb-xen-web	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/etc/remctl/conf.d/sipb-xen-web	(revision 571)
@@ -0,0 +1,7 @@
+web lvcreate     /usr/sbin/sipb-xen-remote-proxy-web /etc/remctl/acl/web
+web lvremove     /usr/sbin/sipb-xen-remote-proxy-web /etc/remctl/acl/web
+web lvrename     /usr/sbin/sipb-xen-remote-proxy-web /etc/remctl/acl/web
+web lvresize     /usr/sbin/sipb-xen-remote-proxy-web /etc/remctl/acl/web
+web lvcopy       /usr/sbin/sipb-xen-remote-proxy-web /etc/remctl/acl/web
+web vmboot       /usr/sbin/sipb-xen-remote-proxy-web /etc/remctl/acl/web
+web listvms      /usr/sbin/sipb-xen-remote-listvms   /etc/remctl/acl/web
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remconffs
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remconffs	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remconffs	(revision 571)
@@ -0,0 +1,236 @@
+#!/usr/bin/python
+
+import fuse
+from fuse import Fuse
+
+from time import time
+
+import stat	# for file properties
+import os	  # for filesystem modes (O_RDONLY, etc)
+import errno   # for error number codes (ENOENT, etc)
+			   # - note: these must be returned as negatives
+
+from syslog import *
+
+import sipb_xen_database
+
+fuse.fuse_python_api = (0, 2)
+
+def getDepth(path):
+	"""
+	Return the depth of a given path, zero-based from root ('/')
+	"""
+	if path == '/':
+		return 0
+	else:
+		return path.count('/')
+
+def getParts(path):
+	"""
+	Return the slash-separated parts of a given path as a list
+	"""
+	# [1:] to exclude leading empty element
+	split = path.split('/')
+	if split[-1]:
+		return split[1:]
+	else:
+		return split[1:-1]
+
+def parse(path):
+	parts = getParts(path)
+	return parts, len(parts)
+
+class MyStat:
+	def __init__(self):
+		self.st_mode = 0
+		self.st_ino = 0
+		self.st_dev = 0
+		self.st_nlink = 0
+		self.st_uid = 0
+		self.st_gid = 0
+		self.st_size = 0
+		self.st_atime = 0
+		self.st_mtime = 0
+		self.st_ctime = 0
+	
+	def toTuple(self):
+		return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink, self.st_uid, self.st_gid, self.st_size, self.st_atime, self.st_mtime, self.st_ctime)
+
+class RemConfFS(Fuse):
+	"""
+	RemConfFS creates a filesytem for configuring remctl, like this:
+	/
+	|-- acl
+	|   |-- machine1
+	|   ...
+	|   `-- machinen
+	`-- conf.d
+	    |-- machine1
+	    ...
+	    `-- machinen
+
+	The machine list and the acls are drawn from a database.
+	
+	This filesystem only implements the getattr, getdir, read, and readlink
+	calls, because this is a read-only filesystem.
+	"""
+	
+	def __init__(self, *args, **kw):
+		"""Initialize the filesystem and set it to allow_other access besides
+		the user who mounts the filesystem (i.e. root)
+		"""
+		Fuse.__init__(self, *args, **kw)
+		self.lasttime = time()
+		self.allow_other = 1
+		
+		openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
+		
+		syslog(LOG_DEBUG, 'Init complete.')
+
+	def getMachines(self):
+		"""Get the list of VMs in the database, clearing the cache if it's 
+		older than 15 seconds"""
+		if time() - self.lasttime > 15:
+			self.lasttime = time()
+			sipb_xen_database.clear_cache()
+		return [machine.name for machine in sipb_xen_database.Machine.select()]
+		
+	def getacl(self, machine_name):
+		"""Build the ACL file for a machine
+		"""
+		machine = sipb_xen_database.Machine.get_by(name=machine_name)
+		users = [acl.user for acl in machine.acl]
+		return "\n".join(map(self.userToPrinc, users)
+				 + ['include /etc/remctl/acl/web',
+				    ''])
+		
+	def getconf(self):
+		"""Build the master conf file, with all machines
+		"""
+		return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control"
+				 " /etc/remctl/remconffs/acl/%s"
+				 % (machine_name, machine_name)
+				 for machine_name in self.getMachines())+'\n'
+	
+	def userToPrinc(self, user):
+		"""Convert Kerberos v4-style names to v5-style and append a default
+		realm if none is specified
+		"""
+		if '@' in user:
+			(princ, realm) = user.split('@')
+		else:
+			princ = user
+			realm = "ATHENA.MIT.EDU"
+		
+		return princ.replace('.', '/') + '@' + realm
+	
+	def getattr(self, path):
+		"""
+		- st_mode (protection bits)
+		- st_ino (inode number)
+		- st_dev (device)
+		- st_nlink (number of hard links)
+		- st_uid (user ID of owner)
+		- st_gid (group ID of owner)
+		- st_size (size of file, in bytes)
+		- st_atime (time of most recent access)
+		- st_mtime (time of most recent content modification)
+		- st_ctime (platform dependent; time of most recent metadata change on Unix,
+					or the time of creation on Windows).
+		"""
+		
+		syslog(LOG_DEBUG, "*** getattr: " + path)
+		
+		depth = getDepth(path)
+		parts = getParts(path)
+		
+		st = MyStat()
+		if path == '/':
+			st.st_mode = stat.S_IFDIR | 0755
+			st.st_nlink = 2
+		elif depth == 1:
+			if parts[0] == 'acl':
+				st.st_mode = stat.S_IFDIR | 0755
+				st.st_nlink = 2
+			elif parts[0] == 'conf':
+				st.st_mode = stat.S_IFREG | 0444
+				st.st_nlink = 1
+				st.st_size = len(self.getconf())
+			else:
+				return -errno.ENOENT
+		elif depth == 2:
+			if parts[0] != 'acl':
+				return -errno.ENOENT
+			if parts[1] not in self.getMachines():
+				return -errno.ENOENT
+			st.st_mode = stat.S_IFREG | 0444
+			st.st_nlink = 1
+			st.st_size = len(self.getacl(parts[1]))
+
+		return st.toTuple()
+	
+	# This call isn't actually used in the version of Fuse on console, but we
+	# wanted to leave it implemented to ease the transition in the future
+	def readdir(self, path, offset):
+		"""Return a generator with the listing for a directory
+		"""
+		syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
+		for (value, zero) in self.getdir(path):
+			yield fuse.Direntry(value)
+	
+	def getdir(self, path):
+		"""Return a list of tuples of the form (item, 0) with the contents of
+		the directory path
+		
+		Fuse doesn't add '.' or '..' on its own, so we have to
+		"""
+		syslog(LOG_DEBUG, '*** getdir %s' % path)
+		
+		parts, depth = parse(path)
+
+		if depth == 0:
+			contents = ('acl', 'conf')
+		elif depth == 1:
+			if parts[0] == 'acl':
+				contents = self.getMachines()
+			else:
+				return -errno.ENOENT
+		else:
+			return -errno.ENOTDIR
+
+		# Format the list the way that Fuse wants it - and don't forget to add
+		# '.' and '..'
+		return [(i, 0) for i in (list(contents) + ['.', '..'])]
+
+	def read(self, path, length, offset):
+		"""Read length bytes starting at offset of path. In most cases, this
+		just gets passed on to the OS
+		"""
+		syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
+		
+		parts, depth = parse(path)
+		
+		if depth == 0:
+			return -errno.EISDIR
+		elif parts[0] == 'conf':
+			return self.getconf()[offset:offset+length]
+		elif parts[0] == 'acl':
+			if depth == 1:
+				return -errno.EISDIR
+			if parts[1] in self.getMachines():
+				return self.getacl(parts[1])[offset:offset+length]
+		return -errno.ENOENT
+	
+	def readlink(self, path):
+		syslog(LOG_DEBUG, '*** readlink %s' % path)
+		return -errno.ENOENT
+
+
+if __name__ == '__main__':
+	sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
+	usage="""
+$0 [mount_path]
+"""
+	server = RemConfFS()
+	server.flags = 0
+	server.main()
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-listvms
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-listvms	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-listvms	(revision 571)
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+
+"""
+Collates the results of listvms from multiple VM servers.  Part of the xvm
+suite.
+"""
+
+from subprocess import PIPE, Popen
+try:
+    from subprocess import CalledProcessError
+except ImportError:
+    # Python 2.4 doesn't implement CalledProcessError
+    class CalledProcessError(Exception):
+        """This exception is raised when a process run by check_call() returns
+        a non-zero exit status. The exit status will be stored in the
+        returncode attribute."""
+        def __init__(self, returncode, cmd):
+            self.returncode = returncode
+            self.cmd = cmd
+        def __str__(self):
+            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+import sys
+import yaml
+
+###
+
+def main(argv):
+    # Query each of the server for their VMs.
+    # TODO get `servers` from a real list of all the VM hosts (instead of
+    # hardcoding the list here)
+    servers = ['black-mesa.mit.edu', 'sx-blade-2.mit.edu']
+    # XXX
+    pipes = [Popen(['remctl', server, 'remote', 'web', 'listvms'], stdout=PIPE)
+             for server in servers]
+    outputs = [p.communicate()[0] for p in pipes]
+    for p in pipes:
+        if p.returncode != 0:
+            raise CalledProcessError(p.returncode, cmd)
+    results = [yaml.load(o, yaml.CSafeLoader) for o in outputs]
+    results = filter(lambda x: x is not None, results)
+
+    # Merge the results and print.
+    merged = {}
+    for result in results:
+        merged.update(result)
+    print yaml.dump(merged, Dumper=yaml.CSafeDumper, default_flow_style=False)
+
+if __name__ == '__main__':
+    main(sys.argv)
+
+# vim:et:sw=2:ts=4
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy	(revision 571)
@@ -0,0 +1,12 @@
+#!/bin/sh
+# invoke as sipb-xen-remote-proxy-$TYPE, with "TYPE" in the remctl sense.
+klist -s || kinit -k host/remote.mit.edu
+#echo remctl black-mesa remote ${0##*-} "$@"
+if false ; then
+  remctl black-mesa remote ${0##*-} "$@"
+else
+  case "$1" in
+    listvms ) shift; sipb-xen-remote-listvms "$@" ;;
+    *       ) remctl black-mesa remote ${0##*-} "$@" ;;
+  esac
+fi
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy-control
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy-control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy-control	(revision 571)
@@ -0,0 +1,1 @@
+link sipb-xen-remote-proxy
Index: /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy-web
===================================================================
--- /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy-web	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remote-proxy-web	(revision 571)
@@ -0,0 +1,1 @@
+link sipb-xen-remote-proxy
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/AuthPanel.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/AuthPanel.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/AuthPanel.java	(revision 571)
@@ -0,0 +1,116 @@
+//
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//  Copyright (C) 2002-2006 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+
+//
+// The panel which implements the user authentication scheme
+//
+
+class AuthPanel extends Panel implements ActionListener {
+
+  TextField passwordField;
+  Button okButton;
+
+  //
+  // Constructor.
+  //
+
+  public AuthPanel(VncViewer viewer)
+  {
+    Label titleLabel = new Label("VNC Authentication", Label.CENTER);
+    titleLabel.setFont(new Font("Helvetica", Font.BOLD, 18));
+
+    Label promptLabel = new Label("Password:", Label.CENTER);
+
+    passwordField = new TextField(10);
+    passwordField.setForeground(Color.black);
+    passwordField.setBackground(Color.white);
+    passwordField.setEchoChar('*');
+
+    okButton = new Button("OK");
+
+    GridBagLayout gridbag = new GridBagLayout();
+    GridBagConstraints gbc = new GridBagConstraints();
+
+    setLayout(gridbag);
+
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.insets = new Insets(0,0,20,0);
+    gridbag.setConstraints(titleLabel,gbc);
+    add(titleLabel);
+
+    gbc.fill = GridBagConstraints.NONE;
+    gbc.gridwidth = 1;
+    gbc.insets = new Insets(0,0,0,0);
+    gridbag.setConstraints(promptLabel,gbc);
+    add(promptLabel);
+
+    gridbag.setConstraints(passwordField,gbc);
+    add(passwordField);
+    passwordField.addActionListener(this);
+
+    // gbc.ipady = 10;
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.insets = new Insets(0,20,0,0);
+    gbc.ipadx = 30;
+    gridbag.setConstraints(okButton,gbc);
+    add(okButton);
+    okButton.addActionListener(this);
+  }
+
+  //
+  // Move keyboard focus to the default object, that is, the password
+  // text field.
+  //
+
+  public void moveFocusToDefaultField()
+  {
+    passwordField.requestFocus();
+  }
+
+  //
+  // This method is called when a button is pressed or return is
+  // pressed in the password text field.
+  //
+
+  public synchronized void actionPerformed(ActionEvent evt)
+  {
+    if (evt.getSource() == passwordField || evt.getSource() == okButton) {
+      passwordField.setEnabled(false);
+      notify();
+    }
+  }
+
+  //
+  // Wait for user entering a password, and return it as String.
+  //
+
+  public synchronized String getPassword() throws Exception
+  {
+    try {
+      wait();
+    } catch (InterruptedException e) { }
+    return passwordField.getText();
+  }
+
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/ButtonPanel.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/ButtonPanel.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/ButtonPanel.java	(revision 571)
@@ -0,0 +1,175 @@
+//
+//  Copyright (C) 2001,2002 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// ButtonPanel class implements panel with four buttons in the
+// VNCViewer desktop window.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+
+class ButtonPanel extends Panel implements ActionListener, ItemListener {
+
+  VncViewer viewer;
+  Button disconnectButton;
+  Button optionsButton;
+  Button recordButton;
+  Button clipboardButton;
+  Button ctrlAltDelButton;
+  Button refreshButton;
+  Checkbox altCheckbox;
+  Checkbox ctrlCheckbox;
+
+  ButtonPanel(VncViewer v) {
+    viewer = v;
+
+    setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+    disconnectButton = new Button("Disconnect");
+    disconnectButton.setEnabled(false);
+    add(disconnectButton);
+    disconnectButton.addActionListener(this);
+    optionsButton = new Button("Options");
+    add(optionsButton);
+    optionsButton.addActionListener(this);
+    clipboardButton = new Button("Clipboard");
+    clipboardButton.setEnabled(false);
+    add(clipboardButton);
+    clipboardButton.addActionListener(this);
+    if (viewer.rec != null) {
+      recordButton = new Button("Record");
+      add(recordButton);
+      recordButton.addActionListener(this);
+    }
+    ctrlAltDelButton = new Button("Send Ctrl-Alt-Del");
+    ctrlAltDelButton.setEnabled(false);
+    add(ctrlAltDelButton);
+    ctrlAltDelButton.addActionListener(this);
+    refreshButton = new Button("Refresh");
+    refreshButton.setEnabled(false);
+    add(refreshButton);
+    refreshButton.addActionListener(this);
+    
+    altCheckbox = new Checkbox("Alt");
+    altCheckbox.setEnabled(false);
+    add(altCheckbox);
+    altCheckbox.addItemListener(this);
+    ctrlCheckbox = new Checkbox("Control");
+    ctrlCheckbox.setEnabled(false);
+    add(ctrlCheckbox);
+    ctrlCheckbox.addItemListener(this);
+  }
+
+  //
+  // Enable buttons on successful connection.
+  //
+
+  public void enableButtons() {
+    disconnectButton.setEnabled(true);
+    clipboardButton.setEnabled(true);
+    refreshButton.setEnabled(true);
+  }
+
+  //
+  // Disable all buttons on disconnect.
+  //
+
+  public void disableButtonsOnDisconnect() {
+    remove(disconnectButton);
+    disconnectButton = new Button("Hide desktop");
+    disconnectButton.setEnabled(true);
+    add(disconnectButton, 0);
+    disconnectButton.addActionListener(this);
+
+    optionsButton.setEnabled(false);
+    clipboardButton.setEnabled(false);
+    ctrlAltDelButton.setEnabled(false);
+    refreshButton.setEnabled(false);
+
+    validate();
+  }
+
+  //
+  // Enable/disable controls that should not be available in view-only
+  // mode.
+  //
+
+  public void enableRemoteAccessControls(boolean enable) {
+    ctrlAltDelButton.setEnabled(enable);
+    ctrlCheckbox.setEnabled(enable);
+    altCheckbox.setEnabled(enable);
+  }
+
+  //
+  // Event processing.
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+
+    viewer.moveFocusToDesktop();
+
+    if (evt.getSource() == disconnectButton) {
+      viewer.disconnect();
+
+    } else if (evt.getSource() == optionsButton) {
+      viewer.options.setVisible(!viewer.options.isVisible());
+
+    } else if (evt.getSource() == recordButton) {
+      viewer.rec.setVisible(!viewer.rec.isVisible());
+
+    } else if (evt.getSource() == clipboardButton) {
+      viewer.clipboard.setVisible(!viewer.clipboard.isVisible());
+
+    } else if (evt.getSource() == ctrlAltDelButton) {
+      try {
+        final int modifiers = InputEvent.CTRL_MASK | InputEvent.ALT_MASK;
+
+        KeyEvent ctrlAltDelEvent =
+          new KeyEvent(this, KeyEvent.KEY_PRESSED, 0, modifiers, 127);
+        viewer.rfb.writeKeyEvent(ctrlAltDelEvent);
+
+        ctrlAltDelEvent =
+          new KeyEvent(this, KeyEvent.KEY_RELEASED, 0, modifiers, 127);
+        viewer.rfb.writeKeyEvent(ctrlAltDelEvent);
+
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    } else if (evt.getSource() == refreshButton) {
+      try {
+	RfbProto rfb = viewer.rfb;
+	rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+					  rfb.framebufferHeight, false);
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+    public void itemStateChanged(ItemEvent evt) {
+	viewer.moveFocusToDesktop();
+	int state = evt.getStateChange();
+	int extraModifiers = 0;
+	if (altCheckbox.getState()) { extraModifiers |= InputEvent.ALT_MASK; }
+	if (ctrlCheckbox.getState()) { extraModifiers |= InputEvent.CTRL_MASK; }
+	viewer.vc.extraModifiers = extraModifiers;
+    }
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/CapabilityInfo.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/CapabilityInfo.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/CapabilityInfo.java	(revision 571)
@@ -0,0 +1,87 @@
+//
+//  Copyright (C) 2003 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// CapabilityInfo.java - A class to hold information about a
+// particular capability as used in the RFB protocol 3.130.
+//
+
+class CapabilityInfo {
+
+  // Public methods
+
+  public CapabilityInfo(int code,
+			String vendorSignature,
+			String nameSignature,
+			String description) {
+    this.code = code;
+    this.vendorSignature = vendorSignature;
+    this.nameSignature = nameSignature;
+    this.description = description;
+    enabled = false;
+  }
+
+  public CapabilityInfo(int code,
+			byte[] vendorSignature,
+			byte[] nameSignature) {
+    this.code = code;
+    this.vendorSignature = new String(vendorSignature);
+    this.nameSignature = new String(nameSignature);
+    this.description = null;
+    enabled = false;
+  }
+
+  public int getCode() {
+    return code;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void enable() {
+    enabled = true;
+  }
+
+  public boolean equals(CapabilityInfo other) {
+    return (other != null && this.code == other.code &&
+	    this.vendorSignature.equals(other.vendorSignature) &&
+	    this.nameSignature.equals(other.nameSignature));
+  }
+
+  public boolean enableIfEquals(CapabilityInfo other) {
+    if (this.equals(other))
+      enable();
+
+    return isEnabled();
+  }
+
+  // Protected data
+
+  protected int code;
+  protected String vendorSignature;
+  protected String nameSignature;
+
+  protected String description;
+  protected boolean enabled;
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/CapsContainer.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/CapsContainer.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/CapsContainer.java	(revision 571)
@@ -0,0 +1,103 @@
+//
+//  Copyright (C) 2003 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// CapsContainer.java - A container of capabilities as used in the RFB
+// protocol 3.130
+//
+
+import java.util.Vector;
+import java.util.Hashtable;
+
+class CapsContainer {
+
+  // Public methods
+
+  public CapsContainer() {
+    infoMap = new Hashtable(64, (float)0.25);
+    orderedList = new Vector(32, 8);
+  }
+
+  public void add(CapabilityInfo capinfo) {
+    Integer key = new Integer(capinfo.getCode());
+    infoMap.put(key, capinfo);
+  }
+
+  public void add(int code, String vendor, String name, String desc) {
+    Integer key = new Integer(code);
+    infoMap.put(key, new CapabilityInfo(code, vendor, name, desc));
+  }
+
+  public boolean isKnown(int code) {
+    return infoMap.containsKey(new Integer(code));
+  }
+
+  public CapabilityInfo getInfo(int code) {
+    return (CapabilityInfo)infoMap.get(new Integer(code));
+  }
+
+  public String getDescription(int code) {
+    CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+    if (capinfo == null)
+      return null;
+
+    return capinfo.getDescription();
+  }
+
+  public boolean enable(CapabilityInfo other) {
+    Integer key = new Integer(other.getCode());
+    CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(key);
+    if (capinfo == null)
+      return false;
+
+    boolean enabled = capinfo.enableIfEquals(other);
+    if (enabled)
+      orderedList.addElement(key);
+
+    return enabled;
+  }
+
+  public boolean isEnabled(int code) {
+    CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+    if (capinfo == null)
+      return false;
+
+    return capinfo.isEnabled();
+  }
+
+  public int numEnabled() {
+    return orderedList.size();
+  }
+
+  public int getByOrder(int idx) {
+    int code;
+    try {
+      code = ((Integer)orderedList.elementAt(idx)).intValue();
+    } catch (ArrayIndexOutOfBoundsException e) {
+      code = 0;
+    }
+    return code;
+  }
+
+  // Protected data
+
+  protected Hashtable infoMap;
+  protected Vector orderedList;
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/ChangeLog
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/ChangeLog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/ChangeLog	(revision 571)
@@ -0,0 +1,1708 @@
+------------------------------------------------------------------------
+r2273 | const_k | 2007-04-27 09:36:00 +0700 (Fri, 27 Apr 2007) | 5 lines
+Changed paths:
+   M /orig/tags/VERSION_1_3_9/vnc_javasrc/RfbProto.java
+   M /orig/tags/VERSION_1_3_9/vnc_javasrc/VncCanvas.java
+   M /orig/tags/VERSION_1_3_9/vnc_javasrc/VncViewer.java
+
+Reverted changes from orig/trunk/vnc_javasrc, revisions 2252:2244, as they really should not go the the 1.3.9 release. These changes were as follows:
+- rev.2245: Printing update statistics on disconnect: number of FramebufferUpdate messages, counters of real and pseudo rectangles in framebuffer updates.
+- rev.2246: Printing more statistics on disconnect: average update rate, and rectangle counters per each encoder (Tight, ZRLE, Hextile, Raw, CopyRect, others).
+- rev.2252: Initial support for continuous updates.
+
+------------------------------------------------------------------------
+r2271 | const_k | 2007-04-26 18:28:24 +0700 (Thu, 26 Apr 2007) | 2 lines
+Changed paths:
+   A /orig/tags/VERSION_1_3_9/vnc_javasrc (from /orig/trunk/vnc_javasrc:2270)
+
+Tagging Java Viewer version 1.3.9. NOTE: Actually this is not version 1.3.9 yet -- some of the recent commits should be reverted to achieve version 1.3.9, see more commits in this directory.
+
+------------------------------------------------------------------------
+r2262 | const_k | 2007-04-25 16:12:53 +0700 (Wed, 25 Apr 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.3.9.
+
+------------------------------------------------------------------------
+r2261 | const_k | 2007-04-25 16:02:32 +0700 (Wed, 25 Apr 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Documented auto-scaling.
+
+------------------------------------------------------------------------
+r2252 | const_k | 2007-04-05 15:45:40 +0700 (Thu, 05 Apr 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Initial support for continuous updates.
+
+------------------------------------------------------------------------
+r2246 | const_k | 2007-03-29 13:00:23 +0700 (Thu, 29 Mar 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Printing more statistics on disconnect: average update rate, and rectangle counters per each encoder (Tight, ZRLE, Hextile, Raw, CopyRect, others).
+
+------------------------------------------------------------------------
+r2245 | const_k | 2007-03-29 11:39:46 +0700 (Thu, 29 Mar 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Printing update statistics on disconnect: number of FramebufferUpdate messages, counters of real and pseudo rectangles in framebuffer updates.
+
+------------------------------------------------------------------------
+r2244 | const_k | 2007-03-29 10:57:07 +0700 (Thu, 29 Mar 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Fixed wrong pixel format interpretation in decoding RichCursor pseudo-encoding.
+
+------------------------------------------------------------------------
+r2243 | const_k | 2007-03-29 10:02:23 +0700 (Thu, 29 Mar 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Requesting encoding types in more suitable order. Now when Tight is preferred, we request Tight,ZRLE,Hextile,others instead of Tight,Hextile,ZRLE,others.
+
+------------------------------------------------------------------------
+r2230 | const_k | 2007-02-17 01:40:31 +0600 (Sat, 17 Feb 2007) | 7 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncCanvas2.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implemented support for auto-scaling. To enable it, the "Scaling factor"
+parameter should be set to "auto". Auto-scaling tries to choose scaling
+factor such way that the whole remote framebuffer will fit on the local
+screen. Currently, auto-scaling is supported only when the remote desktop
+is shown in a separate frame (always true in application mode, and in
+applet mode with "Open new window" parameter set to "yes").
+
+------------------------------------------------------------------------
+r2229 | const_k | 2007-02-16 21:46:17 +0600 (Fri, 16 Feb 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Minor code refactoring - a chunk of code moved to a new method VncViewer.createCanvas().
+
+------------------------------------------------------------------------
+r2228 | const_k | 2007-02-08 18:23:43 +0600 (Thu, 08 Feb 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Optimized ZRLE decoder for better performance.
+
+------------------------------------------------------------------------
+r2227 | const_k | 2007-02-08 16:58:31 +0600 (Thu, 08 Feb 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+
+Minor fix -- a constant was not updated on introducing ZRLE encoding.
+
+------------------------------------------------------------------------
+r2226 | const_k | 2007-02-08 16:54:03 +0600 (Thu, 08 Feb 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Cleaned up and documented issues with session recording and ZRLE.
+
+------------------------------------------------------------------------
+r2225 | const_k | 2007-02-06 10:08:39 +0600 (Tue, 06 Feb 2007) | 2 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc/InStream.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   A /orig/trunk/vnc_javasrc/MemInStream.java
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+   A /orig/trunk/vnc_javasrc/ZlibInStream.java
+
+Initial version of ZRLE decoder. It's fully functional except for session recording which is broken for ZRLE at the moment.
+
+------------------------------------------------------------------------
+r2224 | const_k | 2007-01-30 12:02:24 +0600 (Tue, 30 Jan 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Added documentation on using parameters.
+
+------------------------------------------------------------------------
+r2223 | const_k | 2007-01-30 10:46:54 +0600 (Tue, 30 Jan 2007) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Documented the "Scaling factor" parameter.
+
+------------------------------------------------------------------------
+r2191 | const_k | 2006-12-08 10:55:49 +0600 (Fri, 08 Dec 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Always send the "security result" message in the protocol version 3.8, even after an empty list of authentication capabilities. This almost reverts changes in rev.2180.
+
+------------------------------------------------------------------------
+r2180 | const_k | 2006-12-05 11:17:15 +0600 (Tue, 05 Dec 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Fixed a problem in handing TightVNC protocol extensions - empty authentication capability list assumes not just skipping authentication itself but also not waiting for the "security result" message.
+
+------------------------------------------------------------------------
+r2179 | const_k | 2006-12-05 10:50:40 +0600 (Tue, 05 Dec 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc
+
+Ignoring TAGS file.
+
+------------------------------------------------------------------------
+r2132 | const_k | 2006-11-26 13:33:32 +0600 (Sun, 26 Nov 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Code refactoring. The primary change is that all authentication code has been moved out of AuthPanel which now provides GUI part only.
+
+------------------------------------------------------------------------
+r2131 | const_k | 2006-11-24 13:39:49 +0600 (Fri, 24 Nov 2006) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Improved support for protocol 3.8. Now authentication failures should be
+reported with explanations received from the server. Actual authentication
+code has been moved to RfbProto. AuthPanel does not offer repetitive
+authentication tries, the "Login again" button should be used instead.
+
+------------------------------------------------------------------------
+r2130 | const_k | 2006-11-24 10:53:15 +0600 (Fri, 24 Nov 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+
+Removed an outdated FIXME comment.
+
+------------------------------------------------------------------------
+r2128 | const_k | 2006-11-23 18:00:59 +0600 (Thu, 23 Nov 2006) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas2.java
+
+Disabling focus traversal keys under JVMs 1.4 and higher. This fixes the
+problem with not sending Tab key events to the VNC server.
+
+------------------------------------------------------------------------
+r2127 | const_k | 2006-11-23 16:53:32 +0600 (Thu, 23 Nov 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Fixed rounding problems on calculating the coordinates of changed area.
+
+------------------------------------------------------------------------
+r2126 | const_k | 2006-11-23 16:39:53 +0600 (Thu, 23 Nov 2006) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   A /orig/trunk/vnc_javasrc/VncCanvas2.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implemented enhanced scaling if Java 2D API is available. This works in
+Java 1.2 or higher, but the viewer remains compatible with Java 1.1 where
+it would simply use scaling with decreased image quality.
+
+------------------------------------------------------------------------
+r2124 | const_k | 2006-11-23 10:56:05 +0600 (Thu, 23 Nov 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Small correction in documentation.
+
+------------------------------------------------------------------------
+r2122 | const_k | 2006-11-22 16:06:29 +0600 (Wed, 22 Nov 2006) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+Fixed a compilation problem - a variable was removed but is was initialized
+elsewhere.
+
+------------------------------------------------------------------------
+r2115 | const_k | 2006-11-20 17:50:37 +0600 (Mon, 20 Nov 2006) | 7 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Simple implementation of client-side scaling, controlled by new "Scaling
+Factor" parameter. This implementation provides low-quality scaling but
+is compatible with Java 1.1. Things to do next: (1) GUI for "Scaling
+Factor" parameter; (2) documentation for "Scaling Factor" parameter;
+(3) new scaling implementation based on Java 2D which would require
+Java 2 platform but hopefully would show much higher scaling quality.
+
+------------------------------------------------------------------------
+r2097 | const_k | 2006-09-14 15:50:08 +0700 (Thu, 14 Sep 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Initial support for RFB protocol version 3.8.
+
+------------------------------------------------------------------------
+r2095 | const_k | 2006-09-14 08:48:00 +0700 (Thu, 14 Sep 2006) | 2 lines
+Changed paths:
+   D /orig/trunk/vnc_javasrc/AuthUnixLoginPanel.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Removed support for UnixLogin authentication method -- it was not officially supported or documented.
+
+------------------------------------------------------------------------
+r2094 | const_k | 2006-09-14 08:12:25 +0700 (Thu, 14 Sep 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc
+
+Ignoring files created on compilation.
+
+------------------------------------------------------------------------
+r2079 | const_k | 2006-08-10 17:26:41 +0700 (Thu, 10 Aug 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string and copyright info changed, version 1.3.8.
+
+------------------------------------------------------------------------
+r2064 | const_k | 2006-06-15 20:43:19 +0700 (Thu, 15 Jun 2006) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed for version 1.3dev8.
+
+------------------------------------------------------------------------
+r2063 | const_k | 2006-06-15 20:38:28 +0700 (Thu, 15 Jun 2006) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Updated description of the "Encoding" parameter that now can be set
+and defaults to "Auto".
+
+------------------------------------------------------------------------
+r2039 | const_k | 2005-10-03 22:51:28 +0700 (Mon, 03 Oct 2005) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Improved the VncViewer.setEncodings() method. Now it does not build
+the complete encoding list when auto-selecting encodings.
+
+------------------------------------------------------------------------
+r2038 | const_k | 2005-10-03 22:26:11 +0700 (Mon, 03 Oct 2005) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implemented encoding auto selection based on measuring current network
+throughput.
+
+------------------------------------------------------------------------
+r2037 | const_k | 2005-10-03 20:25:49 +0700 (Mon, 03 Oct 2005) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Improved encoding selection code. Also, now we request compression and
+quality levels regardless of current preferred encoding and color
+format.
+
+------------------------------------------------------------------------
+r2036 | const_k | 2005-10-03 09:52:26 +0700 (Mon, 03 Oct 2005) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Now the encoding array is prepared in the VncViewer.setEncodings()
+method, instead of OptionsFrame.setEncodings(). This will allow to
+implement auto encoding selection in VncViewer.setEncodings().
+
+------------------------------------------------------------------------
+r2035 | const_k | 2005-10-03 08:32:47 +0700 (Mon, 03 Oct 2005) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Added JCFLAGS variable for javac command-line flags.
+
+------------------------------------------------------------------------
+r2033 | const_k | 2005-09-30 19:42:25 +0700 (Fri, 30 Sep 2005) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Reverted accidental change included in the previous commit.
+
+------------------------------------------------------------------------
+r2032 | const_k | 2005-09-30 19:27:17 +0700 (Fri, 30 Sep 2005) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Measuring network throughput. This will allow auto encoding selection
+work properly.
+
+------------------------------------------------------------------------
+r2031 | const_k | 2005-09-30 10:38:26 +0700 (Fri, 30 Sep 2005) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+Starting implementation of automatic encoding selection. Right now,
+the "Auto" choice in the encoding list is equivalent to "Tight", but
+without an option to set the compression level.
+
+------------------------------------------------------------------------
+r2019 | const_k | 2005-07-03 16:03:05 +0700 (Sun, 03 Jul 2005) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed for version 1.3dev7.
+
+------------------------------------------------------------------------
+r2018 | const_k | 2005-07-03 15:57:50 +0700 (Sun, 03 Jul 2005) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/index.html
+
+More information about editing the index.html example.
+
+------------------------------------------------------------------------
+r1906 | const_k | 2004-10-10 18:05:45 +0700 (Sun, 10 Oct 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Added documentation for the "Scale remote cursor" option.
+
+------------------------------------------------------------------------
+r1905 | const_k | 2004-10-10 13:15:54 +0700 (Sun, 10 Oct 2004) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+New "scale remote cursor" option allowing to reduce or enlarge soft
+cursor image in the full-control mode. This change is based on a patch
+from Horizon Wimba.
+
+------------------------------------------------------------------------
+r1903 | const_k | 2004-10-09 19:47:22 +0700 (Sat, 09 Oct 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.3dev6.
+
+------------------------------------------------------------------------
+r1902 | const_k | 2004-10-09 18:08:29 +0700 (Sat, 09 Oct 2004) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Applied a patch from Horizon Wimba, to remove synchronization from the
+paint method and deal with cursor repaints properly.
+
+------------------------------------------------------------------------
+r1838 | const_k | 2004-08-22 13:42:50 +0700 (Sun, 22 Aug 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+A typo fixed.
+
+------------------------------------------------------------------------
+r1836 | const_k | 2004-08-22 12:14:48 +0700 (Sun, 22 Aug 2004) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Made the VncCanvas.paint() method synchronized, to protect cursorX and
+cursorY members from concurrent access.
+
+------------------------------------------------------------------------
+r1742 | const_k | 2004-05-30 21:50:42 +0700 (Sun, 30 May 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.3dev5.
+
+------------------------------------------------------------------------
+r1642 | const_k | 2004-03-04 20:02:16 +0600 (Thu, 04 Mar 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.3dev4.
+
+------------------------------------------------------------------------
+r1641 | const_k | 2004-03-04 19:34:25 +0600 (Thu, 04 Mar 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   A /orig/trunk/vnc_javasrc/AuthUnixLoginPanel.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Added support for Unix login-style authentication.
+
+------------------------------------------------------------------------
+r1639 | const_k | 2004-03-04 00:57:24 +0600 (Thu, 04 Mar 2004) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/CapabilityInfo.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Added support for TightVNC protocol extensions in RFB 3.7 protocol.
+
+------------------------------------------------------------------------
+r1635 | const_k | 2004-03-02 22:55:58 +0600 (Tue, 02 Mar 2004) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Added support for RFB protocol version 3.7, without TightVNC protocol
+extensions yet.
+
+------------------------------------------------------------------------
+r1527 | const_k | 2003-07-24 22:29:13 +0700 (Thu, 24 Jul 2003) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Minor fix to move the keyboard focus to VncCanvas on opening the
+desktop.
+
+------------------------------------------------------------------------
+r1526 | const_k | 2003-07-24 21:42:30 +0700 (Thu, 24 Jul 2003) | 6 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Applied a set of changes by HorizonLive.com, Inc. In the VncCanvas
+class, fixed a problem of createImage() returning null. In the
+VncViewer, problems with some JVMs hanging on destroying the applet
+were solved. Also, implemented a possibility to enable/disable input
+via inter-applet communication.
+
+------------------------------------------------------------------------
+r1496 | const_k | 2003-07-02 19:05:18 +0700 (Wed, 02 Jul 2003) | 3 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc/CapabilityInfo.java
+   A /orig/trunk/vnc_javasrc/CapsContainer.java
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Implemented a Java version of the CapsContainer class that will be
+used in the protocol 3.130 handling code.
+
+------------------------------------------------------------------------
+r1465 | const_k | 2003-05-18 20:45:11 +0700 (Sun, 18 May 2003) | 6 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Code refactored to enable integration of pluggable authentication
+schemes. Now the Java viewer first connects to the server, then shows
+the authentication panel only if the server requires authentication.
+All the authentication code has been moved to the AuthPanel class.
+Also, now the viewer shows status messages on connecting to the server.
+
+------------------------------------------------------------------------
+r1377 | const_k | 2003-03-02 16:54:57 +0600 (Sun, 02 Mar 2003) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+
+Passing through X keysyms for foreign currencies, a modified patch
+from Bernd Krueger-Knauber.
+
+------------------------------------------------------------------------
+r1315 | const_k | 2003-01-22 20:35:58 +0600 (Wed, 22 Jan 2003) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.2.8.
+
+------------------------------------------------------------------------
+r1236 | const_k | 2002-11-13 23:50:33 +0600 (Wed, 13 Nov 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.2.7.
+
+------------------------------------------------------------------------
+r1233 | const_k | 2002-11-12 15:21:28 +0600 (Tue, 12 Nov 2002) | 3 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc/MANIFEST.MF
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Added a MANIFEST file with a Main-Class statement to allow easy
+execution of the JAR file, using java -jar command-line option.
+
+------------------------------------------------------------------------
+r1232 | const_k | 2002-11-12 15:18:48 +0600 (Tue, 12 Nov 2002) | 2 lines
+Changed paths:
+   D /orig/trunk/vnc_javasrc/dir.mk
+
+Removed dir.mk file.
+
+------------------------------------------------------------------------
+r1231 | const_k | 2002-11-12 15:15:04 +0600 (Tue, 12 Nov 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/index.html
+
+Applet height increased by 32 pixels.
+
+------------------------------------------------------------------------
+r1230 | const_k | 2002-11-12 13:34:58 +0600 (Tue, 12 Nov 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Extra .vnc files have been removed, having just index.vnc should be
+enough.
+
+------------------------------------------------------------------------
+r1229 | const_k | 2002-11-12 13:33:04 +0600 (Tue, 12 Nov 2002) | 4 lines
+Changed paths:
+   D /orig/trunk/vnc_javasrc/hextile.vnc
+   A /orig/trunk/vnc_javasrc/index.html
+   M /orig/trunk/vnc_javasrc/index.vnc
+   D /orig/trunk/vnc_javasrc/noshared.vnc
+   D /orig/trunk/vnc_javasrc/shared.vnc
+   D /orig/trunk/vnc_javasrc/tight.vnc
+   D /orig/trunk/vnc_javasrc/zlib.vnc
+
+Extra .vnc files have been removed, having just index.vnc should be
+enough. Also, an example HTML page has been prepared, to simplify
+installation under a standalone Web server.
+
+------------------------------------------------------------------------
+r1228 | const_k | 2002-11-12 13:13:16 +0600 (Tue, 12 Nov 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Documented three ways to use the Java viewer, in the Installation
+section.
+
+------------------------------------------------------------------------
+r1227 | const_k | 2002-11-07 19:12:46 +0600 (Thu, 07 Nov 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Minor change to preserve keyboard focus in VncCanvas after resizing
+the frame, when running in a separate window.
+
+------------------------------------------------------------------------
+r1226 | const_k | 2002-11-06 22:49:20 +0600 (Wed, 06 Nov 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/README
+   A /orig/trunk/vnc_javasrc/ReloginPanel.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implemented new buttons "Login again" and "Close window" near the
+disconnect or error messages in the applet mode, and introduced new
+"Offer Relogin" parameter to control this improvement. Thanks to Peter
+Astrand for the initial version of the "Login again" patch.
+
+------------------------------------------------------------------------
+r1214 | const_k | 2002-10-30 00:26:34 +0600 (Wed, 30 Oct 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Don't defer update requests if there is some data to receive, or if
+the last update included a PointerPos message.
+
+------------------------------------------------------------------------
+r1213 | const_k | 2002-10-29 23:06:06 +0600 (Tue, 29 Oct 2002) | 4 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc/HTTPConnectSocket.java
+   A /orig/trunk/vnc_javasrc/HTTPConnectSocketFactory.java
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Support for connections via HTTP proxies using HTTP CONNECT method.
+Most likely, this will not work in applet mode, due to security
+restrictions in JVMs.
+
+------------------------------------------------------------------------
+r1212 | const_k | 2002-10-29 23:03:21 +0600 (Tue, 29 Oct 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Added support for new CursorPos pseudo-encoding which allows to
+transmit pointer position from server to clients.
+
+------------------------------------------------------------------------
+r1192 | const_k | 2002-09-25 04:29:05 +0700 (Wed, 25 Sep 2002) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RecordingFrame.java
+
+A patch from Harmen van der Wal -- "a workaround for AFAIK a rare
+(Blackdown 1.1.7) SecurityManager.checkPropertyAccess() bug, that
+would otherwise be fatal for an unprivileged applet".
+
+------------------------------------------------------------------------
+r1191 | const_k | 2002-09-25 04:23:48 +0700 (Wed, 25 Sep 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Enhancements to the exception handling mechanisms, a patch from Harmen
+van der Wal.
+
+------------------------------------------------------------------------
+r1190 | const_k | 2002-09-25 04:01:49 +0700 (Wed, 25 Sep 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   A /orig/trunk/vnc_javasrc/SocketFactory.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+A patch from Harmen van der Wal, which makes it easy to plug-in
+alternative transport methods to the viewer. It can be useful for for
+things like HTTP tunneling, SSL support, or perhaps for integration
+with "zebedee", ssh or other tunneling mechanisms.
+
+------------------------------------------------------------------------
+r1189 | const_k | 2002-09-24 08:52:32 +0700 (Tue, 24 Sep 2002) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Reducing max frame size by 30 pixels in each direction, to leave some
+place on the screen, e.g. for the menu bar on Macintosh or the task
+bar on Windows; a patch from Steve Kann.
+
+------------------------------------------------------------------------
+r1171 | const_k | 2002-08-27 19:23:50 +0700 (Tue, 27 Aug 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed, version 1.2.6.
+
+------------------------------------------------------------------------
+r1141 | const | 2002-08-04 23:39:35 +0700 (Sun, 04 Aug 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Version string changed, version 1.2.5. Copyrights updated.
+
+------------------------------------------------------------------------
+r1130 | const | 2002-07-05 15:37:32 +0700 (Fri, 05 Jul 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+More robust and correct methods to determine if Zlib- or Tight-encoded
+data should be saved Raw-encoded or re-compressed in recorded
+sessions. Also, always emit warnings in the Java console if such
+recoding was necessary.
+
+------------------------------------------------------------------------
+r1129 | const | 2002-07-05 15:26:16 +0700 (Fri, 05 Jul 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Minor documentation addition.
+
+------------------------------------------------------------------------
+r1127 | const | 2002-07-05 13:17:23 +0700 (Fri, 05 Jul 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+JPEG quality setting should be enabled in the Options frame only in
+the 24-bit color mode.
+
+------------------------------------------------------------------------
+r1126 | const | 2002-07-05 13:02:37 +0700 (Fri, 05 Jul 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+Unused temporary hack was removed.
+
+------------------------------------------------------------------------
+r1125 | const | 2002-07-04 03:25:47 +0700 (Thu, 04 Jul 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Some documentation on RFB session recording.
+
+------------------------------------------------------------------------
+r1124 | const | 2002-07-04 02:43:43 +0700 (Thu, 04 Jul 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+
+Forgot to remove debugging output.
+
+------------------------------------------------------------------------
+r1123 | const | 2002-07-04 02:38:15 +0700 (Thu, 04 Jul 2002) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Re-compressing Tight-encoded rectangles when recorded session starts
+after the connection was established, to make it possible to
+decompress the data without knowing prior pixel data.
+
+------------------------------------------------------------------------
+r1122 | const | 2002-07-03 21:11:42 +0700 (Wed, 03 Jul 2002) | 6 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Special handling of Zlib-encoded rectangles -- they are written either
+Zlib-encoded if it's a beginning of RFB session, or Raw encoded
+otherwise. This is needed to make sure it will be possible to decode
+saved data without knowing the state of zlib compression stream used
+by the encoder.
+
+------------------------------------------------------------------------
+r1121 | const | 2002-07-03 17:49:59 +0700 (Wed, 03 Jul 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/RecordingFrame.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+The "Record" button now appears only if current SecurityManager allows
+access to the local filesystem.
+Exceptions after an intentional disconnect are not shown in the applet
+panel or window any more.
+
+------------------------------------------------------------------------
+r1120 | const | 2002-07-03 16:40:52 +0700 (Wed, 03 Jul 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RecordingFrame.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implement dynamic toggling of session recording.
+
+------------------------------------------------------------------------
+r1119 | const | 2002-07-03 13:34:35 +0700 (Wed, 03 Jul 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Preparing to implement dynamic toggling of session recording.
+The "Save Session" parameter was removed.
+
+------------------------------------------------------------------------
+r1118 | const | 2002-07-01 12:44:17 +0700 (Mon, 01 Jul 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RecordingFrame.java
+
+Fixed copyright string.
+
+------------------------------------------------------------------------
+r1114 | const | 2002-06-13 01:45:21 +0700 (Thu, 13 Jun 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/RecordingFrame.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+A simple hack to make the RecordingFrame work, at least when the
+recording is being turned on before making the connection. The
+RecordingFrame is still not very useful but at least does allow to
+record a whole session in one file.
+
+------------------------------------------------------------------------
+r1113 | const | 2002-06-12 19:03:20 +0700 (Wed, 12 Jun 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   A /orig/trunk/vnc_javasrc/RecordingFrame.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Added new "Record" button and a GUI to control recording of sessions
+in FBS files.
+
+------------------------------------------------------------------------
+r1112 | const | 2002-06-05 01:01:58 +0700 (Wed, 05 Jun 2002) | 7 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   A /orig/trunk/vnc_javasrc/SessionRecorder.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implemented experimental feature to save RFB sessions in FBS files
+compatible with rfbproxy, and the new "Save Session" parameter where a
+user can set a file name. Color format requested from the server was
+changed to little-endian to make saved sessions similar to ones
+written by the VNC Reflector, and to make colors compatible with RFB
+Session Player.
+
+------------------------------------------------------------------------
+r1111 | const | 2002-06-04 12:55:45 +0700 (Tue, 04 Jun 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Cleanups in "throws" statements.
+
+------------------------------------------------------------------------
+r1110 | const | 2002-06-04 12:50:35 +0700 (Tue, 04 Jun 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Minor cleanup in comment.
+
+------------------------------------------------------------------------
+r1109 | const | 2002-06-04 12:37:20 +0700 (Tue, 04 Jun 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Major Code cleanup: each decoder was moved from the
+processNormalProtocol() method to a separate function.
+
+------------------------------------------------------------------------
+r1108 | const | 2002-06-04 12:19:13 +0700 (Tue, 04 Jun 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Minor cleanup.
+
+------------------------------------------------------------------------
+r1107 | const | 2002-05-23 23:58:40 +0700 (Thu, 23 May 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+New "ENCPASSWORD" parameter, modified patch from Peter Astrand.
+
+------------------------------------------------------------------------
+r1103 | const | 2002-05-19 15:03:47 +0700 (Sun, 19 May 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Throwing Exception instead of IOException if that was not an I/O
+error.
+
+------------------------------------------------------------------------
+r1102 | const | 2002-05-19 13:38:02 +0700 (Sun, 19 May 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+
+Applied patch from Peter Astrand to fix problems with Swedish keys and
+broken JVMs.
+
+------------------------------------------------------------------------
+r1091 | const | 2002-04-25 18:51:58 +0700 (Thu, 25 Apr 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Updated version strings for version 1.2.4.
+
+------------------------------------------------------------------------
+r1090 | const | 2002-04-25 18:49:40 +0700 (Thu, 25 Apr 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Documented new feature to refresh remote desktop in the view-only mode
+using "R"/"r" keys.
+
+------------------------------------------------------------------------
+r1081 | const | 2002-04-23 20:02:45 +0700 (Tue, 23 Apr 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+
+Bugfix: keyboard focus could be set incorrectly. It was returned to
+desktop even when windows such as Options or Clipboard were created.
+This looked like new windows had appeared behind the authenticator or
+desktop window, if the viewer itself was running in a separate window.
+
+------------------------------------------------------------------------
+r1080 | const | 2002-04-10 02:10:47 +0700 (Wed, 10 Apr 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Minor bugfix to prevent the "Refresh" button to disappear after the
+"Disconnect" button changes to "Hide desktop".
+
+------------------------------------------------------------------------
+r1079 | const | 2002-04-10 02:05:52 +0700 (Wed, 10 Apr 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Previous bugfix was broken.
+
+------------------------------------------------------------------------
+r1078 | const | 2002-04-10 02:01:30 +0700 (Wed, 10 Apr 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Minor bugfix to prevent dumping exceptions on pressing "R"/"r" keys
+over disconnected desktop.
+
+------------------------------------------------------------------------
+r1077 | const | 2002-04-10 01:53:08 +0700 (Wed, 10 Apr 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Now "R"/"r" keys can be used to request screen updates in view-only
+mode.
+
+------------------------------------------------------------------------
+r1075 | const | 2002-04-09 02:17:45 +0700 (Tue, 09 Apr 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+"Show offline desktop" parameter documented.
+
+------------------------------------------------------------------------
+r1074 | const | 2002-04-09 02:12:57 +0700 (Tue, 09 Apr 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+"Show Controls" setting moved from OptionsFrame to VncViewer class.
+
+------------------------------------------------------------------------
+r1073 | const | 2002-04-09 02:04:48 +0700 (Tue, 09 Apr 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+New "Show Offline Desktop" parameter allowing the disktop to be still
+displayed even after the remote side closed connection.
+
+------------------------------------------------------------------------
+r1072 | const | 2002-04-03 00:12:54 +0700 (Wed, 03 Apr 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+
+Disabling the password input field on activating connections.
+
+------------------------------------------------------------------------
+r1071 | const | 2002-04-02 22:38:36 +0700 (Tue, 02 Apr 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Reporting more meaningful messages on errors.
+
+------------------------------------------------------------------------
+r1065 | const | 2002-03-25 21:41:27 +0600 (Mon, 25 Mar 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+A cosmetic change.
+
+------------------------------------------------------------------------
+r1034 | const | 2002-03-07 23:27:04 +0600 (Thu, 07 Mar 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Fixed bug causing NullPointerException in view-only mode with disabled
+button panel.
+
+------------------------------------------------------------------------
+r1021 | const | 2002-02-14 21:20:30 +0600 (Thu, 14 Feb 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Made the "Ctrl-Alt-Del" button disabled in the view-only mode.
+
+------------------------------------------------------------------------
+r1020 | const | 2002-02-14 21:19:10 +0600 (Thu, 14 Feb 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Removed calls printing debugging output.
+
+------------------------------------------------------------------------
+r1019 | const | 2002-02-13 05:02:01 +0600 (Wed, 13 Feb 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Added new parameters "Defer screen updates", "Defer cursor updates",
+"Defer update requests", documented in README.
+
+------------------------------------------------------------------------
+r1018 | const | 2002-02-13 03:03:33 +0600 (Wed, 13 Feb 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Made "Restricted colors" option dynamic.
+
+------------------------------------------------------------------------
+r1017 | const | 2002-02-13 02:36:54 +0600 (Wed, 13 Feb 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Code cleanups, preparing to make "Restricted colors" option dynamic.
+
+------------------------------------------------------------------------
+r1016 | const | 2002-02-12 23:32:06 +0600 (Tue, 12 Feb 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Documented applet parameters, updated copyright strings.
+
+------------------------------------------------------------------------
+r1015 | const | 2002-02-12 19:53:30 +0600 (Tue, 12 Feb 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Solved all issues with JPEG image loading.
+Added more comments in the source code.
+
+------------------------------------------------------------------------
+r1014 | const | 2002-02-12 18:13:22 +0600 (Tue, 12 Feb 2002) | 7 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Solved problems with asynchronous JPEG image loading, although the
+solution is not ideal yet. Now the ImageObserver interface is used
+only to track loading of JPEG images, and is not used with drawImage()
+method calls.
+Draft scaling implementation appeared in previous CVS commit was
+temporarily removed in this revision.
+
+------------------------------------------------------------------------
+r1013 | const | 2002-02-08 18:06:31 +0600 (Fri, 08 Feb 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Simple and inefficient scaling implementation, new "Scaling Factor"
+parameter.
+
+------------------------------------------------------------------------
+r1011 | const | 2002-02-08 00:20:53 +0600 (Fri, 08 Feb 2002) | 11 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Preventing authentication retries when the PASSWORD parameter is used.
+Closing windows and disconnecting on the applet shutdown.
+Terminating the application properly on closing the authentication
+window.
+Packing the window on reporting errors when in a separate window;
+this is necessary because it's possible that the window was empty.
+Disconnecting on fatal errors.
+Always forcing the keyboard focus go to the desktop on activating the
+connection.
+Code re-organizations and cleanups e.g. new tryAuthenticate() method.
+
+------------------------------------------------------------------------
+r1009 | const | 2002-01-31 00:25:27 +0600 (Thu, 31 Jan 2002) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+The frame size now should be limited by the screen size.
+JPEG support improved, but drawing is still not reliable.
+Minor code cleanups -- methods re-arranged.
+
+------------------------------------------------------------------------
+r1008 | const | 2002-01-31 00:22:22 +0600 (Thu, 31 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+A small piece of debugging code removed.
+
+------------------------------------------------------------------------
+r1007 | const | 2002-01-30 19:47:03 +0600 (Wed, 30 Jan 2002) | 6 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Drawing model was changed again; now usual off-screen Image is used
+for double-buffering instead of MemoryImageSource.
+Preliminary implementation of JPEG support in the Tight decoder.
+New "JPEG image quality" parameter and corresponding item in the
+Options frame.
+
+------------------------------------------------------------------------
+r1006 | const | 2002-01-25 12:49:36 +0600 (Fri, 25 Jan 2002) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Bugfixes in the Tight decoder: recent changes broke 8-bit color mode.
+Bugfixes in the XCursor encoding support: cursor colors were
+interpreted incorrectly.
+
+------------------------------------------------------------------------
+r1005 | const | 2002-01-15 03:11:07 +0600 (Tue, 15 Jan 2002) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+24-bit Tight decoder finished. Now it parses 24-bit (not 32-bit!)
+color samples correctly, and is able to decode data pre-processed with
+the "Gradient" filter.
+
+------------------------------------------------------------------------
+r1004 | const | 2002-01-15 00:46:06 +0600 (Tue, 15 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Preliminary working support for 24-bit colors in the Tight decoder.
+
+------------------------------------------------------------------------
+r1003 | const | 2002-01-14 20:00:14 +0600 (Mon, 14 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+Color format was not set correctly.
+
+------------------------------------------------------------------------
+r1002 | const | 2002-01-14 19:32:15 +0600 (Mon, 14 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Code cleanups: changes in rfb.is.read() and rfb.is.readFully() calls.
+
+------------------------------------------------------------------------
+r1001 | const | 2002-01-14 19:18:58 +0600 (Mon, 14 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Support for 24-bit color in RichCursor encoding.
+
+------------------------------------------------------------------------
+r1000 | const | 2002-01-13 06:11:34 +0600 (Sun, 13 Jan 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Support for 24-bit colors. At this moment, all decoders support this
+color mode, with two exceptions of Tight and RichCursor.
+
+------------------------------------------------------------------------
+r999 | const | 2002-01-13 05:57:09 +0600 (Sun, 13 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+
+Removed a piece of code used for debugging.
+
+------------------------------------------------------------------------
+r998 | const | 2002-01-13 00:23:11 +0600 (Sun, 13 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+The "View Only" mode now can be turned on/off at any moment.
+
+------------------------------------------------------------------------
+r997 | const | 2002-01-12 22:12:33 +0600 (Sat, 12 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/RfbProto.java
+
+Insert key now can be passed to the remote side.
+
+------------------------------------------------------------------------
+r996 | const | 2002-01-12 20:32:36 +0600 (Sat, 12 Jan 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Dramatically simplified and robust implementation of handling
+XCursor/RichCursor encodings.
+Enhancements and bugfixes for the "Open New Window" mode.
+Other minor enhancements and code cleanups.
+
+------------------------------------------------------------------------
+r995 | const | 2002-01-12 00:36:25 +0600 (Sat, 12 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ClipboardFrame.java
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+Minor code enhancements.
+
+------------------------------------------------------------------------
+r994 | const | 2002-01-11 23:35:33 +0600 (Fri, 11 Jan 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Implemented scrolling of the desktop area, when the desktop is shown
+in a separate window.
+
+------------------------------------------------------------------------
+r993 | const | 2002-01-11 18:51:16 +0600 (Fri, 11 Jan 2002) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ClipboardFrame.java
+   M /orig/trunk/vnc_javasrc/OptionsFrame.java
+
+"Dismiss" buttons renamed to "Close".
+
+------------------------------------------------------------------------
+r992 | const | 2002-01-11 04:19:03 +0600 (Fri, 11 Jan 2002) | 5 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/VncViewer.java
+
+Changes in the button panel. Now keyboard focus moves back to the
+authentication panel or to the desktop after pressing any button on
+the panel. Additionally, keyboard focus should move to the desktop
+automatically when VNC connection is established.
+
+------------------------------------------------------------------------
+r991 | const | 2002-01-11 03:53:10 +0600 (Fri, 11 Jan 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+
+Implemented new "Refresh" button. Pressing it results sending a
+non-incremental FramebufferUpdateRequest message to the server.
+
+------------------------------------------------------------------------
+r990 | const | 2002-01-11 03:51:25 +0600 (Fri, 11 Jan 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/VncCanvas.java
+
+Now the viewer adjusts its desktop/window size on desktop size changes
+on the remote side (working support for NewFBSize pseudo-encoding).
+
+------------------------------------------------------------------------
+r989 | const | 2002-01-11 02:50:00 +0600 (Fri, 11 Jan 2002) | 4 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc/AuthPanel.java
+   M /orig/trunk/vnc_javasrc/ButtonPanel.java
+   A /orig/trunk/vnc_javasrc/ClipboardFrame.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   A /orig/trunk/vnc_javasrc/OptionsFrame.java
+   A /orig/trunk/vnc_javasrc/RfbProto.java
+   A /orig/trunk/vnc_javasrc/VncCanvas.java
+   A /orig/trunk/vnc_javasrc/VncViewer.java
+   D /orig/trunk/vnc_javasrc/authenticationPanel.java
+   D /orig/trunk/vnc_javasrc/clipboardFrame.java
+   M /orig/trunk/vnc_javasrc/dir.mk
+   M /orig/trunk/vnc_javasrc/hextile.vnc
+   M /orig/trunk/vnc_javasrc/index.vnc
+   M /orig/trunk/vnc_javasrc/noshared.vnc
+   D /orig/trunk/vnc_javasrc/optionsFrame.java
+   D /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/shared.vnc
+   M /orig/trunk/vnc_javasrc/tight.vnc
+   D /orig/trunk/vnc_javasrc/vncCanvas.java
+   D /orig/trunk/vnc_javasrc/vncviewer.java
+   M /orig/trunk/vnc_javasrc/zlib.vnc
+
+New "Open New Window" parameter was implemented, now the viewer can
+work in a separate frame instead of running in the applet area.
+Class names were capitalized, to reflect usual Java naming standards.
+
+------------------------------------------------------------------------
+r988 | const | 2002-01-11 00:22:08 +0600 (Fri, 11 Jan 2002) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/hextile.vnc
+   M /orig/trunk/vnc_javasrc/index.vnc
+   M /orig/trunk/vnc_javasrc/noshared.vnc
+   M /orig/trunk/vnc_javasrc/shared.vnc
+   M /orig/trunk/vnc_javasrc/tight.vnc
+   M /orig/trunk/vnc_javasrc/zlib.vnc
+
+Inserted a <br> tag to prevent www.tightvnc.com link appear to the
+left of the applet area.
+
+------------------------------------------------------------------------
+r981 | const | 2001-12-18 03:32:28 +0600 (Tue, 18 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+In application mode, terminate application on window close event.
+
+------------------------------------------------------------------------
+r980 | const | 2001-12-18 02:28:34 +0600 (Tue, 18 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Performance fixes in CopyRect routine, and the FillLargeArea method.
+
+------------------------------------------------------------------------
+r979 | const | 2001-12-18 01:39:40 +0600 (Tue, 18 Dec 2001) | 2 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc/ButtonPanel.java
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/dir.mk
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+Converted to Java 1.1 event model.
+
+------------------------------------------------------------------------
+r978 | const | 2001-12-17 03:49:14 +0600 (Mon, 17 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Got rid of calls to deprecated methods etc.
+
+------------------------------------------------------------------------
+r977 | const | 2001-12-17 03:37:38 +0600 (Mon, 17 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+Minor code cleanups.
+
+------------------------------------------------------------------------
+r976 | const | 2001-12-17 03:37:10 +0600 (Mon, 17 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/authenticationPanel.java
+
+Converted to Java 1.1 event model.
+
+------------------------------------------------------------------------
+r975 | const | 2001-12-17 02:51:05 +0600 (Mon, 17 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+
+Converted to Java 1.1 event model.
+
+------------------------------------------------------------------------
+r974 | const | 2001-12-17 02:19:03 +0600 (Mon, 17 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/clipboardFrame.java
+
+Converted to Java 1.1 event model.
+
+------------------------------------------------------------------------
+r973 | const | 2001-12-17 00:17:20 +0600 (Mon, 17 Dec 2001) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/clipboardFrame.java
+
+Removed debugging code forgotten in the previous version.
+Minor code cleanups and formating changes.
+
+------------------------------------------------------------------------
+r972 | const | 2001-12-17 00:00:08 +0600 (Mon, 17 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/authenticationPanel.java
+   M /orig/trunk/vnc_javasrc/clipboardFrame.java
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+Got rid of most calls to methods deprecated in Java 1.1.
+
+------------------------------------------------------------------------
+r971 | const | 2001-12-16 21:41:38 +0600 (Sun, 16 Dec 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Beeping through java.awt.Toolkit on receiving Bell RFB message.
+
+------------------------------------------------------------------------
+r970 | const | 2001-12-16 21:33:19 +0600 (Sun, 16 Dec 2001) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Drawing techniques have been changed: now all the painting is
+performed in update() and paint() methods of the Canvas component.
+This should solve painting problems under some JVM implementations.
+
+------------------------------------------------------------------------
+r969 | const | 2001-12-16 20:56:29 +0600 (Sun, 16 Dec 2001) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   D /orig/trunk/vnc_javasrc/animatedMemoryImageSource.java
+   M /orig/trunk/vnc_javasrc/dir.mk
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+First step of converting the source to Java 1.1: got rid of
+animatedMemoryImageSource class; using new setAnimated() method in the
+standard MemoryImageSource class instead.
+
+------------------------------------------------------------------------
+r939 | const | 2001-09-16 14:06:15 +0700 (Sun, 16 Sep 2001) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+Addition of new parameters PASSWORD, "Include Controls", and "View
+Only", modified patch from Steve Kann.
+
+------------------------------------------------------------------------
+r901 | const | 2001-06-18 23:46:28 +0700 (Mon, 18 Jun 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string change.
+
+------------------------------------------------------------------------
+r899 | const | 2001-05-12 16:55:47 +0700 (Sat, 12 May 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+(setEncodings): Possible NullPointerException fixed.
+
+------------------------------------------------------------------------
+r884 | const | 2001-03-07 14:06:46 +0600 (Wed, 07 Mar 2001) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Initial "software cursor" position set to (0, 0) instead of (40, 40).
+Minor code clean-up.
+
+------------------------------------------------------------------------
+r868 | const | 2001-02-16 04:45:56 +0600 (Fri, 16 Feb 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Minor performance fix and tiny clean-ups in code and comments.
+
+------------------------------------------------------------------------
+r867 | const | 2001-02-16 03:29:49 +0600 (Fri, 16 Feb 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/clipboardFrame.java
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+
+Tiny changes after looking in the TridiaVNC CVS sources.
+
+------------------------------------------------------------------------
+r866 | const | 2001-02-16 02:48:15 +0600 (Fri, 16 Feb 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+
+Version string changed.
+
+------------------------------------------------------------------------
+r865 | const | 2001-02-15 23:48:43 +0600 (Thu, 15 Feb 2001) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+A number of performance optimizations and code clean-ups for all
+supported encodings.
+
+------------------------------------------------------------------------
+r863 | const | 2001-02-15 01:56:48 +0600 (Thu, 15 Feb 2001) | 9 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Drawing techniques changed: now all drawing is performed through the
+rawPixelsImage object and the pixels[] array, paintImage is not used
+any more.
+Settings "Raw pixel drawing: Fast/Reliable" and "CopyRect:
+Fast/Reliable" removed from the Options panel since they do not make
+sense in new drawing model.
+Currently drawing of solid-color areas is slow but this issue
+hopefully will be fixed in next versions.
+
+------------------------------------------------------------------------
+r858 | const | 2001-02-08 07:06:24 +0600 (Thu, 08 Feb 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+Fixes for compilation on Java 2 platform, from Klaus Erber.
+
+------------------------------------------------------------------------
+r836 | const | 2001-01-28 16:58:51 +0600 (Sun, 28 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/README
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+One more name added to copyright strings. ;-)
+
+------------------------------------------------------------------------
+r835 | const | 2001-01-28 16:51:43 +0600 (Sun, 28 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/hextile.vnc
+   M /orig/trunk/vnc_javasrc/index.vnc
+   M /orig/trunk/vnc_javasrc/noshared.vnc
+   M /orig/trunk/vnc_javasrc/shared.vnc
+   M /orig/trunk/vnc_javasrc/tight.vnc
+   M /orig/trunk/vnc_javasrc/zlib.vnc
+
+www.TridiaVNC.com links chanded to www.TightVNC.com.
+
+------------------------------------------------------------------------
+r834 | const | 2001-01-28 16:43:39 +0600 (Sun, 28 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   A /orig/trunk/vnc_javasrc/hextile.vnc
+   A /orig/trunk/vnc_javasrc/noshared.vnc
+   A /orig/trunk/vnc_javasrc/tight.vnc
+
+More HTML templates for different default settings prepared.
+
+------------------------------------------------------------------------
+r833 | const | 2001-01-28 16:36:14 +0600 (Sun, 28 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+
+Tight encoding is now set by default.
+
+------------------------------------------------------------------------
+r832 | const | 2001-01-27 04:24:59 +0600 (Sat, 27 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+Tiny formatting changes.
+
+------------------------------------------------------------------------
+r831 | const | 2001-01-27 03:11:22 +0600 (Sat, 27 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/authenticationPanel.java
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+From TridiaVNC: set initial input focus to password field.
+
+------------------------------------------------------------------------
+r830 | const | 2001-01-27 02:58:39 +0600 (Sat, 27 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+
+Unneeded changes reverted.
+
+------------------------------------------------------------------------
+r829 | const | 2001-01-27 00:52:44 +0600 (Sat, 27 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Many changes. Cursor shape updates should work in all modes.
+
+------------------------------------------------------------------------
+r826 | const | 2001-01-26 01:31:54 +0600 (Fri, 26 Jan 2001) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Support for EncodingLastRect added.
+Bugfix: "Cursor shape updates: Ignore" option caused exceptions on
+XCursor updates.
+
+------------------------------------------------------------------------
+r825 | const | 2001-01-26 01:10:59 +0600 (Fri, 26 Jan 2001) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+   M /orig/trunk/vnc_javasrc/vncviewer.java
+
+RichCursor and XCursor encodings now work, but only for raw encoding.
+Minor formatting fixes (spaces -> tabs).
+
+------------------------------------------------------------------------
+r824 | const | 2001-01-26 01:09:42 +0600 (Fri, 26 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+
+Minor formatting fixes (spaces -> tabs).
+
+------------------------------------------------------------------------
+r823 | const | 2001-01-25 00:25:22 +0600 (Thu, 25 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+"Cursor shape updates: Ignore" option works for RichCursor encoding.
+
+------------------------------------------------------------------------
+r822 | const | 2001-01-24 23:55:22 +0600 (Wed, 24 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Non-finished RichCursor support, minor code cleanups.
+
+------------------------------------------------------------------------
+r820 | const | 2001-01-23 23:42:45 +0600 (Tue, 23 Jan 2001) | 4 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+
+"Cursor shape updates" item in options frame.
+Minor bugfix: "Compression level" item remained enabled when raw
+encoding was chosen after zlib or tight.
+
+------------------------------------------------------------------------
+r819 | const | 2001-01-23 22:02:50 +0600 (Tue, 23 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+
+Requesting compression level for tight and zlib encodings.
+
+------------------------------------------------------------------------
+r818 | const | 2001-01-22 23:22:03 +0600 (Mon, 22 Jan 2001) | 3 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Major speed optimizations and code cleanups in tight encoding
+implementation.
+
+------------------------------------------------------------------------
+r817 | const | 2001-01-22 20:10:50 +0600 (Mon, 22 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+More error checking.
+
+------------------------------------------------------------------------
+r816 | const | 2001-01-22 20:06:39 +0600 (Mon, 22 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+Error checking, code cleanups.
+
+------------------------------------------------------------------------
+r814 | const | 2001-01-19 12:53:17 +0600 (Fri, 19 Jan 2001) | 2 lines
+Changed paths:
+   M /orig/trunk/vnc_javasrc/Makefile
+   M /orig/trunk/vnc_javasrc/optionsFrame.java
+   M /orig/trunk/vnc_javasrc/rfbProto.java
+   M /orig/trunk/vnc_javasrc/vncCanvas.java
+
+First version of Java vncviewer with tight encoding support.
+
+------------------------------------------------------------------------
+r725 | const | 2000-09-29 22:39:38 +0700 (Fri, 29 Sep 2000) | 2 lines
+Changed paths:
+   A /orig/trunk/vnc_javasrc
+   A /orig/trunk/vnc_javasrc/DesCipher.java
+   A /orig/trunk/vnc_javasrc/LICENCE.TXT
+   A /orig/trunk/vnc_javasrc/Makefile
+   A /orig/trunk/vnc_javasrc/README
+   A /orig/trunk/vnc_javasrc/animatedMemoryImageSource.java
+   A /orig/trunk/vnc_javasrc/authenticationPanel.java
+   A /orig/trunk/vnc_javasrc/clipboardFrame.java
+   A /orig/trunk/vnc_javasrc/dir.mk
+   A /orig/trunk/vnc_javasrc/index.vnc
+   A /orig/trunk/vnc_javasrc/optionsFrame.java
+   A /orig/trunk/vnc_javasrc/rfbProto.java
+   A /orig/trunk/vnc_javasrc/shared.vnc
+   A /orig/trunk/vnc_javasrc/vncCanvas.java
+   A /orig/trunk/vnc_javasrc/vncviewer.java
+   A /orig/trunk/vnc_javasrc/zlib.vnc
+
+Initial revision
+
+------------------------------------------------------------------------
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/ClipboardFrame.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/ClipboardFrame.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/ClipboardFrame.java	(revision 571)
@@ -0,0 +1,133 @@
+//
+//  Copyright (C) 2001 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// Clipboard frame.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+
+class ClipboardFrame extends Frame
+  implements WindowListener, ActionListener {
+
+  TextArea textArea;
+  Button clearButton, closeButton;
+  String selection;
+  VncViewer viewer;
+
+  //
+  // Constructor.
+  //
+
+  ClipboardFrame(VncViewer v) {
+    super("TightVNC Clipboard");
+
+    viewer = v;
+
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weighty = 1.0;
+
+    textArea = new TextArea(5, 40);
+    gridbag.setConstraints(textArea, gbc);
+    add(textArea);
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weightx = 1.0;
+    gbc.weighty = 0.0;
+    gbc.gridwidth = 1;
+
+    clearButton = new Button("Clear");
+    gridbag.setConstraints(clearButton, gbc);
+    add(clearButton);
+    clearButton.addActionListener(this);
+
+    closeButton = new Button("Close");
+    gridbag.setConstraints(closeButton, gbc);
+    add(closeButton);
+    closeButton.addActionListener(this);
+
+    pack();
+
+    addWindowListener(this);
+  }
+
+
+  //
+  // Set the cut text from the RFB server.
+  //
+
+  void setCutText(String text) {
+    selection = text;
+    textArea.setText(text);
+    if (isVisible()) {
+      textArea.selectAll();
+    }
+  }
+
+
+  //
+  // When the focus leaves the window, see if we have new cut text and
+  // if so send it to the RFB server.
+  //
+
+  public void windowDeactivated (WindowEvent evt) {
+    if (selection != null && !selection.equals(textArea.getText())) {
+      selection = textArea.getText();
+      viewer.setCutText(selection);
+    }
+  }
+
+  //
+  // Close our window properly.
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    setVisible(false);
+  }
+
+  //
+  // Ignore window events we're not interested in.
+  //
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+
+
+  //
+  // Respond to button presses
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+    if (evt.getSource() == clearButton) {
+      textArea.setText("");
+    } else if (evt.getSource() == closeButton) {
+      setVisible(false);
+    }
+  }
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/DesCipher.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/DesCipher.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/DesCipher.java	(revision 571)
@@ -0,0 +1,496 @@
+//
+// This DES class has been extracted from package Acme.Crypto for use in VNC.
+// The bytebit[] array has been reversed so that the most significant bit
+// in each byte of the key is ignored, not the least significant.  Also the
+// unnecessary odd parity code has been removed.
+//
+// These changes are:
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+// This software 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.
+//
+
+// DesCipher - the DES encryption method
+//
+// The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
+//
+// Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+//
+// Permission to use, copy, modify, and distribute this software
+// and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+// without fee is hereby granted, provided that this copyright notice is kept 
+// intact. 
+// 
+// WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+// OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+// FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+// DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+// 
+// THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+// CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+// PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+// NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+// SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+// SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+// PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  WIDGET WORKSHOP
+// SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+// HIGH RISK ACTIVITIES.
+//
+//
+// The rest is:
+//
+// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+
+import java.io.*;
+
+/// The DES encryption method.
+// <P>
+// This is surprisingly fast, for pure Java.  On a SPARC 20, wrapped
+// in Acme.Crypto.EncryptedOutputStream or Acme.Crypto.EncryptedInputStream,
+// it does around 7000 bytes/second.
+// <P>
+// Most of this code is by Dave Zimmerman <dzimm@widget.com>, and is
+// Copyright (c) 1996 Widget Workshop, Inc.  See the source file for details.
+// <P>
+// <A HREF="/resources/classes/Acme/Crypto/DesCipher.java">Fetch the software.</A><BR>
+// <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
+// <P>
+// @see Des3Cipher
+// @see EncryptedOutputStream
+// @see EncryptedInputStream
+
+public class DesCipher
+    {
+
+    // Constructor, byte-array key.
+    public DesCipher( byte[] key )
+	{
+	setKey( key );
+	}
+
+    // Key routines.
+
+    private int[] encryptKeys = new int[32];
+    private int[] decryptKeys = new int[32];
+
+    /// Set the key.
+    public void setKey( byte[] key )
+	{
+	deskey( key, true, encryptKeys );
+	deskey( key, false, decryptKeys );
+	}
+
+    // Turn an 8-byte key into internal keys.
+    private void deskey( byte[] keyBlock, boolean encrypting, int[] KnL )
+	{
+	int i, j, l, m, n;
+	int[] pc1m = new int[56];
+	int[] pcr = new int[56];
+	int[] kn = new int[32];
+
+	for ( j = 0; j < 56; ++j )
+	    {
+	    l = pc1[j];
+	    m = l & 07;
+	    pc1m[j] = ( (keyBlock[l >>> 3] & bytebit[m]) != 0 )? 1: 0;
+	    }
+
+	for ( i = 0; i < 16; ++i )
+	    {
+	    if ( encrypting )
+		m = i << 1;
+	    else
+		m = (15-i) << 1;
+	    n = m+1;
+	    kn[m] = kn[n] = 0;
+	    for ( j = 0; j < 28; ++j )
+		{
+		l = j+totrot[i];
+		if ( l < 28 )
+		    pcr[j] = pc1m[l];
+		else
+		    pcr[j] = pc1m[l-28];
+		}
+	    for ( j=28; j < 56; ++j )
+		{
+		l = j+totrot[i];
+		if ( l < 56 )
+		    pcr[j] = pc1m[l];
+		else
+		    pcr[j] = pc1m[l-28];
+		}
+	    for ( j = 0; j < 24; ++j )
+		{
+		if ( pcr[pc2[j]] != 0 )
+		    kn[m] |= bigbyte[j];
+		if ( pcr[pc2[j+24]] != 0 )
+		    kn[n] |= bigbyte[j];
+		}
+	    }
+	cookey( kn, KnL );
+	}
+
+    private void cookey( int[] raw, int KnL[] )
+	{
+	int raw0, raw1;
+	int rawi, KnLi;
+	int i;
+
+	for ( i = 0, rawi = 0, KnLi = 0; i < 16; ++i )
+	    {
+	    raw0 = raw[rawi++];
+	    raw1 = raw[rawi++];
+	    KnL[KnLi]  = (raw0 & 0x00fc0000) <<   6;
+	    KnL[KnLi] |= (raw0 & 0x00000fc0) <<  10;
+	    KnL[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+	    KnL[KnLi] |= (raw1 & 0x00000fc0) >>>  6;
+	    ++KnLi;
+	    KnL[KnLi]  = (raw0 & 0x0003f000) <<  12;
+	    KnL[KnLi] |= (raw0 & 0x0000003f) <<  16;
+	    KnL[KnLi] |= (raw1 & 0x0003f000) >>>  4;
+	    KnL[KnLi] |= (raw1 & 0x0000003f);
+	    ++KnLi;
+	    }
+	}
+
+
+    // Block encryption routines.
+
+    private int[] tempInts = new int[2];
+
+    /// Encrypt a block of eight bytes.
+    public void encrypt( byte[] clearText, int clearOff, byte[] cipherText, int cipherOff )
+	{
+	squashBytesToInts( clearText, clearOff, tempInts, 0, 2 );
+	des( tempInts, tempInts, encryptKeys );
+	spreadIntsToBytes( tempInts, 0, cipherText, cipherOff, 2 );
+	}
+
+    /// Decrypt a block of eight bytes.
+    public void decrypt( byte[] cipherText, int cipherOff, byte[] clearText, int clearOff )
+	{
+	squashBytesToInts( cipherText, cipherOff, tempInts, 0, 2 );
+	des( tempInts, tempInts, decryptKeys );
+	spreadIntsToBytes( tempInts, 0, clearText, clearOff, 2 );
+	}
+
+    // The DES function.
+    private void des( int[] inInts, int[] outInts, int[] keys )
+	{
+	int fval, work, right, leftt;
+	int round;
+	int keysi = 0;
+
+	leftt = inInts[0];
+	right = inInts[1];
+
+	work   = ((leftt >>>  4) ^ right) & 0x0f0f0f0f;
+	right ^= work;
+	leftt ^= (work << 4);
+
+	work   = ((leftt >>> 16) ^ right) & 0x0000ffff;
+	right ^= work;
+	leftt ^= (work << 16);
+
+	work   = ((right >>>  2) ^ leftt) & 0x33333333;
+	leftt ^= work;
+	right ^= (work << 2);
+
+	work   = ((right >>>  8) ^ leftt) & 0x00ff00ff;
+	leftt ^= work;
+	right ^= (work << 8);
+	right  = (right << 1) | ((right >>> 31) & 1);
+
+	work   = (leftt ^ right) & 0xaaaaaaaa;
+	leftt ^= work;
+	right ^= work;
+	leftt  = (leftt << 1) | ((leftt >>> 31) & 1);
+
+	for ( round = 0; round < 8; ++round )
+	    {
+	    work   = (right << 28) | (right >>> 4);
+	    work  ^= keys[keysi++];
+	    fval   = SP7[ work	       & 0x0000003f ];
+	    fval  |= SP5[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP3[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP1[(work >>> 24) & 0x0000003f ];
+	    work   = right ^ keys[keysi++];
+	    fval  |= SP8[ work         & 0x0000003f ];
+	    fval  |= SP6[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP4[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP2[(work >>> 24) & 0x0000003f ];
+	    leftt ^= fval;
+	    work   = (leftt << 28) | (leftt >>> 4);
+	    work  ^= keys[keysi++];
+	    fval   = SP7[ work	       & 0x0000003f ];
+	    fval  |= SP5[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP3[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP1[(work >>> 24) & 0x0000003f ];
+	    work   = leftt ^ keys[keysi++];
+	    fval  |= SP8[ work	       & 0x0000003f ];
+	    fval  |= SP6[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP4[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP2[(work >>> 24) & 0x0000003f ];
+	    right ^= fval;
+	    }
+
+	right  = (right << 31) | (right >>> 1);
+	work   = (leftt ^ right) & 0xaaaaaaaa;
+	leftt ^= work;
+	right ^= work;
+	leftt  = (leftt << 31) | (leftt >>> 1);
+	work   = ((leftt >>>  8) ^ right) & 0x00ff00ff;
+	right ^= work;
+	leftt ^= (work << 8);
+	work   = ((leftt >>>  2) ^ right) & 0x33333333;
+	right ^= work;
+	leftt ^= (work << 2);
+	work   = ((right >>> 16) ^ leftt) & 0x0000ffff;
+	leftt ^= work;
+	right ^= (work << 16);
+	work   = ((right >>>  4) ^ leftt) & 0x0f0f0f0f;
+	leftt ^= work;
+	right ^= (work << 4);
+	outInts[0] = right;
+	outInts[1] = leftt;
+	}
+
+
+    // Tables, permutations, S-boxes, etc.
+
+    private static byte[] bytebit = {
+	(byte)0x01, (byte)0x02, (byte)0x04, (byte)0x08,
+	(byte)0x10, (byte)0x20, (byte)0x40, (byte)0x80
+	};
+    private static int[] bigbyte = {
+	0x800000, 0x400000, 0x200000, 0x100000,
+	0x080000, 0x040000, 0x020000, 0x010000,
+	0x008000, 0x004000, 0x002000, 0x001000,
+	0x000800, 0x000400, 0x000200, 0x000100,
+	0x000080, 0x000040, 0x000020, 0x000010,
+	0x000008, 0x000004, 0x000002, 0x000001
+	};
+    private static byte[] pc1 = {
+         (byte)56, (byte)48, (byte)40, (byte)32, (byte)24, (byte)16, (byte) 8,
+      (byte) 0, (byte)57, (byte)49, (byte)41, (byte)33, (byte)25, (byte)17,
+	 (byte) 9, (byte) 1, (byte)58, (byte)50, (byte)42, (byte)34, (byte)26,
+      (byte)18, (byte)10, (byte) 2, (byte)59, (byte)51, (byte)43, (byte)35,
+	 (byte)62, (byte)54, (byte)46, (byte)38, (byte)30, (byte)22, (byte)14,
+      (byte) 6, (byte)61, (byte)53, (byte)45, (byte)37, (byte)29, (byte)21,
+	 (byte)13, (byte) 5, (byte)60, (byte)52, (byte)44, (byte)36, (byte)28,
+      (byte)20, (byte)12, (byte) 4, (byte)27, (byte)19, (byte)11, (byte)3
+	};
+    private static int[] totrot = {
+        1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28
+	};
+
+    private static byte[] pc2 = {
+	(byte)13, (byte)16, (byte)10, (byte)23, (byte) 0, (byte) 4,
+	          (byte) 2, (byte)27, (byte)14, (byte) 5, (byte)20, (byte) 9,
+	(byte)22, (byte)18, (byte)11, (byte)3 , (byte)25, (byte) 7,
+	          (byte)15, (byte) 6, (byte)26, (byte)19, (byte)12, (byte) 1,
+	(byte)40, (byte)51, (byte)30, (byte)36, (byte)46, (byte)54,
+	          (byte)29, (byte)39, (byte)50, (byte)44, (byte)32, (byte)47,
+	(byte)43, (byte)48, (byte)38, (byte)55, (byte)33, (byte)52,
+	          (byte)45, (byte)41, (byte)49, (byte)35, (byte)28, (byte)31,
+	};
+
+    private static int[] SP1 = {
+        0x01010400, 0x00000000, 0x00010000, 0x01010404,
+	0x01010004, 0x00010404, 0x00000004, 0x00010000,
+	0x00000400, 0x01010400, 0x01010404, 0x00000400,
+	0x01000404, 0x01010004, 0x01000000, 0x00000004,
+	0x00000404, 0x01000400, 0x01000400, 0x00010400,
+	0x00010400, 0x01010000, 0x01010000, 0x01000404,
+	0x00010004, 0x01000004, 0x01000004, 0x00010004,
+	0x00000000, 0x00000404, 0x00010404, 0x01000000,
+	0x00010000, 0x01010404, 0x00000004, 0x01010000,
+	0x01010400, 0x01000000, 0x01000000, 0x00000400,
+	0x01010004, 0x00010000, 0x00010400, 0x01000004,
+	0x00000400, 0x00000004, 0x01000404, 0x00010404,
+	0x01010404, 0x00010004, 0x01010000, 0x01000404,
+	0x01000004, 0x00000404, 0x00010404, 0x01010400,
+	0x00000404, 0x01000400, 0x01000400, 0x00000000,
+	0x00010004, 0x00010400, 0x00000000, 0x01010004
+	};
+    private static int[] SP2 = {
+	0x80108020, 0x80008000, 0x00008000, 0x00108020,
+	0x00100000, 0x00000020, 0x80100020, 0x80008020,
+	0x80000020, 0x80108020, 0x80108000, 0x80000000,
+	0x80008000, 0x00100000, 0x00000020, 0x80100020,
+	0x00108000, 0x00100020, 0x80008020, 0x00000000,
+	0x80000000, 0x00008000, 0x00108020, 0x80100000,
+	0x00100020, 0x80000020, 0x00000000, 0x00108000,
+	0x00008020, 0x80108000, 0x80100000, 0x00008020,
+	0x00000000, 0x00108020, 0x80100020, 0x00100000,
+	0x80008020, 0x80100000, 0x80108000, 0x00008000,
+	0x80100000, 0x80008000, 0x00000020, 0x80108020,
+	0x00108020, 0x00000020, 0x00008000, 0x80000000,
+	0x00008020, 0x80108000, 0x00100000, 0x80000020,
+	0x00100020, 0x80008020, 0x80000020, 0x00100020,
+	0x00108000, 0x00000000, 0x80008000, 0x00008020,
+	0x80000000, 0x80100020, 0x80108020, 0x00108000
+	};
+    private static int[] SP3 = {
+	0x00000208, 0x08020200, 0x00000000, 0x08020008,
+	0x08000200, 0x00000000, 0x00020208, 0x08000200,
+	0x00020008, 0x08000008, 0x08000008, 0x00020000,
+	0x08020208, 0x00020008, 0x08020000, 0x00000208,
+	0x08000000, 0x00000008, 0x08020200, 0x00000200,
+	0x00020200, 0x08020000, 0x08020008, 0x00020208,
+	0x08000208, 0x00020200, 0x00020000, 0x08000208,
+	0x00000008, 0x08020208, 0x00000200, 0x08000000,
+	0x08020200, 0x08000000, 0x00020008, 0x00000208,
+	0x00020000, 0x08020200, 0x08000200, 0x00000000,
+	0x00000200, 0x00020008, 0x08020208, 0x08000200,
+	0x08000008, 0x00000200, 0x00000000, 0x08020008,
+	0x08000208, 0x00020000, 0x08000000, 0x08020208,
+	0x00000008, 0x00020208, 0x00020200, 0x08000008,
+	0x08020000, 0x08000208, 0x00000208, 0x08020000,
+	0x00020208, 0x00000008, 0x08020008, 0x00020200
+	};
+    private static int[] SP4 = {
+	0x00802001, 0x00002081, 0x00002081, 0x00000080,
+	0x00802080, 0x00800081, 0x00800001, 0x00002001,
+	0x00000000, 0x00802000, 0x00802000, 0x00802081,
+	0x00000081, 0x00000000, 0x00800080, 0x00800001,
+	0x00000001, 0x00002000, 0x00800000, 0x00802001,
+	0x00000080, 0x00800000, 0x00002001, 0x00002080,
+	0x00800081, 0x00000001, 0x00002080, 0x00800080,
+	0x00002000, 0x00802080, 0x00802081, 0x00000081,
+	0x00800080, 0x00800001, 0x00802000, 0x00802081,
+	0x00000081, 0x00000000, 0x00000000, 0x00802000,
+	0x00002080, 0x00800080, 0x00800081, 0x00000001,
+	0x00802001, 0x00002081, 0x00002081, 0x00000080,
+	0x00802081, 0x00000081, 0x00000001, 0x00002000,
+	0x00800001, 0x00002001, 0x00802080, 0x00800081,
+	0x00002001, 0x00002080, 0x00800000, 0x00802001,
+	0x00000080, 0x00800000, 0x00002000, 0x00802080
+	};
+    private static int[] SP5 = {
+	0x00000100, 0x02080100, 0x02080000, 0x42000100,
+	0x00080000, 0x00000100, 0x40000000, 0x02080000,
+	0x40080100, 0x00080000, 0x02000100, 0x40080100,
+	0x42000100, 0x42080000, 0x00080100, 0x40000000,
+	0x02000000, 0x40080000, 0x40080000, 0x00000000,
+	0x40000100, 0x42080100, 0x42080100, 0x02000100,
+	0x42080000, 0x40000100, 0x00000000, 0x42000000,
+	0x02080100, 0x02000000, 0x42000000, 0x00080100,
+	0x00080000, 0x42000100, 0x00000100, 0x02000000,
+	0x40000000, 0x02080000, 0x42000100, 0x40080100,
+	0x02000100, 0x40000000, 0x42080000, 0x02080100,
+	0x40080100, 0x00000100, 0x02000000, 0x42080000,
+	0x42080100, 0x00080100, 0x42000000, 0x42080100,
+	0x02080000, 0x00000000, 0x40080000, 0x42000000,
+	0x00080100, 0x02000100, 0x40000100, 0x00080000,
+	0x00000000, 0x40080000, 0x02080100, 0x40000100
+	};
+    private static int[] SP6 = {
+	0x20000010, 0x20400000, 0x00004000, 0x20404010,
+	0x20400000, 0x00000010, 0x20404010, 0x00400000,
+	0x20004000, 0x00404010, 0x00400000, 0x20000010,
+	0x00400010, 0x20004000, 0x20000000, 0x00004010,
+	0x00000000, 0x00400010, 0x20004010, 0x00004000,
+	0x00404000, 0x20004010, 0x00000010, 0x20400010,
+	0x20400010, 0x00000000, 0x00404010, 0x20404000,
+	0x00004010, 0x00404000, 0x20404000, 0x20000000,
+	0x20004000, 0x00000010, 0x20400010, 0x00404000,
+	0x20404010, 0x00400000, 0x00004010, 0x20000010,
+	0x00400000, 0x20004000, 0x20000000, 0x00004010,
+	0x20000010, 0x20404010, 0x00404000, 0x20400000,
+	0x00404010, 0x20404000, 0x00000000, 0x20400010,
+	0x00000010, 0x00004000, 0x20400000, 0x00404010,
+	0x00004000, 0x00400010, 0x20004010, 0x00000000,
+	0x20404000, 0x20000000, 0x00400010, 0x20004010
+	};
+    private static int[] SP7 = {
+	0x00200000, 0x04200002, 0x04000802, 0x00000000,
+	0x00000800, 0x04000802, 0x00200802, 0x04200800,
+	0x04200802, 0x00200000, 0x00000000, 0x04000002,
+	0x00000002, 0x04000000, 0x04200002, 0x00000802,
+	0x04000800, 0x00200802, 0x00200002, 0x04000800,
+	0x04000002, 0x04200000, 0x04200800, 0x00200002,
+	0x04200000, 0x00000800, 0x00000802, 0x04200802,
+	0x00200800, 0x00000002, 0x04000000, 0x00200800,
+	0x04000000, 0x00200800, 0x00200000, 0x04000802,
+	0x04000802, 0x04200002, 0x04200002, 0x00000002,
+	0x00200002, 0x04000000, 0x04000800, 0x00200000,
+	0x04200800, 0x00000802, 0x00200802, 0x04200800,
+	0x00000802, 0x04000002, 0x04200802, 0x04200000,
+	0x00200800, 0x00000000, 0x00000002, 0x04200802,
+	0x00000000, 0x00200802, 0x04200000, 0x00000800,
+	0x04000002, 0x04000800, 0x00000800, 0x00200002
+	};
+    private static int[] SP8 = {
+	0x10001040, 0x00001000, 0x00040000, 0x10041040,
+	0x10000000, 0x10001040, 0x00000040, 0x10000000,
+	0x00040040, 0x10040000, 0x10041040, 0x00041000,
+	0x10041000, 0x00041040, 0x00001000, 0x00000040,
+	0x10040000, 0x10000040, 0x10001000, 0x00001040,
+	0x00041000, 0x00040040, 0x10040040, 0x10041000,
+	0x00001040, 0x00000000, 0x00000000, 0x10040040,
+	0x10000040, 0x10001000, 0x00041040, 0x00040000,
+	0x00041040, 0x00040000, 0x10041000, 0x00001000,
+	0x00000040, 0x10040040, 0x00001000, 0x00041040,
+	0x10001000, 0x00000040, 0x10000040, 0x10040000,
+	0x10040040, 0x10000000, 0x00040000, 0x10001040,
+	0x00000000, 0x10041040, 0x00040040, 0x10000040,
+	0x10040000, 0x10001000, 0x10001040, 0x00000000,
+	0x10041040, 0x00041000, 0x00041000, 0x00001040,
+	0x00001040, 0x00040040, 0x10000000, 0x10041000
+	};
+
+    // Routines taken from other parts of the Acme utilities.
+
+    /// Squash bytes down to ints.
+    public static void squashBytesToInts( byte[] inBytes, int inOff, int[] outInts, int outOff, int intLen )
+        {
+	for ( int i = 0; i < intLen; ++i )
+	    outInts[outOff + i] = 
+		( ( inBytes[inOff + i * 4    ] & 0xff ) << 24 ) |
+		( ( inBytes[inOff + i * 4 + 1] & 0xff ) << 16 ) |
+		( ( inBytes[inOff + i * 4 + 2] & 0xff ) <<  8 ) |
+		  ( inBytes[inOff + i * 4 + 3] & 0xff );
+        }
+
+    /// Spread ints into bytes.
+    public static void spreadIntsToBytes( int[] inInts, int inOff, byte[] outBytes, int outOff, int intLen )
+        {
+	for ( int i = 0; i < intLen; ++i )
+	    {
+	    outBytes[outOff + i * 4    ] = (byte) ( inInts[inOff + i] >>> 24 );
+	    outBytes[outOff + i * 4 + 1] = (byte) ( inInts[inOff + i] >>> 16 );
+	    outBytes[outOff + i * 4 + 2] = (byte) ( inInts[inOff + i] >>>  8 );
+	    outBytes[outOff + i * 4 + 3] = (byte)   inInts[inOff + i];
+	    }
+        }
+    }
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/HTTPConnectSocket.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/HTTPConnectSocket.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/HTTPConnectSocket.java	(revision 571)
@@ -0,0 +1,59 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// HTTPConnectSocket.java together with HTTPConnectSocketFactory.java
+// implement an alternate way to connect to VNC servers via one or two
+// HTTP proxies supporting the HTTP CONNECT method.
+//
+
+import java.net.*;
+import java.io.*;
+
+class HTTPConnectSocket extends Socket {
+
+  public HTTPConnectSocket(String host, int port,
+			   String proxyHost, int proxyPort)
+    throws IOException {
+
+    // Connect to the specified HTTP proxy
+    super(proxyHost, proxyPort);
+
+    // Send the CONNECT request
+    getOutputStream().write(("CONNECT " + host + ":" + port +
+			     " HTTP/1.0\r\n\r\n").getBytes());
+
+    // Read the first line of the response
+    DataInputStream is = new DataInputStream(getInputStream());
+    String str = is.readLine();
+
+    // Check the HTTP error code -- it should be "200" on success
+    if (!str.startsWith("HTTP/1.0 200 ")) {
+      if (str.startsWith("HTTP/1.0 "))
+	str = str.substring(9);
+      throw new IOException("Proxy reports \"" + str + "\"");
+    }
+
+    // Success -- skip remaining HTTP headers
+    do {
+      str = is.readLine();
+    } while (str.length() != 0);
+  }
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/HTTPConnectSocketFactory.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/HTTPConnectSocketFactory.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/HTTPConnectSocketFactory.java	(revision 571)
@@ -0,0 +1,86 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// HTTPConnectSocketFactory.java together with HTTPConnectSocket.java
+// implement an alternate way to connect to VNC servers via one or two
+// HTTP proxies supporting the HTTP CONNECT method.
+//
+
+import java.applet.*;
+import java.net.*;
+import java.io.*;
+
+class HTTPConnectSocketFactory implements SocketFactory {
+
+  public Socket createSocket(String host, int port, Applet applet)
+    throws IOException {
+
+    return createSocket(host, port,
+			applet.getParameter("PROXYHOST1"),
+			applet.getParameter("PROXYPORT1"));
+  }
+
+  public Socket createSocket(String host, int port, String[] args)
+    throws IOException {
+
+    return createSocket(host, port,
+			readArg(args, "PROXYHOST1"),
+			readArg(args, "PROXYPORT1"));
+  }
+
+  public Socket createSocket(String host, int port,
+			     String proxyHost, String proxyPortStr)
+    throws IOException {
+
+    int proxyPort = 0;
+    if (proxyPortStr != null) {
+      try {
+	proxyPort = Integer.parseInt(proxyPortStr);
+      } catch (NumberFormatException e) { }
+    }
+
+    if (proxyHost == null || proxyPort == 0) {
+      System.out.println("Incomplete parameter list for HTTPConnectSocket");
+      return new Socket(host, port);
+    }
+
+    System.out.println("HTTP CONNECT via proxy " + proxyHost +
+		       " port " + proxyPort);
+    HTTPConnectSocket s =
+      new HTTPConnectSocket(host, port, proxyHost, proxyPort);
+
+    return (Socket)s;
+  }
+
+  private String readArg(String[] args, String name) {
+
+    for (int i = 0; i < args.length; i += 2) {
+      if (args[i].equalsIgnoreCase(name)) {
+	try {
+	  return args[i+1];
+	} catch (Exception e) {
+	  return null;
+	}
+      }
+    }
+    return null;
+  }
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/InStream.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/InStream.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/InStream.java	(revision 571)
@@ -0,0 +1,177 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// rdr::InStream marshalls data from a buffer stored in RDR (RFB Data
+// Representation).
+//
+
+abstract public class InStream {
+
+  // check() ensures there is buffer data for at least one item of size
+  // itemSize bytes.  Returns the number of items in the buffer (up to a
+  // maximum of nItems).
+
+  public final int check(int itemSize, int nItems) throws Exception {
+    if (ptr + itemSize * nItems > end) {
+      if (ptr + itemSize > end)
+        return overrun(itemSize, nItems);
+
+      nItems = (end - ptr) / itemSize;
+    }
+    return nItems;
+  }
+
+  public final void check(int itemSize) throws Exception {
+    if (ptr + itemSize > end)
+      overrun(itemSize, 1);
+  }
+
+  // readU/SN() methods read unsigned and signed N-bit integers.
+
+  public final int readS8() throws Exception {
+    check(1); return b[ptr++];
+  }
+
+  public final int readS16() throws Exception {
+    check(2); int b0 = b[ptr++];
+    int b1 = b[ptr++] & 0xff; return b0 << 8 | b1;
+  }
+
+  public final int readS32() throws Exception {
+    check(4); int b0 = b[ptr++];
+    int b1 = b[ptr++] & 0xff;
+    int b2 = b[ptr++] & 0xff;
+    int b3 = b[ptr++] & 0xff;
+    return b0 << 24 | b1 << 16 | b2 << 8 | b3;
+  }
+
+  public final int readU8() throws Exception {
+    return readS8() & 0xff;
+  }
+
+  public final int readU16() throws Exception {
+    return readS16() & 0xffff;
+  }
+
+  public final int readU32() throws Exception {
+    return readS32() & 0xffffffff;
+  }
+
+  // readString() reads a string - a U32 length followed by the data.
+
+  public final String readString() throws Exception {
+    int len = readU32();
+    if (len > maxStringLength)
+      throw new Exception("InStream max string length exceeded");
+
+    char[] str = new char[len];
+    int i = 0;
+    while (i < len) {
+      int j = i + check(1, len - i);
+      while (i < j) {
+	str[i++] = (char)b[ptr++];
+      }
+    }
+
+    return new String(str);
+  }
+
+  // maxStringLength protects against allocating a huge buffer.  Set it
+  // higher if you need longer strings.
+
+  public static int maxStringLength = 65535;
+
+  public final void skip(int bytes) throws Exception {
+    while (bytes > 0) {
+      int n = check(1, bytes);
+      ptr += n;
+      bytes -= n;
+    }
+  }
+
+  // readBytes() reads an exact number of bytes into an array at an offset.
+
+  public void readBytes(byte[] data, int offset, int length) throws Exception {
+    int offsetEnd = offset + length;
+    while (offset < offsetEnd) {
+      int n = check(1, offsetEnd - offset);
+      System.arraycopy(b, ptr, data, offset, n);
+      ptr += n;
+      offset += n;
+    }
+  }
+
+  // readOpaqueN() reads a quantity "without byte-swapping".  Because java has
+  // no byte-ordering, we just use big-endian.
+
+  public final int readOpaque8() throws Exception {
+    return readU8();
+  }
+
+  public final int readOpaque16() throws Exception {
+    return readU16();
+  }
+
+  public final int readOpaque32() throws Exception {
+    return readU32();
+  }
+
+  public final int readOpaque24A() throws Exception {
+    check(3); int b0 = b[ptr++];
+    int b1 = b[ptr++]; int b2 = b[ptr++];
+    return b0 << 24 | b1 << 16 | b2 << 8;
+  }
+
+  public final int readOpaque24B() throws Exception {
+    check(3); int b0 = b[ptr++];
+    int b1 = b[ptr++]; int b2 = b[ptr++];
+    return b0 << 16 | b1 << 8 | b2;
+  }
+
+  // pos() returns the position in the stream.
+
+  abstract public int pos();
+
+  // bytesAvailable() returns true if at least one byte can be read from the
+  // stream without blocking.  i.e. if false is returned then readU8() would
+  // block.
+
+  public boolean bytesAvailable() { return end != ptr; }
+
+  // getbuf(), getptr(), getend() and setptr() are "dirty" methods which allow
+  // you to manipulate the buffer directly.  This is useful for a stream which
+  // is a wrapper around an underlying stream.
+
+  public final byte[] getbuf() { return b; }
+  public final int getptr() { return ptr; }
+  public final int getend() { return end; }
+  public final void setptr(int p) { ptr = p; }
+
+  // overrun() is implemented by a derived class to cope with buffer overrun.
+  // It ensures there are at least itemSize bytes of buffer data.  Returns
+  // the number of items in the buffer (up to a maximum of nItems).  itemSize
+  // is supposed to be "small" (a few bytes).
+
+  abstract protected int overrun(int itemSize, int nItems) throws Exception;
+
+  protected InStream() {}
+  protected byte[] b;
+  protected int ptr;
+  protected int end;
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/LICENCE.TXT
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/LICENCE.TXT	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/LICENCE.TXT	(revision 571)
@@ -0,0 +1,345 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+	  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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 2 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, write to the Free Software
+    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+    USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/MANIFEST.MF
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/MANIFEST.MF	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/MANIFEST.MF	(revision 571)
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: VncViewer
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/Makefile
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/Makefile	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/Makefile	(revision 571)
@@ -0,0 +1,53 @@
+#
+# Making the VNC applet.
+#
+
+CP = cp
+JC = javac
+JCFLAGS = -target 1.1 -source 1.2
+JAR = jar
+ARCHIVE = VncViewer.jar
+MANIFEST = MANIFEST.MF
+PAGES = index.vnc
+INSTALL_DIR = /usr/local/vnc/classes
+
+CLASSES = VncViewer.class RfbProto.class AuthPanel.class VncCanvas.class \
+	  VncCanvas2.class \
+	  OptionsFrame.class ClipboardFrame.class ButtonPanel.class \
+	  DesCipher.class CapabilityInfo.class CapsContainer.class \
+	  RecordingFrame.class SessionRecorder.class \
+	  SocketFactory.class HTTPConnectSocketFactory.class \
+	  VNCProxyConnectSocketFactory.class VNCProxyConnectSocket.class \
+	  HTTPConnectSocket.class ReloginPanel.class \
+	  InStream.class MemInStream.class ZlibInStream.class \
+	  VNCProxyConnectSocketWrapper.class SocketWrapper.class SocketWrapper\$$WrappingSocketImpl.class SIPBTrustManager.class
+
+SOURCES = VncViewer.java RfbProto.java AuthPanel.java VncCanvas.java \
+	  VncCanvas2.java \
+	  OptionsFrame.java ClipboardFrame.java ButtonPanel.java \
+	  DesCipher.java CapabilityInfo.java CapsContainer.java \
+	  RecordingFrame.java SessionRecorder.java \
+	  SocketFactory.java HTTPConnectSocketFactory.java \
+	  VNCProxyConnectSocketFactory.java VNCProxyConnectSocket.java \
+	  HTTPConnectSocket.java ReloginPanel.java \
+	  InStream.java MemInStream.java ZlibInStream.java \
+	  VNCProxyConnectSocketWrapper.java SocketWrapper.java SIPBTrustManager.java
+
+EXTRAJAR = trust.store
+
+all: $(CLASSES) $(ARCHIVE)
+
+$(CLASSES): $(SOURCES)
+	$(JC) $(JCFLAGS) -O $(SOURCES)
+
+$(ARCHIVE): $(CLASSES) $(MANIFEST)
+	$(JAR) cfm $(ARCHIVE) $(MANIFEST) $(CLASSES) $(EXTRAJAR)
+
+install: $(CLASSES) $(ARCHIVE)
+	$(CP) $(CLASSES) $(ARCHIVE) $(PAGES) $(INSTALL_DIR)
+
+export:: $(CLASSES) $(ARCHIVE) $(PAGES)
+	@$(ExportJavaClasses)
+
+clean::
+	$(RM) *.class *.jar
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/MemInStream.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/MemInStream.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/MemInStream.java	(revision 571)
@@ -0,0 +1,32 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+public class MemInStream extends InStream {
+
+  public MemInStream(byte[] data, int offset, int len) {
+    b = data;
+    ptr = offset;
+    end = offset + len;
+  }
+
+  public int pos() { return ptr; }
+
+  protected int overrun(int itemSize, int nItems) throws Exception {
+    throw new Exception("MemInStream overrun: end of stream");
+  }
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/OptionsFrame.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/OptionsFrame.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/OptionsFrame.java	(revision 571)
@@ -0,0 +1,411 @@
+//
+//  Copyright (C) 2001 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2001 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// Options frame.
+//
+// This deals with all the options the user can play with.
+// It sets the encodings array and some booleans.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+
+class OptionsFrame extends Frame
+  implements WindowListener, ActionListener, ItemListener {
+
+  static String[] names = {
+    "Encoding",
+    "Compression level",
+    "JPEG image quality",
+    "Cursor shape updates",
+    "Use CopyRect",
+    "Restricted colors",
+    "Mouse buttons 2 and 3",
+    "View only",
+    "Scale remote cursor",
+    "Share desktop",
+  };
+
+  static String[][] values = {
+    { "Auto", "Raw", "RRE", "CoRRE", "Hextile", "Zlib", "Tight", "ZRLE" },
+    { "Default", "1", "2", "3", "4", "5", "6", "7", "8", "9" },
+    { "JPEG off", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" },
+    { "Enable", "Ignore", "Disable" },
+    { "Yes", "No" },
+    { "Yes", "No" },
+    { "Normal", "Reversed" },
+    { "Yes", "No" },
+    { "No", "50%", "75%", "125%", "150%" },
+    { "Yes", "No" },
+  };
+
+  final int
+    encodingIndex        = 0,
+    compressLevelIndex   = 1,
+    jpegQualityIndex     = 2,
+    cursorUpdatesIndex   = 3,
+    useCopyRectIndex     = 4,
+    eightBitColorsIndex  = 5,
+    mouseButtonIndex     = 6,
+    viewOnlyIndex        = 7,
+    scaleCursorIndex     = 8,
+    shareDesktopIndex    = 9;
+
+  Label[] labels = new Label[names.length];
+  Choice[] choices = new Choice[names.length];
+  Button closeButton;
+  VncViewer viewer;
+
+
+  //
+  // The actual data which other classes look at:
+  //
+
+  int preferredEncoding;
+  int compressLevel;
+  int jpegQuality;
+  boolean useCopyRect;
+  boolean requestCursorUpdates;
+  boolean ignoreCursorUpdates;
+
+  boolean eightBitColors;
+
+  boolean reverseMouseButtons2And3;
+  boolean shareDesktop;
+  boolean viewOnly;
+  int scaleCursor;
+
+  boolean autoScale;
+  int scalingFactor;
+
+  //
+  // Constructor.  Set up the labels and choices from the names and values
+  // arrays.
+  //
+
+  OptionsFrame(VncViewer v) {
+    super("TightVNC Options");
+
+    viewer = v;
+
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.fill = GridBagConstraints.BOTH;
+
+    for (int i = 0; i < names.length; i++) {
+      labels[i] = new Label(names[i]);
+      gbc.gridwidth = 1;
+      gridbag.setConstraints(labels[i],gbc);
+      add(labels[i]);
+
+      choices[i] = new Choice();
+      gbc.gridwidth = GridBagConstraints.REMAINDER;
+      gridbag.setConstraints(choices[i],gbc);
+      add(choices[i]);
+      choices[i].addItemListener(this);
+
+      for (int j = 0; j < values[i].length; j++) {
+	choices[i].addItem(values[i][j]);
+      }
+    }
+
+    closeButton = new Button("Close");
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gridbag.setConstraints(closeButton, gbc);
+    add(closeButton);
+    closeButton.addActionListener(this);
+
+    pack();
+
+    addWindowListener(this);
+
+    // Set up defaults
+
+    choices[encodingIndex].select("Auto");
+    choices[compressLevelIndex].select("Default");
+    choices[jpegQualityIndex].select("6");
+    choices[cursorUpdatesIndex].select("Enable");
+    choices[useCopyRectIndex].select("Yes");
+    choices[eightBitColorsIndex].select("No");
+    choices[mouseButtonIndex].select("Normal");
+    choices[viewOnlyIndex].select("No");
+    choices[scaleCursorIndex].select("No");
+    choices[shareDesktopIndex].select("Yes");
+
+    // But let them be overridden by parameters
+
+    for (int i = 0; i < names.length; i++) {
+      String s = viewer.readParameter(names[i], false);
+      if (s != null) {
+	for (int j = 0; j < values[i].length; j++) {
+	  if (s.equalsIgnoreCase(values[i][j])) {
+	    choices[i].select(j);
+	  }
+	}
+      }
+    }
+
+    // FIXME: Provide some sort of GUI for "Scaling Factor".
+
+    autoScale = false;
+    scalingFactor = 100;
+    String s = viewer.readParameter("Scaling Factor", false);
+    if (s != null) {
+      if (s.equalsIgnoreCase("Auto")) {
+	autoScale = true;
+      } else {
+	// Remove the '%' char at the end of string if present.
+	if (s.charAt(s.length() - 1) == '%') {
+	  s = s.substring(0, s.length() - 1);
+	}
+	// Convert to an integer.
+	try {
+	  scalingFactor = Integer.parseInt(s);
+	}
+	catch (NumberFormatException e) {
+	  scalingFactor = 100;
+	}
+	// Make sure scalingFactor is in the range of [1..1000].
+	if (scalingFactor < 1) {
+	  scalingFactor = 1;
+	} else if (scalingFactor > 1000) {
+	  scalingFactor = 1000;
+	}
+      }
+    }
+
+    // Make the booleans and encodings array correspond to the state of the GUI
+
+    setEncodings();
+    setColorFormat();
+    setOtherOptions();
+  }
+
+
+  //
+  // Disable the shareDesktop option
+  //
+
+  void disableShareDesktop() {
+    labels[shareDesktopIndex].setEnabled(false);
+    choices[shareDesktopIndex].setEnabled(false);
+  }
+
+
+  //
+  // setEncodings looks at the encoding, compression level, JPEG
+  // quality level, cursor shape updates and copyRect choices and sets
+  // corresponding variables properly. Then it calls the VncViewer's
+  // setEncodings method to send a SetEncodings message to the RFB
+  // server.
+  //
+
+  void setEncodings() {
+    useCopyRect = choices[useCopyRectIndex].getSelectedItem().equals("Yes");
+
+    preferredEncoding = RfbProto.EncodingRaw;
+    boolean enableCompressLevel = false;
+    boolean enableQualityLevel = false;
+
+    if (choices[encodingIndex].getSelectedItem().equals("RRE")) {
+      preferredEncoding = RfbProto.EncodingRRE;
+    } else if (choices[encodingIndex].getSelectedItem().equals("CoRRE")) {
+      preferredEncoding = RfbProto.EncodingCoRRE;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Hextile")) {
+      preferredEncoding = RfbProto.EncodingHextile;
+    } else if (choices[encodingIndex].getSelectedItem().equals("ZRLE")) {
+      preferredEncoding = RfbProto.EncodingZRLE;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Zlib")) {
+      preferredEncoding = RfbProto.EncodingZlib;
+      enableCompressLevel = true;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Tight")) {
+      preferredEncoding = RfbProto.EncodingTight;
+      enableCompressLevel = true;
+      enableQualityLevel = !eightBitColors;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Auto")) {
+      preferredEncoding = -1;
+      enableQualityLevel = !eightBitColors;
+    }
+
+    // Handle compression level setting.
+
+    try {
+      compressLevel =
+        Integer.parseInt(choices[compressLevelIndex].getSelectedItem());
+    }
+    catch (NumberFormatException e) {
+      compressLevel = -1;
+    }
+    if (compressLevel < 1 || compressLevel > 9) {
+      compressLevel = -1;
+    }
+    labels[compressLevelIndex].setEnabled(enableCompressLevel);
+    choices[compressLevelIndex].setEnabled(enableCompressLevel);
+
+    // Handle JPEG quality setting.
+
+    try {
+      jpegQuality =
+        Integer.parseInt(choices[jpegQualityIndex].getSelectedItem());
+    }
+    catch (NumberFormatException e) {
+      jpegQuality = -1;
+    }
+    if (jpegQuality < 0 || jpegQuality > 9) {
+      jpegQuality = -1;
+    }
+    labels[jpegQualityIndex].setEnabled(enableQualityLevel);
+    choices[jpegQualityIndex].setEnabled(enableQualityLevel);
+
+    // Request cursor shape updates if necessary.
+
+    requestCursorUpdates =
+      !choices[cursorUpdatesIndex].getSelectedItem().equals("Disable");
+
+    if (requestCursorUpdates) {
+      ignoreCursorUpdates =
+	choices[cursorUpdatesIndex].getSelectedItem().equals("Ignore");
+    }
+
+    viewer.setEncodings();
+  }
+
+  //
+  // setColorFormat sets eightBitColors variable depending on the GUI
+  // setting, causing switches between 8-bit and 24-bit colors mode if
+  // necessary.
+  //
+
+  void setColorFormat() {
+
+    eightBitColors =
+      choices[eightBitColorsIndex].getSelectedItem().equals("Yes");
+
+    boolean enableJPEG = !eightBitColors &&
+      (choices[encodingIndex].getSelectedItem().equals("Tight") ||
+       choices[encodingIndex].getSelectedItem().equals("Auto"));
+
+    labels[jpegQualityIndex].setEnabled(enableJPEG);
+    choices[jpegQualityIndex].setEnabled(enableJPEG);
+  }
+
+  //
+  // setOtherOptions looks at the "other" choices (ones which don't set the
+  // encoding or the color format) and sets the boolean flags appropriately.
+  //
+
+  void setOtherOptions() {
+
+    reverseMouseButtons2And3
+      = choices[mouseButtonIndex].getSelectedItem().equals("Reversed");
+
+    viewOnly 
+      = choices[viewOnlyIndex].getSelectedItem().equals("Yes");
+    if (viewer.vc != null)
+      viewer.vc.enableInput(!viewOnly);
+
+    shareDesktop
+      = choices[shareDesktopIndex].getSelectedItem().equals("Yes");
+
+    String scaleString = choices[scaleCursorIndex].getSelectedItem();
+    if (scaleString.endsWith("%"))
+      scaleString = scaleString.substring(0, scaleString.length() - 1);
+    try {
+      scaleCursor = Integer.parseInt(scaleString);
+    }
+    catch (NumberFormatException e) {
+      scaleCursor = 0;
+    }
+    if (scaleCursor < 10 || scaleCursor > 500) {
+      scaleCursor = 0;
+    }
+    if (requestCursorUpdates && !ignoreCursorUpdates && !viewOnly) {
+      labels[scaleCursorIndex].setEnabled(true);
+      choices[scaleCursorIndex].setEnabled(true);
+    } else {
+      labels[scaleCursorIndex].setEnabled(false);
+      choices[scaleCursorIndex].setEnabled(false);
+    }
+    if (viewer.vc != null)
+      viewer.vc.createSoftCursor(); // update cursor scaling
+  }
+
+
+  //
+  // Respond to actions on Choice controls
+  //
+
+  public void itemStateChanged(ItemEvent evt) {
+    Object source = evt.getSource();
+
+    if (source == choices[encodingIndex] ||
+        source == choices[compressLevelIndex] ||
+        source == choices[jpegQualityIndex] ||
+        source == choices[cursorUpdatesIndex] ||
+        source == choices[useCopyRectIndex]) {
+
+      setEncodings();
+
+      if (source == choices[cursorUpdatesIndex]) {
+        setOtherOptions();      // update scaleCursor state
+      }
+
+    } else if (source == choices[eightBitColorsIndex]) {
+
+      setColorFormat();
+
+    } else if (source == choices[mouseButtonIndex] ||
+	       source == choices[shareDesktopIndex] ||
+	       source == choices[viewOnlyIndex] ||
+	       source == choices[scaleCursorIndex]) {
+
+      setOtherOptions();
+    }
+  }
+
+  //
+  // Respond to button press
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+    if (evt.getSource() == closeButton)
+      setVisible(false);
+  }
+
+  //
+  // Respond to window events
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    setVisible(false);
+  }
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowDeactivated(WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/README
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/README	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/README	(revision 571)
@@ -0,0 +1,493 @@
+
+  TightVNC Java Viewer version 1.3.9
+
+======================================================================
+
+This distribution is based on the standard VNC source and includes new
+TightVNC-specific features and fixes, such as additional low-bandwidth
+optimizations, major GUI improvements, and more.
+
+	Copyright (C) 1999 AT&T Laboratories Cambridge.
+	Copyright (C) 2000 Tridia Corp.
+	Copyright (C) 2002-2003 RealVNC Ltd.
+	Copyright (C) 2001-2004 HorizonLive.com, Inc.
+	Copyright (C) 2000-2007 Constantin Kaplinsky
+	Copyright (C) 2000-2007 TightVNC Group
+	All rights reserved.
+
+This software is distributed under the GNU General Public Licence as
+published by the Free Software Foundation. See the file LICENCE.TXT for the
+conditions under which this software is made available. TightVNC also
+contains code from other sources. See the Acknowledgements section below, and
+the individual files for details of the conditions under which they are made
+available.
+
+
+Compiling from the sources
+==========================
+
+To compile all the .java files to .class files, simply do:
+
+	% make all
+
+This will also generate a JAR (Java archive) file containing all the classes. 
+Most JVM (Java Virtual Machine) implementations are able to use either a set
+of .class files, or the JAR archive.
+
+
+Installation
+============
+
+There are three basic ways to use TightVNC Java viewer:
+
+  1. Running applet as part of TightVNC server installation.
+
+     Both the Unix and Windows versions of TightVNC servers include small
+     built-in HTTP server which can serve Java viewer to Web clients. This
+     enables easy Web access to the shared desktop without need to install
+     any software on the client computer. Unix and Windows versions of
+     TightVNC servers are different in the way they store the .class and .jar
+     files: the Unix server (Xvnc) is able to serve any set of files present
+     in a particular directory, while the Windows server (WinVNC) has all the
+     .class and .jar files inside the WinVNC executable file. Therefore, for
+     Xvnc, it's enough to copy the files into a correct directory, but for
+     WinVNC, the server binaries should be rebuild if the built-in Java
+     viewer should be updated.
+
+     To install the Java viewer under Xvnc, copy all the .class files, the
+     .jar file and the .vnc files to an installation directory (e.g.
+     /usr/local/vnc/classes):
+
+         cp *.class *.jar *.vnc /usr/local/vnc/classes
+
+     Also, make sure that the vncserver script is configured to point to the
+     installation directory (see the Xvnc manual page for the description of
+     the -httpd command-line option).
+
+  2. Running applet hosted on a standalone Web server.
+
+     Another possibility to use the Java viewer is to install it under a
+     fully-functional HTTP server such as Apache or IIS. Obviously, this
+     method requires running an HTTP server, and due to the Java security
+     restrictions, it's also required that the server should be installed on
+     the same machine which is running the TightVNC server. In this case,
+     installation is simply copying the .class and .jar files into a
+     directory that is under control of the HTTP server. Also, an HTML page
+     should be created which will act as a the base document for the viewer
+     applet (see an example named index.html in this distribution).
+
+     NOTE: Provided index.html page is an example only. Before using that
+     file, edit it with a text editor. See more information inside
+     index.html.
+
+  3. Running the viewer as a standalone application.
+
+     Finally, the Java viewer can be executed locally on the client machine,
+     but this method requires installation of either JRE (Java Runtime
+     Environment) or JDK (Java Development Kit). If all the .class files are
+     in the current directory, the Java viewer can be executed like this,
+     from the command line:
+
+         java VncViewer HOST vnchost PORT 5900
+
+     The parameters HOST and PORT are required, but there is a number of
+     optional parameters as well (see the Parameters section below).
+
+Parameters
+==========
+
+TightVNC Java viewer supports a number of parameters allowing you to
+customize its behavior. Most parameters directly correspond to the settings
+found in the Options window. However, there are parameters that do not
+correspond to those settings. For such parameters, you can see a note "no GUI
+equivalent", in the documentation below.
+
+Parameters can be specified in one of the two ways, depending on how the Java
+viewer is used:
+
+  1. When the Java viewer is run as an applet (embedded within an HTML
+     document), parameters should be specified in the <PARAM> HTML tags,
+     within the appropriate <APPLET> section. Here is an example:
+
+    <APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar WIDTH=400 HEIGHT=300>
+      <PARAM NAME="PORT" VALUE=5901>
+      <PARAM NAME="Scaling factor" VALUE=50>
+    </APPLET>
+
+  2. When run as a standalone application, the Java viewer reads parameters
+     from the command line. Command-line arguments should be specified in
+     pairs -- first goes parameter name, then parameter value. Here is a
+     command line example:
+
+     java VncViewer HOST vnchost PORT 5901 "Scaling factor" 50
+
+Both parameter names and their values are case-insensitive. The only
+exception is the "PASSWORD" parameter, as VNC passwords are case-sensitive.
+
+Here is the complete list of parameters supported in TightVNC Java viewer:
+
+--> "HOST" (no GUI equivalent)
+
+    Value: host name or IP address of the VNC server.
+    Default: in applet mode, the host from which the applet was loaded.
+
+    This parameter tells the viewer which server to connect to. It's not
+    needed in the applet mode, because default Java security policy allow
+    connections from applets to the only one host anyway, and that is the
+    host from which the applet was loaded. However, this parameter is
+    required if the viewer is used as a standalone application.
+
+--> "PORT" (no GUI equivalent)
+
+    Value: TCP port number on the VNC server.
+    Default: none.
+
+    This parameter is required in all cases. Note that this port is not the
+    one used for HTTP connection from the browser, it is the port used for
+    RFB connection. Usually, VNC servers use ports 58xx for HTTP connections,
+    and ports 59xx for RFB connections. Thus, most likely, this parameter
+    should be set to something like 5900, 5901 etc.
+
+--> "PASSWORD"
+
+    Value: session password in plain text.
+    Default: none, ask user.
+
+    DO NOT EVER USE THIS PARAMETER, unless you really know what you are
+    doing. It's extremely dangerous from the security point of view. When
+    this parameter is set, the viewer won't ever ask for a password.
+
+--> "ENCPASSWORD"
+
+    Value: encrypted session password in hex-ascii.
+    Default: none, ask user.
+
+    The same as the "PASSWORD" parameter but DES-encrypted using a fixed key.
+    Its value should be represented in hex-ascii e.g. "494015f9a35e8b22".
+    This parameter has higher priority over the "PASSWORD" parameter. DO NOT
+    EVER USE THIS PARAMETER, unless you really know what you are doing. It's
+    extremely dangerous from the security point of view, and encryption does
+    not actually help here since the decryption key is always known.
+
+--> "Encoding"
+
+    Values: "Auto", "Raw", "RRE", "CoRRE", "Hextile", "ZRLE", "Zlib", "Tight".
+    Default: "Auto".
+
+    The preferred encoding. If the value is "Auto", then the viewer will
+    continuously estimate average network throughput and request encodings
+    that are appropriate for current connection speed. "Hextile" is an
+    encoding that was designed for fast networks, while "Tight" is better
+    suited for low-bandwidth connections. From the other side, "Tight"
+    decoder in the TightVNC Java viewer seems to be more efficient than
+    "Hextile" decoder so it may be ok for fast networks too. "ZRLE" encoding
+    is similar to "Tight", but it does not support JPEG compression and
+    compression levels. Unlike "Tight" encoding, "ZRLE" is supported in
+    recent versions of RealVNC products. Other encodings are not efficient
+    and provided for compatibility reasons.
+
+--> "Compression level"
+
+    Values: "Default", "1", "2", "3", "4", "5", "6", "7", "8", "9".
+    Default: "Default". ;-)
+
+    Use specified compression level for "Tight" and "Zlib" encodings. Level 1
+    uses minimum of CPU time on the server but achieves weak compression
+    ratios. Level 9 offers best compression but may be slow in terms of CPU
+    time consumption on the server side. Use high levels with very slow
+    network connections, and low levels when working over higher-speed
+    networks. The "Default" value means that the server's default compression
+    level should be used.
+
+--> "JPEG image quality"
+
+    Values: "JPEG off", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9".
+    Default: "6".
+
+    Use the specified image quality level in "Tight" encoding. Quality level
+    0 denotes bad image quality but very impressive compression ratios, while
+    level 9 offers very good image quality at lower compression ratios. If
+    the value is "JPEG off", the server will not use lossy JPEG compression
+    in "Tight" encoding.
+
+--> "Cursor shape updates"
+
+    Values: "Enable", "Ignore", "Disable".
+    Default: "Enable".
+
+    Cursor shape updates is a protocol extension used to handle remote cursor
+    movements locally on the client side, saving bandwidth and eliminating
+    delays in mouse pointer movement. Note that current implementation of
+    cursor shape updates does not allow a client to track mouse cursor
+    position at the server side. This means that clients would not see mouse
+    cursor movements if mouse was moved either locally on the server, or by
+    another remote VNC client. Set this parameter to "Disable" if you always
+    want to see real cursor position on the remote side. Setting this option
+    to "Ignore" is similar to "Enable" but the remote cursor will not be
+    visible at all. This can be a reasonable setting if you don't care about
+    cursor shape and don't want to see two mouse cursors, one above another.
+
+--> "Use CopyRect"
+
+    Values: "Yes", "No".
+    Default: "Yes".
+
+    The "CopyRect" encoding saves bandwidth and drawing time when parts of
+    the remote screen are moving around. Most likely, you don't want to
+    change this setting.
+
+--> "Restricted colors"
+
+    Values: "Yes", "No".
+    Default: "No".
+
+    If set to "No", then 24-bit color format is used to represent pixel data. 
+    If set to "Yes", then only 8 bits are used to represent each pixel. 8-bit
+    color format can save bandwidth, but colors may look very inaccurate.
+
+--> "Mouse buttons 2 and 3"
+
+    Values: "Normal", "Reversed".
+    Default: "Normal".
+
+    If set to "Reversed", then right mouse button (button 2) will act as it
+    was middle mouse button (button 3), and vice versa.
+
+--> "View only"
+
+    Values: "Yes", "No".
+    Default: "No".
+
+    If set to "Yes", then all keyboard and mouse events in the desktop window
+    will be silently ignored and will not be passed to the remote side.
+
+--> "Scale remote cursor"
+
+    Values: "No", "50%", "75%", "125%", "150%".
+    Default: "No".
+
+    If a percentage value is specified, the remote cursor is reduced
+    or enlarged accordingly. Scaling takes place only when "View only"
+    is set to "No", and "Cursor shape updates" is set to "Enable".
+
+--> "Share desktop"
+
+    Values: "Yes", "No".
+    Default: "Yes".
+
+    Share the connection with other clients on the same VNC server. The exact
+    behaviour in each case depends on the server configuration.
+
+--> "Open new window" (no GUI equivalent, applicable only in the applet mode)
+
+    Values: "Yes", "No".
+    Default: "No".
+
+    Operate in a separate window. This makes possible resizing the desktop,
+    and adds scroll bars when necessary. If the server supports variable
+    desktop size, the window will resize automatically when remote desktop
+    size changes.
+
+--> "Scaling factor" (no GUI equivalent)
+
+    Value: an integer in the range of [1..1000], or the string "auto".
+    Default: "100".
+
+    Scale local representation of the remote desktop. The value is
+    interpreted as scaling factor in percents. The default value of 100%
+    corresponds to the original framebuffer size. Values below 100 reduce
+    image size, values above 100 enlarge the image proportionally. If the
+    parameter is set to "auto", automatic scaling is performed. Auto-scaling
+    tries to choose scaling factor such way that the whole remote framebuffer
+    will fit on the local screen. Currently, auto-scaling is supported only
+    when the remote desktop is shown in a separate frame (always true in the
+    application mode, and also in the applet mode with "Open new window"
+    parameter set to "yes").
+
+--> "Show controls" (no GUI equivalent)
+
+    Values: "Yes", "No".
+    Default: "Yes".
+
+    Set to "No" if you want to get rid of that button panel at the top.
+
+--> "Offer relogin" (no GUI equivalent, not applicable in the applet mode)
+
+    Values: "Yes", "No".
+    Default: "Yes".
+
+    If set to "No", the buttons "Login again" and "Close window" won't be
+    shown on disconnects or after an error has occured.
+
+--> "Show offline desktop" (no GUI equivalent)
+
+    Values: "Yes", "No".
+    Default: "No".
+
+    If set to "Yes", the viewer would continue to display desktop even
+    if the remote side has closed the connection. In this case, if the
+    button panel is enabled, then the "Disconnect" button would be
+    changed to "Hide desktop" after the connection is lost.
+
+--> "Defer screen updates" (no GUI equivalent)
+
+    Value: time in milliseconds.
+    Default: "20".
+
+    When updating the desktop contents after receiving an update from server,
+    schedule repaint within the specified number of milliseconds. Small delay
+    helps to coalesce several small updates into one drawing operation,
+    improving CPU usage. Set this parameter to 0 to disable deferred updates.
+
+--> "Defer cursor updates" (no GUI equivalent)
+
+    Value: time in milliseconds.
+    Default: "10".
+
+    When updating the desktop after moving the mouse, schedule repaint within
+    the specified number of milliseconds. This setting makes sense only when
+    "Cursor shape updates" parameter is set to "Enable". Small delay helps to
+    coalesce several small updates into one drawing operation, improving CPU
+    usage. Set this parameter to 0 to disable deferred cursor updates.
+
+--> "Defer update requests" (no GUI equivalent)
+
+    Value: time in milliseconds.
+    Default: "50".
+
+    After processing an update received from server, wait for the specified
+    number of milliseconds before requesting next screen update. Such delay
+    will end immediately on every mouse or keyboard event if not in the "view
+    only" mode. Small delay helps the server to coalesce several small
+    updates into one framebuffer update, improving both bandwidth and CPU
+    usage. Increasing the parameter value does not affect responsiveness on
+    mouse and keyboard events, but causes delays in updating the screen when
+    there is no mouse and keyboard activity on the client side.
+
+--> "SocketFactory" (no GUI equivalent)
+
+    Value: name of the class.
+    Default: none.
+
+    This option provides the way to define an alternate I/O implementation.
+    The dynamically referenced class must implement a SocketFactory
+    interface, and create a Socket, as configured by this parameter. See the
+    source in SocketFactory.java.
+
+
+RECORDING VNC SESSIONS
+======================
+
+Current version of the TightVNC Java viewer is able to record VNC (RFB)
+sessions in files for later playback. The data format in saved session files
+is compatible with the rfbproxy program written by Tim Waugh. Most important
+thing about session recording is that it's supported only if Java security
+manager allows access to local filesystem. Typically, it would not work for
+unsigned applets. To use this feature, either use TightVNC Java viewer as a
+standalone application (Java Runtime Environment or Java Development Kit
+should be installed), or as a signed applet. The code checks if it's possible
+to support session recording, and if everything's fine, the new "Record"
+button should appear in the button panel. Pressing this button opens new
+window which controls session recording. The GUI is pretty self-explained.
+
+Other important facts about session recording:
+
+--> All sessions are recorded in the 24-bit color format. If you use
+    restricted colors (8-bit format), it will be temporarly switched to
+    24-bit mode during session recording.
+
+--> All sessions are recorded with cursor shape updates turned off. This is
+    necessary to represent remote cursor movements in recorded sessions.
+
+--> Closing and re-opening the recording control window does not affect the
+    recording. It's not necessary to keep that window open during recording a
+    session.
+
+--> Avoid using Zlib and ZRLE encodings when recording sessions. If you have
+    started recording BEFORE opening a VNC session, then you are ok. But
+    otherwise, all Zlib-encoded updates will be saved Raw-encoded (that is,
+    without compression at all). The case with ZRLE is even worse -- ZRLE
+    updates will not be saved at all, so the resulting session file may be
+    corrupted. Zlib decoding depends on the pixel data received earlier, thus
+    saving the data received from the server at an arbitrary moment is not
+    sufficient to decompress it correctly. And there is no way to tell Zlib
+    or ZRLE decoder to reset decompressor's state -- that's a limitation of
+    these encoders. The viewer could re-compress raw pixel data again before
+    saving Zlib-encoded sessions, but unfortunately Java API does not allow
+    to flush zlib data streams making it impossible to save Zlib-encoded RFB
+    pixel data without using native code.
+
+--> Usually, Tight encoding is the most suitable one for session recording,
+    but some of the issues described above for the Zlib encoding affect the
+    Tight encoding as well. Unlike Zlib sessions, Tight-encoded sessions are
+    always saved Tight-encoded, but the viewer has to re-compress parts of
+    data to synchronize encoder's and decoder's zlib streams. And, due to
+    Java zlib API limitations, zlib streams' states have to be reset on each
+    compressed rectangle, causing compression ratios to be lower than in the
+    original VNC session. If you want to achieve the best possible
+    performance, turn recording on BEFORE connecting to the VNC server,
+    otherwise CPU usage and compression ratios may be notably less efficient.
+
+
+HINTS
+=====
+
+--> To refresh remote desktop in the view-only mode, press "r" or "R"
+    on the keyboard.
+
+
+ACKNOWLEDGEMENTS
+================
+
+This distribution contains Java DES software by Dave Zimmerman
+<dzimm@widget.com> and Jef Poskanzer <jef@acme.com>.  This is:
+
+    Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+
+    Permission to use, copy, modify, and distribute this software and its
+    documentation for NON-COMMERCIAL or COMMERCIAL purposes and without fee
+    is hereby granted, provided that this copyright notice is kept intact.
+    
+    WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
+    SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT
+    NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+    PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE
+    LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,
+    MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+    
+    THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+    CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+    PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+    NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+    SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+    SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+    PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  WIDGET
+    WORKSHOP SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF
+    FITNESS FOR HIGH RISK ACTIVITIES.
+
+    Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights
+    reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+    BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+    WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+    ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+    Visit the ACME Labs Java page for up-to-date versions of this and other
+    fine Java utilities: http://www.acme.com/java/
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/RecordingFrame.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/RecordingFrame.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/RecordingFrame.java	(revision 571)
@@ -0,0 +1,311 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// Recording frame. It allows to control recording RFB sessions into
+// FBS (FrameBuffer Stream) files.
+//
+
+import java.io.*;
+import java.awt.*;
+import java.awt.event.*;
+
+class RecordingFrame extends Frame
+  implements WindowListener, ActionListener {
+
+  boolean recording;
+
+  TextField fnameField;
+  Button browseButton;
+
+  Label statusLabel;
+
+  Button recordButton, nextButton, closeButton;
+  VncViewer viewer;
+
+  //
+  // Check if current security manager allows to create a
+  // RecordingFrame object.
+  //
+
+  public static boolean checkSecurity() {
+    SecurityManager security = System.getSecurityManager();
+    if (security != null) {
+      try {
+	security.checkPropertyAccess("user.dir");
+	security.checkPropertyAccess("file.separator");
+	// Work around (rare) checkPropertyAccess bug
+	System.getProperty("user.dir");
+      } catch (SecurityException e) {
+	System.out.println("SecurityManager restricts session recording.");
+	return false;
+      }
+    }
+    return true;
+  }
+
+  //
+  // Constructor.
+  //
+
+  RecordingFrame(VncViewer v) {
+    super("TightVNC Session Recording");
+
+    viewer = v;
+
+    // Determine initial filename for next saved session.
+    // FIXME: Check SecurityManager.
+
+    String fname = nextNewFilename(System.getProperty("user.dir") +
+				   System.getProperty("file.separator") +
+				   "vncsession.fbs");
+
+    // Construct new panel with file name field and "Browse" button.
+
+    Panel fnamePanel = new Panel();
+    GridBagLayout fnameGridbag = new GridBagLayout();
+    fnamePanel.setLayout(fnameGridbag);
+
+    GridBagConstraints fnameConstraints = new GridBagConstraints();
+    fnameConstraints.gridwidth = GridBagConstraints.RELATIVE;
+    fnameConstraints.fill = GridBagConstraints.BOTH;
+    fnameConstraints.weightx = 4.0;
+
+    fnameField = new TextField(fname, 64);
+    fnameGridbag.setConstraints(fnameField, fnameConstraints);
+    fnamePanel.add(fnameField);
+    fnameField.addActionListener(this);
+
+    fnameConstraints.gridwidth = GridBagConstraints.REMAINDER;
+    fnameConstraints.weightx = 1.0;
+
+    browseButton = new Button("Browse");
+    fnameGridbag.setConstraints(browseButton, fnameConstraints);
+    fnamePanel.add(browseButton);
+    browseButton.addActionListener(this);
+
+    // Construct the frame.
+
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weighty = 1.0;
+    gbc.insets = new Insets(10, 0, 0, 0);
+
+    Label helpLabel =
+      new Label("File name to save next recorded session in:", Label.CENTER);
+    gridbag.setConstraints(helpLabel, gbc);
+    add(helpLabel);
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weighty = 0.0;
+    gbc.insets = new Insets(0, 0, 0, 0);
+
+    gridbag.setConstraints(fnamePanel, gbc);
+    add(fnamePanel);
+
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weighty = 1.0;
+    gbc.insets = new Insets(10, 0, 10, 0);
+
+    statusLabel = new Label("", Label.CENTER);
+    gridbag.setConstraints(statusLabel, gbc);
+    add(statusLabel);
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weightx = 1.0;
+    gbc.weighty = 0.0;
+    gbc.gridwidth = 1;
+    gbc.insets = new Insets(0, 0, 0, 0);
+
+    recordButton = new Button("Record");
+    gridbag.setConstraints(recordButton, gbc);
+    add(recordButton);
+    recordButton.addActionListener(this);
+
+    nextButton = new Button("Next file");
+    gridbag.setConstraints(nextButton, gbc);
+    add(nextButton);
+    nextButton.addActionListener(this);
+
+    closeButton = new Button("Close");
+    gridbag.setConstraints(closeButton, gbc);
+    add(closeButton);
+    closeButton.addActionListener(this);
+
+    // Set correct text, font and color for the statusLabel.
+    stopRecording();
+
+    pack();
+
+    addWindowListener(this);
+  }
+
+  //
+  // If the given string ends with ".NNN" where NNN is a decimal
+  // number, increase this number by one. Otherwise, append ".001"
+  // to the given string.
+  //
+
+  protected String nextFilename(String fname) {
+    int len = fname.length();
+    int suffixPos = len;
+    int suffixNum = 1;
+
+    if (len > 4 && fname.charAt(len - 4) == '.') {
+      try {
+	suffixNum = Integer.parseInt(fname.substring(len - 3, len)) + 1;
+	suffixPos = len - 4;
+      } catch (NumberFormatException e) { }
+    }
+
+    char[] zeroes = {'0', '0', '0'};
+    String suffix = String.valueOf(suffixNum);
+    if (suffix.length() < 3) {
+      suffix = new String(zeroes, 0, 3 - suffix.length()) + suffix;
+    }
+
+    return fname.substring(0, suffixPos) + '.' + suffix;
+  }
+
+  //
+  // Find next name of a file which does not exist yet.
+  //
+
+  protected String nextNewFilename(String fname) {
+    String newName = fname;
+    File f;
+    try {
+      do {
+	newName = nextFilename(newName);
+	f = new File(newName);
+      } while (f.exists());
+    } catch (SecurityException e) { }
+
+    return newName;
+  }
+
+  //
+  // Let the user choose a file name showing a FileDialog.
+  //
+
+  protected boolean browseFile() {
+    File currentFile = new File(fnameField.getText());
+
+    FileDialog fd =
+      new FileDialog(this, "Save next session as...", FileDialog.SAVE);
+    fd.setDirectory(currentFile.getParent());
+    fd.setVisible(true);
+    if (fd.getFile() != null) {
+      String newDir = fd.getDirectory();
+      String sep = System.getProperty("file.separator");
+      if (newDir.length() > 0) {
+	if (!sep.equals(newDir.substring(newDir.length() - sep.length())))
+	  newDir += sep;
+      }
+      String newFname = newDir + fd.getFile();
+      if (newFname.equals(fnameField.getText())) {
+	fnameField.setText(newFname);
+	return true;
+      }
+    }
+    return false;
+  }
+
+  //
+  // Start recording.
+  //
+
+  public void startRecording() {
+    statusLabel.setText("Status: Recording...");
+    statusLabel.setFont(new Font("Helvetica", Font.BOLD, 12));
+    statusLabel.setForeground(Color.red);
+    recordButton.setLabel("Stop recording");
+
+    recording = true;
+
+    viewer.setRecordingStatus(fnameField.getText());
+  }
+
+  //
+  // Stop recording.
+  //
+
+  public void stopRecording() {
+    statusLabel.setText("Status: Not recording.");
+    statusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+    statusLabel.setForeground(Color.black);
+    recordButton.setLabel("Record");
+
+    recording = false;
+
+    viewer.setRecordingStatus(null);
+  }
+
+  //
+  // Close our window properly.
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    setVisible(false);
+  }
+
+  //
+  // Ignore window events we're not interested in.
+  //
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowDeactivated (WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+
+
+  //
+  // Respond to button presses
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+    if (evt.getSource() == browseButton) {
+      if (browseFile() && recording)
+	startRecording();
+
+    } else if (evt.getSource() == recordButton) {
+      if (!recording) {
+	startRecording();
+      } else {
+	stopRecording();
+        fnameField.setText(nextNewFilename(fnameField.getText()));
+      }
+
+    } else if (evt.getSource() == nextButton) {
+      fnameField.setText(nextNewFilename(fnameField.getText()));
+      if (recording)
+	startRecording();
+
+    } else if (evt.getSource() == closeButton) {
+      setVisible(false);
+
+    }
+  }
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/ReloginPanel.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/ReloginPanel.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/ReloginPanel.java	(revision 571)
@@ -0,0 +1,65 @@
+//
+//  Copyright (C) 2002 Cendio Systems.  All Rights Reserved.
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// ReloginPanel class implements panel with a button for logging in again,
+// after fatal errors or disconnect
+//
+
+
+import java.awt.*;
+import java.awt.event.*;
+import java.applet.*;
+
+//
+// The panel which implements the Relogin button
+//
+
+class ReloginPanel extends Panel implements ActionListener {
+  Button reloginButton;
+  Button closeButton;
+  VncViewer viewer;
+
+  //
+  // Constructor.
+  //
+  public ReloginPanel(VncViewer v) {
+    viewer = v;
+    setLayout(new FlowLayout(FlowLayout.CENTER));
+    reloginButton = new Button("Login again");
+    add(reloginButton);
+    reloginButton.addActionListener(this);
+    if (viewer.inSeparateFrame) {
+      closeButton = new Button("Close window");
+      add(closeButton);
+      closeButton.addActionListener(this);
+    }
+  }
+
+  //
+  // This method is called when a button is pressed.
+  //
+  public synchronized void actionPerformed(ActionEvent evt) {
+    if (viewer.inSeparateFrame)
+      viewer.vncFrame.dispose();
+    if (evt.getSource() == reloginButton)
+      viewer.getAppletContext().showDocument(viewer.getDocumentBase());
+  }
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/RfbProto.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/RfbProto.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/RfbProto.java	(revision 571)
@@ -0,0 +1,1314 @@
+//
+//  Copyright (C) 2001-2004 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2001-2006 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// RfbProto.java
+//
+
+import java.io.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.net.Socket;
+import java.util.zip.*;
+
+class RfbProto {
+
+  final static String
+    versionMsg_3_3 = "RFB 003.003\n",
+    versionMsg_3_7 = "RFB 003.007\n",
+    versionMsg_3_8 = "RFB 003.008\n";
+
+  // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC
+  final static String
+    StandardVendor  = "STDV",
+    TridiaVncVendor = "TRDV",
+    TightVncVendor  = "TGHT";
+
+  // Security types
+  final static int
+    SecTypeInvalid = 0,
+    SecTypeNone    = 1,
+    SecTypeVncAuth = 2,
+    SecTypeTight   = 16;
+
+  // Supported tunneling types
+  final static int
+    NoTunneling = 0;
+  final static String
+    SigNoTunneling = "NOTUNNEL";
+
+  // Supported authentication types
+  final static int
+    AuthNone      = 1,
+    AuthVNC       = 2,
+    AuthUnixLogin = 129;
+  final static String
+    SigAuthNone      = "NOAUTH__",
+    SigAuthVNC       = "VNCAUTH_",
+    SigAuthUnixLogin = "ULGNAUTH";
+
+  // VNC authentication results
+  final static int
+    VncAuthOK      = 0,
+    VncAuthFailed  = 1,
+    VncAuthTooMany = 2;
+
+  // Server-to-client messages
+  final static int
+    FramebufferUpdate   = 0,
+    SetColourMapEntries = 1,
+    Bell                = 2,
+    ServerCutText       = 3;
+
+  // Client-to-server messages
+  final static int
+    SetPixelFormat           = 0,
+    FixColourMapEntries      = 1,
+    SetEncodings             = 2,
+    FramebufferUpdateRequest = 3,
+    KeyboardEvent            = 4,
+    PointerEvent             = 5,
+    ClientCutText            = 6;
+
+  // Supported encodings and pseudo-encodings
+  final static int
+    EncodingRaw            = 0,
+    EncodingCopyRect       = 1,
+    EncodingRRE            = 2,
+    EncodingCoRRE          = 4,
+    EncodingHextile        = 5,
+    EncodingZlib           = 6,
+    EncodingTight          = 7,
+    EncodingZRLE           = 16,
+    EncodingCompressLevel0 = 0xFFFFFF00,
+    EncodingQualityLevel0  = 0xFFFFFFE0,
+    EncodingXCursor        = 0xFFFFFF10,
+    EncodingRichCursor     = 0xFFFFFF11,
+    EncodingPointerPos     = 0xFFFFFF18,
+    EncodingLastRect       = 0xFFFFFF20,
+    EncodingNewFBSize      = 0xFFFFFF21;
+  final static String
+    SigEncodingRaw            = "RAW_____",
+    SigEncodingCopyRect       = "COPYRECT",
+    SigEncodingRRE            = "RRE_____",
+    SigEncodingCoRRE          = "CORRE___",
+    SigEncodingHextile        = "HEXTILE_",
+    SigEncodingZlib           = "ZLIB____",
+    SigEncodingTight          = "TIGHT___",
+    SigEncodingZRLE           = "ZRLE____",
+    SigEncodingCompressLevel0 = "COMPRLVL",
+    SigEncodingQualityLevel0  = "JPEGQLVL",
+    SigEncodingXCursor        = "X11CURSR",
+    SigEncodingRichCursor     = "RCHCURSR",
+    SigEncodingPointerPos     = "POINTPOS",
+    SigEncodingLastRect       = "LASTRECT",
+    SigEncodingNewFBSize      = "NEWFBSIZ";
+
+  final static int MaxNormalEncoding = 255;
+
+  // Contstants used in the Hextile decoder
+  final static int
+    HextileRaw                 = 1,
+    HextileBackgroundSpecified = 2,
+    HextileForegroundSpecified = 4,
+    HextileAnySubrects         = 8,
+    HextileSubrectsColoured    = 16;
+
+  // Contstants used in the Tight decoder
+  final static int TightMinToCompress = 12;
+  final static int
+    TightExplicitFilter = 0x04,
+    TightFill           = 0x08,
+    TightJpeg           = 0x09,
+    TightMaxSubencoding = 0x09,
+    TightFilterCopy     = 0x00,
+    TightFilterPalette  = 0x01,
+    TightFilterGradient = 0x02;
+
+
+  String host;
+  int port;
+  Socket sock;
+  DataInputStream is;
+  OutputStream os;
+  SessionRecorder rec;
+  boolean inNormalProtocol = false;
+  VncViewer viewer;
+
+  // Java on UNIX does not call keyPressed() on some keys, for example
+  // swedish keys To prevent our workaround to produce duplicate
+  // keypresses on JVMs that actually works, keep track of if
+  // keyPressed() for a "broken" key was called or not. 
+  boolean brokenKeyPressed = false;
+
+  // This will be set to true on the first framebuffer update
+  // containing Zlib-, ZRLE- or Tight-encoded data.
+  boolean wereZlibUpdates = false;
+
+  // This will be set to false if the startSession() was called after
+  // we have received at least one Zlib-, ZRLE- or Tight-encoded
+  // framebuffer update.
+  boolean recordFromBeginning = true;
+
+  // This fields are needed to show warnings about inefficiently saved
+  // sessions only once per each saved session file.
+  boolean zlibWarningShown;
+  boolean tightWarningShown;
+
+  // Before starting to record each saved session, we set this field
+  // to 0, and increment on each framebuffer update. We don't flush
+  // the SessionRecorder data into the file before the second update. 
+  // This allows us to write initial framebuffer update with zero
+  // timestamp, to let the player show initial desktop before
+  // playback.
+  int numUpdatesInSession;
+
+  // Measuring network throughput.
+  boolean timing;
+  long timeWaitedIn100us;
+  long timedKbits;
+
+  // Protocol version and TightVNC-specific protocol options.
+  int serverMajor, serverMinor;
+  int clientMajor, clientMinor;
+  boolean protocolTightVNC;
+  CapsContainer tunnelCaps, authCaps;
+  CapsContainer serverMsgCaps, clientMsgCaps;
+  CapsContainer encodingCaps;
+
+  // If true, informs that the RFB socket was closed.
+  private boolean closed;
+
+  //
+  // Constructor. Make TCP connection to RFB server.
+  //
+
+  RfbProto(String h, int p, VncViewer v) throws IOException {
+    viewer = v;
+    host = h;
+    port = p;
+
+    if (viewer.socketFactory == null) {
+	System.out.println("Null socketFactory");
+      sock = new Socket(host, port);
+    } else {
+      try {
+	Class factoryClass = Class.forName(viewer.socketFactory);
+	SocketFactory factory = (SocketFactory)factoryClass.newInstance();
+	System.out.println("Using socketFactory " + factory);
+	if (viewer.inAnApplet)
+	  sock = factory.createSocket(host, port, viewer);
+	else
+	  sock = factory.createSocket(host, port, viewer.mainArgs);
+      } catch(Exception e) {
+	e.printStackTrace();
+	throw new IOException(e.getMessage());
+      }
+    }
+    is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
+						     16384));
+    os = sock.getOutputStream();
+
+    timing = false;
+    timeWaitedIn100us = 5;
+    timedKbits = 0;
+  }
+
+
+  synchronized void close() {
+    try {
+      sock.close();
+      closed = true;
+      System.out.println("RFB socket closed " + sock);
+      if (rec != null) {
+	rec.close();
+	rec = null;
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  synchronized boolean closed() {
+    return closed;
+  }
+
+  //
+  // Read server's protocol version message
+  //
+
+  void readVersionMsg() throws Exception {
+
+    byte[] b = new byte[12];
+
+    readFully(b);
+
+    if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ')
+	|| (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9')
+	|| (b[6] < '0') || (b[6] > '9') || (b[7] != '.')
+	|| (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9')
+	|| (b[10] < '0') || (b[10] > '9') || (b[11] != '\n'))
+    {
+      throw new Exception("Host " + host + " port " + port +
+			  " is not an RFB server");
+    }
+
+    serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
+    serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0');
+
+    if (serverMajor < 3) {
+      throw new Exception("RFB server does not support protocol version 3");
+    }
+  }
+
+
+  //
+  // Write our protocol version message
+  //
+
+  void writeVersionMsg() throws IOException {
+    clientMajor = 3;
+    if (serverMajor > 3 || serverMinor >= 8) {
+      clientMinor = 8;
+      os.write(versionMsg_3_8.getBytes());
+    } else if (serverMinor >= 7) {
+      clientMinor = 7;
+      os.write(versionMsg_3_7.getBytes());
+    } else {
+      clientMinor = 3;
+      os.write(versionMsg_3_3.getBytes());
+    }
+    protocolTightVNC = false;
+  }
+
+
+  //
+  // Negotiate the authentication scheme.
+  //
+
+  int negotiateSecurity() throws Exception {
+    return (clientMinor >= 7) ?
+      selectSecurityType() : readSecurityType();
+  }
+
+  //
+  // Read security type from the server (protocol version 3.3).
+  //
+
+  int readSecurityType() throws Exception {
+    int secType = is.readInt();
+
+    switch (secType) {
+    case SecTypeInvalid:
+      readConnFailedReason();
+      return SecTypeInvalid;	// should never be executed
+    case SecTypeNone:
+    case SecTypeVncAuth:
+      return secType;
+    default:
+      throw new Exception("Unknown security type from RFB server: " + secType);
+    }
+  }
+
+  //
+  // Select security type from the server's list (protocol versions 3.7/3.8).
+  //
+
+  int selectSecurityType() throws Exception {
+    int secType = SecTypeInvalid;
+
+    // Read the list of secutiry types.
+    int nSecTypes = is.readUnsignedByte();
+    if (nSecTypes == 0) {
+      readConnFailedReason();
+      return SecTypeInvalid;	// should never be executed
+    }
+    byte[] secTypes = new byte[nSecTypes];
+    readFully(secTypes);
+
+    // Find out if the server supports TightVNC protocol extensions
+    for (int i = 0; i < nSecTypes; i++) {
+      if (secTypes[i] == SecTypeTight) {
+	protocolTightVNC = true;
+	os.write(SecTypeTight);
+	return SecTypeTight;
+      }
+    }
+
+    // Find first supported security type.
+    for (int i = 0; i < nSecTypes; i++) {
+      if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) {
+	secType = secTypes[i];
+	break;
+      }
+    }
+
+    if (secType == SecTypeInvalid) {
+      throw new Exception("Server did not offer supported security type");
+    } else {
+      os.write(secType);
+    }
+
+    return secType;
+  }
+
+  //
+  // Perform "no authentication".
+  //
+
+  void authenticateNone() throws Exception {
+    if (clientMinor >= 8)
+      readSecurityResult("No authentication");
+  }
+
+  //
+  // Perform standard VNC Authentication.
+  //
+
+  void authenticateVNC(String pw) throws Exception {
+    byte[] challenge = new byte[16];
+    readFully(challenge);
+
+    if (pw.length() > 8)
+      pw = pw.substring(0, 8);	// Truncate to 8 chars
+
+    // Truncate password on the first zero byte.
+    int firstZero = pw.indexOf(0);
+    if (firstZero != -1)
+      pw = pw.substring(0, firstZero);
+
+    byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
+    System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
+
+    DesCipher des = new DesCipher(key);
+
+    des.encrypt(challenge, 0, challenge, 0);
+    des.encrypt(challenge, 8, challenge, 8);
+
+    os.write(challenge);
+
+    readSecurityResult("VNC authentication");
+  }
+
+  //
+  // Read security result.
+  // Throws an exception on authentication failure.
+  //
+
+  void readSecurityResult(String authType) throws Exception {
+    int securityResult = is.readInt();
+
+    switch (securityResult) {
+    case VncAuthOK:
+      System.out.println(authType + ": success");
+      break;
+    case VncAuthFailed:
+      if (clientMinor >= 8)
+        readConnFailedReason();
+      throw new Exception(authType + ": failed");
+    case VncAuthTooMany:
+      throw new Exception(authType + ": failed, too many tries");
+    default:
+      throw new Exception(authType + ": unknown result " + securityResult);
+    }
+  }
+
+  //
+  // Read the string describing the reason for a connection failure,
+  // and throw an exception.
+  //
+
+  void readConnFailedReason() throws Exception {
+    int reasonLen = is.readInt();
+    byte[] reason = new byte[reasonLen];
+    readFully(reason);
+    throw new Exception(new String(reason));
+  }
+
+  //
+  // Initialize capability lists (TightVNC protocol extensions).
+  //
+
+  void initCapabilities() {
+    tunnelCaps    = new CapsContainer();
+    authCaps      = new CapsContainer();
+    serverMsgCaps = new CapsContainer();
+    clientMsgCaps = new CapsContainer();
+    encodingCaps  = new CapsContainer();
+
+    // Supported authentication methods
+    authCaps.add(AuthNone, StandardVendor, SigAuthNone,
+		 "No authentication");
+    authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
+		 "Standard VNC password authentication");
+
+    // Supported encoding types
+    encodingCaps.add(EncodingCopyRect, StandardVendor,
+		     SigEncodingCopyRect, "Standard CopyRect encoding");
+    encodingCaps.add(EncodingRRE, StandardVendor,
+		     SigEncodingRRE, "Standard RRE encoding");
+    encodingCaps.add(EncodingCoRRE, StandardVendor,
+		     SigEncodingCoRRE, "Standard CoRRE encoding");
+    encodingCaps.add(EncodingHextile, StandardVendor,
+		     SigEncodingHextile, "Standard Hextile encoding");
+    encodingCaps.add(EncodingZRLE, StandardVendor,
+		     SigEncodingZRLE, "Standard ZRLE encoding");
+    encodingCaps.add(EncodingZlib, TridiaVncVendor,
+		     SigEncodingZlib, "Zlib encoding");
+    encodingCaps.add(EncodingTight, TightVncVendor,
+		     SigEncodingTight, "Tight encoding");
+
+    // Supported pseudo-encoding types
+    encodingCaps.add(EncodingCompressLevel0, TightVncVendor,
+		     SigEncodingCompressLevel0, "Compression level");
+    encodingCaps.add(EncodingQualityLevel0, TightVncVendor,
+		     SigEncodingQualityLevel0, "JPEG quality level");
+    encodingCaps.add(EncodingXCursor, TightVncVendor,
+		     SigEncodingXCursor, "X-style cursor shape update");
+    encodingCaps.add(EncodingRichCursor, TightVncVendor,
+		     SigEncodingRichCursor, "Rich-color cursor shape update");
+    encodingCaps.add(EncodingPointerPos, TightVncVendor,
+		     SigEncodingPointerPos, "Pointer position update");
+    encodingCaps.add(EncodingLastRect, TightVncVendor,
+		     SigEncodingLastRect, "LastRect protocol extension");
+    encodingCaps.add(EncodingNewFBSize, TightVncVendor,
+		     SigEncodingNewFBSize, "Framebuffer size change");
+  }
+
+  //
+  // Setup tunneling (TightVNC protocol extensions)
+  //
+
+  void setupTunneling() throws IOException {
+    int nTunnelTypes = is.readInt();
+    if (nTunnelTypes != 0) {
+      readCapabilityList(tunnelCaps, nTunnelTypes);
+
+      // We don't support tunneling yet.
+      writeInt(NoTunneling);
+    }
+  }
+
+  //
+  // Negotiate authentication scheme (TightVNC protocol extensions)
+  //
+
+  int negotiateAuthenticationTight() throws Exception {
+    int nAuthTypes = is.readInt();
+    if (nAuthTypes == 0)
+      return AuthNone;
+
+    readCapabilityList(authCaps, nAuthTypes);
+    for (int i = 0; i < authCaps.numEnabled(); i++) {
+      int authType = authCaps.getByOrder(i);
+      if (authType == AuthNone || authType == AuthVNC) {
+	writeInt(authType);
+	return authType;
+      }
+    }
+    throw new Exception("No suitable authentication scheme found");
+  }
+
+  //
+  // Read a capability list (TightVNC protocol extensions)
+  //
+
+  void readCapabilityList(CapsContainer caps, int count) throws IOException {
+    int code;
+    byte[] vendor = new byte[4];
+    byte[] name = new byte[8];
+    for (int i = 0; i < count; i++) {
+      code = is.readInt();
+      readFully(vendor);
+      readFully(name);
+      caps.enable(new CapabilityInfo(code, vendor, name));
+    }
+  }
+
+  //
+  // Write a 32-bit integer into the output stream.
+  //
+
+  void writeInt(int value) throws IOException {
+    byte[] b = new byte[4];
+    b[0] = (byte) ((value >> 24) & 0xff);
+    b[1] = (byte) ((value >> 16) & 0xff);
+    b[2] = (byte) ((value >> 8) & 0xff);
+    b[3] = (byte) (value & 0xff);
+    os.write(b);
+  }
+
+  //
+  // Write the client initialisation message
+  //
+
+  void writeClientInit() throws IOException {
+    if (viewer.options.shareDesktop) {
+      os.write(1);
+    } else {
+      os.write(0);
+    }
+    viewer.options.disableShareDesktop();
+  }
+
+
+  //
+  // Read the server initialisation message
+  //
+
+  String desktopName;
+  int framebufferWidth, framebufferHeight;
+  int bitsPerPixel, depth;
+  boolean bigEndian, trueColour;
+  int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
+
+  void readServerInit() throws IOException {
+    framebufferWidth = is.readUnsignedShort();
+    framebufferHeight = is.readUnsignedShort();
+    bitsPerPixel = is.readUnsignedByte();
+    depth = is.readUnsignedByte();
+    bigEndian = (is.readUnsignedByte() != 0);
+    trueColour = (is.readUnsignedByte() != 0);
+    redMax = is.readUnsignedShort();
+    greenMax = is.readUnsignedShort();
+    blueMax = is.readUnsignedShort();
+    redShift = is.readUnsignedByte();
+    greenShift = is.readUnsignedByte();
+    blueShift = is.readUnsignedByte();
+    byte[] pad = new byte[3];
+    readFully(pad);
+    int nameLength = is.readInt();
+    byte[] name = new byte[nameLength];
+    readFully(name);
+    desktopName = new String(name);
+
+    // Read interaction capabilities (TightVNC protocol extensions)
+    if (protocolTightVNC) {
+      int nServerMessageTypes = is.readUnsignedShort();
+      int nClientMessageTypes = is.readUnsignedShort();
+      int nEncodingTypes = is.readUnsignedShort();
+      is.readUnsignedShort();
+      readCapabilityList(serverMsgCaps, nServerMessageTypes);
+      readCapabilityList(clientMsgCaps, nClientMessageTypes);
+      readCapabilityList(encodingCaps, nEncodingTypes);
+    }
+
+    inNormalProtocol = true;
+  }
+
+
+  //
+  // Create session file and write initial protocol messages into it.
+  //
+
+  void startSession(String fname) throws IOException {
+    rec = new SessionRecorder(fname);
+    rec.writeHeader();
+    rec.write(versionMsg_3_3.getBytes());
+    rec.writeIntBE(SecTypeNone);
+    rec.writeShortBE(framebufferWidth);
+    rec.writeShortBE(framebufferHeight);
+    byte[] fbsServerInitMsg =	{
+      32, 24, 0, 1, 0,
+      (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF,
+      16, 8, 0, 0, 0, 0
+    };
+    rec.write(fbsServerInitMsg);
+    rec.writeIntBE(desktopName.length());
+    rec.write(desktopName.getBytes());
+    numUpdatesInSession = 0;
+
+    // FIXME: If there were e.g. ZRLE updates only, that should not
+    //        affect recording of Zlib and Tight updates. So, actually
+    //        we should maintain separate flags for Zlib, ZRLE and
+    //        Tight, instead of one ``wereZlibUpdates'' variable.
+    //
+    if (wereZlibUpdates)
+      recordFromBeginning = false;
+
+    zlibWarningShown = false;
+    tightWarningShown = false;
+  }
+
+  //
+  // Close session file.
+  //
+
+  void closeSession() throws IOException {
+    if (rec != null) {
+      rec.close();
+      rec = null;
+    }
+  }
+
+
+  //
+  // Set new framebuffer size
+  //
+
+  void setFramebufferSize(int width, int height) {
+    framebufferWidth = width;
+    framebufferHeight = height;
+  }
+
+
+  //
+  // Read the server message type
+  //
+
+  int readServerMessageType() throws IOException {
+    int msgType = is.readUnsignedByte();
+
+    // If the session is being recorded:
+    if (rec != null) {
+      if (msgType == Bell) {	// Save Bell messages in session files.
+	rec.writeByte(msgType);
+	if (numUpdatesInSession > 0)
+	  rec.flush();
+      }
+    }
+
+    return msgType;
+  }
+
+
+  //
+  // Read a FramebufferUpdate message
+  //
+
+  int updateNRects;
+
+  void readFramebufferUpdate() throws IOException {
+    is.readByte();
+    updateNRects = is.readUnsignedShort();
+
+    // If the session is being recorded:
+    if (rec != null) {
+      rec.writeByte(FramebufferUpdate);
+      rec.writeByte(0);
+      rec.writeShortBE(updateNRects);
+    }
+
+    numUpdatesInSession++;
+  }
+
+  // Read a FramebufferUpdate rectangle header
+
+  int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
+
+  void readFramebufferUpdateRectHdr() throws Exception {
+    updateRectX = is.readUnsignedShort();
+    updateRectY = is.readUnsignedShort();
+    updateRectW = is.readUnsignedShort();
+    updateRectH = is.readUnsignedShort();
+    updateRectEncoding = is.readInt();
+
+    if (updateRectEncoding == EncodingZlib ||
+        updateRectEncoding == EncodingZRLE ||
+	updateRectEncoding == EncodingTight)
+      wereZlibUpdates = true;
+
+    // If the session is being recorded:
+    if (rec != null) {
+      if (numUpdatesInSession > 1)
+	rec.flush();		// Flush the output on each rectangle.
+      rec.writeShortBE(updateRectX);
+      rec.writeShortBE(updateRectY);
+      rec.writeShortBE(updateRectW);
+      rec.writeShortBE(updateRectH);
+      if (updateRectEncoding == EncodingZlib && !recordFromBeginning) {
+	// Here we cannot write Zlib-encoded rectangles because the
+	// decoder won't be able to reproduce zlib stream state.
+	if (!zlibWarningShown) {
+	  System.out.println("Warning: Raw encoding will be used " +
+			     "instead of Zlib in recorded session.");
+	  zlibWarningShown = true;
+	}
+	rec.writeIntBE(EncodingRaw);
+      } else {
+	rec.writeIntBE(updateRectEncoding);
+	if (updateRectEncoding == EncodingTight && !recordFromBeginning &&
+	    !tightWarningShown) {
+	  System.out.println("Warning: Re-compressing Tight-encoded " +
+			     "updates for session recording.");
+	  tightWarningShown = true;
+	}
+      }
+    }
+
+    if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding)
+      return;
+
+    if (updateRectX + updateRectW > framebufferWidth ||
+	updateRectY + updateRectH > framebufferHeight) {
+      throw new Exception("Framebuffer update rectangle too large: " +
+			  updateRectW + "x" + updateRectH + " at (" +
+			  updateRectX + "," + updateRectY + ")");
+    }
+  }
+
+  // Read CopyRect source X and Y.
+
+  int copyRectSrcX, copyRectSrcY;
+
+  void readCopyRect() throws IOException {
+    copyRectSrcX = is.readUnsignedShort();
+    copyRectSrcY = is.readUnsignedShort();
+
+    // If the session is being recorded:
+    if (rec != null) {
+      rec.writeShortBE(copyRectSrcX);
+      rec.writeShortBE(copyRectSrcY);
+    }
+  }
+
+
+  //
+  // Read a ServerCutText message
+  //
+
+  String readServerCutText() throws IOException {
+    byte[] pad = new byte[3];
+    readFully(pad);
+    int len = is.readInt();
+    byte[] text = new byte[len];
+    readFully(text);
+    return new String(text);
+  }
+
+
+  //
+  // Read an integer in compact representation (1..3 bytes).
+  // Such format is used as a part of the Tight encoding.
+  // Also, this method records data if session recording is active and
+  // the viewer's recordFromBeginning variable is set to true.
+  //
+
+  int readCompactLen() throws IOException {
+    int[] portion = new int[3];
+    portion[0] = is.readUnsignedByte();
+    int byteCount = 1;
+    int len = portion[0] & 0x7F;
+    if ((portion[0] & 0x80) != 0) {
+      portion[1] = is.readUnsignedByte();
+      byteCount++;
+      len |= (portion[1] & 0x7F) << 7;
+      if ((portion[1] & 0x80) != 0) {
+	portion[2] = is.readUnsignedByte();
+	byteCount++;
+	len |= (portion[2] & 0xFF) << 14;
+      }
+    }
+
+    if (rec != null && recordFromBeginning)
+      for (int i = 0; i < byteCount; i++)
+	rec.writeByte(portion[i]);
+
+    return len;
+  }
+
+
+  //
+  // Write a FramebufferUpdateRequest message
+  //
+
+  void writeFramebufferUpdateRequest(int x, int y, int w, int h,
+				     boolean incremental)
+       throws IOException
+  {
+    byte[] b = new byte[10];
+
+    b[0] = (byte) FramebufferUpdateRequest;
+    b[1] = (byte) (incremental ? 1 : 0);
+    b[2] = (byte) ((x >> 8) & 0xff);
+    b[3] = (byte) (x & 0xff);
+    b[4] = (byte) ((y >> 8) & 0xff);
+    b[5] = (byte) (y & 0xff);
+    b[6] = (byte) ((w >> 8) & 0xff);
+    b[7] = (byte) (w & 0xff);
+    b[8] = (byte) ((h >> 8) & 0xff);
+    b[9] = (byte) (h & 0xff);
+
+    os.write(b);
+  }
+
+
+  //
+  // Write a SetPixelFormat message
+  //
+
+  void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian,
+			   boolean trueColour,
+			   int redMax, int greenMax, int blueMax,
+			   int redShift, int greenShift, int blueShift)
+       throws IOException
+  {
+    byte[] b = new byte[20];
+
+    b[0]  = (byte) SetPixelFormat;
+    b[4]  = (byte) bitsPerPixel;
+    b[5]  = (byte) depth;
+    b[6]  = (byte) (bigEndian ? 1 : 0);
+    b[7]  = (byte) (trueColour ? 1 : 0);
+    b[8]  = (byte) ((redMax >> 8) & 0xff);
+    b[9]  = (byte) (redMax & 0xff);
+    b[10] = (byte) ((greenMax >> 8) & 0xff);
+    b[11] = (byte) (greenMax & 0xff);
+    b[12] = (byte) ((blueMax >> 8) & 0xff);
+    b[13] = (byte) (blueMax & 0xff);
+    b[14] = (byte) redShift;
+    b[15] = (byte) greenShift;
+    b[16] = (byte) blueShift;
+
+    os.write(b);
+  }
+
+
+  //
+  // Write a FixColourMapEntries message.  The values in the red, green and
+  // blue arrays are from 0 to 65535.
+  //
+
+  void writeFixColourMapEntries(int firstColour, int nColours,
+				int[] red, int[] green, int[] blue)
+       throws IOException
+  {
+    byte[] b = new byte[6 + nColours * 6];
+
+    b[0] = (byte) FixColourMapEntries;
+    b[2] = (byte) ((firstColour >> 8) & 0xff);
+    b[3] = (byte) (firstColour & 0xff);
+    b[4] = (byte) ((nColours >> 8) & 0xff);
+    b[5] = (byte) (nColours & 0xff);
+
+    for (int i = 0; i < nColours; i++) {
+      b[6 + i * 6]     = (byte) ((red[i] >> 8) & 0xff);
+      b[6 + i * 6 + 1] = (byte) (red[i] & 0xff);
+      b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff);
+      b[6 + i * 6 + 3] = (byte) (green[i] & 0xff);
+      b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff);
+      b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff);
+    }
+ 
+    os.write(b);
+  }
+
+
+  //
+  // Write a SetEncodings message
+  //
+
+  void writeSetEncodings(int[] encs, int len) throws IOException {
+    byte[] b = new byte[4 + 4 * len];
+
+    b[0] = (byte) SetEncodings;
+    b[2] = (byte) ((len >> 8) & 0xff);
+    b[3] = (byte) (len & 0xff);
+
+    for (int i = 0; i < len; i++) {
+      b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff);
+      b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff);
+      b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff);
+      b[7 + 4 * i] = (byte) (encs[i] & 0xff);
+    }
+
+    os.write(b);
+  }
+
+
+  //
+  // Write a ClientCutText message
+  //
+
+  void writeClientCutText(String text) throws IOException {
+    byte[] b = new byte[8 + text.length()];
+
+    b[0] = (byte) ClientCutText;
+    b[4] = (byte) ((text.length() >> 24) & 0xff);
+    b[5] = (byte) ((text.length() >> 16) & 0xff);
+    b[6] = (byte) ((text.length() >> 8) & 0xff);
+    b[7] = (byte) (text.length() & 0xff);
+
+    System.arraycopy(text.getBytes(), 0, b, 8, text.length());
+
+    os.write(b);
+  }
+
+
+  //
+  // A buffer for putting pointer and keyboard events before being sent.  This
+  // is to ensure that multiple RFB events generated from a single Java Event 
+  // will all be sent in a single network packet.  The maximum possible
+  // length is 4 modifier down events, a single key event followed by 4
+  // modifier up events i.e. 9 key events or 72 bytes.
+  //
+
+  byte[] eventBuf = new byte[72];
+  int eventBufLen;
+
+
+  // Useful shortcuts for modifier masks.
+
+  final static int CTRL_MASK  = InputEvent.CTRL_MASK;
+  final static int SHIFT_MASK = InputEvent.SHIFT_MASK;
+  final static int META_MASK  = InputEvent.META_MASK;
+  final static int ALT_MASK   = InputEvent.ALT_MASK;
+
+
+  //
+  // Write a pointer event message.  We may need to send modifier key events
+  // around it to set the correct modifier state.
+  //
+
+  int pointerMask = 0;
+
+  void writePointerEvent(MouseEvent evt) throws IOException {
+    int modifiers = evt.getModifiers();
+
+    int mask2 = 2;
+    int mask3 = 4;
+    if (viewer.options.reverseMouseButtons2And3) {
+      mask2 = 4;
+      mask3 = 2;
+    }
+
+    // Note: For some reason, AWT does not set BUTTON1_MASK on left
+    // button presses. Here we think that it was the left button if
+    // modifiers do not include BUTTON2_MASK or BUTTON3_MASK.
+
+    if (evt.getID() == MouseEvent.MOUSE_PRESSED) {
+      if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
+        pointerMask = mask2;
+        modifiers &= ~ALT_MASK;
+      } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
+        pointerMask = mask3;
+        modifiers &= ~META_MASK;
+      } else {
+        pointerMask = 1;
+      }
+    } else if (evt.getID() == MouseEvent.MOUSE_RELEASED) {
+      pointerMask = 0;
+      if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
+        modifiers &= ~ALT_MASK;
+      } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
+        modifiers &= ~META_MASK;
+      }
+    }
+
+    eventBufLen = 0;
+    writeModifierKeyEvents(modifiers);
+
+    int x = evt.getX();
+    int y = evt.getY();
+
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+
+    eventBuf[eventBufLen++] = (byte) PointerEvent;
+    eventBuf[eventBufLen++] = (byte) pointerMask;
+    eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
+    eventBuf[eventBufLen++] = (byte) (x & 0xff);
+    eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
+    eventBuf[eventBufLen++] = (byte) (y & 0xff);
+
+    //
+    // Always release all modifiers after an "up" event
+    //
+
+    if (pointerMask == 0) {
+      writeModifierKeyEvents(0);
+    }
+
+    os.write(eventBuf, 0, eventBufLen);
+  }
+
+
+  //
+  // Write a key event message.  We may need to send modifier key events
+  // around it to set the correct modifier state.  Also we need to translate
+  // from the Java key values to the X keysym values used by the RFB protocol.
+  //
+
+  void writeKeyEvent(KeyEvent evt) throws IOException {
+
+    int keyChar = evt.getKeyChar();
+
+    //
+    // Ignore event if only modifiers were pressed.
+    //
+
+    // Some JVMs return 0 instead of CHAR_UNDEFINED in getKeyChar().
+    if (keyChar == 0)
+      keyChar = KeyEvent.CHAR_UNDEFINED;
+
+    if (keyChar == KeyEvent.CHAR_UNDEFINED) {
+      int code = evt.getKeyCode();
+      if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT ||
+          code == KeyEvent.VK_META || code == KeyEvent.VK_ALT)
+        return;
+    }
+
+    //
+    // Key press or key release?
+    //
+
+    boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
+
+    int key;
+    if (evt.isActionKey()) {
+
+      //
+      // An action key should be one of the following.
+      // If not then just ignore the event.
+      //
+
+      switch(evt.getKeyCode()) {
+      case KeyEvent.VK_HOME:      key = 0xff50; break;
+      case KeyEvent.VK_LEFT:      key = 0xff51; break;
+      case KeyEvent.VK_UP:        key = 0xff52; break;
+      case KeyEvent.VK_RIGHT:     key = 0xff53; break;
+      case KeyEvent.VK_DOWN:      key = 0xff54; break;
+      case KeyEvent.VK_PAGE_UP:   key = 0xff55; break;
+      case KeyEvent.VK_PAGE_DOWN: key = 0xff56; break;
+      case KeyEvent.VK_END:       key = 0xff57; break;
+      case KeyEvent.VK_INSERT:    key = 0xff63; break;
+      case KeyEvent.VK_F1:        key = 0xffbe; break;
+      case KeyEvent.VK_F2:        key = 0xffbf; break;
+      case KeyEvent.VK_F3:        key = 0xffc0; break;
+      case KeyEvent.VK_F4:        key = 0xffc1; break;
+      case KeyEvent.VK_F5:        key = 0xffc2; break;
+      case KeyEvent.VK_F6:        key = 0xffc3; break;
+      case KeyEvent.VK_F7:        key = 0xffc4; break;
+      case KeyEvent.VK_F8:        key = 0xffc5; break;
+      case KeyEvent.VK_F9:        key = 0xffc6; break;
+      case KeyEvent.VK_F10:       key = 0xffc7; break;
+      case KeyEvent.VK_F11:       key = 0xffc8; break;
+      case KeyEvent.VK_F12:       key = 0xffc9; break;
+      default:
+        return;
+      }
+
+    } else {
+
+      //
+      // A "normal" key press.  Ordinary ASCII characters go straight through.
+      // For CTRL-<letter>, CTRL is sent separately so just send <letter>.
+      // Backspace, tab, return, escape and delete have special keysyms.
+      // Anything else we ignore.
+      //
+
+      key = keyChar;
+
+      if (key < 0x20) {
+        if (evt.isControlDown()) {
+          key += 0x60;
+        } else {
+          switch(key) {
+          case KeyEvent.VK_BACK_SPACE: key = 0xff08; break;
+          case KeyEvent.VK_TAB:        key = 0xff09; break;
+          case KeyEvent.VK_ENTER:      key = 0xff0d; break;
+          case KeyEvent.VK_ESCAPE:     key = 0xff1b; break;
+          }
+        }
+      } else if (key == 0x7f) {
+	// Delete
+	key = 0xffff;
+      } else if (key > 0xff) {
+	// JDK1.1 on X incorrectly passes some keysyms straight through,
+	// so we do too.  JDK1.1.4 seems to have fixed this.
+	// The keysyms passed are 0xff00 .. XK_BackSpace .. XK_Delete
+	// Also, we pass through foreign currency keysyms (0x20a0..0x20af).
+	if ((key < 0xff00 || key > 0xffff) &&
+	    !(key >= 0x20a0 && key <= 0x20af))
+	  return;
+      }
+    }
+
+    // Fake keyPresses for keys that only generates keyRelease events
+    if ((key == 0xe5) || (key == 0xc5) || // XK_aring / XK_Aring
+	(key == 0xe4) || (key == 0xc4) || // XK_adiaeresis / XK_Adiaeresis
+	(key == 0xf6) || (key == 0xd6) || // XK_odiaeresis / XK_Odiaeresis
+	(key == 0xa7) || (key == 0xbd) || // XK_section / XK_onehalf
+	(key == 0xa3)) {                  // XK_sterling
+      // Make sure we do not send keypress events twice on platforms
+      // with correct JVMs (those that actually report KeyPress for all
+      // keys)	
+      if (down)
+	brokenKeyPressed = true;
+
+      if (!down && !brokenKeyPressed) {
+	// We've got a release event for this key, but haven't received
+        // a press. Fake it. 
+	eventBufLen = 0;
+	writeModifierKeyEvents(evt.getModifiers());
+	writeKeyEvent(key, true);
+	os.write(eventBuf, 0, eventBufLen);
+      }
+
+      if (!down)
+	brokenKeyPressed = false;  
+    }
+
+    eventBufLen = 0;
+    writeModifierKeyEvents(evt.getModifiers());
+    writeKeyEvent(key, down);
+
+    // Always release all modifiers after an "up" event
+    if (!down)
+      writeModifierKeyEvents(0);
+
+    os.write(eventBuf, 0, eventBufLen);
+  }
+
+
+  //
+  // Add a raw key event with the given X keysym to eventBuf.
+  //
+
+  void writeKeyEvent(int keysym, boolean down) {
+    eventBuf[eventBufLen++] = (byte) KeyboardEvent;
+    eventBuf[eventBufLen++] = (byte) (down ? 1 : 0);
+    eventBuf[eventBufLen++] = (byte) 0;
+    eventBuf[eventBufLen++] = (byte) 0;
+    eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff);
+    eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff);
+    eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff);
+    eventBuf[eventBufLen++] = (byte) (keysym & 0xff);
+  }
+
+
+  //
+  // Write key events to set the correct modifier state.
+  //
+
+  int oldModifiers = 0;
+
+  void writeModifierKeyEvents(int newModifiers) {
+    if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
+      writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
+
+    if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK))
+      writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0);
+
+    if ((newModifiers & META_MASK) != (oldModifiers & META_MASK))
+      writeKeyEvent(0xffe7, (newModifiers & META_MASK) != 0);
+
+    if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK))
+      writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0);
+
+    oldModifiers = newModifiers;
+  }
+
+
+  //
+  // Compress and write the data into the recorded session file. This
+  // method assumes the recording is on (rec != null).
+  //
+
+  void recordCompressedData(byte[] data, int off, int len) throws IOException {
+    Deflater deflater = new Deflater();
+    deflater.setInput(data, off, len);
+    int bufSize = len + len / 100 + 12;
+    byte[] buf = new byte[bufSize];
+    deflater.finish();
+    int compressedSize = deflater.deflate(buf);
+    recordCompactLen(compressedSize);
+    rec.write(buf, 0, compressedSize);
+  }
+
+  void recordCompressedData(byte[] data) throws IOException {
+    recordCompressedData(data, 0, data.length);
+  }
+
+  //
+  // Write an integer in compact representation (1..3 bytes) into the
+  // recorded session file. This method assumes the recording is on
+  // (rec != null).
+  //
+
+  void recordCompactLen(int len) throws IOException {
+    byte[] buf = new byte[3];
+    int bytes = 0;
+    buf[bytes++] = (byte)(len & 0x7F);
+    if (len > 0x7F) {
+      buf[bytes-1] |= 0x80;
+      buf[bytes++] = (byte)(len >> 7 & 0x7F);
+      if (len > 0x3FFF) {
+	buf[bytes-1] |= 0x80;
+	buf[bytes++] = (byte)(len >> 14 & 0xFF);
+      }
+    }
+    rec.write(buf, 0, bytes);
+  }
+
+  public void startTiming() {
+    timing = true;
+
+    // Carry over up to 1s worth of previous rate for smoothing.
+
+    if (timeWaitedIn100us > 10000) {
+      timedKbits = timedKbits * 10000 / timeWaitedIn100us;
+      timeWaitedIn100us = 10000;
+    }
+  }
+
+  public void stopTiming() {
+    timing = false; 
+    if (timeWaitedIn100us < timedKbits/2)
+      timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
+  }
+
+  public long kbitsPerSecond() {
+    return timedKbits * 10000 / timeWaitedIn100us;
+  }
+
+  public long timeWaited() {
+    return timeWaitedIn100us;
+  }
+
+  public void readFully(byte b[]) throws IOException {
+    readFully(b, 0, b.length);
+  }
+
+  public void readFully(byte b[], int off, int len) throws IOException {
+    long before = 0;
+    if (timing)
+      before = System.currentTimeMillis();
+
+    is.readFully(b, off, len);
+
+    if (timing) {
+      long after = System.currentTimeMillis();
+      long newTimeWaited = (after - before) * 10;
+      int newKbits = len * 8 / 1000;
+
+      // limit rate to between 10kbit/s and 40Mbit/s
+
+      if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
+      if (newTimeWaited < newKbits/4)    newTimeWaited = newKbits/4;
+
+      timeWaitedIn100us += newTimeWaited;
+      timedKbits += newKbits;
+    }
+  }
+
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/SIPBTrustManager.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/SIPBTrustManager.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/SIPBTrustManager.java	(revision 571)
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2006 Perry Nguyen <pfnguyen@hanhuy.com>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class SIPBTrustManager implements X509TrustManager {
+    private X509TrustManager trustManager;
+    private final static char[] KEY_STORE_PASSWORD =
+        { 'f', 'o', 'o', 'b', 'a', 'r' };
+    private final static String KEY_STORE_RESOURCE =
+        "trust.store";
+
+    private KeyStore loadKeyStore() throws Exception {
+        InputStream in = getClass().getClassLoader().getResourceAsStream(
+                KEY_STORE_RESOURCE);
+        KeyStore ks = null;
+        try {
+            if (in == null) {
+                //log.severe("Unable to open KeyStore");
+                throw new NullPointerException();
+            }
+            ks = KeyStore.getInstance(KeyStore.getDefaultType());
+            ks.load(in, KEY_STORE_PASSWORD);
+	    /*if (log.isLoggable(Level.FINEST)) {
+                for (Enumeration<String> aliases = ks.aliases();
+                aliases.hasMoreElements();) {
+                    String alias = aliases.nextElement();
+                    log.finest("ALIAS: " + alias);
+                }
+		}*/
+        } catch (NoSuchAlgorithmException e) {
+            throwError(e);
+        } catch (CertificateException e) {
+            throwError(e);
+        } catch (IOException e) {
+            throwError(e);
+        } catch (KeyStoreException e) {
+            throwError(e);
+        } finally {
+            try {
+                if (in != null)
+                    in.close();
+            }
+            catch (IOException e) { } // ignore
+        }
+        return ks;
+    }
+    private void createTrustManager() {
+	try {
+	    try {
+		KeyStore keystore = loadKeyStore();
+		TrustManagerFactory factory = TrustManagerFactory.getInstance(
+									      TrustManagerFactory.getDefaultAlgorithm());
+		factory.init(keystore);
+		TrustManager[] trustManagers = factory.getTrustManagers();
+		if (trustManagers.length == 0)
+		    throw new IllegalStateException("No trust manager found");
+		setTrustManager((X509TrustManager) trustManagers[0]);
+	    } catch (NoSuchAlgorithmException e) {
+		throwError(e);
+	    } catch (KeyStoreException e) {
+		throwError(e);
+	    }
+	} catch (Exception e) {
+	    e.printStackTrace();
+	}
+    }
+    private void throwError(Exception e) throws Exception {
+        //HttpClientError error = new HttpClientError(e.getMessage());
+        //error.initCause(e);
+        throw e;
+    }
+    public X509TrustManager getTrustManager() {
+        if (trustManager == null)
+            createTrustManager();
+        return trustManager;
+    }
+
+    public void setTrustManager(X509TrustManager trustManager) {
+        this.trustManager = trustManager;
+    }
+
+    public void checkClientTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+        getTrustManager().checkClientTrusted(chain, authType);
+    }
+
+    public void checkServerTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+        getTrustManager().checkServerTrusted(chain, authType);
+
+    }
+
+    public X509Certificate[] getAcceptedIssuers() {
+        return getTrustManager().getAcceptedIssuers();
+    }
+
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/SessionRecorder.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/SessionRecorder.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/SessionRecorder.java	(revision 571)
@@ -0,0 +1,193 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// SessionRecorder is a class to write FBS (FrameBuffer Stream) files.
+// FBS files are used to save RFB sessions for later playback.
+//
+
+import java.io.*;
+
+class SessionRecorder {
+
+  protected FileOutputStream f;
+  protected DataOutputStream df;
+  protected long startTime, lastTimeOffset;
+
+  protected byte[] buffer;
+  protected int bufferSize;
+  protected int bufferBytes;
+
+  public SessionRecorder(String name, int bufsize) throws IOException {
+    f = new FileOutputStream(name);
+    df = new DataOutputStream(f);
+    startTime = System.currentTimeMillis();
+    lastTimeOffset = 0;
+    
+    bufferSize = bufsize;
+    bufferBytes = 0;
+    buffer = new byte[bufferSize];
+  }
+
+  public SessionRecorder(String name) throws IOException {
+    this(name, 65536);
+  }
+
+  //
+  // Close the file, free resources.
+  //
+
+  public void close() throws IOException {
+    try {
+      flush();
+    } catch (IOException e) {
+    }
+
+    df = null;
+    f.close();
+    f = null;
+    buffer = null;
+  }
+
+  //
+  // Write the FBS file header as defined in the rfbproxy utility.
+  //
+
+  public void writeHeader() throws IOException {
+    df.write("FBS 001.000\n".getBytes());
+  }
+
+  //
+  // Write one byte.
+  //
+
+  public void writeByte(int b) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes++] = (byte)b;
+  }
+
+  //
+  // Write 16-bit value, big-endian.
+  //
+
+  public void writeShortBE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes++] = (byte)(v >> 8);
+    buffer[bufferBytes++] = (byte)v;
+  }
+
+  //
+  // Write 32-bit value, big-endian.
+  //
+
+  public void writeIntBE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes]     = (byte)(v >> 24);
+    buffer[bufferBytes + 1] = (byte)(v >> 16);
+    buffer[bufferBytes + 2] = (byte)(v >> 8);
+    buffer[bufferBytes + 3] = (byte)v;
+    bufferBytes += 4;
+  }
+
+  //
+  // Write 16-bit value, little-endian.
+  //
+
+  public void writeShortLE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes++] = (byte)v;
+    buffer[bufferBytes++] = (byte)(v >> 8);
+  }
+
+  //
+  // Write 32-bit value, little-endian.
+  //
+
+  public void writeIntLE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes]     = (byte)v;
+    buffer[bufferBytes + 1] = (byte)(v >> 8);
+    buffer[bufferBytes + 2] = (byte)(v >> 16);
+    buffer[bufferBytes + 3] = (byte)(v >> 24);
+    bufferBytes += 4;
+  }
+
+  //
+  // Write byte arrays.
+  //
+
+  public void write(byte b[], int off, int len) throws IOException {
+    prepareWriting();
+    while (len > 0) {
+      if (bufferBytes > bufferSize - 4)
+	flush(false);
+
+      int partLen;
+      if (bufferBytes + len > bufferSize) {
+	partLen = bufferSize - bufferBytes;
+      } else {
+	partLen = len;
+      }
+      System.arraycopy(b, off, buffer, bufferBytes, partLen);
+      bufferBytes += partLen;
+      off += partLen;
+      len -= partLen;
+    }
+  }
+
+  public void write(byte b[]) throws IOException {
+    write(b, 0, b.length);
+  }
+
+  //
+  // Flush the output. This method saves buffered data in the
+  // underlying file object adding data sizes and timestamps. If the
+  // updateTimeOffset is set to false, then the current time offset
+  // will not be changed for next write operation.
+  //
+
+  public void flush(boolean updateTimeOffset) throws IOException {
+    if (bufferBytes > 0) {
+      df.writeInt(bufferBytes);
+      df.write(buffer, 0, (bufferBytes + 3) & 0x7FFFFFFC);
+      df.writeInt((int)lastTimeOffset);
+      bufferBytes = 0;
+      if (updateTimeOffset)
+	lastTimeOffset = -1;
+    }
+  }
+
+  public void flush() throws IOException {
+    flush(true);
+  }
+
+  //
+  // Before writing any data, remember time offset and flush the
+  // buffer before it becomes full.
+  //
+
+  protected void prepareWriting() throws IOException {
+    if (lastTimeOffset == -1)
+      lastTimeOffset = System.currentTimeMillis() - startTime;
+    if (bufferBytes > bufferSize - 4)
+      flush(false);
+  }
+
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/SocketFactory.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/SocketFactory.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/SocketFactory.java	(revision 571)
@@ -0,0 +1,36 @@
+//
+//  Copyright (C) 2002 HorizonLive.com, Inc.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// SocketFactory.java describes an interface used to substitute the
+// standard Socket class by its alternative implementations.
+//
+
+import java.applet.*;
+import java.net.*;
+import java.io.*;
+
+public interface SocketFactory {
+
+  public Socket createSocket(String host, int port, Applet applet)
+    throws IOException;
+
+  public Socket createSocket(String host, int port, String[] args)
+    throws IOException;
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/SocketWrapper.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/SocketWrapper.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/SocketWrapper.java	(revision 571)
@@ -0,0 +1,262 @@
+/*
+ * Written by Dawid Kurzyniec and released to the public domain, as explained
+ * at http://creativecommons.org/licenses/publicdomain
+ */
+
+//package edu.emory.mathcs.util.net;
+
+import java.io.*;
+import java.net.*;
+import java.nio.channels.*;
+
+/**
+ * Wrapper for sockets which enables to add functionality in subclasses
+ * on top of existing, connected sockets. It is useful when direct subclassing
+ * of delegate socket class is not possible, e.g. if the delegate socket is
+ * created by a library. Possible usage example is socket factory chaining.
+ * This class delegates all socket-related requests to the wrapped delegate,
+ * as of JDK 1.4.
+ *
+ * @author Dawid Kurzyniec
+ * @version 1.4
+ */
+public abstract class SocketWrapper extends Socket {
+
+    /**
+     * the wrapped delegate socket.
+     */
+    protected final Socket delegate;
+
+    /**
+     * Creates new socket wrapper for a given socket. The delegate
+     * must be connected and bound and it must not be closed.
+     * @param delegate the delegate socket to wrap
+     * @throws SocketException if the delegate socket is closed, not bound,
+     *                         or not connected
+     */
+    protected SocketWrapper(Socket delegate) throws SocketException {
+        super(new WrappingSocketImpl(delegate));
+        this.delegate = delegate;
+	System.out.println("Creating SocketWrapper $Rev$");
+    }
+
+    public SocketChannel getChannel() {
+        return delegate.getChannel();
+    }
+
+    /**
+     * Returns true, indicating that the socket is bound.
+     *
+     * @return true
+     */
+    public boolean isBound() {
+        return true;
+    }
+
+    public boolean isClosed() {
+        return super.isClosed() || delegate.isClosed();
+    }
+
+    /**
+     * Returns true, indicating that the socket is connected.
+     *
+     * @return true
+     */
+    public boolean isConnected() {
+        return true;
+    }
+
+    public boolean isInputShutdown() {
+        return super.isInputShutdown() || delegate.isInputShutdown();
+    }
+
+    public boolean isOutputShutdown() {
+        return super.isInputShutdown() || delegate.isOutputShutdown();
+    }
+
+    private static class WrappingSocketImpl extends SocketImpl {
+        private final Socket delegate;
+        WrappingSocketImpl(Socket delegate) throws SocketException {
+            if (delegate == null) {
+                throw new NullPointerException();
+            }
+            if (delegate.isClosed()) {
+                throw new SocketException("Delegate server socket is closed");
+            }
+            if (!(delegate.isBound())) {
+                throw new SocketException("Delegate server socket is not bound");
+            }
+            if (!(delegate.isConnected())) {
+                throw new SocketException("Delegate server socket is not connected");
+            }
+            this.delegate = delegate;
+        }
+
+        protected void create(boolean stream) {}
+
+        protected void connect(String host, int port) {
+            // delegate is always connected, thus this method is never called
+            throw new UnsupportedOperationException();
+        }
+
+        protected void connect(InetAddress address, int port) {
+            // delegate is always connected, thus this method is never called
+            throw new UnsupportedOperationException();
+        }
+
+        protected void connect(SocketAddress address, int timeout) {
+            // delegate is always connected, thus this method is never called
+            throw new UnsupportedOperationException();
+        }
+
+        protected void bind(InetAddress host, int port) {
+            // delegate is always bound, thus this method is never called
+            throw new UnsupportedOperationException();
+        }
+
+        protected void listen(int backlog) {
+            // this wrapper is never used by a ServerSocket
+            throw new UnsupportedOperationException();
+        }
+
+        protected void accept(SocketImpl s) {
+            // this wrapper is never used by a ServerSocket
+            throw new UnsupportedOperationException();
+        }
+
+        protected InputStream getInputStream() throws IOException {
+            return delegate.getInputStream();
+        }
+
+        protected OutputStream getOutputStream() throws IOException {
+            return delegate.getOutputStream();
+        }
+
+        protected int available() throws IOException {
+            return getInputStream().available();
+        }
+
+        protected void close() throws IOException {
+	    System.out.println("Calling delegate.close");
+            delegate.close();
+        }
+
+        protected void shutdownInput() throws IOException {
+            delegate.shutdownInput();
+        }
+
+        protected void shutdownOutput() throws IOException {
+            delegate.shutdownOutput();
+        }
+
+        protected FileDescriptor getFileDescriptor() {
+            // this wrapper is never used by a ServerSocket
+            throw new UnsupportedOperationException();
+        }
+
+        protected InetAddress getInetAddress() {
+            return delegate.getInetAddress();
+        }
+
+        protected int getPort() {
+            return delegate.getPort();
+        }
+
+        protected boolean supportsUrgentData() {
+            return false; // must be overridden in sub-class
+        }
+
+        protected void sendUrgentData (int data) throws IOException {
+            delegate.sendUrgentData(data);
+        }
+
+        protected int getLocalPort() {
+            return delegate.getLocalPort();
+        }
+
+        public Object getOption(int optID) throws SocketException {
+            switch (optID) {
+                case SocketOptions.IP_TOS:
+                    return new Integer(delegate.getTrafficClass());
+                case SocketOptions.SO_BINDADDR:
+                    return delegate.getLocalAddress();
+                case SocketOptions.SO_KEEPALIVE:
+                    return Boolean.valueOf(delegate.getKeepAlive());
+                case SocketOptions.SO_LINGER:
+                    return new Integer(delegate.getSoLinger());
+                case SocketOptions.SO_OOBINLINE:
+                    return Boolean.valueOf(delegate.getOOBInline());
+                case SocketOptions.SO_RCVBUF:
+                    return new Integer(delegate.getReceiveBufferSize());
+                case SocketOptions.SO_REUSEADDR:
+                    return Boolean.valueOf(delegate.getReuseAddress());
+                case SocketOptions.SO_SNDBUF:
+                    return new Integer(delegate.getSendBufferSize());
+                case SocketOptions.SO_TIMEOUT:
+                    return new Integer(delegate.getSoTimeout());
+                case SocketOptions.TCP_NODELAY:
+                    return Boolean.valueOf(delegate.getTcpNoDelay());
+                case SocketOptions.SO_BROADCAST:
+                default:
+                    throw new IllegalArgumentException("Unsupported option type");
+            }
+        }
+
+        public void setOption(int optID, Object value) throws SocketException {
+            switch (optID) {
+                case SocketOptions.SO_BINDADDR:
+                    throw new IllegalArgumentException("Socket is bound");
+                case SocketOptions.SO_KEEPALIVE:
+                    delegate.setKeepAlive(((Boolean)value).booleanValue());
+                    break;
+                case SocketOptions.SO_LINGER:
+                    if (value instanceof Boolean) {
+                        delegate.setSoLinger(((Boolean)value).booleanValue(), 0);
+                    }
+                    else {
+                        delegate.setSoLinger(true, ((Integer)value).intValue());
+                    }
+                    break;
+                case SocketOptions.SO_OOBINLINE:
+                    delegate.setOOBInline(((Boolean)value).booleanValue());
+                    break;
+                case SocketOptions.SO_RCVBUF:
+                    delegate.setReceiveBufferSize(((Integer)value).intValue());
+                    break;
+                case SocketOptions.SO_REUSEADDR:
+                    delegate.setReuseAddress(((Boolean)value).booleanValue());
+                    break;
+                case SocketOptions.SO_SNDBUF:
+                    delegate.setSendBufferSize(((Integer)value).intValue());
+                    break;
+                case SocketOptions.SO_TIMEOUT:
+                    delegate.setSoTimeout(((Integer)value).intValue());
+                    break;
+                case SocketOptions.TCP_NODELAY:
+                    delegate.setTcpNoDelay(((Boolean)value).booleanValue());
+                    break;
+                case SocketOptions.SO_BROADCAST:
+                default:
+                    throw new IllegalArgumentException("Unsupported option type");
+            }
+        }
+    }
+
+    public void close() throws IOException {
+	System.out.println("Calling SocketWrapper.delegate.close");
+	delegate.close();
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SocketWrapper)) return false;
+        SocketWrapper that = (SocketWrapper)obj;
+        return this.delegate.equals(that.delegate);
+    }
+
+    public int hashCode() {
+        return delegate.hashCode() ^ 0x01010101;
+    }
+    public String toString() {
+	return "<SocketWrapper " + super.toString() + "(delegating to " + delegate.toString() +  ")" + ">";
+    }
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocket.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocket.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocket.java	(revision 571)
@@ -0,0 +1,61 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//  Copyright 2007 MIT Student Information Processing Board
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// VNCProxySocket.java together with VNCProxySocketFactory.java
+// implement an alternate way to connect to VNC servers via one or two
+// VNCProxy proxies supporting the VNCProxy VNCCONNECT method.
+//
+
+import java.net.*;
+import java.io.*;
+
+class VNCProxyConnectSocket extends Socket {
+
+  public VNCProxyConnectSocket(String host, int port,
+                               String vmname, String authtoken)
+    throws IOException {
+
+    // Connect to the specified HTTP proxy
+    super(host, port);
+
+    // Send the CONNECT request
+    getOutputStream().write(("CONNECTVNC " + vmname +
+                             " VNCProxy/1.0\r\nAuth-token: " + authtoken +
+                             "\r\n\r\n").getBytes());
+
+    // Read the first line of the response
+    DataInputStream is = new DataInputStream(getInputStream());
+    String str = is.readLine();
+
+    // Check the HTTP error code -- it should be "200" on success
+    if (!str.startsWith("VNCProxy/1.0 200 ")) {
+      if (str.startsWith("VNCProxy/1.0 "))
+        str = str.substring(13);
+      throw new IOException("Proxy reports \"" + str + "\"");
+    }
+
+    // Success -- skip remaining HTTP headers
+    do {
+      str = is.readLine();
+    } while (str.length() != 0);
+  }
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocketFactory.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocketFactory.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocketFactory.java	(revision 571)
@@ -0,0 +1,98 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//  Copyright 2007 MIT Student Information Processing Board
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// VNCProxyConnectSocketFactory.java together with VNCProxyConnectSocket.java
+// implement an alternate way to connect to VNC servers via one or two
+// VNCProxy proxies supporting the VNCProxy CONNECT method.
+//
+
+import java.applet.*;
+import java.net.*;
+import javax.net.ssl.*;
+import java.io.*;
+
+class VNCProxyConnectSocketFactory implements SocketFactory {
+
+    SSLSocketFactory factory;
+    
+    public VNCProxyConnectSocketFactory() {
+	try {
+	    SSLContext c = SSLContext.getInstance("SSL");
+	    c.init(null,
+		   new TrustManager[] { new SIPBTrustManager() },
+		   null);
+	    factory =
+		(SSLSocketFactory)c.getSocketFactory();
+	} catch (Exception e) {
+	    e.printStackTrace();
+	}
+    }
+
+  public Socket createSocket(String host, int port, Applet applet)
+    throws IOException {
+
+    return createSocket(host, port,
+			applet.getParameter("VMNAME"),
+			applet.getParameter("AUTHTOKEN"));
+  }
+
+  public Socket createSocket(String host, int port, String[] args)
+    throws IOException {
+
+    return createSocket(host, port,
+			readArg(args, "VMNAME"),
+			readArg(args, "AUTHTOKEN"));
+  }
+
+  public Socket createSocket(String host, int port,
+			     String vmname, String authtoken)
+    throws IOException {
+
+    if (vmname == null || authtoken == null) {
+      System.out.println("Incomplete parameter list for VNCProxyConnectSocket");
+      return new Socket(host, port);
+    }
+
+    System.out.println("VNCProxy CONNECT via proxy " + host +
+		       " port " + port + " to vm " + vmname);
+    SSLSocket ssls = (SSLSocket)factory.createSocket(host, port);
+    ssls.startHandshake();
+    VNCProxyConnectSocketWrapper s =
+      new VNCProxyConnectSocketWrapper(ssls, vmname, authtoken);
+
+    return (Socket)s;
+  }
+
+  private String readArg(String[] args, String name) {
+
+    for (int i = 0; i < args.length; i += 2) {
+      if (args[i].equalsIgnoreCase(name)) {
+	try {
+	  return args[i+1];
+	} catch (Exception e) {
+	  return null;
+	}
+      }
+    }
+    return null;
+  }
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocketWrapper.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocketWrapper.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/VNCProxyConnectSocketWrapper.java	(revision 571)
@@ -0,0 +1,60 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//  Copyright 2007 MIT Student Information Processing Board
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// VNCProxySocket.java together with VNCProxySocketFactory.java
+// implement an alternate way to connect to VNC servers via one or two
+// VNCProxy proxies supporting the VNCProxy VNCCONNECT method.
+//
+
+import java.net.*;
+import java.io.*;
+
+class VNCProxyConnectSocketWrapper extends SocketWrapper {
+
+  public VNCProxyConnectSocketWrapper(Socket sock,
+                               String vmname, String authtoken)
+    throws IOException {
+
+    super(sock);
+
+    // Send the CONNECT request
+    getOutputStream().write(("CONNECTVNC " + vmname +
+                             " VNCProxy/1.0\r\nAuth-token: " + authtoken +
+                             "\r\n\r\n").getBytes());
+
+    // Read the first line of the response
+    DataInputStream is = new DataInputStream(getInputStream());
+    String str = is.readLine();
+
+    // Check the HTTP error code -- it should be "200" on success
+    if (!str.startsWith("VNCProxy/1.0 200 ")) {
+      if (str.startsWith("VNCProxy/1.0 "))
+        str = str.substring(13);
+      throw new IOException("Proxy reports \"" + str + "\"");
+    }
+
+    // Success -- skip remaining HTTP headers
+    do {
+      str = is.readLine();
+    } while (str.length() != 0);
+  }
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/VncCanvas.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/VncCanvas.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/VncCanvas.java	(revision 571)
@@ -0,0 +1,1807 @@
+//
+//  Copyright (C) 2004 Horizon Wimba.  All Rights Reserved.
+//  Copyright (C) 2001-2003 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2001,2002 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.io.*;
+import java.lang.*;
+import java.util.zip.*;
+
+
+//
+// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
+//
+
+class VncCanvas extends Canvas
+  implements KeyListener, MouseListener, MouseMotionListener {
+
+  VncViewer viewer;
+  RfbProto rfb;
+  ColorModel cm8, cm24;
+  Color[] colors;
+  int bytesPixel;
+
+  int maxWidth = 0, maxHeight = 0;
+  int scalingFactor;
+  int scaledWidth, scaledHeight;
+
+  Image memImage;
+  Graphics memGraphics;
+
+  Image rawPixelsImage;
+  MemoryImageSource pixelsSource;
+  byte[] pixels8;
+  int[] pixels24;
+
+  // ZRLE encoder's data.
+  byte[] zrleBuf;
+  int zrleBufLen = 0;
+  byte[] zrleTilePixels8;
+  int[] zrleTilePixels24;
+  ZlibInStream zrleInStream;
+  boolean zrleRecWarningShown = false;
+
+  // Zlib encoder's data.
+  byte[] zlibBuf;
+  int zlibBufLen = 0;
+  Inflater zlibInflater;
+
+  // Tight encoder's data.
+  final static int tightZlibBufferSize = 512;
+  Inflater[] tightInflaters;
+
+  // Since JPEG images are loaded asynchronously, we have to remember
+  // their position in the framebuffer. Also, this jpegRect object is
+  // used for synchronization between the rfbThread and a JVM's thread
+  // which decodes and loads JPEG images.
+  Rectangle jpegRect;
+
+  // True if we process keyboard and mouse events.
+  boolean inputEnabled;
+  int extraModifiers = 0;
+
+  //
+  // The constructors.
+  //
+
+  public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
+    throws IOException {
+
+    viewer = v;
+    maxWidth = maxWidth_;
+    maxHeight = maxHeight_;
+
+    rfb = viewer.rfb;
+    scalingFactor = viewer.options.scalingFactor;
+
+    tightInflaters = new Inflater[4];
+
+    cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
+    cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
+
+    colors = new Color[256];
+    for (int i = 0; i < 256; i++)
+      colors[i] = new Color(cm8.getRGB(i));
+
+    setPixelFormat();
+
+    inputEnabled = false;
+    if (!viewer.options.viewOnly)
+      enableInput(true);
+
+    // Keyboard listener is enabled even in view-only mode, to catch
+    // 'r' or 'R' key presses used to request screen update.
+    addKeyListener(this);
+  }
+
+  public VncCanvas(VncViewer v) throws IOException {
+    this(v, 0, 0);
+  }
+
+  //
+  // Callback methods to determine geometry of our Component.
+  //
+
+  public Dimension getPreferredSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  public Dimension getMinimumSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  public Dimension getMaximumSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  //
+  // All painting is performed here.
+  //
+
+  public void update(Graphics g) {
+    paint(g);
+  }
+
+  public void paint(Graphics g) {
+    synchronized(memImage) {
+      if (rfb.framebufferWidth == scaledWidth) {
+        g.drawImage(memImage, 0, 0, null);
+      } else {
+        paintScaledFrameBuffer(g);
+      }
+    }
+    if (showSoftCursor) {
+      int x0 = cursorX - hotX, y0 = cursorY - hotY;
+      Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
+      if (r.intersects(g.getClipBounds())) {
+	g.drawImage(softCursor, x0, y0, null);
+      }
+    }
+  }
+
+  public void paintScaledFrameBuffer(Graphics g) {
+    g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
+  }
+
+  //
+  // Override the ImageObserver interface method to handle drawing of
+  // JPEG-encoded data.
+  //
+
+  public boolean imageUpdate(Image img, int infoflags,
+                             int x, int y, int width, int height) {
+    if ((infoflags & (ALLBITS | ABORT)) == 0) {
+      return true;		// We need more image data.
+    } else {
+      // If the whole image is available, draw it now.
+      if ((infoflags & ALLBITS) != 0) {
+	if (jpegRect != null) {
+	  synchronized(jpegRect) {
+	    memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
+	    scheduleRepaint(jpegRect.x, jpegRect.y,
+			    jpegRect.width, jpegRect.height);
+	    jpegRect.notify();
+	  }
+	}
+      }
+      return false;		// All image data was processed.
+    }
+  }
+
+  //
+  // Start/stop receiving mouse events. Keyboard events are received
+  // even in view-only mode, because we want to map the 'r' key to the
+  // screen refreshing function.
+  //
+
+  public synchronized void enableInput(boolean enable) {
+    if (enable && !inputEnabled) {
+      inputEnabled = true;
+      addMouseListener(this);
+      addMouseMotionListener(this);
+      if (viewer.showControls) {
+	viewer.buttonPanel.enableRemoteAccessControls(true);
+      }
+      createSoftCursor();	// scaled cursor
+    } else if (!enable && inputEnabled) {
+      inputEnabled = false;
+      removeMouseListener(this);
+      removeMouseMotionListener(this);
+      if (viewer.showControls) {
+	viewer.buttonPanel.enableRemoteAccessControls(false);
+      }
+      createSoftCursor();	// non-scaled cursor
+    }
+  }
+
+  public void setPixelFormat() throws IOException {
+    if (viewer.options.eightBitColors) {
+      rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
+      bytesPixel = 1;
+    } else {
+      rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
+      bytesPixel = 4;
+    }
+    updateFramebufferSize();
+  }
+
+  void updateFramebufferSize() {
+
+    // Useful shortcuts.
+    int fbWidth = rfb.framebufferWidth;
+    int fbHeight = rfb.framebufferHeight;
+
+    // Calculate scaling factor for auto scaling.
+    if (maxWidth > 0 && maxHeight > 0) {
+      int f1 = maxWidth * 100 / fbWidth;
+      int f2 = maxHeight * 100 / fbHeight;
+      scalingFactor = Math.min(f1, f2);
+      if (scalingFactor > 100)
+	scalingFactor = 100;
+      System.out.println("Scaling desktop at " + scalingFactor + "%");
+    }
+
+    // Update scaled framebuffer geometry.
+    scaledWidth = (fbWidth * scalingFactor + 50) / 100;
+    scaledHeight = (fbHeight * scalingFactor + 50) / 100;
+
+    // Create new off-screen image either if it does not exist, or if
+    // its geometry should be changed. It's not necessary to replace
+    // existing image if only pixel format should be changed.
+    if (memImage == null) {
+      memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
+      memGraphics = memImage.getGraphics();
+    } else if (memImage.getWidth(null) != fbWidth ||
+	       memImage.getHeight(null) != fbHeight) {
+      synchronized(memImage) {
+	memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
+	memGraphics = memImage.getGraphics();
+      }
+    }
+
+    // Images with raw pixels should be re-allocated on every change
+    // of geometry or pixel format.
+    if (bytesPixel == 1) {
+
+      pixels24 = null;
+      pixels8 = new byte[fbWidth * fbHeight];
+
+      pixelsSource =
+	new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
+
+      zrleTilePixels24 = null;
+      zrleTilePixels8 = new byte[64 * 64];
+
+    } else {
+
+      pixels8 = null;
+      pixels24 = new int[fbWidth * fbHeight];
+
+      pixelsSource =
+	new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
+
+      zrleTilePixels8 = null;
+      zrleTilePixels24 = new int[64 * 64];
+
+    }
+    pixelsSource.setAnimated(true);
+    rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
+
+    // Update the size of desktop containers.
+    if (viewer.inSeparateFrame) {
+      if (viewer.desktopScrollPane != null)
+	resizeDesktopFrame();
+    } else {
+      setSize(scaledWidth, scaledHeight);
+    }
+    viewer.moveFocusToDesktop();
+  }
+
+  void resizeDesktopFrame() {
+    setSize(scaledWidth, scaledHeight);
+
+    // FIXME: Find a better way to determine correct size of a
+    // ScrollPane.  -- const
+    Insets insets = viewer.desktopScrollPane.getInsets();
+    viewer.desktopScrollPane.setSize(scaledWidth +
+				     2 * Math.min(insets.left, insets.right),
+				     scaledHeight +
+				     2 * Math.min(insets.top, insets.bottom));
+
+    viewer.vncFrame.pack();
+
+    // Try to limit the frame size to the screen size.
+
+    Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
+    Dimension frameSize = viewer.vncFrame.getSize();
+    Dimension newSize = frameSize;
+
+    // Reduce Screen Size by 30 pixels in each direction;
+    // This is a (poor) attempt to account for
+    //     1) Menu bar on Macintosh (should really also account for
+    //        Dock on OSX).  Usually 22px on top of screen.
+    //     2) Taxkbar on Windows (usually about 28 px on bottom)
+    //     3) Other obstructions.
+
+    screenSize.height -= 30;
+    screenSize.width  -= 30;
+
+    boolean needToResizeFrame = false;
+    if (frameSize.height > screenSize.height) {
+      newSize.height = screenSize.height;
+      needToResizeFrame = true;
+    }
+    if (frameSize.width > screenSize.width) {
+      newSize.width = screenSize.width;
+      needToResizeFrame = true;
+    }
+    if (needToResizeFrame) {
+      viewer.vncFrame.setSize(newSize);
+    }
+
+    viewer.desktopScrollPane.doLayout();
+  }
+
+  //
+  // processNormalProtocol() - executed by the rfbThread to deal with the
+  // RFB socket.
+  //
+
+  public void processNormalProtocol() throws Exception {
+
+    // Start/stop session recording if necessary.
+    viewer.checkRecordingStatus();
+
+    rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+				      rfb.framebufferHeight, false);
+
+    //
+    // main dispatch loop
+    //
+
+    while (true) {
+
+      // Read message type from the server.
+      int msgType = rfb.readServerMessageType();
+
+      // Process the message depending on its type.
+      switch (msgType) {
+      case RfbProto.FramebufferUpdate:
+	rfb.readFramebufferUpdate();
+
+	boolean cursorPosReceived = false;
+
+	for (int i = 0; i < rfb.updateNRects; i++) {
+	  rfb.readFramebufferUpdateRectHdr();
+	  int rx = rfb.updateRectX, ry = rfb.updateRectY;
+	  int rw = rfb.updateRectW, rh = rfb.updateRectH;
+
+	  if (rfb.updateRectEncoding == rfb.EncodingLastRect)
+	    break;
+
+	  if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
+	    rfb.setFramebufferSize(rw, rh);
+	    updateFramebufferSize();
+	    break;
+	  }
+
+	  if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
+	      rfb.updateRectEncoding == rfb.EncodingRichCursor) {
+	    handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
+	    continue;
+	  }
+
+	  if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
+	    softCursorMove(rx, ry);
+	    cursorPosReceived = true;
+	    continue;
+	  }
+
+          rfb.startTiming();
+
+	  switch (rfb.updateRectEncoding) {
+	  case RfbProto.EncodingRaw:
+	    handleRawRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingCopyRect:
+	    handleCopyRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingRRE:
+	    handleRRERect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingCoRRE:
+	    handleCoRRERect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingHextile:
+	    handleHextileRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingZRLE:
+	    handleZRLERect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingZlib:
+            handleZlibRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingTight:
+	    handleTightRect(rx, ry, rw, rh);
+	    break;
+	  default:
+	    throw new Exception("Unknown RFB rectangle encoding " +
+				rfb.updateRectEncoding);
+	  }
+
+          rfb.stopTiming();
+	}
+
+	boolean fullUpdateNeeded = false;
+
+	// Start/stop session recording if necessary. Request full
+	// update if a new session file was opened.
+	if (viewer.checkRecordingStatus())
+	  fullUpdateNeeded = true;
+
+	// Defer framebuffer update request if necessary. But wake up
+	// immediately on keyboard or mouse event. Also, don't sleep
+	// if there is some data to receive, or if the last update
+	// included a PointerPos message.
+	if (viewer.deferUpdateRequests > 0 &&
+	    rfb.is.available() == 0 && !cursorPosReceived) {
+	  synchronized(rfb) {
+	    try {
+	      rfb.wait(viewer.deferUpdateRequests);
+	    } catch (InterruptedException e) {
+	    }
+	  }
+	}
+
+	// Before requesting framebuffer update, check if the pixel
+	// format should be changed. If it should, request full update
+	// instead of an incremental one.
+	if (viewer.options.eightBitColors != (bytesPixel == 1)) {
+	  setPixelFormat();
+	  fullUpdateNeeded = true;
+	}
+
+        viewer.autoSelectEncodings();
+
+	rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+					  rfb.framebufferHeight,
+					  !fullUpdateNeeded);
+
+	break;
+
+      case RfbProto.SetColourMapEntries:
+	throw new Exception("Can't handle SetColourMapEntries message");
+
+      case RfbProto.Bell:
+        Toolkit.getDefaultToolkit().beep();
+	break;
+
+      case RfbProto.ServerCutText:
+	String s = rfb.readServerCutText();
+	viewer.clipboard.setCutText(s);
+	break;
+
+      default:
+	throw new Exception("Unknown RFB message type " + msgType);
+      }
+    }
+  }
+
+
+  //
+  // Handle a raw rectangle. The second form with paint==false is used
+  // by the Hextile decoder for raw-encoded tiles.
+  //
+
+  void handleRawRect(int x, int y, int w, int h) throws IOException {
+    handleRawRect(x, y, w, h, true);
+  }
+
+  void handleRawRect(int x, int y, int w, int h, boolean paint)
+    throws IOException {
+
+    if (bytesPixel == 1) {
+      for (int dy = y; dy < y + h; dy++) {
+	rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
+	if (rfb.rec != null) {
+	  rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+	}
+      }
+    } else {
+      byte[] buf = new byte[w * 4];
+      int i, offset;
+      for (int dy = y; dy < y + h; dy++) {
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+	  rfb.rec.write(buf);
+	}
+	offset = dy * rfb.framebufferWidth + x;
+	for (i = 0; i < w; i++) {
+	  pixels24[offset + i] =
+	    (buf[i * 4 + 2] & 0xFF) << 16 |
+	    (buf[i * 4 + 1] & 0xFF) << 8 |
+	    (buf[i * 4] & 0xFF);
+	}
+      }
+    }
+
+    handleUpdatedPixels(x, y, w, h);
+    if (paint)
+      scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a CopyRect rectangle.
+  //
+
+  void handleCopyRect(int x, int y, int w, int h) throws IOException {
+
+    rfb.readCopyRect();
+    memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
+			 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle an RRE-encoded rectangle.
+  //
+
+  void handleRRERect(int x, int y, int w, int h) throws IOException {
+
+    int nSubrects = rfb.is.readInt();
+
+    byte[] bg_buf = new byte[bytesPixel];
+    rfb.readFully(bg_buf);
+    Color pixel;
+    if (bytesPixel == 1) {
+      pixel = colors[bg_buf[0] & 0xFF];
+    } else {
+      pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+    }
+    memGraphics.setColor(pixel);
+    memGraphics.fillRect(x, y, w, h);
+
+    byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
+    rfb.readFully(buf);
+    DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
+
+    if (rfb.rec != null) {
+      rfb.rec.writeIntBE(nSubrects);
+      rfb.rec.write(bg_buf);
+      rfb.rec.write(buf);
+    }
+
+    int sx, sy, sw, sh;
+
+    for (int j = 0; j < nSubrects; j++) {
+      if (bytesPixel == 1) {
+	pixel = colors[ds.readUnsignedByte()];
+      } else {
+	ds.skip(4);
+	pixel = new Color(buf[j*12+2] & 0xFF,
+			  buf[j*12+1] & 0xFF,
+			  buf[j*12]   & 0xFF);
+      }
+      sx = x + ds.readUnsignedShort();
+      sy = y + ds.readUnsignedShort();
+      sw = ds.readUnsignedShort();
+      sh = ds.readUnsignedShort();
+
+      memGraphics.setColor(pixel);
+      memGraphics.fillRect(sx, sy, sw, sh);
+    }
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a CoRRE-encoded rectangle.
+  //
+
+  void handleCoRRERect(int x, int y, int w, int h) throws IOException {
+    int nSubrects = rfb.is.readInt();
+
+    byte[] bg_buf = new byte[bytesPixel];
+    rfb.readFully(bg_buf);
+    Color pixel;
+    if (bytesPixel == 1) {
+      pixel = colors[bg_buf[0] & 0xFF];
+    } else {
+      pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+    }
+    memGraphics.setColor(pixel);
+    memGraphics.fillRect(x, y, w, h);
+
+    byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
+    rfb.readFully(buf);
+
+    if (rfb.rec != null) {
+      rfb.rec.writeIntBE(nSubrects);
+      rfb.rec.write(bg_buf);
+      rfb.rec.write(buf);
+    }
+
+    int sx, sy, sw, sh;
+    int i = 0;
+
+    for (int j = 0; j < nSubrects; j++) {
+      if (bytesPixel == 1) {
+	pixel = colors[buf[i++] & 0xFF];
+      } else {
+	pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
+	i += 4;
+      }
+      sx = x + (buf[i++] & 0xFF);
+      sy = y + (buf[i++] & 0xFF);
+      sw = buf[i++] & 0xFF;
+      sh = buf[i++] & 0xFF;
+
+      memGraphics.setColor(pixel);
+      memGraphics.fillRect(sx, sy, sw, sh);
+    }
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a Hextile-encoded rectangle.
+  //
+
+  // These colors should be kept between handleHextileSubrect() calls.
+  private Color hextile_bg, hextile_fg;
+
+  void handleHextileRect(int x, int y, int w, int h) throws IOException {
+
+    hextile_bg = new Color(0);
+    hextile_fg = new Color(0);
+
+    for (int ty = y; ty < y + h; ty += 16) {
+      int th = 16;
+      if (y + h - ty < 16)
+	th = y + h - ty;
+
+      for (int tx = x; tx < x + w; tx += 16) {
+	int tw = 16;
+	if (x + w - tx < 16)
+	  tw = x + w - tx;
+
+	handleHextileSubrect(tx, ty, tw, th);
+      }
+
+      // Finished with a row of tiles, now let's show it.
+      scheduleRepaint(x, y, w, h);
+    }
+  }
+
+  //
+  // Handle one tile in the Hextile-encoded data.
+  //
+
+  void handleHextileSubrect(int tx, int ty, int tw, int th)
+    throws IOException {
+
+    int subencoding = rfb.is.readUnsignedByte();
+    if (rfb.rec != null) {
+      rfb.rec.writeByte(subencoding);
+    }
+
+    // Is it a raw-encoded sub-rectangle?
+    if ((subencoding & rfb.HextileRaw) != 0) {
+      handleRawRect(tx, ty, tw, th, false);
+      return;
+    }
+
+    // Read and draw the background if specified.
+    byte[] cbuf = new byte[bytesPixel];
+    if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
+      rfb.readFully(cbuf);
+      if (bytesPixel == 1) {
+	hextile_bg = colors[cbuf[0] & 0xFF];
+      } else {
+	hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
+      }
+      if (rfb.rec != null) {
+	rfb.rec.write(cbuf);
+      }
+    }
+    memGraphics.setColor(hextile_bg);
+    memGraphics.fillRect(tx, ty, tw, th);
+
+    // Read the foreground color if specified.
+    if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
+      rfb.readFully(cbuf);
+      if (bytesPixel == 1) {
+	hextile_fg = colors[cbuf[0] & 0xFF];
+      } else {
+	hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
+      }
+      if (rfb.rec != null) {
+	rfb.rec.write(cbuf);
+      }
+    }
+
+    // Done with this tile if there is no sub-rectangles.
+    if ((subencoding & rfb.HextileAnySubrects) == 0)
+      return;
+
+    int nSubrects = rfb.is.readUnsignedByte();
+    int bufsize = nSubrects * 2;
+    if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
+      bufsize += nSubrects * bytesPixel;
+    }
+    byte[] buf = new byte[bufsize];
+    rfb.readFully(buf);
+    if (rfb.rec != null) {
+      rfb.rec.writeByte(nSubrects);
+      rfb.rec.write(buf);
+    }
+
+    int b1, b2, sx, sy, sw, sh;
+    int i = 0;
+
+    if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
+
+      // Sub-rectangles are all of the same color.
+      memGraphics.setColor(hextile_fg);
+      for (int j = 0; j < nSubrects; j++) {
+	b1 = buf[i++] & 0xFF;
+	b2 = buf[i++] & 0xFF;
+	sx = tx + (b1 >> 4);
+	sy = ty + (b1 & 0xf);
+	sw = (b2 >> 4) + 1;
+	sh = (b2 & 0xf) + 1;
+	memGraphics.fillRect(sx, sy, sw, sh);
+      }
+    } else if (bytesPixel == 1) {
+
+      // BGR233 (8-bit color) version for colored sub-rectangles.
+      for (int j = 0; j < nSubrects; j++) {
+	hextile_fg = colors[buf[i++] & 0xFF];
+	b1 = buf[i++] & 0xFF;
+	b2 = buf[i++] & 0xFF;
+	sx = tx + (b1 >> 4);
+	sy = ty + (b1 & 0xf);
+	sw = (b2 >> 4) + 1;
+	sh = (b2 & 0xf) + 1;
+	memGraphics.setColor(hextile_fg);
+	memGraphics.fillRect(sx, sy, sw, sh);
+      }
+
+    } else {
+
+      // Full-color (24-bit) version for colored sub-rectangles.
+      for (int j = 0; j < nSubrects; j++) {
+	hextile_fg = new Color(buf[i+2] & 0xFF,
+			       buf[i+1] & 0xFF,
+			       buf[i] & 0xFF);
+	i += 4;
+	b1 = buf[i++] & 0xFF;
+	b2 = buf[i++] & 0xFF;
+	sx = tx + (b1 >> 4);
+	sy = ty + (b1 & 0xf);
+	sw = (b2 >> 4) + 1;
+	sh = (b2 & 0xf) + 1;
+	memGraphics.setColor(hextile_fg);
+	memGraphics.fillRect(sx, sy, sw, sh);
+      }
+
+    }
+  }
+
+  //
+  // Handle a ZRLE-encoded rectangle.
+  //
+  // FIXME: Currently, session recording is not fully supported for ZRLE.
+  //
+
+  void handleZRLERect(int x, int y, int w, int h) throws Exception {
+
+    if (zrleInStream == null)
+      zrleInStream = new ZlibInStream();
+
+    int nBytes = rfb.is.readInt();
+    if (nBytes > 64 * 1024 * 1024)
+      throw new Exception("ZRLE decoder: illegal compressed data size");
+
+    if (zrleBuf == null || zrleBufLen < nBytes) {
+      zrleBufLen = nBytes + 4096;
+      zrleBuf = new byte[zrleBufLen];
+    }
+
+    // FIXME: Do not wait for all the data before decompression.
+    rfb.readFully(zrleBuf, 0, nBytes);
+
+    if (rfb.rec != null) {
+      if (rfb.recordFromBeginning) {
+        rfb.rec.writeIntBE(nBytes);
+        rfb.rec.write(zrleBuf, 0, nBytes);
+      } else if (!zrleRecWarningShown) {
+        System.out.println("Warning: ZRLE session can be recorded" +
+                           " only from the beginning");
+        System.out.println("Warning: Recorded file may be corrupted");
+        zrleRecWarningShown = true;
+      }
+    }
+
+    zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
+
+    for (int ty = y; ty < y+h; ty += 64) {
+
+      int th = Math.min(y+h-ty, 64);
+
+      for (int tx = x; tx < x+w; tx += 64) {
+
+        int tw = Math.min(x+w-tx, 64);
+
+        int mode = zrleInStream.readU8();
+        boolean rle = (mode & 128) != 0;
+        int palSize = mode & 127;
+        int[] palette = new int[128];
+
+        readZrlePalette(palette, palSize);
+
+        if (palSize == 1) {
+          int pix = palette[0];
+          Color c = (bytesPixel == 1) ?
+            colors[pix] : new Color(0xFF000000 | pix);
+          memGraphics.setColor(c);
+          memGraphics.fillRect(tx, ty, tw, th);
+          continue;
+        }
+
+        if (!rle) {
+          if (palSize == 0) {
+            readZrleRawPixels(tw, th);
+          } else {
+            readZrlePackedPixels(tw, th, palette, palSize);
+          }
+        } else {
+          if (palSize == 0) {
+            readZrlePlainRLEPixels(tw, th);
+          } else {
+            readZrlePackedRLEPixels(tw, th, palette);
+          }
+        }
+        handleUpdatedZrleTile(tx, ty, tw, th);
+      }
+    }
+
+    zrleInStream.reset();
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  int readPixel(InStream is) throws Exception {
+    int pix;
+    if (bytesPixel == 1) {
+      pix = is.readU8();
+    } else {
+      int p1 = is.readU8();
+      int p2 = is.readU8();
+      int p3 = is.readU8();
+      pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
+    }
+    return pix;
+  }
+
+  void readPixels(InStream is, int[] dst, int count) throws Exception {
+    int pix;
+    if (bytesPixel == 1) {
+      byte[] buf = new byte[count];
+      is.readBytes(buf, 0, count);
+      for (int i = 0; i < count; i++) {
+        dst[i] = (int)buf[i] & 0xFF;
+      }
+    } else {
+      byte[] buf = new byte[count * 3];
+      is.readBytes(buf, 0, count * 3);
+      for (int i = 0; i < count; i++) {
+        dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
+                  (buf[i*3+1] & 0xFF) << 8 |
+                  (buf[i*3] & 0xFF));
+      }
+    }
+  }
+
+  void readZrlePalette(int[] palette, int palSize) throws Exception {
+    readPixels(zrleInStream, palette, palSize);
+  }
+
+  void readZrleRawPixels(int tw, int th) throws Exception {
+    if (bytesPixel == 1) {
+      zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
+    } else {
+      readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
+    }
+  }
+
+  void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
+    throws Exception {
+
+    int bppp = ((palSize > 16) ? 8 :
+                ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
+    int ptr = 0;
+
+    for (int i = 0; i < th; i++) {
+      int eol = ptr + tw;
+      int b = 0;
+      int nbits = 0;
+
+      while (ptr < eol) {
+        if (nbits == 0) {
+          b = zrleInStream.readU8();
+          nbits = 8;
+        }
+        nbits -= bppp;
+        int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
+        if (bytesPixel == 1) {
+          zrleTilePixels8[ptr++] = (byte)palette[index];
+        } else {
+          zrleTilePixels24[ptr++] = palette[index];
+        }
+      }
+    }
+  }
+
+  void readZrlePlainRLEPixels(int tw, int th) throws Exception {
+    int ptr = 0;
+    int end = ptr + tw * th;
+    while (ptr < end) {
+      int pix = readPixel(zrleInStream);
+      int len = 1;
+      int b;
+      do {
+        b = zrleInStream.readU8();
+        len += b;
+      } while (b == 255);
+
+      if (!(len <= end - ptr))
+        throw new Exception("ZRLE decoder: assertion failed" +
+                            " (len <= end-ptr)");
+
+      if (bytesPixel == 1) {
+        while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
+      } else {
+        while (len-- > 0) zrleTilePixels24[ptr++] = pix;
+      }
+    }
+  }
+
+  void readZrlePackedRLEPixels(int tw, int th, int[] palette)
+    throws Exception {
+
+    int ptr = 0;
+    int end = ptr + tw * th;
+    while (ptr < end) {
+      int index = zrleInStream.readU8();
+      int len = 1;
+      if ((index & 128) != 0) {
+        int b;
+        do {
+          b = zrleInStream.readU8();
+          len += b;
+        } while (b == 255);
+        
+        if (!(len <= end - ptr))
+          throw new Exception("ZRLE decoder: assertion failed" +
+                              " (len <= end - ptr)");
+      }
+
+      index &= 127;
+      int pix = palette[index];
+
+      if (bytesPixel == 1) {
+        while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
+      } else {
+        while (len-- > 0) zrleTilePixels24[ptr++] = pix;
+      }
+    }
+  }
+
+  //
+  // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
+  //
+
+  void handleUpdatedZrleTile(int x, int y, int w, int h) {
+    Object src, dst;
+    if (bytesPixel == 1) {
+      src = zrleTilePixels8; dst = pixels8;
+    } else {
+      src = zrleTilePixels24; dst = pixels24;
+    }
+    int offsetSrc = 0;
+    int offsetDst = (y * rfb.framebufferWidth + x);
+    for (int j = 0; j < h; j++) {
+      System.arraycopy(src, offsetSrc, dst, offsetDst, w);
+      offsetSrc += w;
+      offsetDst += rfb.framebufferWidth;
+    }
+    handleUpdatedPixels(x, y, w, h);
+  }
+
+  //
+  // Handle a Zlib-encoded rectangle.
+  //
+
+  void handleZlibRect(int x, int y, int w, int h) throws Exception {
+
+    int nBytes = rfb.is.readInt();
+
+    if (zlibBuf == null || zlibBufLen < nBytes) {
+      zlibBufLen = nBytes * 2;
+      zlibBuf = new byte[zlibBufLen];
+    }
+
+    rfb.readFully(zlibBuf, 0, nBytes);
+
+    if (rfb.rec != null && rfb.recordFromBeginning) {
+      rfb.rec.writeIntBE(nBytes);
+      rfb.rec.write(zlibBuf, 0, nBytes);
+    }
+
+    if (zlibInflater == null) {
+      zlibInflater = new Inflater();
+    }
+    zlibInflater.setInput(zlibBuf, 0, nBytes);
+
+    if (bytesPixel == 1) {
+      for (int dy = y; dy < y + h; dy++) {
+	zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
+	if (rfb.rec != null && !rfb.recordFromBeginning)
+	  rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+      }
+    } else {
+      byte[] buf = new byte[w * 4];
+      int i, offset;
+      for (int dy = y; dy < y + h; dy++) {
+	zlibInflater.inflate(buf);
+	offset = dy * rfb.framebufferWidth + x;
+	for (i = 0; i < w; i++) {
+	  pixels24[offset + i] =
+	    (buf[i * 4 + 2] & 0xFF) << 16 |
+	    (buf[i * 4 + 1] & 0xFF) << 8 |
+	    (buf[i * 4] & 0xFF);
+	}
+	if (rfb.rec != null && !rfb.recordFromBeginning)
+	  rfb.rec.write(buf);
+      }
+    }
+
+    handleUpdatedPixels(x, y, w, h);
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a Tight-encoded rectangle.
+  //
+
+  void handleTightRect(int x, int y, int w, int h) throws Exception {
+
+    int comp_ctl = rfb.is.readUnsignedByte();
+    if (rfb.rec != null) {
+      if (rfb.recordFromBeginning ||
+	  comp_ctl == (rfb.TightFill << 4) ||
+	  comp_ctl == (rfb.TightJpeg << 4)) {
+	// Send data exactly as received.
+	rfb.rec.writeByte(comp_ctl);
+      } else {
+	// Tell the decoder to flush each of the four zlib streams.
+	rfb.rec.writeByte(comp_ctl | 0x0F);
+      }
+    }
+
+    // Flush zlib streams if we are told by the server to do so.
+    for (int stream_id = 0; stream_id < 4; stream_id++) {
+      if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
+	tightInflaters[stream_id] = null;
+      }
+      comp_ctl >>= 1;
+    }
+
+    // Check correctness of subencoding value.
+    if (comp_ctl > rfb.TightMaxSubencoding) {
+      throw new Exception("Incorrect tight subencoding: " + comp_ctl);
+    }
+
+    // Handle solid-color rectangles.
+    if (comp_ctl == rfb.TightFill) {
+
+      if (bytesPixel == 1) {
+	int idx = rfb.is.readUnsignedByte();
+	memGraphics.setColor(colors[idx]);
+	if (rfb.rec != null) {
+	  rfb.rec.writeByte(idx);
+	}
+      } else {
+	byte[] buf = new byte[3];
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+	  rfb.rec.write(buf);
+	}
+	Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
+			     (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
+	memGraphics.setColor(bg);
+      }
+      memGraphics.fillRect(x, y, w, h);
+      scheduleRepaint(x, y, w, h);
+      return;
+
+    }
+
+    if (comp_ctl == rfb.TightJpeg) {
+
+      // Read JPEG data.
+      byte[] jpegData = new byte[rfb.readCompactLen()];
+      rfb.readFully(jpegData);
+      if (rfb.rec != null) {
+	if (!rfb.recordFromBeginning) {
+	  rfb.recordCompactLen(jpegData.length);
+	}
+	rfb.rec.write(jpegData);
+      }
+
+      // Create an Image object from the JPEG data.
+      Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
+
+      // Remember the rectangle where the image should be drawn.
+      jpegRect = new Rectangle(x, y, w, h);
+
+      // Let the imageUpdate() method do the actual drawing, here just
+      // wait until the image is fully loaded and drawn.
+      synchronized(jpegRect) {
+	Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
+	try {
+	  // Wait no longer than three seconds.
+	  jpegRect.wait(3000);
+	} catch (InterruptedException e) {
+	  throw new Exception("Interrupted while decoding JPEG image");
+	}
+      }
+
+      // Done, jpegRect is not needed any more.
+      jpegRect = null;
+      return;
+
+    }
+
+    // Read filter id and parameters.
+    int numColors = 0, rowSize = w;
+    byte[] palette8 = new byte[2];
+    int[] palette24 = new int[256];
+    boolean useGradient = false;
+    if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
+      int filter_id = rfb.is.readUnsignedByte();
+      if (rfb.rec != null) {
+	rfb.rec.writeByte(filter_id);
+      }
+      if (filter_id == rfb.TightFilterPalette) {
+	numColors = rfb.is.readUnsignedByte() + 1;
+	if (rfb.rec != null) {
+	  rfb.rec.writeByte(numColors - 1);
+	}
+        if (bytesPixel == 1) {
+	  if (numColors != 2) {
+	    throw new Exception("Incorrect tight palette size: " + numColors);
+	  }
+	  rfb.readFully(palette8);
+	  if (rfb.rec != null) {
+	    rfb.rec.write(palette8);
+	  }
+	} else {
+	  byte[] buf = new byte[numColors * 3];
+	  rfb.readFully(buf);
+	  if (rfb.rec != null) {
+	    rfb.rec.write(buf);
+	  }
+	  for (int i = 0; i < numColors; i++) {
+	    palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
+			    (buf[i * 3 + 1] & 0xFF) << 8 |
+			    (buf[i * 3 + 2] & 0xFF));
+	  }
+	}
+	if (numColors == 2)
+	  rowSize = (w + 7) / 8;
+      } else if (filter_id == rfb.TightFilterGradient) {
+	useGradient = true;
+      } else if (filter_id != rfb.TightFilterCopy) {
+	throw new Exception("Incorrect tight filter id: " + filter_id);
+      }
+    }
+    if (numColors == 0 && bytesPixel == 4)
+      rowSize *= 3;
+
+    // Read, optionally uncompress and decode data.
+    int dataSize = h * rowSize;
+    if (dataSize < rfb.TightMinToCompress) {
+      // Data size is small - not compressed with zlib.
+      if (numColors != 0) {
+	// Indexed colors.
+	byte[] indexedData = new byte[dataSize];
+	rfb.readFully(indexedData);
+	if (rfb.rec != null) {
+	  rfb.rec.write(indexedData);
+	}
+	if (numColors == 2) {
+	  // Two colors.
+	  if (bytesPixel == 1) {
+	    decodeMonoData(x, y, w, h, indexedData, palette8);
+	  } else {
+	    decodeMonoData(x, y, w, h, indexedData, palette24);
+	  }
+	} else {
+	  // 3..255 colors (assuming bytesPixel == 4).
+	  int i = 0;
+	  for (int dy = y; dy < y + h; dy++) {
+	    for (int dx = x; dx < x + w; dx++) {
+	      pixels24[dy * rfb.framebufferWidth + dx] =
+		palette24[indexedData[i++] & 0xFF];
+	    }
+	  }
+	}
+      } else if (useGradient) {
+	// "Gradient"-processed data
+	byte[] buf = new byte[w * h * 3];
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+	  rfb.rec.write(buf);
+	}
+	decodeGradientData(x, y, w, h, buf);
+      } else {
+	// Raw truecolor data.
+	if (bytesPixel == 1) {
+	  for (int dy = y; dy < y + h; dy++) {
+	    rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
+	    if (rfb.rec != null) {
+	      rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+	    }
+	  }
+	} else {
+	  byte[] buf = new byte[w * 3];
+	  int i, offset;
+	  for (int dy = y; dy < y + h; dy++) {
+	    rfb.readFully(buf);
+	    if (rfb.rec != null) {
+	      rfb.rec.write(buf);
+	    }
+	    offset = dy * rfb.framebufferWidth + x;
+	    for (i = 0; i < w; i++) {
+	      pixels24[offset + i] =
+		(buf[i * 3] & 0xFF) << 16 |
+		(buf[i * 3 + 1] & 0xFF) << 8 |
+		(buf[i * 3 + 2] & 0xFF);
+	    }
+	  }
+	}
+      }
+    } else {
+      // Data was compressed with zlib.
+      int zlibDataLen = rfb.readCompactLen();
+      byte[] zlibData = new byte[zlibDataLen];
+      rfb.readFully(zlibData);
+      if (rfb.rec != null && rfb.recordFromBeginning) {
+	rfb.rec.write(zlibData);
+      }
+      int stream_id = comp_ctl & 0x03;
+      if (tightInflaters[stream_id] == null) {
+	tightInflaters[stream_id] = new Inflater();
+      }
+      Inflater myInflater = tightInflaters[stream_id];
+      myInflater.setInput(zlibData);
+      byte[] buf = new byte[dataSize];
+      myInflater.inflate(buf);
+      if (rfb.rec != null && !rfb.recordFromBeginning) {
+	rfb.recordCompressedData(buf);
+      }
+
+      if (numColors != 0) {
+	// Indexed colors.
+	if (numColors == 2) {
+	  // Two colors.
+	  if (bytesPixel == 1) {
+	    decodeMonoData(x, y, w, h, buf, palette8);
+	  } else {
+	    decodeMonoData(x, y, w, h, buf, palette24);
+	  }
+	} else {
+	  // More than two colors (assuming bytesPixel == 4).
+	  int i = 0;
+	  for (int dy = y; dy < y + h; dy++) {
+	    for (int dx = x; dx < x + w; dx++) {
+	      pixels24[dy * rfb.framebufferWidth + dx] =
+		palette24[buf[i++] & 0xFF];
+	    }
+	  }
+	}
+      } else if (useGradient) {
+	// Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
+	decodeGradientData(x, y, w, h, buf);
+      } else {
+	// Compressed truecolor data.
+	if (bytesPixel == 1) {
+	  int destOffset = y * rfb.framebufferWidth + x;
+	  for (int dy = 0; dy < h; dy++) {
+	    System.arraycopy(buf, dy * w, pixels8, destOffset, w);
+	    destOffset += rfb.framebufferWidth;
+	  }
+	} else {
+	  int srcOffset = 0;
+	  int destOffset, i;
+	  for (int dy = 0; dy < h; dy++) {
+	    myInflater.inflate(buf);
+	    destOffset = (y + dy) * rfb.framebufferWidth + x;
+	    for (i = 0; i < w; i++) {
+	      pixels24[destOffset + i] =
+		(buf[srcOffset] & 0xFF) << 16 |
+		(buf[srcOffset + 1] & 0xFF) << 8 |
+		(buf[srcOffset + 2] & 0xFF);
+	      srcOffset += 3;
+	    }
+	  }
+	}
+      }
+    }
+
+    handleUpdatedPixels(x, y, w, h);
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
+  //
+
+  void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
+
+    int dx, dy, n;
+    int i = y * rfb.framebufferWidth + x;
+    int rowBytes = (w + 7) / 8;
+    byte b;
+
+    for (dy = 0; dy < h; dy++) {
+      for (dx = 0; dx < w / 8; dx++) {
+	b = src[dy*rowBytes+dx];
+	for (n = 7; n >= 0; n--)
+	  pixels8[i++] = palette[b >> n & 1];
+      }
+      for (n = 7; n >= 8 - w % 8; n--) {
+	pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
+      }
+      i += (rfb.framebufferWidth - w);
+    }
+  }
+
+  void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
+
+    int dx, dy, n;
+    int i = y * rfb.framebufferWidth + x;
+    int rowBytes = (w + 7) / 8;
+    byte b;
+
+    for (dy = 0; dy < h; dy++) {
+      for (dx = 0; dx < w / 8; dx++) {
+	b = src[dy*rowBytes+dx];
+	for (n = 7; n >= 0; n--)
+	  pixels24[i++] = palette[b >> n & 1];
+      }
+      for (n = 7; n >= 8 - w % 8; n--) {
+	pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
+      }
+      i += (rfb.framebufferWidth - w);
+    }
+  }
+
+  //
+  // Decode data processed with the "Gradient" filter.
+  //
+
+  void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
+
+    int dx, dy, c;
+    byte[] prevRow = new byte[w * 3];
+    byte[] thisRow = new byte[w * 3];
+    byte[] pix = new byte[3];
+    int[] est = new int[3];
+
+    int offset = y * rfb.framebufferWidth + x;
+
+    for (dy = 0; dy < h; dy++) {
+
+      /* First pixel in a row */
+      for (c = 0; c < 3; c++) {
+	pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
+	thisRow[c] = pix[c];
+      }
+      pixels24[offset++] =
+	(pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
+
+      /* Remaining pixels of a row */
+      for (dx = 1; dx < w; dx++) {
+	for (c = 0; c < 3; c++) {
+	  est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
+		    (prevRow[(dx-1) * 3 + c] & 0xFF));
+	  if (est[c] > 0xFF) {
+	    est[c] = 0xFF;
+	  } else if (est[c] < 0x00) {
+	    est[c] = 0x00;
+	  }
+	  pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
+	  thisRow[dx * 3 + c] = pix[c];
+	}
+	pixels24[offset++] =
+	  (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
+      }
+
+      System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
+      offset += (rfb.framebufferWidth - w);
+    }
+  }
+
+  //
+  // Display newly updated area of pixels.
+  //
+
+  void handleUpdatedPixels(int x, int y, int w, int h) {
+
+    // Draw updated pixels of the off-screen image.
+    pixelsSource.newPixels(x, y, w, h);
+    memGraphics.setClip(x, y, w, h);
+    memGraphics.drawImage(rawPixelsImage, 0, 0, null);
+    memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
+  }
+
+  //
+  // Tell JVM to repaint specified desktop area.
+  //
+
+  void scheduleRepaint(int x, int y, int w, int h) {
+    // Request repaint, deferred if necessary.
+    if (rfb.framebufferWidth == scaledWidth) {
+      repaint(viewer.deferScreenUpdates, x, y, w, h);
+    } else {
+      int sx = x * scalingFactor / 100;
+      int sy = y * scalingFactor / 100;
+      int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
+      int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
+      repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
+    }
+  }
+
+  //
+  // Handle events.
+  //
+
+  public void keyPressed(KeyEvent evt) {
+    processLocalKeyEvent(evt);
+  }
+  public void keyReleased(KeyEvent evt) {
+    processLocalKeyEvent(evt);
+  }
+  public void keyTyped(KeyEvent evt) {
+    evt.consume();
+  }
+
+  public void mousePressed(MouseEvent evt) {
+    processLocalMouseEvent(evt, false);
+  }
+  public void mouseReleased(MouseEvent evt) {
+    processLocalMouseEvent(evt, false);
+  }
+  public void mouseMoved(MouseEvent evt) {
+    processLocalMouseEvent(evt, true);
+  }
+  public void mouseDragged(MouseEvent evt) {
+    processLocalMouseEvent(evt, true);
+  }
+
+  public void processLocalKeyEvent(KeyEvent evt) {
+    if (viewer.rfb != null && rfb.inNormalProtocol) {
+      if (!inputEnabled) {
+	if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
+	    evt.getID() == KeyEvent.KEY_PRESSED ) {
+	  // Request screen update.
+	  try {
+	    rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+					      rfb.framebufferHeight, false);
+	  } catch (IOException e) {
+	    e.printStackTrace();
+	  }
+	}
+      } else {
+	// Input enabled.
+	synchronized(rfb) {
+	  try {
+	    if (extraModifiers != 0) {
+	      evt.setModifiers(evt.getModifiers() | extraModifiers);
+	    }
+	    rfb.writeKeyEvent(evt);
+	  } catch (Exception e) {
+	    e.printStackTrace();
+	  }
+	  rfb.notify();
+	}
+      }
+    }
+    // Don't ever pass keyboard events to AWT for default processing. 
+    // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
+    evt.consume();
+  }
+
+  public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
+    if (viewer.rfb != null && rfb.inNormalProtocol) {
+      if (moved) {
+	softCursorMove(evt.getX(), evt.getY());
+      }
+      if (rfb.framebufferWidth != scaledWidth) {
+        int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
+        int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
+        evt.translatePoint(sx - evt.getX(), sy - evt.getY());
+      }
+      synchronized(rfb) {
+	try {
+	  rfb.writePointerEvent(evt);
+	} catch (Exception e) {
+	  e.printStackTrace();
+	}
+	rfb.notify();
+      }
+    }
+  }
+
+  //
+  // Ignored events.
+  //
+
+  public void mouseClicked(MouseEvent evt) {}
+  public void mouseEntered(MouseEvent evt) {}
+  public void mouseExited(MouseEvent evt) {}
+
+
+  //////////////////////////////////////////////////////////////////
+  //
+  // Handle cursor shape updates (XCursor and RichCursor encodings).
+  //
+
+  boolean showSoftCursor = false;
+
+  MemoryImageSource softCursorSource;
+  Image softCursor;
+
+  int cursorX = 0, cursorY = 0;
+  int cursorWidth, cursorHeight;
+  int origCursorWidth, origCursorHeight;
+  int hotX, hotY;
+  int origHotX, origHotY;
+
+  //
+  // Handle cursor shape update (XCursor and RichCursor encodings).
+  //
+
+  synchronized void
+    handleCursorShapeUpdate(int encodingType,
+			    int xhot, int yhot, int width, int height)
+    throws IOException {
+
+    softCursorFree();
+
+    if (width * height == 0)
+      return;
+
+    // Ignore cursor shape data if requested by user.
+    if (viewer.options.ignoreCursorUpdates) {
+      int bytesPerRow = (width + 7) / 8;
+      int bytesMaskData = bytesPerRow * height;
+
+      if (encodingType == rfb.EncodingXCursor) {
+	rfb.is.skipBytes(6 + bytesMaskData * 2);
+      } else {
+	// rfb.EncodingRichCursor
+	rfb.is.skipBytes(width * height + bytesMaskData);
+      }
+      return;
+    }
+
+    // Decode cursor pixel data.
+    softCursorSource = decodeCursorShape(encodingType, width, height);
+
+    // Set original (non-scaled) cursor dimensions.
+    origCursorWidth = width;
+    origCursorHeight = height;
+    origHotX = xhot;
+    origHotY = yhot;
+
+    // Create off-screen cursor image.
+    createSoftCursor();
+
+    // Show the cursor.
+    showSoftCursor = true;
+    repaint(viewer.deferCursorUpdates,
+	    cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
+  }
+
+  //
+  // decodeCursorShape(). Decode cursor pixel data and return
+  // corresponding MemoryImageSource instance.
+  //
+
+  synchronized MemoryImageSource
+    decodeCursorShape(int encodingType, int width, int height)
+    throws IOException {
+
+    int bytesPerRow = (width + 7) / 8;
+    int bytesMaskData = bytesPerRow * height;
+
+    int[] softCursorPixels = new int[width * height];
+
+    if (encodingType == rfb.EncodingXCursor) {
+
+      // Read foreground and background colors of the cursor.
+      byte[] rgb = new byte[6];
+      rfb.readFully(rgb);
+      int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
+			(rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
+		       (0xFF000000 | (rgb[0] & 0xFF) << 16 |
+			(rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
+
+      // Read pixel and mask data.
+      byte[] pixBuf = new byte[bytesMaskData];
+      rfb.readFully(pixBuf);
+      byte[] maskBuf = new byte[bytesMaskData];
+      rfb.readFully(maskBuf);
+
+      // Decode pixel data into softCursorPixels[].
+      byte pixByte, maskByte;
+      int x, y, n, result;
+      int i = 0;
+      for (y = 0; y < height; y++) {
+	for (x = 0; x < width / 8; x++) {
+	  pixByte = pixBuf[y * bytesPerRow + x];
+	  maskByte = maskBuf[y * bytesPerRow + x];
+	  for (n = 7; n >= 0; n--) {
+	    if ((maskByte >> n & 1) != 0) {
+	      result = colors[pixByte >> n & 1];
+	    } else {
+	      result = 0;	// Transparent pixel
+	    }
+	    softCursorPixels[i++] = result;
+	  }
+	}
+	for (n = 7; n >= 8 - width % 8; n--) {
+	  if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
+	    result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
+	  } else {
+	    result = 0;		// Transparent pixel
+	  }
+	  softCursorPixels[i++] = result;
+	}
+      }
+
+    } else {
+      // encodingType == rfb.EncodingRichCursor
+
+      // Read pixel and mask data.
+      byte[] pixBuf = new byte[width * height * bytesPixel];
+      rfb.readFully(pixBuf);
+      byte[] maskBuf = new byte[bytesMaskData];
+      rfb.readFully(maskBuf);
+
+      // Decode pixel data into softCursorPixels[].
+      byte pixByte, maskByte;
+      int x, y, n, result;
+      int i = 0;
+      for (y = 0; y < height; y++) {
+	for (x = 0; x < width / 8; x++) {
+	  maskByte = maskBuf[y * bytesPerRow + x];
+	  for (n = 7; n >= 0; n--) {
+	    if ((maskByte >> n & 1) != 0) {
+	      if (bytesPixel == 1) {
+		result = cm8.getRGB(pixBuf[i]);
+	      } else {
+		result = 0xFF000000 |
+		  (pixBuf[i * 4 + 2] & 0xFF) << 16 |
+		  (pixBuf[i * 4 + 1] & 0xFF) << 8 |
+		  (pixBuf[i * 4] & 0xFF);
+	      }
+	    } else {
+	      result = 0;	// Transparent pixel
+	    }
+	    softCursorPixels[i++] = result;
+	  }
+	}
+	for (n = 7; n >= 8 - width % 8; n--) {
+	  if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
+	    if (bytesPixel == 1) {
+	      result = cm8.getRGB(pixBuf[i]);
+	    } else {
+	      result = 0xFF000000 |
+		(pixBuf[i * 4 + 2] & 0xFF) << 16 |
+		(pixBuf[i * 4 + 1] & 0xFF) << 8 |
+		(pixBuf[i * 4] & 0xFF);
+	    }
+	  } else {
+	    result = 0;		// Transparent pixel
+	  }
+	  softCursorPixels[i++] = result;
+	}
+      }
+
+    }
+
+    return new MemoryImageSource(width, height, softCursorPixels, 0, width);
+  }
+
+  //
+  // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
+  // Uses softCursorSource as a source for new cursor image.
+  //
+
+  synchronized void
+    createSoftCursor() {
+
+    if (softCursorSource == null)
+      return;
+
+    int scaleCursor = viewer.options.scaleCursor;
+    if (scaleCursor == 0 || !inputEnabled)
+      scaleCursor = 100;
+
+    // Save original cursor coordinates.
+    int x = cursorX - hotX;
+    int y = cursorY - hotY;
+    int w = cursorWidth;
+    int h = cursorHeight;
+
+    cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
+    cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
+    hotX = (origHotX * scaleCursor + 50) / 100;
+    hotY = (origHotY * scaleCursor + 50) / 100;
+    softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
+
+    if (scaleCursor != 100) {
+      softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
+						Image.SCALE_SMOOTH);
+    }
+
+    if (showSoftCursor) {
+      // Compute screen area to update.
+      x = Math.min(x, cursorX - hotX);
+      y = Math.min(y, cursorY - hotY);
+      w = Math.max(w, cursorWidth);
+      h = Math.max(h, cursorHeight);
+
+      repaint(viewer.deferCursorUpdates, x, y, w, h);
+    }
+  }
+
+  //
+  // softCursorMove(). Moves soft cursor into a particular location.
+  //
+
+  synchronized void softCursorMove(int x, int y) {
+    int oldX = cursorX;
+    int oldY = cursorY;
+    cursorX = x;
+    cursorY = y;
+    if (showSoftCursor) {
+      repaint(viewer.deferCursorUpdates,
+	      oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
+      repaint(viewer.deferCursorUpdates,
+	      cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
+    }
+  }
+
+  //
+  // softCursorFree(). Remove soft cursor, dispose resources.
+  //
+
+  synchronized void softCursorFree() {
+    if (showSoftCursor) {
+      showSoftCursor = false;
+      softCursor = null;
+      softCursorSource = null;
+
+      repaint(viewer.deferCursorUpdates,
+	      cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
+    }
+  }
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/VncCanvas2.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/VncCanvas2.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/VncCanvas2.java	(revision 571)
@@ -0,0 +1,63 @@
+//
+//  Copyright (C) 2006 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+import java.awt.*;
+import java.io.*;
+
+//
+// VncCanvas2 is a special version of VncCanvas which may use Java 2 API.
+//
+
+class VncCanvas2 extends VncCanvas {
+
+  public VncCanvas2(VncViewer v) throws IOException {
+    super(v);
+    disableFocusTraversalKeys();
+  }
+
+  public VncCanvas2(VncViewer v, int maxWidth_, int maxHeight_)
+    throws IOException {
+
+    super(v, maxWidth_, maxHeight_);
+    disableFocusTraversalKeys();
+  }
+
+  public void paintScaledFrameBuffer(Graphics g) {
+    Graphics2D g2d = (Graphics2D)g;
+    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+                         RenderingHints.VALUE_RENDER_QUALITY);
+    g2d.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
+  }
+
+  //
+  // Try to disable focus traversal keys (JVMs 1.4 and higher).
+  //
+
+  private void disableFocusTraversalKeys() {
+    try {
+      Class[] argClasses = { Boolean.TYPE };
+      java.lang.reflect.Method method =
+        getClass().getMethod("setFocusTraversalKeysEnabled", argClasses);
+      Object[] argObjects = { new Boolean(false) };
+      method.invoke(this, argObjects);
+    } catch (Exception e) {}
+  }
+
+}
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/VncViewer.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/VncViewer.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/VncViewer.java	(revision 571)
@@ -0,0 +1,972 @@
+//
+//  Copyright (C) 2001-2004 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This 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 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software 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 software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// VncViewer.java - the VNC viewer applet.  This class mainly just sets up the
+// user interface, leaving it to the VncCanvas to do the actual rendering of
+// a VNC desktop.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.*;
+
+public class VncViewer extends java.applet.Applet
+  implements java.lang.Runnable, WindowListener {
+
+  boolean inAnApplet = true;
+  boolean inSeparateFrame = false;
+
+  //
+  // main() is called when run as a java program from the command line.
+  // It simply runs the applet inside a newly-created frame.
+  //
+
+  public static void main(String[] argv) {
+    VncViewer v = new VncViewer();
+    v.mainArgs = argv;
+    v.inAnApplet = false;
+    v.inSeparateFrame = true;
+
+    v.init();
+    v.start();
+  }
+
+  String[] mainArgs;
+
+  RfbProto rfb;
+  Thread rfbThread;
+
+  Frame vncFrame;
+  Container vncContainer;
+  ScrollPane desktopScrollPane;
+  GridBagLayout gridbag;
+  ButtonPanel buttonPanel;
+  Label connStatusLabel;
+  VncCanvas vc;
+  OptionsFrame options;
+  ClipboardFrame clipboard;
+  RecordingFrame rec;
+
+  // Control session recording.
+  Object recordingSync;
+  String sessionFileName;
+  boolean recordingActive;
+  boolean recordingStatusChanged;
+  String cursorUpdatesDef;
+  String eightBitColorsDef;
+
+  // Variables read from parameter values.
+  String socketFactory;
+  String host;
+  int port;
+  String passwordParam;
+  boolean showControls;
+  boolean offerRelogin;
+  boolean showOfflineDesktop;
+  int deferScreenUpdates;
+  int deferCursorUpdates;
+  int deferUpdateRequests;
+
+  // Reference to this applet for inter-applet communication.
+  public static java.applet.Applet refApplet;
+
+  //
+  // init()
+  //
+
+  public void init() {
+
+    readParameters();
+
+    refApplet = this;
+
+    if (inSeparateFrame) {
+      vncFrame = new Frame("TightVNC");
+      if (!inAnApplet) {
+	vncFrame.add("Center", this);
+      }
+      vncContainer = vncFrame;
+    } else {
+      vncContainer = this;
+    }
+
+    recordingSync = new Object();
+
+    options = new OptionsFrame(this);
+    clipboard = new ClipboardFrame(this);
+    if (RecordingFrame.checkSecurity())
+      rec = new RecordingFrame(this);
+
+    sessionFileName = null;
+    recordingActive = false;
+    recordingStatusChanged = false;
+    cursorUpdatesDef = null;
+    eightBitColorsDef = null;
+
+    if (inSeparateFrame)
+      vncFrame.addWindowListener(this);
+
+    rfbThread = new Thread(this);
+    rfbThread.start();
+  }
+
+  public void update(Graphics g) {
+  }
+
+  //
+  // run() - executed by the rfbThread to deal with the RFB socket.
+  //
+
+  public void run() {
+
+    gridbag = new GridBagLayout();
+    vncContainer.setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.anchor = GridBagConstraints.NORTHWEST;
+
+    if (showControls) {
+      buttonPanel = new ButtonPanel(this);
+      gridbag.setConstraints(buttonPanel, gbc);
+      vncContainer.add(buttonPanel);
+    }
+
+    try {
+      connectAndAuthenticate();
+      doProtocolInitialisation();
+
+      // FIXME: Use auto-scaling not only in a separate frame.
+      if (options.autoScale && inSeparateFrame) {
+	Dimension screenSize;
+	try {
+	  screenSize = vncContainer.getToolkit().getScreenSize();
+	} catch (Exception e) {
+	  screenSize = new Dimension(0, 0);
+	}
+	createCanvas(screenSize.width - 32, screenSize.height - 32);
+      } else {
+	createCanvas(0, 0);
+      }
+
+      gbc.weightx = 1.0;
+      gbc.weighty = 1.0;
+
+      if (inSeparateFrame) {
+
+	// Create a panel which itself is resizeable and can hold
+	// non-resizeable VncCanvas component at the top left corner.
+	Panel canvasPanel = new Panel();
+	canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+	canvasPanel.add(vc);
+
+	// Create a ScrollPane which will hold a panel with VncCanvas
+	// inside.
+	desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
+	gbc.fill = GridBagConstraints.BOTH;
+	gridbag.setConstraints(desktopScrollPane, gbc);
+	desktopScrollPane.add(canvasPanel);
+
+	// Finally, add our ScrollPane to the Frame window.
+	vncFrame.add(desktopScrollPane);
+	vncFrame.setTitle(rfb.desktopName);
+	vncFrame.pack();
+	vc.resizeDesktopFrame();
+
+      } else {
+
+	// Just add the VncCanvas component to the Applet.
+	gridbag.setConstraints(vc, gbc);
+	add(vc);
+	validate();
+
+      }
+
+      if (showControls)
+	buttonPanel.enableButtons();
+
+      moveFocusToDesktop();
+      processNormalProtocol();
+
+    } catch (NoRouteToHostException e) {
+      fatalError("Network error: no route to server: " + host, e);
+    } catch (UnknownHostException e) {
+      fatalError("Network error: server name unknown: " + host, e);
+    } catch (ConnectException e) {
+      fatalError("Network error: could not connect to server: " +
+		 host + ":" + port, e);
+    } catch (EOFException e) {
+      if (showOfflineDesktop) {
+	e.printStackTrace();
+	System.out.println("Network error: remote side closed connection");
+	if (vc != null) {
+	  vc.enableInput(false);
+	}
+	if (inSeparateFrame) {
+	  vncFrame.setTitle(rfb.desktopName + " [disconnected]");
+	}
+	if (rfb != null && !rfb.closed())
+	  rfb.close();
+	if (showControls && buttonPanel != null) {
+	  buttonPanel.disableButtonsOnDisconnect();
+	  if (inSeparateFrame) {
+	    vncFrame.pack();
+	  } else {
+	    validate();
+	  }
+	}
+      } else {
+	fatalError("Network error: remote side closed connection", e);
+      }
+    } catch (IOException e) {
+      String str = e.getMessage();
+      if (str != null && str.length() != 0) {
+	fatalError("Network Error: " + str, e);
+      } else {
+	fatalError(e.toString(), e);
+      }
+    } catch (Exception e) {
+      String str = e.getMessage();
+      if (str != null && str.length() != 0) {
+	fatalError("Error: " + str, e);
+      } else {
+	fatalError(e.toString(), e);
+      }
+    }
+    
+  }
+
+  //
+  // Create a VncCanvas instance.
+  //
+
+  void createCanvas(int maxWidth, int maxHeight) throws IOException {
+    // Determine if Java 2D API is available and use a special
+    // version of VncCanvas if it is present.
+    vc = null;
+    try {
+      // This throws ClassNotFoundException if there is no Java 2D API.
+      Class cl = Class.forName("java.awt.Graphics2D");
+      // If we could load Graphics2D class, then we can use VncCanvas2D.
+      cl = Class.forName("VncCanvas2");
+      Class[] argClasses = { this.getClass(), Integer.TYPE, Integer.TYPE };
+      java.lang.reflect.Constructor cstr = cl.getConstructor(argClasses);
+      Object[] argObjects =
+        { this, new Integer(maxWidth), new Integer(maxHeight) };
+      vc = (VncCanvas)cstr.newInstance(argObjects);
+    } catch (Exception e) {
+      System.out.println("Warning: Java 2D API is not available");
+    }
+
+    // If we failed to create VncCanvas2D, use old VncCanvas.
+    if (vc == null)
+      vc = new VncCanvas(this, maxWidth, maxHeight);
+  }
+
+
+  //
+  // Process RFB socket messages.
+  // If the rfbThread is being stopped, ignore any exceptions,
+  // otherwise rethrow the exception so it can be handled.
+  //
+ 
+  void processNormalProtocol() throws Exception {
+    try {
+      vc.processNormalProtocol();
+    } catch (Exception e) {
+      if (rfbThread == null) {
+	System.out.println("Ignoring RFB socket exceptions" +
+			   " because applet is stopping");
+      } else {
+	throw e;
+      }
+    }
+  }
+
+
+  //
+  // Connect to the RFB server and authenticate the user.
+  //
+
+  void connectAndAuthenticate() throws Exception
+  {
+    showConnectionStatus("Initializing...");
+    if (inSeparateFrame) {
+      vncFrame.pack();
+      vncFrame.show();
+    } else {
+      validate();
+    }
+
+    showConnectionStatus("Connecting to " + host + ", port " + port + "...");
+
+    rfb = new RfbProto(host, port, this);
+    showConnectionStatus("Connected to server");
+
+    rfb.readVersionMsg();
+    showConnectionStatus("RFB server supports protocol version " +
+			 rfb.serverMajor + "." + rfb.serverMinor);
+
+    rfb.writeVersionMsg();
+    showConnectionStatus("Using RFB protocol version " +
+			 rfb.clientMajor + "." + rfb.clientMinor);
+
+    int secType = rfb.negotiateSecurity();
+    int authType;
+    if (secType == RfbProto.SecTypeTight) {
+      showConnectionStatus("Enabling TightVNC protocol extensions");
+      rfb.initCapabilities();
+      rfb.setupTunneling();
+      authType = rfb.negotiateAuthenticationTight();
+    } else {
+      authType = secType;
+    }
+
+    switch (authType) {
+    case RfbProto.AuthNone:
+      showConnectionStatus("No authentication needed");
+      rfb.authenticateNone();
+      break;
+    case RfbProto.AuthVNC:
+      showConnectionStatus("Performing standard VNC authentication");
+      if (passwordParam != null) {
+        rfb.authenticateVNC(passwordParam);
+      } else {
+        String pw = askPassword();
+        rfb.authenticateVNC(pw);
+      }
+      break;
+    default:
+      throw new Exception("Unknown authentication scheme " + authType);
+    }
+  }
+
+
+  //
+  // Show a message describing the connection status.
+  // To hide the connection status label, use (msg == null).
+  //
+
+  void showConnectionStatus(String msg)
+  {
+    if (msg == null) {
+      if (vncContainer.isAncestorOf(connStatusLabel)) {
+	vncContainer.remove(connStatusLabel);
+      }
+      return;
+    }
+
+    System.out.println(msg);
+
+    if (connStatusLabel == null) {
+      connStatusLabel = new Label("Status: " + msg);
+      connStatusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+    } else {
+      connStatusLabel.setText("Status: " + msg);
+    }
+
+    if (!vncContainer.isAncestorOf(connStatusLabel)) {
+      GridBagConstraints gbc = new GridBagConstraints();
+      gbc.gridwidth = GridBagConstraints.REMAINDER;
+      gbc.fill = GridBagConstraints.HORIZONTAL;
+      gbc.anchor = GridBagConstraints.NORTHWEST;
+      gbc.weightx = 1.0;
+      gbc.weighty = 1.0;
+      gbc.insets = new Insets(20, 30, 20, 30);
+      gridbag.setConstraints(connStatusLabel, gbc);
+      vncContainer.add(connStatusLabel);
+    }
+
+    if (inSeparateFrame) {
+      vncFrame.pack();
+    } else {
+      validate();
+    }
+  }
+
+
+  //
+  // Show an authentication panel.
+  //
+
+  String askPassword() throws Exception
+  {
+    showConnectionStatus(null);
+
+    AuthPanel authPanel = new AuthPanel(this);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.anchor = GridBagConstraints.NORTHWEST;
+    gbc.weightx = 1.0;
+    gbc.weighty = 1.0;
+    gbc.ipadx = 100;
+    gbc.ipady = 50;
+    gridbag.setConstraints(authPanel, gbc);
+    vncContainer.add(authPanel);
+
+    if (inSeparateFrame) {
+      vncFrame.pack();
+    } else {
+      validate();
+    }
+
+    authPanel.moveFocusToDefaultField();
+    String pw = authPanel.getPassword();
+    vncContainer.remove(authPanel);
+
+    return pw;
+  }
+
+
+  //
+  // Do the rest of the protocol initialisation.
+  //
+
+  void doProtocolInitialisation() throws IOException
+  {
+    rfb.writeClientInit();
+    rfb.readServerInit();
+
+    System.out.println("Desktop name is " + rfb.desktopName);
+    System.out.println("Desktop size is " + rfb.framebufferWidth + " x " +
+		       rfb.framebufferHeight);
+
+    setEncodings();
+
+    showConnectionStatus(null);
+  }
+
+
+  //
+  // Send current encoding list to the RFB server.
+  //
+
+  int[] encodingsSaved;
+  int nEncodingsSaved;
+
+  void setEncodings()        { setEncodings(false); }
+  void autoSelectEncodings() { setEncodings(true); }
+
+  void setEncodings(boolean autoSelectOnly) {
+    if (options == null || rfb == null || !rfb.inNormalProtocol)
+      return;
+
+    int preferredEncoding = options.preferredEncoding;
+    if (preferredEncoding == -1) {
+      long kbitsPerSecond = rfb.kbitsPerSecond();
+      if (nEncodingsSaved < 1) {
+        // Choose Tight or ZRLE encoding for the very first update.
+        System.out.println("Using Tight/ZRLE encodings");
+        preferredEncoding = RfbProto.EncodingTight;
+      } else if (kbitsPerSecond > 2000 &&
+                 encodingsSaved[0] != RfbProto.EncodingHextile) {
+        // Switch to Hextile if the connection speed is above 2Mbps.
+        System.out.println("Throughput " + kbitsPerSecond +
+                           " kbit/s - changing to Hextile encoding");
+        preferredEncoding = RfbProto.EncodingHextile;
+      } else if (kbitsPerSecond < 1000 &&
+                 encodingsSaved[0] != RfbProto.EncodingTight) {
+        // Switch to Tight/ZRLE if the connection speed is below 1Mbps.
+        System.out.println("Throughput " + kbitsPerSecond +
+                           " kbit/s - changing to Tight/ZRLE encodings");
+        preferredEncoding = RfbProto.EncodingTight;
+      } else {
+        // Don't change the encoder.
+        if (autoSelectOnly)
+          return;
+        preferredEncoding = encodingsSaved[0];
+      }
+    } else {
+      // Auto encoder selection is not enabled.
+      if (autoSelectOnly)
+        return;
+    }
+
+    int[] encodings = new int[20];
+    int nEncodings = 0;
+
+    encodings[nEncodings++] = preferredEncoding;
+    if (options.useCopyRect) {
+      encodings[nEncodings++] = RfbProto.EncodingCopyRect;
+    }
+
+    if (preferredEncoding != RfbProto.EncodingTight) {
+      encodings[nEncodings++] = RfbProto.EncodingTight;
+    }
+    if (preferredEncoding != RfbProto.EncodingZRLE) {
+      encodings[nEncodings++] = RfbProto.EncodingZRLE;
+    }
+    if (preferredEncoding != RfbProto.EncodingHextile) {
+      encodings[nEncodings++] = RfbProto.EncodingHextile;
+    }
+    if (preferredEncoding != RfbProto.EncodingZlib) {
+      encodings[nEncodings++] = RfbProto.EncodingZlib;
+    }
+    if (preferredEncoding != RfbProto.EncodingCoRRE) {
+      encodings[nEncodings++] = RfbProto.EncodingCoRRE;
+    }
+    if (preferredEncoding != RfbProto.EncodingRRE) {
+      encodings[nEncodings++] = RfbProto.EncodingRRE;
+    }
+
+    if (options.compressLevel >= 0 && options.compressLevel <= 9) {
+      encodings[nEncodings++] =
+        RfbProto.EncodingCompressLevel0 + options.compressLevel;
+    }
+    if (options.jpegQuality >= 0 && options.jpegQuality <= 9) {
+      encodings[nEncodings++] =
+        RfbProto.EncodingQualityLevel0 + options.jpegQuality;
+    }
+
+    if (options.requestCursorUpdates) {
+      encodings[nEncodings++] = RfbProto.EncodingXCursor;
+      encodings[nEncodings++] = RfbProto.EncodingRichCursor;
+      if (!options.ignoreCursorUpdates)
+	encodings[nEncodings++] = RfbProto.EncodingPointerPos;
+    }
+
+    encodings[nEncodings++] = RfbProto.EncodingLastRect;
+    encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
+
+    boolean encodingsWereChanged = false;
+    if (nEncodings != nEncodingsSaved) {
+      encodingsWereChanged = true;
+    } else {
+      for (int i = 0; i < nEncodings; i++) {
+        if (encodings[i] != encodingsSaved[i]) {
+          encodingsWereChanged = true;
+          break;
+        }
+      }
+    }
+
+    if (encodingsWereChanged) {
+      try {
+        rfb.writeSetEncodings(encodings, nEncodings);
+        if (vc != null) {
+          vc.softCursorFree();
+        }
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+      encodingsSaved = encodings;
+      nEncodingsSaved = nEncodings;
+    }
+  }
+
+
+  //
+  // setCutText() - send the given cut text to the RFB server.
+  //
+
+  void setCutText(String text) {
+    try {
+      if (rfb != null && rfb.inNormalProtocol) {
+	rfb.writeClientCutText(text);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+
+  //
+  // Order change in session recording status. To stop recording, pass
+  // null in place of the fname argument.
+  //
+
+  void setRecordingStatus(String fname) {
+    synchronized(recordingSync) {
+      sessionFileName = fname;
+      recordingStatusChanged = true;
+    }
+  }
+
+  //
+  // Start or stop session recording. Returns true if this method call
+  // causes recording of a new session.
+  //
+
+  boolean checkRecordingStatus() throws IOException {
+    synchronized(recordingSync) {
+      if (recordingStatusChanged) {
+	recordingStatusChanged = false;
+	if (sessionFileName != null) {
+	  startRecording();
+	  return true;
+	} else {
+	  stopRecording();
+	}
+      }
+    }
+    return false;
+  }
+
+  //
+  // Start session recording.
+  //
+
+  protected void startRecording() throws IOException {
+    synchronized(recordingSync) {
+      if (!recordingActive) {
+	// Save settings to restore them after recording the session.
+	cursorUpdatesDef =
+	  options.choices[options.cursorUpdatesIndex].getSelectedItem();
+	eightBitColorsDef =
+	  options.choices[options.eightBitColorsIndex].getSelectedItem();
+	// Set options to values suitable for recording.
+	options.choices[options.cursorUpdatesIndex].select("Disable");
+	options.choices[options.cursorUpdatesIndex].setEnabled(false);
+	options.setEncodings();
+	options.choices[options.eightBitColorsIndex].select("No");
+	options.choices[options.eightBitColorsIndex].setEnabled(false);
+	options.setColorFormat();
+      } else {
+	rfb.closeSession();
+      }
+
+      System.out.println("Recording the session in " + sessionFileName);
+      rfb.startSession(sessionFileName);
+      recordingActive = true;
+    }
+  }
+
+  //
+  // Stop session recording.
+  //
+
+  protected void stopRecording() throws IOException {
+    synchronized(recordingSync) {
+      if (recordingActive) {
+	// Restore options.
+	options.choices[options.cursorUpdatesIndex].select(cursorUpdatesDef);
+	options.choices[options.cursorUpdatesIndex].setEnabled(true);
+	options.setEncodings();
+	options.choices[options.eightBitColorsIndex].select(eightBitColorsDef);
+	options.choices[options.eightBitColorsIndex].setEnabled(true);
+	options.setColorFormat();
+
+	rfb.closeSession();
+	System.out.println("Session recording stopped.");
+      }
+      sessionFileName = null;
+      recordingActive = false;
+    }
+  }
+
+
+  //
+  // readParameters() - read parameters from the html source or from the
+  // command line.  On the command line, the arguments are just a sequence of
+  // param_name/param_value pairs where the names and values correspond to
+  // those expected in the html applet tag source.
+  //
+
+  void readParameters() {
+    host = readParameter("HOST", !inAnApplet);
+    if (host == null) {
+      host = getCodeBase().getHost();
+      if (host.equals("")) {
+	fatalError("HOST parameter not specified");
+      }
+    }
+
+    String str = readParameter("PORT", true);
+    port = Integer.parseInt(str);
+
+    // Read "ENCPASSWORD" or "PASSWORD" parameter if specified.
+    readPasswordParameters();
+
+    if (inAnApplet) {
+      str = readParameter("Open New Window", false);
+      if (str != null && str.equalsIgnoreCase("Yes"))
+	inSeparateFrame = true;
+    }
+
+    // "Show Controls" set to "No" disables button panel.
+    showControls = true;
+    str = readParameter("Show Controls", false);
+    if (str != null && str.equalsIgnoreCase("No"))
+      showControls = false;
+
+    // "Offer Relogin" set to "No" disables "Login again" and "Close
+    // window" buttons under error messages in applet mode.
+    offerRelogin = true;
+    str = readParameter("Offer Relogin", false);
+    if (str != null && str.equalsIgnoreCase("No"))
+      offerRelogin = false;
+
+    // Do we continue showing desktop on remote disconnect?
+    showOfflineDesktop = false;
+    str = readParameter("Show Offline Desktop", false);
+    if (str != null && str.equalsIgnoreCase("Yes"))
+      showOfflineDesktop = true;
+
+    // Fine tuning options.
+    deferScreenUpdates = readIntParameter("Defer screen updates", 20);
+    deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
+    deferUpdateRequests = readIntParameter("Defer update requests", 50);
+
+    // SocketFactory.
+    socketFactory = readParameter("SocketFactory", false);
+  }
+
+  //
+  // Read password parameters. If an "ENCPASSWORD" parameter is set,
+  // then decrypt the password into the passwordParam string. Otherwise,
+  // try to read the "PASSWORD" parameter directly to passwordParam.
+  //
+
+  private void readPasswordParameters() {
+    String encPasswordParam = readParameter("ENCPASSWORD", false);
+    if (encPasswordParam == null) {
+      passwordParam = readParameter("PASSWORD", false);
+    } else {
+      // ENCPASSWORD is hexascii-encoded. Decode.
+      byte[] pw = {0, 0, 0, 0, 0, 0, 0, 0};
+      int len = encPasswordParam.length() / 2;
+      if (len > 8)
+        len = 8;
+      for (int i = 0; i < len; i++) {
+        String hex = encPasswordParam.substring(i*2, i*2+2);
+        Integer x = new Integer(Integer.parseInt(hex, 16));
+        pw[i] = x.byteValue();
+      }
+      // Decrypt the password.
+      byte[] key = {23, 82, 107, 6, 35, 78, 88, 7};
+      DesCipher des = new DesCipher(key);
+      des.decrypt(pw, 0, pw, 0);
+      passwordParam = new String(pw);
+    }
+  }
+
+  public String readParameter(String name, boolean required) {
+    if (inAnApplet) {
+      String s = getParameter(name);
+      if ((s == null) && required) {
+	fatalError(name + " parameter not specified");
+      }
+      return s;
+    }
+
+    for (int i = 0; i < mainArgs.length; i += 2) {
+      if (mainArgs[i].equalsIgnoreCase(name)) {
+	try {
+	  return mainArgs[i+1];
+	} catch (Exception e) {
+	  if (required) {
+	    fatalError(name + " parameter not specified");
+	  }
+	  return null;
+	}
+      }
+    }
+    if (required) {
+      fatalError(name + " parameter not specified");
+    }
+    return null;
+  }
+
+  int readIntParameter(String name, int defaultValue) {
+    String str = readParameter(name, false);
+    int result = defaultValue;
+    if (str != null) {
+      try {
+	result = Integer.parseInt(str);
+      } catch (NumberFormatException e) { }
+    }
+    return result;
+  }
+
+  //
+  // moveFocusToDesktop() - move keyboard focus either to VncCanvas.
+  //
+
+  void moveFocusToDesktop() {
+    if (vncContainer != null) {
+      if (vc != null && vncContainer.isAncestorOf(vc))
+	vc.requestFocus();
+    }
+  }
+
+  //
+  // disconnect() - close connection to server.
+  //
+
+  synchronized public void disconnect() {
+    System.out.println("Disconnect");
+
+    if (rfb != null && !rfb.closed())
+      rfb.close();
+    options.dispose();
+    clipboard.dispose();
+    if (rec != null)
+      rec.dispose();
+
+    if (inAnApplet) {
+      showMessage("Disconnected");
+    } else {
+      System.exit(0);
+    }
+  }
+
+  //
+  // fatalError() - print out a fatal error message.
+  // FIXME: Do we really need two versions of the fatalError() method?
+  //
+
+  synchronized public void fatalError(String str) {
+    System.out.println(str);
+
+    if (inAnApplet) {
+      // vncContainer null, applet not inited,
+      // can not present the error to the user.
+      Thread.currentThread().stop();
+    } else {
+      System.exit(1);
+    }
+  }
+
+  synchronized public void fatalError(String str, Exception e) {
+ 
+    if (rfb != null && rfb.closed()) {
+      // Not necessary to show error message if the error was caused
+      // by I/O problems after the rfb.close() method call.
+      System.out.println("RFB thread finished");
+      return;
+    }
+
+    System.out.println(str);
+    e.printStackTrace();
+
+    if (rfb != null)
+      rfb.close();
+
+    if (inAnApplet) {
+      showMessage(str);
+    } else {
+      System.exit(1);
+    }
+  }
+
+  //
+  // Show message text and optionally "Relogin" and "Close" buttons.
+  //
+
+  void showMessage(String msg) {
+    vncContainer.removeAll();
+
+    Label errLabel = new Label(msg, Label.CENTER);
+    errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+
+    if (offerRelogin) {
+
+      Panel gridPanel = new Panel(new GridLayout(0, 1));
+      Panel outerPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
+      outerPanel.add(gridPanel);
+      vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 16));
+      vncContainer.add(outerPanel);
+      Panel textPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
+      textPanel.add(errLabel);
+      gridPanel.add(textPanel);
+      gridPanel.add(new ReloginPanel(this));
+
+    } else {
+
+      vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
+      vncContainer.add(errLabel);
+
+    }
+
+    if (inSeparateFrame) {
+      vncFrame.pack();
+    } else {
+      validate();
+    }
+  }
+
+  //
+  // Stop the applet.
+  // Main applet thread will terminate on first exception
+  // after seeing that rfbThread has been set to null.
+  //
+
+  public void stop() {
+    System.out.println("Stopping applet");
+    rfbThread = null;
+  }
+
+  //
+  // This method is called before the applet is destroyed.
+  //
+
+  public void destroy() {
+    System.out.println("Destroying applet");
+
+    vncContainer.removeAll();
+    options.dispose();
+    clipboard.dispose();
+    if (rec != null)
+      rec.dispose();
+    if (rfb != null && !rfb.closed())
+      rfb.close();
+    if (inSeparateFrame)
+      vncFrame.dispose();
+  }
+
+  //
+  // Start/stop receiving mouse events.
+  //
+
+  public void enableInput(boolean enable) {
+    vc.enableInput(enable);
+  }
+
+  //
+  // Close application properly on window close event.
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    System.out.println("Closing window");
+    if (rfb != null)
+      disconnect();
+
+    vncContainer.hide();
+
+    if (!inAnApplet) {
+      System.exit(0);
+    }
+  }
+
+  //
+  // Ignore window events we're not interested in.
+  //
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowDeactivated (WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/WhatsNew
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/WhatsNew	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/WhatsNew	(revision 571)
@@ -0,0 +1,930 @@
++--------------------------------------------------------------------+
+|   This is a brief summary of changes introduced in each TightVNC   |
+|   release. For more details, please see ChangeLog files included   |
+|   in TightVNC source and binary archives.                          |
++--------------------------------------------------------------------+
+
+* TightVNC 1.3.9
+
+  - All platforms: Added support for the standard RFB protocol version
+    3.8 with TightVNC extensions.
+
+  - All platforms: Made "host:port" parsing maximally compatible with
+    VNC4. Interpreting a number in host names like somehost:5900 as an
+    actual port number if it's not in the range [0..99].
+
+  - Windows Server: Various user interface enhancements - changes in
+    GUI labels, tray icon with a red border when incoming connections
+    are not possible for any reason, more information in the tray icon
+    tip, smarter logic in displaying the Properties dialog, and more.
+ 
+  - Windows Server: Fixed a problem introduced in TightVNC 1.3.8 -
+    default passwords were not respected if user passwords were not
+    set.
+
+  - Windows Server: Slightly improved handling of passwords. One of
+    the notable changes is that now it's enough to enter a view-only
+    password without providing primary password.
+
+  - Windows Server: Fixed problems with running WinVNC service and
+    Terminal Services. When a Remote Desktop (RDP) client connected to
+    the console, WinVNC showed black screen and did not restore normal
+    operations even on disconnection of that RDP client. Now, we
+    always share the console correctly, and disable simultaneous RDP
+    and VNC sessions. The changes were ported from VNC 4.1.2.
+
+  - Windows Server: Better way of simulating Ctrl+Alt+Del. There are
+    reports that this solves the problem with greyed username and
+    password fields on Windows 2003 Server (bug #887617).
+
+  - Windows Server: Bugfix for the bug #1109102: attempt to restart
+    the machine remotely via TightVNC led to disconnect if there was
+    some non-saved data, and further connections were rejected.
+
+  - Windows Viewer: Multiple selection now works in file transfers,
+    thanks to developers at Novell and personally Rohit Kumar.
+
+  - Windows Viewer: The viewer terminated silently when the server
+    dropped connection right after accepting it. Now we report such
+    errors.
+
+  - Windows version source archive: Included project files for
+    compiling with Visual C++ 2005 Express Edition. Also, all required
+    libraries are now included within the source distribution.
+
+  - Unix Server: Applied patches from Debian Linux that port Xvnc to
+    x86_64 platform (tightvnc-1.2.9-amd64support.p and
+    tightvnc-1.3_alpha7-x86_64.patch), thanks to Quanah Gibson-Mount.
+
+  - Java viewer: Implemented scaling, either with a fixed scaling
+    factor or automatic. If Java 2D API is available (Java 1.2 and
+    higher), then high-quality scaling is used. From the other side,
+    the viewer remains compatible with Java 1.1 where it would simply
+    use scaling with much decreased image quality. Scaling can be
+    enabled with new "Scaling Factor" parameter but cannot be
+    controlled from the GUI yet.
+
+  - Java viewer: Added support for ZRLE encoding.
+
+  - Java viewer: Disabled focus traversal keys under JVMs 1.4 and
+    higher. This fixes the problem with not sending Tab key events to
+    the VNC server.
+
+  - Java viewer: Fixed wrong pixel format interpretation at decoding
+    RichCursor pseudo-encoding (local cursor could be rendered in
+    wrong colors).
+
+  - Other improvements and bugfixes, see ChangeLog files within the
+    distribution for more details.
+
+* TightVNC 1.3.8, release candidate version
+
+  - Win32 server: Fixed major problem with disconnecting clients on
+    screen locking, user logoff and logon, in the service mode.
+
+  - Win32 server: Added support for DFMirage driver direct screen
+    access mode (from DemoForge LLC).
+
+  - Win32 server: Added support for multiple monitors (from DemoForge
+    LLC).
+
+  - Win32 server: Improved layout and functionality of the Properties
+    dialog.
+
+  - Win32 server: More accurate password handling - now the server
+    code tries to distinguish between "empty" and "unset" passwords
+    better.
+
+  - Win32 server: New -shareall, -shareprimary and -sharearea
+    command-line options, working similarly to the -sharewindow
+    option.
+
+  - Win32 server: Fixed problems with restoring desktop wallpaper.
+
+  - Win32 viewer: Fixed bug with not enabling JPEG compression by
+    default.
+
+  - Win32 viewer: Fixed bug with not setting proper size of the viewer
+    window.
+
+  - Unix server: Port numbers are now calculated modulo 65536 with
+    vncviewer's -listen option. That makes it possible to listen on
+    TCP ports under 5900.
+
+  - Java viewer: Automatic encoding selection based on measuring
+    current network throughput.
+
+  - Other improvements and bugfixes, see ChangeLog files within the
+    distribution for more details.
+
+* TightVNC 1.3dev7, release candidate version
+
+  - Win32 server: Fixed the problem with "olemainthreadwndname not
+    responding" in service mode under Windows NT 4.0. Under that OS,
+    the TightVNC service could hang on logoff.
+
+  - Win32 server: Removed the code for "desktop optimizations" that
+    was rather harmful than useful. Hopefully, this should fix
+    problems with crashing Delphi applications. Also this should
+    prevent settings like font smoothing always set to true on
+    disconnect.
+
+  - Win32 server: Fixed the issue with port number edit boxes that
+    were labeled incorrectly in the Properties dialog.
+
+  - Win32 server: Disallowing clipboard transfers in view-only mode.
+
+  - Win32 server: Fixed the problem with carriage return/linefeed
+    conversion of clipboard data.
+
+  - Win32 server: Fixed the problem with wallpaper being removed only
+    after completing the initial screen transmission.
+
+  - Win32 server: Minor improvements in the File Transfers dialog.
+
+  - Win32 server: More context help messages in Properties and File
+    Transfers dialogs.
+
+  - Unix server: Fixed a serious bug with sending cursor updates when
+    there was no FrameBufferUpdateRequest from that client.
+
+  - Unix server: Fixed problems with building Xvnc on modern linux
+    distributions, such as Fedora Core 3.
+
+  - Unix server: Disallowing clipboard transfers for view-only
+    clients.
+
+  - Other improvements and bugfixes, see ChangeLog files within the
+    distribution for more details.
+
+* TightVNC 1.3dev6, Win32 release candidate version
+
+  - Win32 server: Improved layout of the Properties dialog, added
+    context help for every option. Also, current mirror driver status
+    is shown in the Hooks tab.
+
+  - Win32 server: Implemented new checkbox "Enable applet params in
+    URLs" corresponding to EnableURLParams registry setting.
+
+  - Win32 server: The option "Don't use mirror display driver even if
+    available" is now functional.
+
+  - Win32 server: New option "Blank screen on client connections". 
+    When set and new client connects, the server's monitor is forced
+    to go to power saving mode.
+
+  - Win32 server: Fixed bugs with saving certain settings in the
+    registry, and bugs with setting default values when the registry
+    is not writable.
+
+  - Win32 server: Fixed a problem with one-pixel mouse offset.
+
+  - Win32 server: Fixed problems with inter-thread locking, this
+    should solve "Unhandled message type received" problems.
+
+  - Win32 server: Fixed a problem with the setting "Block remote input
+    on local activity", it was not working with DLL hooks disabled.
+
+  - Win32 server: Fixed various problems with file transfer
+    implementation. Error handling was improved, the C: drive bug
+    under Win98/Me seems to be solved.
+
+  - Win32 viewer: New "Auto" scaling mode. In this mode, the viewer
+    scales remote desktop to fit local window or screen size. If the
+    window size is changed, the scaling factor is adjusted
+    automatically.
+
+  - Win32 viewer: Now the viewer checks server's capabilities and does
+    not ever use non-standard protocol messages not supported by the
+    server. This change affects file transfers only, as other features
+    do not use non-standard protocol messages..
+
+  - Java viewer: New "scale remote cursor" option has been added. It
+    allows to reduce or enlarge cursor image in the full-control mode.
+
+  - Java viewer: A cursor repaint problem has been fixed.
+
+  - Other improvements and bugfixes, see ChangeLog files within the
+    distribution for more details.
+
+* TightVNC 1.3dev5, development version
+
+  - Win32 server: Support for the "DFMirage" mirror video driver has
+    been added (the driver itself will be available separately). Using
+    the mirror driver greatly increases the speed and reliability of
+    updates, and also desreases CPU utilization on the server.
+
+  - Win32 server: New polling algorithm has been implemented. It's
+    similar to that found in x0rfbserver. New algorithm uses minimum
+    CPU time when there are no changes on the screen, and detects
+    major changes very quickly, resulting in greatly improved
+    responsiveness on the client side.
+
+  - Win32 server: Improved methods for filtering screen changes that
+    actually do not change anything. New algorithm not only works
+    faster, but it also detects changes much more accurately, leaving
+    less work to encoders.
+
+  - Win32 viewer: A special mode for Unix users has been implemented:
+    when ScrollLock is on, the viewer will send Meta on pressing Alt
+    keys.
+
+  - Win32 server: Fixed a problem with view-only clients that were
+    enabled full control on just opening the Properties dialog of the
+    server.
+
+  - Win32 server: It should not ever hang any more on changing ports
+    or the LoopbackOnly setting.
+
+  - Win32 server: DisableTrayIcon and RemoveWallpaper settings are
+    working again.
+
+  - Win32 server: The problem with not saving Query Settings has been
+    fixed.
+
+  - Win32 server: The polling mode "on event received only" has been
+    fixed - it did not work correctly in previous version.
+
+  - Win32 server: Fixed a number of issues with mouse handling,
+    including that annoying problem with pointer jumping on slow
+    connections.
+
+  - Win32 server: Applied a bugfix from HorizonLive solving the
+    problem with crashes or incorrect operation after color depth
+    changes on the server's desktop.
+
+  - Win32 viewer: It does not crash any more on entering long
+    passwords in the authentication window.
+
+  - Win32 viewer: Positioning and resizing logic of the viewer window
+    has been improved.
+
+  - Win32 viewer: Now the viewer chooses more reasonable file names
+    for saved .vnc sessions.
+
+  - Win32 viewer: In the full-screen mode, the viewer allows other
+    windows to be shown above the remote desktop. This makes hotkeys
+    such as Shift-Ctrl-Alt-O useful in the full-screen mode.
+
+  - Unix version: A number of bugfixes -- copying clipboard to
+    non-authenticated clients in Xvnc, delayed cursor shape updates in
+    Xvnc, and crashing on switching between KDE virtual desktops in
+    vncviewer.
+
+  - Unix viewer: Support for the new -autopass option has been added,
+    a patch from Ki NETWORKS, Inc.
+
+  - Other changes, see ChangeLog files within the distribution for
+    more details.
+
+* TightVNC 1.3dev4, development version
+
+  - Featuring updated Unix version and Java viewer, supporting RFB
+    protocol version 3.7, with or without TightVNC protocol
+    extensions. Version 3.3 of the protocol is supported as well.
+
+  - Win32 version: Built-in Java viewer was absent in the previous
+    development version; now it's available again.
+
+  - Win32 version: Now the server does not crash on remote
+    Ctrl-Alt-Del events, and on changing display modes.
+
+  - Win32 version: A problem with reinstalling the service has been
+    fixed (WinVNC -reinstall command-line option). In previous
+    versions, reinstalling the service could fail if a user did not
+    close "Service unregistered" message box within a few seconds.
+
+  - Win32 version: Now the server does not hang on selecting equal RFB
+    and HTTP port numbers. A warning is shown instead.
+
+  - Win32 version: The server does not hang on toggling loopback
+    connection options, and on changing port/display numbers.
+
+  - Win32 version: WinVNC does not crash on choosing "Kill All
+    Clients" during file download.
+
+  - Win32 version: CopyRect handling in the server has been fixed, the
+    CopyRect encoding is enabled again.
+
+  - Win32 version: The Advanced Properties dialog of the server has
+    been removed. The controls of that dialog has been moved to tabs
+    in the Properties dialog.
+
+  - Win32 version: Context help in the server's Properties dialog has
+    been implemented (although not all descriptions are ready yet).
+
+  - Unix viewer: Fixed a bug with the viewer crashing on selecting
+    text in Xvnc, and then choosing F8 / Clipboard: local -> remote,
+    twice.
+
+  - There was some progress on supporting pluggable encryption and
+    authentication methods, in both Win32 and Unix versions, and in
+    the Java viewer.
+
+  - Other changes, see ChangeLog files within the distribution for
+    more details.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.3dev3, Win32 development (unstable) version
+
+  - All features and fixes from 1.2.9 and 1.3dev1 versions included.
+
+  - Improved GUI of the viewer featuring toolbar, hotkeys, pre-set
+    connection profiles, more configuration options, context help in
+    dialogs, and more. Finally, the viewer remembers all
+    per-connection and global settings in the registry.
+
+  - File transfers between viewer and server machines.
+
+  - Support for RFB protocol version 3.7, with TightVNC extensions.
+
+  - A possibility to turn off hooking via VNCHooks.dll in WinVNC while
+    full screen polling is in use.
+
+  - Other changes, see ChangeLog files within the distribution for
+    more details.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.9
+
+  - Win32 version: Major security-related bug in the server has been
+    fixed -- handling of the "QueryAllowNoPass" option was seriously
+    broken. Together with fixing this bug, the whole authentication
+    logic in the server code has been redesigned.
+
+  - Win32 version: Now the HKEY_CURRENT_USER registry hive is being
+    closed properly on restoring display settings, on disconnect. This
+    change should solve the problem with unloading the registry on
+    logout, when WinVNC is running as a service.
+
+  - Win32 version: Problems with "QuerySetting" and "QueryTimeout"
+    options have been fixed -- the settings could be copied from user
+    configuration to default settings without user's intention.
+
+  - Win32 version: A long-standing bug has been fixed -- the logic to
+    handle retries after authentication failures was flawed, and used
+    to delete the same object twice under certain conditions.
+
+  - Win32 version: Now it's possible to specify port numbers with the
+    winvnc -connect option, using the "host::port" format. Also,
+    providing a -connect option without arguments now brings up the
+    "Add New Client" dialog.
+
+  - Unix version: New "Request refresh" button has been implemented in
+    the viewer's F8 popup menu.
+
+  - Unix version: Xvnc compilation fixes for HP-UX and MacOS X have
+    been applied, from Ki NETWORKS, Inc.
+
+  - Unix version: New vncpasswd -f command-line option has been
+    implemented. It allows providing passwords on stdin and writes
+    encrypted passwords to stdout. In addition, the password file name
+    "-" now denotes stdout. Finally, a buffer overflow has been fixed
+    in vncpasswd -- it could be caused by a long file name in the
+    command line.
+
+  - Unix version: A patch to fix input focus problems in the X11
+    viewer has been applied, from Greg Breland.
+
+  - Unix version: A patch fixing Xvnc crashes on Sparc has been
+    applied, from the RealVNC distribution.
+
+  - Unix version: A problem with incorrect port interpretation has
+    been fixed, in the vncviewer's -tunnel option handling. Thanks to
+    Clark Sessions.
+
+  - Java viewer: A modification from Bernd Krueger-Knauber has been
+    accepted, to pass through X keysyms for foreign currencies.
+
+  - Java viewer: The problem with initial keyboard focus not set to
+    the desktop on some JVMs has been fixed.
+
+  - Other minor improvements and bugfixes.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.8
+
+  - Unix and Win32 versions: Support for a separate view-only password
+    has been implemented. Now the servers support two passwords -- one
+    to allow full control, another to restrict remote keyboard and
+    mouse input.
+
+  - Win32 version: The password reset problem has been solved. In
+    versions starting from 1.2.4, the password could get changed in
+    the registry on opening Properties dialog and just hitting the OK
+    button.
+
+  - Win32 version: New "-reload" command-line option has been
+    implemented in Win32 server. It forces the running instance to
+    reload the registry settings.
+
+  - Win32 version: "RemoveWallpaper" and "LockSetting" options have
+    been made configurable in the Properties dialog; the code has been
+    ported from RealVNC 3.3.6.
+
+  - Win32 version: Support for "AllowEditClients" registry setting has
+    been ported from RealVNC 3.3.6.
+
+  - Unix version: New "-x11cursor" option has been implemented in
+    vncviewer; a patch from Peter Astrand. This option allows using a
+    real X11 cursor with X11-style cursor shape updates, disables the
+    dot cursor, and disables cursor position updates in non-fullscreen
+    mode.
+
+  - Unix version: New "RunCommand" command to customize the X11
+    vncviewer popup menu has been implemented; a patch from Peter
+    Astrand.
+
+  - Unix version: Several patches from Debian Linux have been applied.
+    This should fix a number of bugs and improve building on some
+    platforms supported by Debian Linux.
+
+  - Unix version: A problem with Xvnc eating all CPU time after xfs
+    restarts has been fixed; a patch from Martin Koegler.
+
+  - Other minor improvements and bugfixes.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.7
+
+  - Unix and Win32 versions, Java viewer: The most significant problem
+    with local cursor handling has been solved -- now clients can see
+    remote cursor movements performed on the server or by another
+    client. New PointerPos encoding and cursor shape updates both
+    minimize bandwidth requirements and greatly improve responsiveness
+    of the mouse pointer, while still allow to track correct pointer
+    position in all situations.
+
+  - Unix and Win32 versions: In all the places where display numbers
+    had to be used, now it's easy to use port numbers as well. The
+    viewers now allow to use new "hostname::port" syntax, in addition
+    to the traditional "hostname:display" format. The same new syntax
+    can be used in the "Add new client" dialog of Win32 server. In the
+    server, now it's equally easy to set display and port numbers. 
+    Besides that, HTTP and RFB port numbers can be set individually.
+
+  - Unix and Win32 versions: In servers, decreased JPEG quality
+    factors for low quality levels. This improves bandwidth usage
+    while the image quality remains satisfactory in most cases. In
+    clients, JPEG compression is now enabled by default, because
+    usually it's a reasonable choice. To prevent viewers from
+    requesting JPEG compression, new -nojpeg option can be used.
+
+  - Unix and Win32 versions: Improved installer under Windows, better
+    RPMs for Linux.
+
+  - Win32 version: Major enhancements in layout and functionality of
+    the dialog boxes.
+
+  - Win32 version: New keyboard handling code has been ported from
+    RealVNC 3.3.6. This should solve all the issues with arrow keys
+    acting as numbers in console windows, and shift+arrows not working
+    under Win2k.
+
+  - Win32 version: Adopted WinVNC -reinstall option from RealVNC
+    3.3.5, together with a number of other changes in different
+    places. The viewer now accepts a port number after the -listen
+    command-line option, an improvement from RealVNC 3.3.6.
+
+  - Win32 version: Eliminated high CPU usage on the server before
+    sending cursor shape updates.
+
+  - Unix version: Bugfix for Xvnc's -localhost and -interface options
+    that were broken on many systems, thanks to Luke Mewburn for the
+    bugfix. Xvnc -version command-line option is now supported.
+
+  - Tight encoding is now documented in rfbproto.h files within source
+    archives.
+
+  - Java viewer: Implemented new buttons "Login again" and "Close
+    window" near the disconnect or error messages in the applet mode,
+    and introduced new "Offer Relogin" parameter to control this
+    improvement. Thanks to Peter Astrand for the initial version of
+    the "Login again" patch.
+
+  - Java viewer: Support for connections via HTTP proxies using HTTP
+    CONNECT method. This will not work in the applet mode, due to Java
+    security restrictions.
+
+  - Java viewer: Extra .vnc files have been removed, having just
+    index.vnc should be enough. Also, an example HTML page has been
+    prepared, to simplify installation under a standalone Web server.
+
+  - Java viewer: Added a MANIFEST to the JAR archive, to allow easy
+    execution of the JAR file, using java -jar command-line option.
+
+  - Other minor improvements and bugfixes.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.3dev1, Win32 development (unstable) version
+
+  - Implemented partial screen sharing. Any single window or any
+    rectangular screen area can be shared instead of the whole screen.
+    The position and dimensions of the shared screen area can be
+    changed dynamically, and client windows will adjust their
+    dimensions on the fly. The user interface to choose shared screen
+    area is very intuitive and easy to use.
+
+  - Screen resolution changes won't cause WinVNC to disconnect clients
+    any more (but changes in screen color format still result in
+    disconnects, this will be fixed later).
+
+  - It's possible to make WinVNC ignore remote inputs when local mouse
+    or keyboard is in use. Remote events will be re-enabled after a
+    specified timeout (3 seconds by default).
+
+  - There may be other changes I forgot to mention. :-)
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.6
+
+  - Win32 version: In this version, when WinVNC binds to a local TCP
+    port, it does not try to check several times if the port is in
+    use. It just re-uses the port if the display number is not set to
+    "Auto". One visible effect of this change is that the delay
+    between starting up and showing the icon is greatly reduced.
+
+  - Unix version: Fixed the bug which caused the vncserver script to
+    fail when the XAUTHORITY environment variable was not set.
+
+  - Unix version: Fixed the bug which prevented the vncpasswd utility
+    from setting correct permissons on the passwd file.
+
+  - Unix version: Fixed a repeated challenge replay attack
+    vulnerability, bugtraq id 5296.
+
+  - Unix version: Added files to simplify building of Linux RPMs,
+    thanks to Peter Astrand.
+
+  - Unix version: Improved scrolling in the full-screen mode, modified
+    patch from Ville Herva.
+
+  - Minor cleanups.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.5
+
+  - Win32 version: Fixed a problem in the I/O subsystem that was
+    introduced in TightVNC 1.2.2 and was causing major slowdown in
+    communication with clients.
+
+  - Win32 version: Enabled remote upgrade in the installation script. 
+    Also, the installer will install a copy of the TightVNC Web site,
+    and will create shortcuts to most important documentation pages.
+
+  - Win32 version: Implemented new feature to specify applet
+    parameters in URL requests being sent to the built-in HTTP server. 
+    Added support for new "EnableURLParams" registry setting which can
+    be used to enable this feature.
+
+  - Win32 version: Added support for the NewFBSize pseudo-encoding
+    allowing to change framebuffer geometry on the fly on server's
+    request.
+
+  - Win32 version: Included "solution" and "project" files for MS
+    Visual Studio 7, from Andrew van der Stock, applied a set of minor
+    fixes to suppress compilation warnings under MS Visual Studio 7.
+
+  - Win32 version: The viewer now tries to preserve the size and
+    position of the desktop window after applying new connection
+    options.
+
+  - Unix version: Implemented new feature to specify applet parameters
+    in URL requests being sent to the built-in HTTP server. Added
+    support for new $PARAMS variable in .vnc HTML templates.
+
+  - Unix version: Added the possibility to keep users' vnc directories
+    under /tmp, as suggested by Ivan Popov. This mode can be enabled
+    by editing the $vncUserDir variable in the vncserver script. Also,
+    new -t option has been implemented in the vncpasswd utility which
+    allows to change VNC password files under /tmp.
+
+  - Unix version: Applied Xvnc -viewonly patch from Ehud Karni.
+
+  - Unix version: Applied Linux/PowerPC Xvnc fix from Peter A. Castro.
+
+  - Unix version: Bug fixed: Xvnc failed to reset compression level
+    and JPEG image quality on reading lists of encodings supported by
+    clients.
+
+  - Unix version: Made the viewer handle XCursor encoding operating on
+    the framebuffer instead of setting new cursors directly in X.
+
+  - Unix version: Applied a number of porting fixes from Ki Networks,
+    Inc.
+
+  - Java viewer: Added new feature allowing to save RFB sessions in
+    FBS files compatible with rfbproxy. This feature works only if JVM
+    security manager allows access to the local filesystem, which is
+    usually true only when the viewer is used as a standalone
+    application or if the viewer applet is cryptographically signed.
+    New "Record" button will appear in the button panel if this
+    feature is enabled.
+
+  - Java viewer: Added new "ENCPASSWORD" parameter, modified patch
+    from Peter Astrand.
+
+  - Java viewer: Applied patch from Peter Astrand to fix problems with
+    Swedish keys and broken JVMs.
+
+  - Other minor fixes and cleanups.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.4
+
+  - Win32 version: WinVNC crashes on reporting zero statistics were
+    fixed. This should eliminate crashes when using x2vnc and win2vnc
+    client programs.
+
+  - Win32 version: a problem with listening viewer was fixed.
+    Initiating multiple non-shared connections could crash the viewer
+    application.
+
+  - Win32 version: real passwords are never placed into the password
+    text control in the WinVNC Properties dialog any more. This should
+    prevent grabbing plain-text passwords from that text control.
+
+  - Win32 version: logging on errors was improved to provide better
+    diagnosis for errors, especially for those causing the message
+    "Connection closed" right after authentication.
+
+  - Win32 version: handling of log files was improved. Now WinVNC
+    should be able to save backup copies of log files under
+    Win95/98/Me. Also, all log files are now written in MS-DOS/Windows
+    text format instead of the Unix one.
+
+  - Win32 version: a problem with reporting error messages in the
+    listening viewer was fixed.
+
+  - Win32 version: reporting incorrect statistics in the Tight encoder
+    was fixed.
+
+  - Win32 version: HTML pages and templates for the built-in HTTP
+    server were improved.
+
+  - Unix version: applied patch from Ki Networks, Inc. solving build
+    problems on a number of commercial Unix systems, and fixing a
+    number of minor bugs and typos.
+
+  - Unix version: added a possibility to denote standard input with
+    the "-" file name instead of a real password file name.
+
+  - Unix version: fixed a bug causing vncpasswd utility work
+    incorrectly when a file name argument was given in the command
+    line.
+
+  - Unix version: applied patch to solve keyboard focus problems in
+    the full-screen vncviewer, from Peter Astrand. The patch does not
+    seem to solve all the issues, but definitely makes things better.
+    New grabKeyboard resource was added to control full-screen mode
+    behavior.
+
+  - Java viewer: new "Show Offline Desktop" parameter was added to
+    make the desktop still visible even after the remote side has
+    closed connection.
+
+  - Java viewer: error messages were made much more meaningful.
+
+  - Java viewer: keyboard focus problems were fixed. This should
+    prevent opening new windows (e.g. Options or Clipboard) behind the
+    active authenticator or desktop window.
+
+  - Java viewer: now "R"/"r" keys can be used to request screen
+    updates in view-only mode.
+
+  - Java viewer: applied patch from Peter Astrand to fix problems with
+    Swedish keys and broken JVMs.
+
+  - Other minor fixes and cleanups.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.3
+
+  - Unix and Win32 versions: zlib library was updated to the most
+    recent version (1.1.4) where a potential security issue was fixed.
+
+  - Unix and Win32 versions: fixed blocking I/O problems in built-in
+    HTTP servers. Older versions had to wait while one client finishes
+    his transaction, only then they served new client connections,
+    thus making easy denial-of-service attacks possible.
+
+  - Unix and Win32 versions: updated built-in Java viewer, see details
+    below.
+
+  - Win32 version: Added support for mouse wheel events. Wheel mouse
+    support is fully compatible and interoperable with Unix version
+    where this feature was available for a long time.
+
+  - Win32 version (WinVNC): The -connect command-line option now
+    accepts a display number after a hostname.
+
+  - Win32 version: Creating associations for .vnc files in the
+    installer.
+
+  - Java viewer was GREATLY improved: the code was converted to Java
+    1.1, painting techniques were re-designed completely (now the
+    viewer should work in MacOS), several new parameters were added,
+    all parameters were documented in the README file. Most important
+    new features include: support for 24-bit colors, JPEG support in
+    Tight encoding, RFB Bell message support, new "Refresh" button, a
+    possibility to operate in a separate scrollable window, dynamic
+    view-only mode. Many more changes were introduces, see the
+    ChangeLog for more information. Please note that new Java viewer
+    class names were changed, e.g. vncviewer.jar file has become
+    VncViewer.jar etc.
+
+  - Unix version: a number of changes in the vncserver script, e.g.
+    the default color depth is now 24, extra delay after Xvnc startup
+    removed, font path is now configurable in the beginning of the
+    script, and more.
+
+  - Unix version: zlib library was removed from the core X sources.
+    Instead, both vncviewer and Xvnc now can use either system zlib
+    and JPEG libraries, or ones packaged within TightVNC source
+    archive in the lib/ directory. Unix sources are distributed in two
+    versions: one with these libraries for those who don't have them
+    installed in the system, and another version without libraries,
+    copied directly from CVS, for those who do have zlib and/or JPEG
+    libraries installed. In the former case, build procedure would
+    include additional "make libs" step. System libraries will be
+    linked dynamically, libraries included in the source archive will
+    be linked in statically.
+
+  - Unix version now includes comprehensive manual pages for
+    vncviewer, vncserver, Xvnc, vncconnect and vncpasswd programs. The
+    vncinstall script in the source distribution now accepts one more
+    parameter allowing to specify where to install manual pages.
+
+  - Unix version (Xvnc): a number of patches from Red Hat Linux vnc
+    package were incorporated into the TightVNC codebase. This adds
+    support for more architectures including s390 and s390x, adds a
+    possibility to use tcp_wrappers for Xvnc access control.
+
+  - Unix version (Xvnc): several bugfixes, e.g. applied patch to fix
+    crash in the code dealing with font server; fixed word alignment
+    problem in raw encoder experienced by Sparc users.
+
+  - Unix version is no more distributed as patches to a standard VNC
+    release. This is because patches cannot handle changes in binary
+    files and handle file removals very inefficiently.
+
+  - Other minor fixes and cleanups.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.2
+
+  - Win32 server: long-standing Win9x resource consumption problem has
+    been fixed. Now the server thread does not use blocking I/O, and
+    therefore is always ready to process messages from the VNCHooks
+    DLL.
+
+  - Win32 server: now built-in HTTP daemon may be enabled and disabled
+    interactively from the Advanced Preferences dialog (this setting
+    is saved in new "EnableHTTPDaemon" registry key).
+
+  - Win32 server: changes in layout and text of the Advanced
+    Preferences dialog.
+
+  - Xvnc: Minor bugfix which should prevent potential dereference of a
+    NULL pointer.
+
+  - Unix viewer: Now viewer window would be raised on beep (bell)
+    event, unless new -noraiseonbeep option is provided in the command
+    line or "raiseOnBeep" resource set to False.
+
+  - One more packaging option for the Unix source: ready to build
+    archive with Zlib and JPEG libraries inside.
+
+  - Other minor fixes and cleanups.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.1
+
+  - Win32 server: added support for reverse connections on ports other
+    than 5500, modified patch from Steve Kann.
+
+  - Win32 viewer: added support for new command-line options:
+    -noshared and -encoding XXX.
+
+  - Bugfixes in Win32 viewer: changes in exception handling eliminate
+    Borland C++ compilation problems causing application crashes on
+    repetitive connections, notably in the listen mode. Also, now
+    warning exceptions causing disconnects are reported to user,
+    except for the case when a user has closed the viewer window.
+
+  - Better packaging in Win32 version: self-installing package is
+    available, vncviewer now shows correct icon image.
+
+  - Unix vncviewer: Default tunneling command template has been
+    changed, to allow tunneled connections to hosts where only
+    loopback VNC connections are enabled. New -via <GATEWAY>
+    command-line option provides enhanced tunneling functionality, now
+    one can make vncviewer tunnel connections to a VNC host via third
+    machine acting as a gateway.
+
+  - Java viewer: Addition of new parameters PASSWORD, "Show Controls",
+    and "View Only", modified patch from Steve Kann.
+
+----------------------------------------------------------------------
+
+* TightVNC 1.2.0
+
+  - Tight encoding is now configurable and can operate at different
+    compression levels where low compression levels are very fast in
+    terms of CPU usage. New "-compresslevel N" option implemented in
+    vncviewer to set compression levels for Tight encoding (1 - fast,
+    9 - best).
+
+  - Enhanced techniques to split large rectangles in Tight encoder;
+    now it tries to find large solid-color areas and send them in
+    separate rectangles.
+
+  - Lossy JPEG compression in Tight encoding has been implemented, new
+    "-quality N" vncviewer option should be used to enable this
+    feature (0 - low image quality and best compression, 9 - best
+    image quality). JPEG compression is used only for screen areas
+    that seem to be suitable for JPEG compression (although algorithms
+    to detect such areas are not perfect, of course).
+
+  - New "XCursor" and "RichCursor" encodings implemented. They are
+    used to transmit cursor shape updates from server to clients
+    ("local cursor" feature requested by many users). Mouse movement
+    no longer causes framebuffer updates to happen, vncviewer
+    processes mouse locally when this feature is active. New
+    -nocursorshape vncviewer option turns this feature off.
+
+  - A number of recent changes from both TridiaVNC and AT&T's releases
+    merged into the source, now the code is based on version 3.3.3r2
+    for Unix part, and on 3.3.3r9 for Win32.
+
+  - Unix vncviewer: When -tunnel option is specified in the command
+    line, special rules are now used to choose preferred encoding. Now
+    viewer does not think that server is running on the same machine
+    when tunneling is on and the preferred encoding is now "tight"
+    with default compression instead of raw.
+
+  - Xvnc: Rules to set default pixel formats have been changed: now
+    they are RGB565 instead of BGR556 for color depth 16, and RGB888
+    instead of BGR888 for depth 24. This makes Xvnc compatible with
+    Imlib renderer used in Gnome and also helps to avoid unnecessary
+    pixel format translations in many cases.
+
+  - Xvnc: X11 modifier mapped to META key is now Mod4 instead of Mod1.
+    New -compatiblekbd option implemented in Xvnc to force META and
+    ALT keys behave the same way as they do in the original AT&T's
+    version.
+
+  - A number of bugs fixed: viewer crashes after inflate() call, Xvnc
+    CoRRE encoding problems, Xvnc bit-order issues in XCursor and
+    RichCursor encodings, etc.
+
+  - Java viewer now supports Tight encoding and cursor shape updates.
+    Drawing techniques were changed, settings "Raw pixel drawing:
+    Fast/Reliable" and "CopyRect: Fast/Reliable" removed from the
+    Options panel since they do not make sense in new drawing model.
+
+  - Other new features, optimizations, fixes and cleanups, see
+    ChangeLog files.
+
+----------------------------------------------------------------------
+
+* VNC Tight Encoding 1.1
+
+  - New ``gradient'' filter implemented in servers (it can be disabled
+    in Xvnc with new -lazytight option). The filter preprocess
+    full-color screen areas prior to compression in order to achieve
+    better compression ratios (with the cost of slower compression).
+    Vncviewers of version 1.0 had support for this filter already, but
+    there was small bug causing image distortions in certain cases. So
+    it is recommended to upgrade both servers and viewers.
+
+  - Stupid bug fixed: extra unused color was included in palettes in
+    many cases; compression ratios used to be worse than they should
+    be.
+
+  - The algorithm used to split large rectangles into parts has been
+    changed. This change can increase compression ratios in many
+    situations.
+
+  - Byte-order issues in servers have been (hopefully) fixed.
+
+  - Performance tuning, code rewrites and cleanups in various places.
+
+----------------------------------------------------------------------
+
+* VNC Tight Encoding 1.0
+
+  - Initial release.
+
+----------------------------------------------------------------------
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/ZlibInStream.java
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/ZlibInStream.java	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/ZlibInStream.java	(revision 571)
@@ -0,0 +1,111 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// A ZlibInStream reads from a zlib.io.InputStream
+//
+
+public class ZlibInStream extends InStream {
+
+  static final int defaultBufSize = 16384;
+
+  public ZlibInStream(int bufSize_) {
+    bufSize = bufSize_;
+    b = new byte[bufSize];
+    ptr = end = ptrOffset = 0;
+    inflater = new java.util.zip.Inflater();
+  }
+
+  public ZlibInStream() { this(defaultBufSize); }
+
+  public void setUnderlying(InStream is, int bytesIn_) {
+    underlying = is;
+    bytesIn = bytesIn_;
+    ptr = end = 0;
+  }
+
+  public void reset() throws Exception {
+    ptr = end = 0;
+    if (underlying == null) return;
+
+    while (bytesIn > 0) {
+      decompress();
+      end = 0; // throw away any data
+    }
+    underlying = null;
+  }
+
+  public int pos() { return ptrOffset + ptr; }
+
+  protected int overrun(int itemSize, int nItems) throws Exception {
+    if (itemSize > bufSize)
+      throw new Exception("ZlibInStream overrun: max itemSize exceeded");
+    if (underlying == null)
+      throw new Exception("ZlibInStream overrun: no underlying stream");
+
+    if (end - ptr != 0)
+      System.arraycopy(b, ptr, b, 0, end - ptr);
+
+    ptrOffset += ptr;
+    end -= ptr;
+    ptr = 0;
+
+    while (end < itemSize) {
+      decompress();
+    }
+
+    if (itemSize * nItems > end)
+      nItems = end / itemSize;
+
+    return nItems;
+  }
+
+  // decompress() calls the decompressor once.  Note that this won't
+  // necessarily generate any output data - it may just consume some input
+  // data.  Returns false if wait is false and we would block on the underlying
+  // stream.
+
+  private void decompress() throws Exception {
+    try {
+      underlying.check(1);
+      int avail_in = underlying.getend() - underlying.getptr();
+      if (avail_in > bytesIn)
+        avail_in = bytesIn;
+
+      if (inflater.needsInput()) {
+        inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in);
+      }
+
+      int n = inflater.inflate(b, end, bufSize - end); 
+
+      end += n;
+      if (inflater.needsInput()) {
+        bytesIn -= avail_in;
+        underlying.setptr(underlying.getptr() + avail_in);
+      }
+    } catch (java.util.zip.DataFormatException e) {
+      throw new Exception("ZlibInStream: inflate failed");
+    }
+  }
+
+  private InStream underlying;
+  private int bufSize;
+  private int ptrOffset;
+  private java.util.zip.Inflater inflater;
+  private int bytesIn;
+}
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/index.html
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/index.html	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/index.html	(revision 571)
@@ -0,0 +1,29 @@
+<!-- 
+     index.html - an example HTML page for TightVNC Java viewer applet, to be
+     used with a standalone Web server running on the same machine where the
+     TightVNC server is running. Before using this example, please MAKE SURE
+     to check the following:
+
+     * the value of the PORT parameter should be set correctly (normally, the
+       port number is 5900 + display number);
+
+     * the CODE and ARCHIVE attributes of the <APPLET> tag should point to
+       the correct directory (this example assumes that this page is in the
+       same directory with .jar and .class files);
+
+     * the WIDTH and HEIGHT attributes of the <APPLET> tag correspond to the
+       actual desktop size on the server (height should be increased to leave
+       enough space for the button panel).
+-->
+
+<HTML>
+<TITLE>
+TightVNC desktop
+</TITLE>
+<APPLET CODE="VncViewer.class" ARCHIVE="VncViewer.jar"
+        WIDTH="800" HEIGHT="632">
+<PARAM NAME="PORT" VALUE="5901">
+</APPLET>
+<BR>
+<A href="http://www.tightvnc.com/">TightVNC site</A>
+</HTML>
Index: /branches/wsgi/packages/sipb-xen-vnc-client/code/index.vnc
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/code/index.vnc	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/code/index.vnc	(revision 571)
@@ -0,0 +1,25 @@
+<!-- 
+     index.vnc - default HTML page for TightVNC Java viewer applet, to be
+     used with Xvnc. On any file ending in .vnc, the HTTP server embedded in
+     Xvnc will substitute the following variables when preceded by a dollar:
+     USER, DESKTOP, DISPLAY, APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT,
+     PARAMS. Use two dollar signs ($$) to get a dollar sign in the generated
+     HTML page.
+
+     NOTE: the $PARAMS variable is not supported by the standard VNC, so
+     make sure you have TightVNC on the server side, if you're using this
+     variable.
+-->
+
+<HTML>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+<APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar
+        WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=PORT value=$PORT>
+$PARAMS
+</APPLET>
+<BR>
+<A href="http://www.tightvnc.com/">TightVNC site</A>
+</HTML>
Index: /branches/wsgi/packages/sipb-xen-vnc-client/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/debian/changelog	(revision 571)
@@ -0,0 +1,5 @@
+sipb-xen-vnc-client (1) unstable; urgency=low
+
+  * Initial Release.
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Fri, 28 Mar 2008 23:02:26 -0500
+
Index: /branches/wsgi/packages/sipb-xen-vnc-client/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-vnc-client/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/debian/control	(revision 571)
@@ -0,0 +1,11 @@
+Source: sipb-xen-vnc-client
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), subversion, sun-java5-jdk
+Standards-Version: 3.7.2
+
+Package: sipb-xen-vnc-client
+Architecture: all
+Depends: ${misc:Depends}
+Description: Install the custom sipb-xen VNC client
Index: /branches/wsgi/packages/sipb-xen-vnc-client/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-vnc-client/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/debian/rules	(revision 571)
@@ -0,0 +1,9 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/class/makefile.mk
+
+DEB_SRCDIR=code
+
+binary-fixup/sipb-xen-vnc-client::
+	svn co https://sipb-xen-dev.mit.edu:1111/trunk/packages/sipb-xen-vnc-client/code/ $(DEB_DESTDIR)/usr/local/lib/sipb-xen-vnc-client
Index: /branches/wsgi/packages/sipb-xen-vnc-client/debian/sipb-xen-vnc-client.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-client/debian/sipb-xen-vnc-client.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-client/debian/sipb-xen-vnc-client.install	(revision 571)
@@ -0,0 +1,2 @@
+files/* .
+code/VncViewer.jar usr/local/lib/sipb-xen-vnc-client/
Index: /branches/wsgi/packages/sipb-xen-vnc-server/code/get_port.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/code/get_port.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/code/get_port.py	(revision 571)
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+import sys
+import glob
+sys.path.append('/usr/lib/xen-default/lib/python/')
+import xen.xm
+import xen.xm.XenAPI
+import xen.xend.XendClient
+import time
+import xmlrpclib
+
+prefix = "d_"
+server = xen.xm.XenAPI.Session(xen.xend.XendClient.uri)
+
+def findPort(name):
+    try:
+        state = server.xend.domain(prefix + name, True)
+        for (key,value) in state[1:]:
+            if key == 'device' and value[0] == 'vfb':
+                location=dict(value[1:]).get('location')
+                return location
+    except xmlrpclib.Fault:
+        return None
+
+if __name__ == '__main__':
+    print findPort(sys.argv[1])
Index: /branches/wsgi/packages/sipb-xen-vnc-server/code/vncexternalauth.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/code/vncexternalauth.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/code/vncexternalauth.py	(revision 571)
@@ -0,0 +1,206 @@
+"""
+Wrapper for sipb-xen VNC proxying
+"""
+
+# twisted imports
+from twisted.internet import reactor, protocol, defer
+from twisted.python import log
+
+# python imports
+import sys
+import struct
+import string
+import cPickle
+# Python 2.5:
+#import hashlib
+import sha
+import hmac
+import base64
+import socket
+import time
+import get_port
+
+TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
+
+def getPort(name, auth_data):
+    if (auth_data["machine"] == name):
+        port = get_port.findPort(name)
+        if port is None:
+            return 0
+        return int(port.split(':')[1])
+    else:
+        return None
+    
+class VNCAuthOutgoing(protocol.Protocol):
+    
+    def __init__(self,socks):
+        self.socks=socks
+
+    def connectionMade(self):
+        peer = self.transport.getPeer()
+        self.socks.makeReply(200)
+        self.socks.otherConn=self
+
+    def connectionLost(self, reason):
+        self.socks.transport.loseConnection()
+
+    def dataReceived(self,data):
+        self.socks.write(data)
+
+    def write(self,data):
+        self.transport.write(data)
+
+
+class VNCAuth(protocol.Protocol):
+    
+    def __init__(self,server="localhost"):
+        self.server=server
+        self.auth=None
+    
+    def connectionMade(self):
+        self.buf=""
+        self.otherConn=None
+
+    def validateToken(self, token):
+        global TOKEN_KEY
+        self.auth_error = "Invalid token"
+        try:
+            token = base64.urlsafe_b64decode(token)
+            token = cPickle.loads(token)
+            m = hmac.new(TOKEN_KEY, digestmod=sha)
+            m.update(token['data'])
+            if (m.digest() == token['digest']):
+                data = cPickle.loads(token['data'])
+                expires = data["expires"]
+                if (time.time() < expires):
+                    self.auth = data["user"]
+                    self.auth_error = None
+                    self.auth_machine = data["machine"]
+                    self.auth_data = data
+                else:
+                    self.auth_error = "Token has expired; please try logging in again"
+        except (TypeError, cPickle.UnpicklingError):
+            self.auth = None            
+            print sys.exc_info()
+
+    def dataReceived(self,data):
+        if self.otherConn:
+            self.otherConn.write(data)
+            return
+        self.buf=self.buf+data
+        if ('\r\n\r\n' in self.buf) or ('\n\n' in self.buf) or ('\r\r' in self.buf):
+            lines = self.buf.splitlines()
+            args = lines.pop(0).split()
+            command = args.pop(0)
+            headers = {}
+            for line in lines:
+                try:
+                    (header, data) = line.split(": ", 1)
+                    headers[header] = data
+                except ValueError:
+                    pass
+
+            if command == "AUTHTOKEN":
+                user = args[0]
+                token = headers["Auth-token"]
+                if token == "1": #FIXME
+                    self.auth = user
+                    self.makeReply(200, "Authentication successful")
+                else:
+                    self.makeReply(401)
+            elif command == "CONNECTVNC":
+                vmname = args[0]
+                if ("Auth-token" in headers):
+                    token = headers["Auth-token"]
+                    self.validateToken(token)
+                    if self.auth is not None:
+                        port = getPort(vmname, self.auth_data)
+                        if port is not None: # FIXME
+                            if port != 0:
+                                d = self.connectClass(self.server, port, VNCAuthOutgoing, self)
+                                d.addErrback(lambda result, self=self: self.makeReply(404, result.getErrorMessage()))
+                            else:
+                                self.makeReply(404, "Unable to find VNC for VM "+vmname)
+                        else:
+                            self.makeReply(401, "Unauthorized to connect to VM "+vmname)
+                    else:
+                        if self.auth_error:
+                            self.makeReply(401, self.auth_error)
+                        else:
+                            self.makeReply(401, "Invalid token")
+                else:
+                    self.makeReply(401, "Login first")
+            else:
+                self.makeReply(501, "unknown method "+command)
+            self.buf=''
+        if False and '\000' in self.buf[8:]:
+            head,self.buf=self.buf[:8],self.buf[8:]
+            try:
+                version,code,port=struct.unpack("!BBH",head[:4])
+            except struct.error:
+                raise RuntimeError, "struct error with head='%s' and buf='%s'"%(repr(head),repr(self.buf))
+            user,self.buf=string.split(self.buf,"\000",1)
+            if head[4:7]=="\000\000\000": # domain is after
+                server,self.buf=string.split(self.buf,'\000',1)
+                #server=gethostbyname(server)
+            else:
+                server=socket.inet_ntoa(head[4:8])
+            assert version==4, "Bad version code: %s"%version
+            if not self.authorize(code,server,port,user):
+                self.makeReply(91)
+                return
+            if code==1: # CONNECT
+                d = self.connectClass(server, port, SOCKSv4Outgoing, self)
+                d.addErrback(lambda result, self=self: self.makeReply(91))
+            else:
+                raise RuntimeError, "Bad Connect Code: %s" % code
+            assert self.buf=="","hmm, still stuff in buffer... %s" % repr(self.buf)
+
+    def connectionLost(self, reason):
+        if self.otherConn:
+            self.otherConn.transport.loseConnection()
+
+    def authorize(self,code,server,port,user):
+        log.msg("code %s connection to %s:%s (user %s) authorized" % (code,server,port,user))
+        return 1
+
+    def connectClass(self, host, port, klass, *args):
+        return protocol.ClientCreator(reactor, klass, *args).connectTCP(host,port)
+
+    def makeReply(self,reply,message=""):
+        self.transport.write("VNCProxy/1.0 %d %s\r\n\r\n" % (reply, message))
+        if int(reply / 100)!=2: self.transport.loseConnection()
+
+    def write(self,data):
+        self.transport.write(data)
+
+    def log(self,proto,data):
+        peer = self.transport.getPeer()
+        their_peer = self.otherConn.transport.getPeer()
+        print "%s\t%s:%d %s %s:%d\n"%(time.ctime(),
+                                        peer.host,peer.port,
+                                        ((proto==self and '<') or '>'),
+                                        their_peer.host,their_peer.port),
+        while data:
+            p,data=data[:16],data[16:]
+            print string.join(map(lambda x:'%02X'%ord(x),p),' ')+' ',
+            print ((16-len(p))*3*' '),
+            for c in p:
+                if len(repr(c))>3: print '.',
+                else: print c,
+            print ""
+        print ""
+
+
+class VNCAuthFactory(protocol.Factory):
+    """A factory for a VNC auth proxy.
+    
+    Constructor accepts one argument, a log file name.
+    """
+    
+    def __init__(self, server):
+        self.server = server
+    
+    def buildProtocol(self, addr):
+        return VNCAuth(self.server)
+
Index: /branches/wsgi/packages/sipb-xen-vnc-server/code/vncproxy.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/code/vncproxy.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/code/vncproxy.py	(revision 571)
@@ -0,0 +1,12 @@
+#! /usr/bin/python
+from twisted.internet import reactor, ssl
+import vncexternalauth
+
+sslContext = ssl.DefaultOpenSSLContextFactory(
+	'vncproxykey.pem',
+	'vncproxy.crt',
+)
+
+if '__main__' == __name__:
+    reactor.listenSSL(10003,vncexternalauth.VNCAuthFactory("localhost"), contextFactory=sslContext)
+    reactor.run()
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/changelog	(revision 571)
@@ -0,0 +1,5 @@
+sipb-xen-vnc-server (1) unstable; urgency=low
+
+  * Initial Release.
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Fri, 28 Mar 2008 19:28:22 -0500
+
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/control	(revision 571)
@@ -0,0 +1,11 @@
+Source: sipb-xen-vnc-server
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), subversion
+Standards-Version: 3.7.2
+
+Package: sipb-xen-vnc-server
+Architecture: all
+Depends: ${misc:Depends}, daemon, python-twisted-core, xen-utils-3.1-1
+Description: Install and enable the VNC server
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/rules	(revision 571)
@@ -0,0 +1,6 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+
+binary-fixup/sipb-xen-vnc-server::
+	svn co https://sipb-xen-dev.mit.edu:1111/trunk/packages/sipb-xen-vnc-server/code/ $(DEB_DESTDIR)/usr/local/lib/sipb-xen-vnc-server
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.init
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.init	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.init	(revision 571)
@@ -0,0 +1,124 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          sipb-xen-vnc-server
+# Required-Start:    $local_fs $remote_fs
+# Required-Stop:     $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: sipb-xen VNC Proxy Server
+# Description:       
+### END INIT INFO
+
+# Author: SIPB Xen Project <sipb-xen@mit.edu>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="The sipb-xen VNC Proxy Server"
+NAME=sipb-xen-vnc-server
+DAEMON=/usr/local/lib/sipb-xen-vnc-server/vncproxy.py
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	daemon --running -n $NAME && return 1
+	daemon -r -U -D "$(dirname $DAEMON)" -O daemon.info -E daemon.err -n $NAME -U $DAEMON $DAEMON_ARGS || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	daemon --stop -n $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Many daemons don't delete their pidfiles when they exit.
+	rm -f $PIDFILE
+	return "$RETVAL"
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.postinst
===================================================================
--- /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.postinst	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-vnc-server/debian/sipb-xen-vnc-server.postinst	(revision 571)
@@ -0,0 +1,44 @@
+#!/bin/sh
+# postinst script for #PACKAGE#
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+	if [ -z "$2" ]; then
+	    echo "Please be sure to copy vncproxy.crt and vncproxykey.pem into /usr/local/lib/sipb-xen-vnc-server/"
+	fi
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
Index: /branches/wsgi/packages/sipb-xen-www/code/Makefile
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/Makefile	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/Makefile	(revision 571)
@@ -0,0 +1,11 @@
+DIRS = templates
+
+all:
+	for dir in $(DIRS); do \
+		(cd $$dir; $(MAKE) all); \
+	done
+
+clean:
+	for dir in $(DIRS); do \
+		(cd $$dir; $(MAKE) clean); \
+	done
Index: /branches/wsgi/packages/sipb-xen-www/code/cache_acls.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/cache_acls.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/cache_acls.py	(revision 571)
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+from sipb_xen_database import *
+import sys
+import getafsgroups
+import subprocess
+
+def expandLocker(name):
+    groups = getafsgroups.getLockerAcl(name)
+    cell = getafsgroups.getCell(name)
+    ans = set()
+    for group in groups:
+        if ':' in group:
+            ans.update(getafsgroups.getAfsGroupMembers(group, cell))
+        else:
+            ans.add(group)
+    return ans
+
+def isUser(name):
+    p = subprocess.Popen(['vos', 'examine', 'user.'+name],
+                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    if p.wait():
+        return False
+    return True
+    
+
+def expandName(name):
+    if ':' not in name:
+        if isUser(name):
+            return [name]
+        return []
+    try:
+        return getafsgroups.getAfsGroupMembers(name, 'athena.mit.edu')
+    except getafsgroups.AfsProcessError:
+        return []
+
+def accessList(m):
+    people = set()
+    people.update(expandLocker(m.owner))
+    people.update(expandName(m.administrator))
+    return people
+
+def refreshMachine(m):
+    people = accessList(m)
+    old_people = set(a.user for a in m.acl)
+    for removed in old_people - people:
+        ma = [x for x in m.acl if x.user == removed][0]
+        ctx.current.delete(ma)
+    for p in people - old_people:
+        ma = MachineAccess(machine_id=m.machine_id, user=p)
+        ctx.current.save(ma)
+    
+def refreshCache():
+    transaction = ctx.current.create_transaction()
+
+    try:
+        machines = Machine.select()
+        for m in machines:
+            refreshMachine(m)
+        ctx.current.flush()
+            
+        # Atomically execute our changes
+        transaction.commit()
+    except:
+        # Failed! Rollback all the changes.
+        transaction.rollback()
+        raise
+
+if __name__ == '__main__':
+    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
+    refreshCache()
Index: /branches/wsgi/packages/sipb-xen-www/code/controls.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/controls.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/controls.py	(revision 571)
@@ -0,0 +1,278 @@
+"""
+Functions to perform remctls.
+"""
+
+from sipb_xen_database import Machine, Disk, Type, NIC, CDROM, ctx, meta
+import validation
+from webcommon import CodeError, InvalidInput
+import random
+import subprocess
+import sys
+import time
+import re
+import cache_acls
+import yaml
+
+# ... and stolen from xend/uuid.py
+def randomUUID():
+    """Generate a random UUID."""
+
+    return [ random.randint(0, 255) for _ in range(0, 16) ]
+
+def uuidToString(u):
+    """Turn a numeric UUID to a hyphen-seperated one."""
+    return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
+                     "%02x" * 6]) % tuple(u)
+# end stolen code
+
+def kinit(username = 'daemon/sipb-xen.mit.edu', keytab = '/etc/sipb-xen.keytab'):
+    """Kinit with a given username and keytab"""
+
+    p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
+                         stderr=subprocess.PIPE)
+    e = p.wait()
+    if e:
+        raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
+
+def checkKinit():
+    """If we lack tickets, kinit."""
+    p = subprocess.Popen(['klist', '-s'])
+    if p.wait():
+        kinit()
+
+def remctl(*args, **kws):
+    """Perform a remctl and return the output.
+
+    kinits if necessary, and outputs errors to stderr.
+    """
+    checkKinit()
+    p = subprocess.Popen(['remctl', 'remote.mit.edu']
+                         + list(args),
+                         stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE)
+    v = p.wait()
+    if kws.get('err'):
+        return p.stdout.read(), p.stderr.read()
+    if v:
+        print >> sys.stderr, 'Error', v, 'on remctl', args, ':'
+        print >> sys.stderr, p.stderr.read()
+        raise CodeError('ERROR on remctl')
+    return p.stdout.read()
+
+def lvcreate(machine, disk):
+    """Create a single disk for a machine"""
+    remctl('web', 'lvcreate', machine.name,
+           disk.guest_device_name, str(disk.size))
+    
+def makeDisks(machine):
+    """Update the lvm partitions to add a disk."""
+    for disk in machine.disks:
+        lvcreate(machine, disk)
+
+def lvcopy(machine_orig_name, machine, rootpw):
+    """Copy a golden image onto a machine's disk"""
+    remctl('web', 'lvcopy', machine_orig_name, machine.name, rootpw)
+
+def bootMachine(machine, cdtype):
+    """Boot a machine with a given boot CD.
+
+    If cdtype is None, give no boot cd.  Otherwise, it is the string
+    id of the CD (e.g. 'gutsy_i386')
+    """
+    if cdtype is not None:
+        out, err = remctl('control', machine.name, 'create', 
+                          cdtype, err=True)
+    else:
+        out, err = remctl('control', machine.name, 'create',
+                          err=True)
+    if 'already exists' in out:
+        raise InvalidInput('action', 'create',
+                           'VM %s is already on' % machine.name)
+    elif err:
+        raise CodeError('"%s" on "control %s create %s' 
+                        % (err, machine.name, cdtype))
+
+def createVm(owner, contact, name, memory, disk_size, machine_type, cdrom, clone_from):
+    """Create a VM and put it in the database"""
+    # put stuff in the table
+    transaction = ctx.current.create_transaction()
+    try:
+        validation.validMemory(owner, memory)
+        validation.validDisk(owner, disk_size  * 1. / 1024)
+        validation.validAddVm(owner)
+        res = meta.engine.execute('select nextval('
+                                  '\'"machines_machine_id_seq"\')')
+        id = res.fetchone()[0]
+        machine = Machine()
+        machine.machine_id = id
+        machine.name = name
+        machine.memory = memory
+        machine.owner = owner
+        machine.administrator = owner
+        machine.contact = contact
+        machine.uuid = uuidToString(randomUUID())
+        machine.boot_off_cd = True
+        machine.type_id = machine_type.type_id
+        ctx.current.save(machine)
+        disk = Disk(machine_id=machine.machine_id,
+                    guest_device_name='hda', size=disk_size)
+        open_nics = NIC.select_by(machine_id=None)
+        if not open_nics: #No IPs left!
+            raise CodeError("No IP addresses left!  "
+                            "Contact xvm@mit.edu.")
+        nic = open_nics[0]
+        nic.machine_id = machine.machine_id
+        nic.hostname = name
+        ctx.current.save(nic)
+        ctx.current.save(disk)
+        cache_acls.refreshMachine(machine)
+        transaction.commit()
+    except:
+        transaction.rollback()
+        raise
+    makeDisks(machine)
+    if clone_from:
+        lvcopy(clone_from, machine, 'password')
+    # tell it to boot with cdrom
+    bootMachine(machine, cdrom)
+    return machine
+
+def getList():
+    """Return a dictionary mapping machine names to dicts."""
+    value_string = remctl('web', 'listvms')
+    value_dict = yaml.load(value_string, yaml.CSafeLoader)
+    return value_dict
+
+def parseStatus(s):
+    """Parse a status string into nested tuples of strings.
+
+    s = output of xm list --long <machine_name>
+    """
+    values = re.split('([()])', s)
+    stack = [[]]
+    for v in values[2:-2]: #remove initial and final '()'
+        if not v:
+            continue
+        v = v.strip()
+        if v == '(':
+            stack.append([])
+        elif v == ')':
+            if len(stack[-1]) == 1:
+                stack[-1].append('')
+            stack[-2].append(stack[-1])
+            stack.pop()
+        else:
+            if not v:
+                continue
+            stack[-1].extend(v.split())
+    return stack[-1]
+
+def statusInfo(machine):
+    """Return the status list for a given machine.
+
+    Gets and parses xm list --long
+    """
+    value_string, err_string = remctl('control', machine.name, 'list-long', 
+                                      err=True)
+    if 'Unknown command' in err_string:
+        raise CodeError("ERROR in remctl list-long %s is not registered" % 
+                        (machine.name,))
+    elif 'does not exist' in err_string:
+        return None
+    elif err_string:
+        raise CodeError("ERROR in remctl list-long %s:  %s" % 
+                        (machine.name, err_string))
+    status = parseStatus(value_string)
+    return status
+
+def deleteVM(machine):
+    """Delete a VM."""
+    remctl('control', machine.name, 'destroy', err=True)
+    transaction = ctx.current.create_transaction()
+    delete_disk_pairs = [(machine.name, d.guest_device_name) 
+                         for d in machine.disks]
+    try:
+        for nic in machine.nics:
+            nic.machine_id = None
+            nic.hostname = None
+            ctx.current.save(nic)
+        for disk in machine.disks:
+            ctx.current.delete(disk)
+        for access in machine.acl:
+            ctx.current.delete(access)
+        ctx.current.delete(machine)
+        transaction.commit()
+    except:
+        transaction.rollback()
+        raise
+    for mname, dname in delete_disk_pairs:
+        remctl('web', 'lvremove', mname, dname)
+
+def commandResult(user, fields):
+    start_time = 0
+    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
+    action = fields.getfirst('action')
+    cdrom = fields.getfirst('cdrom')
+    if cdrom is not None and not CDROM.get(cdrom):
+        raise CodeError("Invalid cdrom type '%s'" % cdrom)    
+    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 
+                      'Delete VM'):
+        raise CodeError("Invalid action '%s'" % action)
+    if action == 'Reboot':
+        if cdrom is not None:
+            out, err = remctl('control', machine.name, 'reboot', cdrom,
+                              err=True)
+        else:
+            out, err = remctl('control', machine.name, 'reboot',
+                              err=True)
+        if err:
+            if re.match("Error: Domain '.*' does not exist.", err):
+                raise InvalidInput("action", "reboot", 
+                                   "Machine is not on")
+            else:
+                print >> sys.stderr, 'Error on reboot:'
+                print >> sys.stderr, err
+                raise CodeError('ERROR on remctl')
+                
+    elif action == 'Power on':
+        if validation.maxMemory(user, machine) < machine.memory:
+            raise InvalidInput('action', 'Power on',
+                               "You don't have enough free RAM quota "
+                               "to turn on this machine.")
+        bootMachine(machine, cdrom)
+    elif action == 'Power off':
+        out, err = remctl('control', machine.name, 'destroy', err=True)
+        if err:
+            if re.match("Error: Domain '.*' does not exist.", err):
+                raise InvalidInput("action", "Power off", 
+                                   "Machine is not on.")
+            else:
+                print >> sys.stderr, 'Error on power off:'
+                print >> sys.stderr, err
+                raise CodeError('ERROR on remctl')
+    elif action == 'Shutdown':
+        out, err = remctl('control', machine.name, 'shutdown', err=True)
+        if err:
+            if re.match("Error: Domain '.*' does not exist.", err):
+                raise InvalidInput("action", "Shutdown", 
+                                   "Machine is not on.")
+            else:
+                print >> sys.stderr, 'Error on Shutdown:'
+                print >> sys.stderr, err
+                raise CodeError('ERROR on remctl')
+    elif action == 'Delete VM':
+        deleteVM(machine)
+
+    d = dict(user=user,
+             command=action,
+             machine=machine)
+    return d
+
+def resizeDisk(machine_name, disk_name, new_size):
+    remctl("web", "lvresize", machine_name, disk_name, new_size)
+
+def renameMachine(machine, old_name, new_name):
+    for disk in machine.disks:
+        remctl("web", "lvrename", old_name, 
+               disk.guest_device_name, new_name)
+    
Index: /branches/wsgi/packages/sipb-xen-www/code/getafsgroups.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/getafsgroups.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/getafsgroups.py	(revision 571)
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+import pprint
+import subprocess
+
+# import ldap
+# l = ldap.open("W92-130-LDAP-2.mit.edu")
+# # ldap.mit.edu is 1/2 broken right now so we're going to the working backend
+# l.simple_bind_s("", "")
+
+# def getLdapGroups(user):
+#     """
+#     getLdapGroups(user): returns a generator for the list of LDAP groups containing user
+#     """
+#     for user_data in l.search_s("ou=affiliates,dc=mit,dc=edu", ldap.SCOPE_ONELEVEL, "uid=" + user, []):
+#         for group_data in l.search_s("ou=groups,dc=mit,dc=edu", ldap.SCOPE_ONELEVEL, "uniqueMember="+user_data[0], ['cn']):
+#             yield group_data[1]['cn'][0]
+
+# def checkLdapGroups(user, group):
+#     """
+#     checkLdapGroups(user, group): returns True if and only if user is in LDAP group group
+#     """
+#     for result_data in l.search_s("ou=affiliates,dc=mit,dc=edu", ldap.SCOPE_ONELEVEL, "uid=" + user, []):
+#         if l.search_s("ou=groups,dc=mit,dc=edu", ldap.SCOPE_ONELEVEL, "(&(cn=" + group + ")(uniqueMember="+result_data[0] + "))", []) != []:
+#             return True
+#     return False
+
+class AfsProcessError(Exception):
+    pass
+
+def getAfsGroupMembers(group, cell):
+    p = subprocess.Popen(["pts", "membership", "-noauth", group, '-c', cell], 
+                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    err = p.stderr.read()
+    if err: #Error code doesn't reveal missing groups, but stderr does
+        raise AfsProcessError(err)
+    return [line.strip() for line in p.stdout.readlines()[1:]]
+
+def getLockerPath(locker):
+    if '/' in locker or locker in ['.', '..']:
+        raise AfsProcessError("Locker '%s' is invalid." % locker)
+    return '/mit/' + locker
+
+def getCell(locker):
+    p = subprocess.Popen(["fs", "whichcell", getLockerPath(locker)], 
+                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    if p.wait():
+        raise AfsProcessError(p.stderr.read())
+    return p.stdout.read().split()[-1][1:-1]
+
+def getLockerAcl(locker):
+    p = subprocess.Popen(["fs", "listacl", getLockerPath(locker)], 
+                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    if p.wait():
+        raise AfsProcessError(p.stderr.read())
+    lines = p.stdout.readlines()
+    values = []
+    for line in lines[1:]:
+        fields = line.split()
+        if fields[0] == 'Negative':
+            break
+        if 'a' in fields[1]:
+            values.append(fields[0])
+    return values
+
+def notLockerOwner(user, locker):
+    """
+    notLockerOwner(user, locker) returns false if and only if user administers locker.
+
+    If the user does not own the locker, returns the string reason for
+    the failure.
+    """
+    try:
+        cell = getCell(locker)
+        values = getLockerAcl(locker)
+    except AfsProcessError, e:
+        return str(e)
+
+    for entry in values:
+        if entry == user or (entry[0:6] == "system" and
+                                user in getAfsGroupMembers(entry, cell)):
+            return False
+    return "You don't have admin bits on " + getLockerPath(locker)
+
+
+if __name__ == "__main__":
+#    print list(getldapgroups("tabbott"))
+    print "tabbott" in getAfsGroupMembers("system:debathena", 'athena.mit.edu')
+    print "tabbott" in getAfsGroupMembers("system:debathena", 'sipb.mit.edu')
+    print "tabbott" in getAfsGroupMembers("system:debathena-root", 'athena.mit.edu')
+    print "tabbott" in getAfsGroupMembers("system:hmmt-request", 'athena.mit.edu')
+    print notLockerOwner("tabbott", "tabbott")
+    print notLockerOwner("tabbott", "debathena")
+    print notLockerOwner("tabbott", "sipb")
+    print notLockerOwner("tabbott", "lsc")
+    print notLockerOwner("tabbott", "scripts")
+    print notLockerOwner("ecprice", "hmmt")
Index: /branches/wsgi/packages/sipb-xen-www/code/main.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/main.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/main.py	(revision 571)
@@ -0,0 +1,712 @@
+#!/usr/bin/python
+"""Main CGI script for web interface"""
+
+import base64
+import cPickle
+import cgi
+import datetime
+import hmac
+import os
+import sha
+import simplejson
+import sys
+import time
+import urllib
+from StringIO import StringIO
+
+def revertStandardError():
+    """Move stderr to stdout, and return the contents of the old stderr."""
+    errio = sys.stderr
+    if not isinstance(errio, StringIO):
+        return None
+    sys.stderr = sys.stdout
+    errio.seek(0)
+    return errio.read()
+
+def printError():
+    """Revert stderr to stdout, and print the contents of stderr"""
+    if isinstance(sys.stderr, StringIO):
+        print revertStandardError()
+
+if __name__ == '__main__':
+    import atexit
+    atexit.register(printError)
+    sys.stderr = StringIO()
+
+sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
+
+import templates
+from Cheetah.Template import Template
+import sipb_xen_database
+from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
+import validation
+import cache_acls
+from webcommon import InvalidInput, CodeError, g
+import controls
+
+class Checkpoint:
+    def __init__(self):
+        self.start_time = time.time()
+        self.checkpoints = []
+
+    def checkpoint(self, s):
+        self.checkpoints.append((s, time.time()))
+
+    def __str__(self):
+        return ('Timing info:\n%s\n' %
+                '\n'.join(['%s: %s' % (d, t - self.start_time) for
+                           (d, t) in self.checkpoints]))
+
+checkpoint = Checkpoint()
+
+def jquote(string):
+    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
+
+def helppopup(subj):
+    """Return HTML code for a (?) link to a specified help topic"""
+    return ('<span class="helplink"><a href="help?' +
+            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
+            +'" target="_blank" ' +
+            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
+
+def makeErrorPre(old, addition):
+    if addition is None:
+        return
+    if old:
+        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
+    else:
+        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
+
+Template.sipb_xen_database = sipb_xen_database
+Template.helppopup = staticmethod(helppopup)
+Template.err = None
+
+class JsonDict:
+    """Class to store a dictionary that will be converted to JSON"""
+    def __init__(self, **kws):
+        self.data = kws
+        if 'err' in kws:
+            err = kws['err']
+            del kws['err']
+            self.addError(err)
+
+    def __str__(self):
+        return simplejson.dumps(self.data)
+
+    def addError(self, text):
+        """Add stderr text to be displayed on the website."""
+        self.data['err'] = \
+            makeErrorPre(self.data.get('err'), text)
+
+class Defaults:
+    """Class to store default values for fields."""
+    memory = 256
+    disk = 4.0
+    cdrom = ''
+    autoinstall = ''
+    name = ''
+    type = 'linux-hvm'
+
+    def __init__(self, max_memory=None, max_disk=None, **kws):
+        if max_memory is not None:
+            self.memory = min(self.memory, max_memory)
+        if max_disk is not None:
+            self.max_disk = min(self.disk, max_disk)
+        for key in kws:
+            setattr(self, key, kws[key])
+
+
+
+DEFAULT_HEADERS = {'Content-Type': 'text/html'}
+
+def error(op, user, fields, err, emsg):
+    """Print an error page when a CodeError occurs"""
+    d = dict(op=op, user=user, errorMessage=str(err),
+             stderr=emsg)
+    return templates.error(searchList=[d])
+
+def invalidInput(op, user, fields, err, emsg):
+    """Print an error page when an InvalidInput exception occurs"""
+    d = dict(op=op, user=user, err_field=err.err_field,
+             err_value=str(err.err_value), stderr=emsg,
+             errorMessage=str(err))
+    return templates.invalid(searchList=[d])
+
+def hasVnc(status):
+    """Does the machine with a given status list support VNC?"""
+    if status is None:
+        return False
+    for l in status:
+        if l[0] == 'device' and l[1][0] == 'vfb':
+            d = dict(l[1][1:])
+            return 'location' in d
+    return False
+
+def parseCreate(user, fields):
+    name = fields.getfirst('name')
+    if not validation.validMachineName(name):
+        raise InvalidInput('name', name, 'You must provide a machine name.  Max 22 chars, alnum plus \'-\' and \'_\'.')
+    name = name.lower()
+
+    if Machine.get_by(name=name):
+        raise InvalidInput('name', name,
+                           "Name already exists.")
+
+    owner = validation.testOwner(user, fields.getfirst('owner'))
+
+    memory = fields.getfirst('memory')
+    memory = validation.validMemory(owner, memory, on=True)
+
+    disk_size = fields.getfirst('disk')
+    disk_size = validation.validDisk(owner, disk_size)
+
+    vm_type = fields.getfirst('vmtype')
+    vm_type = validation.validVmType(vm_type)
+
+    cdrom = fields.getfirst('cdrom')
+    if cdrom is not None and not CDROM.get(cdrom):
+        raise CodeError("Invalid cdrom type '%s'" % cdrom)
+
+    clone_from = fields.getfirst('clone_from')
+    if clone_from and clone_from != 'ice3':
+        raise CodeError("Invalid clone image '%s'" % clone_from)
+
+    return dict(contact=user, name=name, memory=memory, disk_size=disk_size,
+                owner=owner, machine_type=vm_type, cdrom=cdrom, clone_from=clone_from)
+
+def create(user, fields):
+    """Handler for create requests."""
+    try:
+        parsed_fields = parseCreate(user, fields)
+        machine = controls.createVm(**parsed_fields)
+    except InvalidInput, err:
+        pass
+    else:
+        err = None
+    g.clear() #Changed global state
+    d = getListDict(user)
+    d['err'] = err
+    if err:
+        for field in fields.keys():
+            setattr(d['defaults'], field, fields.getfirst(field))
+    else:
+        d['new_machine'] = parsed_fields['name']
+    return templates.list(searchList=[d])
+
+
+def getListDict(user):
+    """Gets the list of local variables used by list.tmpl."""
+    checkpoint.checkpoint('Starting')
+    machines = g.machines
+    checkpoint.checkpoint('Got my machines')
+    on = {}
+    has_vnc = {}
+    xmlist = g.xmlist
+    checkpoint.checkpoint('Got uptimes')
+    can_clone = 'ice3' not in g.xmlist_raw
+    for m in machines:
+        if m not in xmlist:
+            has_vnc[m] = 'Off'
+            m.uptime = None
+        else:
+            m.uptime = xmlist[m]['uptime']
+            if xmlist[m]['console']:
+                has_vnc[m] = True
+            elif m.type.hvm:
+                has_vnc[m] = "WTF?"
+            else:
+                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
+    max_memory = validation.maxMemory(user)
+    max_disk = validation.maxDisk(user)
+    checkpoint.checkpoint('Got max mem/disk')
+    defaults = Defaults(max_memory=max_memory,
+                        max_disk=max_disk,
+                        owner=user,
+                        cdrom='gutsy-i386')
+    checkpoint.checkpoint('Got defaults')
+    def sortkey(machine):
+        return (machine.owner != user, machine.owner, machine.name)
+    machines = sorted(machines, key=sortkey)
+    d = dict(user=user,
+             cant_add_vm=validation.cantAddVm(user),
+             max_memory=max_memory,
+             max_disk=max_disk,
+             defaults=defaults,
+             machines=machines,
+             has_vnc=has_vnc,
+             can_clone=can_clone)
+    return d
+
+def listVms(user, fields):
+    """Handler for list requests."""
+    checkpoint.checkpoint('Getting list dict')
+    d = getListDict(user)
+    checkpoint.checkpoint('Got list dict')
+    return templates.list(searchList=[d])
+
+def vnc(user, fields):
+    """VNC applet page.
+
+    Note that due to same-domain restrictions, the applet connects to
+    the webserver, which needs to forward those requests to the xen
+    server.  The Xen server runs another proxy that (1) authenticates
+    and (2) finds the correct port for the VM.
+
+    You might want iptables like:
+
+    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
+      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
+    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
+      --dport 10003 -j SNAT --to-source 18.187.7.142
+    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
+      --dport 10003 -j ACCEPT
+
+    Remember to enable iptables!
+    echo 1 > /proc/sys/net/ipv4/ip_forward
+    """
+    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
+
+    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
+
+    data = {}
+    data["user"] = user
+    data["machine"] = machine.name
+    data["expires"] = time.time()+(5*60)
+    pickled_data = cPickle.dumps(data)
+    m = hmac.new(TOKEN_KEY, digestmod=sha)
+    m.update(pickled_data)
+    token = {'data': pickled_data, 'digest': m.digest()}
+    token = cPickle.dumps(token)
+    token = base64.urlsafe_b64encode(token)
+
+    status = controls.statusInfo(machine)
+    has_vnc = hasVnc(status)
+
+    d = dict(user=user,
+             on=status,
+             has_vnc=has_vnc,
+             machine=machine,
+             hostname=os.environ.get('SERVER_NAME', 'localhost'),
+             authtoken=token)
+    return templates.vnc(searchList=[d])
+
+def getHostname(nic):
+    """Find the hostname associated with a NIC.
+
+    XXX this should be merged with the similar logic in DNS and DHCP.
+    """
+    if nic.hostname and '.' in nic.hostname:
+        return nic.hostname
+    elif nic.machine:
+        return nic.machine.name + '.xvm.mit.edu'
+    else:
+        return None
+
+
+def getNicInfo(data_dict, machine):
+    """Helper function for info, get data on nics for a machine.
+
+    Modifies data_dict to include the relevant data, and returns a list
+    of (key, name) pairs to display "name: data_dict[key]" to the user.
+    """
+    data_dict['num_nics'] = len(machine.nics)
+    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
+                           ('nic%s_mac', 'NIC %s MAC Addr'),
+                           ('nic%s_ip', 'NIC %s IP'),
+                           ]
+    nic_fields = []
+    for i in range(len(machine.nics)):
+        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
+        if not i:
+            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
+        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
+        data_dict['nic%s_ip' % i] = machine.nics[i].ip
+    if len(machine.nics) == 1:
+        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
+    return nic_fields
+
+def getDiskInfo(data_dict, machine):
+    """Helper function for info, get data on disks for a machine.
+
+    Modifies data_dict to include the relevant data, and returns a list
+    of (key, name) pairs to display "name: data_dict[key]" to the user.
+    """
+    data_dict['num_disks'] = len(machine.disks)
+    disk_fields_template = [('%s_size', '%s size')]
+    disk_fields = []
+    for disk in machine.disks:
+        name = disk.guest_device_name
+        disk_fields.extend([(x % name, y % name) for x, y in
+                            disk_fields_template])
+        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
+    return disk_fields
+
+def command(user, fields):
+    """Handler for running commands like boot and delete on a VM."""
+    back = fields.getfirst('back')
+    try:
+        d = controls.commandResult(user, fields)
+        if d['command'] == 'Delete VM':
+            back = 'list'
+    except InvalidInput, err:
+        if not back:
+            raise
+        #print >> sys.stderr, err
+        result = err
+    else:
+        result = 'Success!'
+        if not back:
+            return templates.command(searchList=[d])
+    if back == 'list':
+        g.clear() #Changed global state
+        d = getListDict(user)
+        d['result'] = result
+        return templates.list(searchList=[d])
+    elif back == 'info':
+        machine = validation.testMachineId(user, fields.getfirst('machine_id'))
+        return ({'Status': '302',
+                 'Location': '/info?machine_id=%d' % machine.machine_id},
+                "You shouldn't see this message.")
+    else:
+        raise InvalidInput('back', back, 'Not a known back page.')
+
+def modifyDict(user, fields):
+    """Modify a machine as specified by CGI arguments.
+
+    Return a list of local variables for modify.tmpl.
+    """
+    olddisk = {}
+    transaction = ctx.current.create_transaction()
+    try:
+        machine = validation.testMachineId(user, fields.getfirst('machine_id'))
+        owner = validation.testOwner(user, fields.getfirst('owner'), machine)
+        admin = validation.testAdmin(user, fields.getfirst('administrator'),
+                                     machine)
+        contact = validation.testContact(user, fields.getfirst('contact'),
+                                         machine)
+        name = validation.testName(user, fields.getfirst('name'), machine)
+        oldname = machine.name
+        command = "modify"
+
+        memory = fields.getfirst('memory')
+        if memory is not None:
+            memory = validation.validMemory(owner, memory, machine, on=False)
+            machine.memory = memory
+
+        vm_type = validation.validVmType(fields.getfirst('vmtype'))
+        if vm_type is not None:
+            machine.type = vm_type
+
+        disksize = validation.testDisk(owner, fields.getfirst('disk'))
+        if disksize is not None:
+            disksize = validation.validDisk(owner, disksize, machine)
+            disk = machine.disks[0]
+            if disk.size != disksize:
+                olddisk[disk.guest_device_name] = disksize
+                disk.size = disksize
+                ctx.current.save(disk)
+
+        update_acl = False
+        if owner is not None and owner != machine.owner:
+            machine.owner = owner
+            update_acl = True
+        if name is not None:
+            machine.name = name
+        if admin is not None and admin != machine.administrator:
+            machine.administrator = admin
+            update_acl = True
+        if contact is not None:
+            machine.contact = contact
+
+        ctx.current.save(machine)
+        if update_acl:
+            cache_acls.refreshMachine(machine)
+        transaction.commit()
+    except:
+        transaction.rollback()
+        raise
+    for diskname in olddisk:
+        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
+    if name is not None:
+        controls.renameMachine(machine, oldname, name)
+    return dict(user=user,
+                command=command,
+                machine=machine)
+
+def modify(user, fields):
+    """Handler for modifying attributes of a machine."""
+    try:
+        modify_dict = modifyDict(user, fields)
+    except InvalidInput, err:
+        result = None
+        machine = validation.testMachineId(user, fields.getfirst('machine_id'))
+    else:
+        machine = modify_dict['machine']
+        result = 'Success!'
+        err = None
+    info_dict = infoDict(user, machine)
+    info_dict['err'] = err
+    if err:
+        for field in fields.keys():
+            setattr(info_dict['defaults'], field, fields.getfirst(field))
+    info_dict['result'] = result
+    return templates.info(searchList=[info_dict])
+
+
+def helpHandler(user, fields):
+    """Handler for help messages."""
+    simple = fields.getfirst('simple')
+    subjects = fields.getlist('subject')
+
+    help_mapping = {'ParaVM Console': """
+ParaVM machines do not support local console access over VNC.  To
+access the serial console of these machines, you can SSH with Kerberos
+to console.xvm.mit.edu, using the name of the machine as your
+username.""",
+                    'HVM/ParaVM': """
+HVM machines use the virtualization features of the processor, while
+ParaVM machines use Xen's emulation of virtualization features.  You
+want an HVM virtualized machine.""",
+                    'CPU Weight': """
+Don't ask us!  We're as mystified as you are.""",
+                    'Owner': """
+The owner field is used to determine <a
+href="help?subject=Quotas">quotas</a>.  It must be the name of a
+locker that you are an AFS administrator of.  In particular, you or an
+AFS group you are a member of must have AFS rlidwka bits on the
+locker.  You can check who administers the LOCKER locker using the
+commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
+href="help?subject=Administrator">administrator</a>.""",
+                    'Administrator': """
+The administrator field determines who can access the console and
+power on and off the machine.  This can be either a user or a moira
+group.""",
+                    'Quotas': """
+Quotas are determined on a per-locker basis.  Each locker may have a
+maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
+active machines.""",
+                    'Console': """
+<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
+setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
+your machine will run just fine, but the applet's display of the
+console will suffer artifacts.
+"""
+                    }
+
+    if not subjects:
+        subjects = sorted(help_mapping.keys())
+
+    d = dict(user=user,
+             simple=simple,
+             subjects=subjects,
+             mapping=help_mapping)
+
+    return templates.help(searchList=[d])
+
+
+def badOperation(u, e):
+    """Function called when accessing an unknown URI."""
+    raise CodeError("Unknown operation")
+
+def infoDict(user, machine):
+    """Get the variables used by info.tmpl."""
+    status = controls.statusInfo(machine)
+    checkpoint.checkpoint('Getting status info')
+    has_vnc = hasVnc(status)
+    if status is None:
+        main_status = dict(name=machine.name,
+                           memory=str(machine.memory))
+        uptime = None
+        cputime = None
+    else:
+        main_status = dict(status[1:])
+        start_time = float(main_status.get('start_time', 0))
+        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
+        cpu_time_float = float(main_status.get('cpu_time', 0))
+        cputime = datetime.timedelta(seconds=int(cpu_time_float))
+    checkpoint.checkpoint('Status')
+    display_fields = """name uptime memory state cpu_weight on_reboot 
+     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
+    display_fields = [('name', 'Name'),
+                      ('owner', 'Owner'),
+                      ('administrator', 'Administrator'),
+                      ('contact', 'Contact'),
+                      ('type', 'Type'),
+                      'NIC_INFO',
+                      ('uptime', 'uptime'),
+                      ('cputime', 'CPU usage'),
+                      ('memory', 'RAM'),
+                      'DISK_INFO',
+                      ('state', 'state (xen format)'),
+                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
+                      ('on_reboot', 'Action on VM reboot'),
+                      ('on_poweroff', 'Action on VM poweroff'),
+                      ('on_crash', 'Action on VM crash'),
+                      ('on_xend_start', 'Action on Xen start'),
+                      ('on_xend_stop', 'Action on Xen stop'),
+                      ('bootloader', 'Bootloader options'),
+                      ]
+    fields = []
+    machine_info = {}
+    machine_info['name'] = machine.name
+    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
+    machine_info['owner'] = machine.owner
+    machine_info['administrator'] = machine.administrator
+    machine_info['contact'] = machine.contact
+
+    nic_fields = getNicInfo(machine_info, machine)
+    nic_point = display_fields.index('NIC_INFO')
+    display_fields = (display_fields[:nic_point] + nic_fields +
+                      display_fields[nic_point+1:])
+
+    disk_fields = getDiskInfo(machine_info, machine)
+    disk_point = display_fields.index('DISK_INFO')
+    display_fields = (display_fields[:disk_point] + disk_fields +
+                      display_fields[disk_point+1:])
+
+    main_status['memory'] += ' MiB'
+    for field, disp in display_fields:
+        if field in ('uptime', 'cputime') and locals()[field] is not None:
+            fields.append((disp, locals()[field]))
+        elif field in machine_info:
+            fields.append((disp, machine_info[field]))
+        elif field in main_status:
+            fields.append((disp, main_status[field]))
+        else:
+            pass
+            #fields.append((disp, None))
+
+    checkpoint.checkpoint('Got fields')
+
+
+    max_mem = validation.maxMemory(machine.owner, machine, False)
+    checkpoint.checkpoint('Got mem')
+    max_disk = validation.maxDisk(machine.owner, machine)
+    defaults = Defaults()
+    for name in 'machine_id name administrator owner memory contact'.split():
+        setattr(defaults, name, getattr(machine, name))
+    defaults.type = machine.type.type_id
+    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
+    checkpoint.checkpoint('Got defaults')
+    d = dict(user=user,
+             on=status is not None,
+             machine=machine,
+             defaults=defaults,
+             has_vnc=has_vnc,
+             uptime=str(uptime),
+             ram=machine.memory,
+             max_mem=max_mem,
+             max_disk=max_disk,
+             owner_help=helppopup("Owner"),
+             fields = fields)
+    return d
+
+def info(user, fields):
+    """Handler for info on a single VM."""
+    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
+    d = infoDict(user, machine)
+    checkpoint.checkpoint('Got infodict')
+    return templates.info(searchList=[d])
+
+def unauthFront(_, fields):
+    """Information for unauth'd users."""
+    return templates.unauth(searchList=[{'simple' : True}])
+
+mapping = dict(list=listVms,
+               vnc=vnc,
+               command=command,
+               modify=modify,
+               info=info,
+               create=create,
+               help=helpHandler,
+               unauth=unauthFront)
+
+def printHeaders(headers):
+    """Print a dictionary as HTTP headers."""
+    for key, value in headers.iteritems():
+        print '%s: %s' % (key, value)
+    print
+
+
+def getUser():
+    """Return the current user based on the SSL environment variables"""
+    email = os.environ.get('SSL_CLIENT_S_DN_Email', None)
+    if email is None:
+        return None
+    return email.split("@")[0]
+
+def main(operation, user, fields):
+    start_time = time.time()
+    fun = mapping.get(operation, badOperation)
+
+    if fun not in (helpHandler, ):
+        connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
+    try:
+        checkpoint.checkpoint('Before')
+        output = fun(u, fields)
+        checkpoint.checkpoint('After')
+
+        headers = dict(DEFAULT_HEADERS)
+        if isinstance(output, tuple):
+            new_headers, output = output
+            headers.update(new_headers)
+        e = revertStandardError()
+        if e:
+            output.addError(e)
+        printHeaders(headers)
+        output_string =  str(output)
+        checkpoint.checkpoint('output as a string')
+        print output_string
+        if fields.has_key('timedebug'):
+            print '<pre>%s</pre>' % checkpoint
+    except Exception, err:
+        if not fields.has_key('js'):
+            if isinstance(err, CodeError):
+                print 'Content-Type: text/html\n'
+                e = revertStandardError()
+                print error(operation, u, fields, err, e)
+                sys.exit(1)
+            if isinstance(err, InvalidInput):
+                print 'Content-Type: text/html\n'
+                e = revertStandardError()
+                print invalidInput(operation, u, fields, err, e)
+                sys.exit(1)
+        print 'Content-Type: text/plain\n'
+        print 'Uh-oh!  We experienced an error.'
+        print 'Please email xvm-dev@mit.edu with the contents of this page.'
+        print '----'
+        e = revertStandardError()
+        print e
+        print '----'
+        raise
+
+if __name__ == '__main__':
+    fields = cgi.FieldStorage()
+
+    if fields.has_key('sqldebug'):
+        import logging
+        logging.basicConfig()
+        logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
+        logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.INFO)
+
+    u = getUser()
+    g.user = u
+    operation = os.environ.get('PATH_INFO', '')
+    if not operation:
+        print "Status: 301 Moved Permanently"
+        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
+        sys.exit(0)
+
+    if u is None:
+        operation = 'unauth'
+
+    if operation.startswith('/'):
+        operation = operation[1:]
+    if not operation:
+        operation = 'list'
+
+    if os.getenv("SIPB_XEN_PROFILE"):
+        import profile
+        profile.run('main(operation, u, fields)', 'log-'+operation)
+    else:
+        main(operation, u, fields)
Index: /branches/wsgi/packages/sipb-xen-www/code/static/VncViewer.jar
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/static/VncViewer.jar	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/static/VncViewer.jar	(revision 571)
@@ -0,0 +1,1 @@
+link /usr/local/lib/sipb-xen-vnc-client/VncViewer.jar
Index: /branches/wsgi/packages/sipb-xen-www/code/static/about.html
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/static/about.html	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/static/about.html	(revision 571)
@@ -0,0 +1,32 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head><title>About SIPB Virtual Servers</title>
+  <link href="favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <link rel="stylesheet" href="style.css" type="text/css" />
+  <link rel="stylesheet" href="layout.css" type="text/css" media="screen" />
+  <script type="text/javascript" src="prototype.js"></script>
+</head>
+<body id="body">
+
+<h1>About SIPB Virtual Servers</h1>
+
+SIPB Virtual Servers is an experimental service from the <a href="http://sipb.mit.edu/">Student Information Processing Board</a> which attempts to provide members of the MIT community with an easy way to create virtual machines which they can remotely administer.
+
+<h2>SIPB Virtual Servers is an Alpha Service</h2>
+
+Lots of services claim to be beta when they're really almost release-worthy. SIPB Virtual Servers is an alpha service in the true sense of the word. This means that we make almost no promises. Specifically, we make <strong>no promises</strong> about:
+
+<ul>
+<li><strong>Security</strong>: All of the current developers of SIPB Virtual Servers have root access to black-mesa, the host machine for all of the virtual machines, through both root and null instances.</li>
+<li><strong>Uptime</strong>: The SIPB Virtual Servers developers reserve the right to bring down both black-mesa and its VMs without warning or notification.</li>
+<li><strong>Data integrity</strong>: We do not back up data on SIPB Virtual Servers VMs.</li>
+</ul>
+
+<p>If you're OK with this, we hope you enjoy using and helping us test <a href="/">SIPB Virtual Servers</a>.</p>
+
+<hr />
+Questions? Contact <a href="mailto:xvm@mit.edu">xvm@mit.edu</a>.
+</body>
+</html>
Index: /branches/wsgi/packages/sipb-xen-www/code/static/layout.css
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/static/layout.css	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/static/layout.css	(revision 571)
@@ -0,0 +1,30 @@
+/*
+  Good layout ideas stolen from Debathena.
+  Hey, we use some different fonts (in style.css.)
+  And the background color is unmistakably different.
+  Some other things are tweaked too.
+*/
+
+/* This file contains screen-only layout declarations that won't be
+   used for printing. */
+
+/* Make <body /> show up as a centered white box with rounded border,
+   over the darker enclosing <html />. */
+html {
+    padding: .75em;
+    background: #09c;
+}
+body {
+    position: relative;
+    margin: 0 auto;
+    padding: 1.2em;
+    border: 1px solid black;
+    -moz-border-radius: .4em;
+    -webkit-border-radius: .4em;
+    border-radius: .4em;
+    background: #fff;
+}
+
+body.help {
+    max-width: 50em;
+}
Index: /branches/wsgi/packages/sipb-xen-www/code/static/prototype.js
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/static/prototype.js	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/static/prototype.js	(revision 571)
@@ -0,0 +1,3277 @@
+/*  Prototype JavaScript framework, version 1.5.1.1
+ *  (c) 2005-2007 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+/*--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.5.1.1',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      (document.createElement('div').__proto__ !==
+       document.createElement('form').__proto__)
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+}
+
+var Class = {
+  create: function() {
+    return function() {
+      this.initialize.apply(this, arguments);
+    }
+  }
+}
+
+var Abstract = new Object();
+
+Object.extend = function(destination, source) {
+  for (var property in source) {
+    destination[property] = source[property];
+  }
+  return destination;
+}
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (object === undefined) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : object.toString();
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch(type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (object.ownerDocument === document) return;
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (value !== undefined)
+        results.push(property.toJSON() + ': ' + value);
+    }
+    return '{' + results.join(', ') + '}';
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({}, object);
+  }
+});
+
+Function.prototype.bind = function() {
+  var __method = this, args = $A(arguments), object = args.shift();
+  return function() {
+    return __method.apply(object, args.concat($A(arguments)));
+  }
+}
+
+Function.prototype.bindAsEventListener = function(object) {
+  var __method = this, args = $A(arguments), object = args.shift();
+  return function(event) {
+    return __method.apply(object, [event || window.event].concat(args));
+  }
+}
+
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getFullYear() + '-' +
+    (this.getMonth() + 1).toPaddedString(2) + '-' +
+    this.getDate().toPaddedString(2) + 'T' +
+    this.getHours().toPaddedString(2) + ':' +
+    this.getMinutes().toPaddedString(2) + ':' +
+    this.getSeconds().toPaddedString(2) + '"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) {}
+    }
+
+    return returnValue;
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create();
+PeriodicalExecuter.prototype = {
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.callback(this);
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+}
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = count === undefined ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return this;
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = truncation === undefined ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : this;
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = document.createElement('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return {};
+
+    return match[1].split(separator || '&').inject({}, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (hash[key].constructor != Array) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    var result = '';
+    for (var i = 0; i < count; i++) result += this;
+    return result;
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (typeof replacement == 'function') return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+}
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create();
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+Template.prototype = {
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern  = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    return this.template.gsub(this.pattern, function(match) {
+      var before = match[1];
+      if (before == '\\') return match[2];
+      return before + String.interpret(object[match[3]]);
+    });
+  }
+}
+
+var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+var Enumerable = {
+  each: function(iterator) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator) {
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.map(iterator);
+  },
+
+  all: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!(iterator || Prototype.K)(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator) {
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!(iterator || Prototype.K)(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push((iterator || Prototype.K)(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(pattern, iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      var stringValue = value.toString();
+      if (stringValue.match(pattern))
+        results.push((iterator || Prototype.K)(value, index));
+    })
+    return results;
+  },
+
+  include: function(object) {
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = fillWith === undefined ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator) {
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (result == undefined || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (result == undefined || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator) {
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      ((iterator || Prototype.K)(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator) {
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (typeof args.last() == 'function')
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+}
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray
+});
+var $A = Array.from = function(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) {
+    return iterable.toArray();
+  } else {
+    var results = [];
+    for (var i = 0, length = iterable.length; i < length; i++)
+      results.push(iterable[i]);
+    return results;
+  }
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = Array.from = function(iterable) {
+    if (!iterable) return [];
+    if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
+      iterable.toArray) {
+      return iterable.toArray();
+    } else {
+      var results = [];
+      for (var i = 0, length = iterable.length; i < length; i++)
+        results.push(iterable[i]);
+      return results;
+    }
+  }
+}
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse)
+  Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(value && value.constructor == Array ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  indexOf: function(object) {
+    for (var i = 0, length = this.length; i < length; i++)
+      if (this[i] == object) return i;
+    return -1;
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (value !== undefined) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (arguments[i].constructor == Array) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  }
+}
+var Hash = function(object) {
+  if (object instanceof Hash) this.merge(object);
+  else Object.extend(this, object || {});
+};
+
+Object.extend(Hash, {
+  toQueryString: function(obj) {
+    var parts = [];
+    parts.add = arguments.callee.addPair;
+
+    this.prototype._each.call(obj, function(pair) {
+      if (!pair.key) return;
+      var value = pair.value;
+
+      if (value && typeof value == 'object') {
+        if (value.constructor == Array) value.each(function(value) {
+          parts.add(pair.key, value);
+        });
+        return;
+      }
+      parts.add(pair.key, value);
+    });
+
+    return parts.join('&');
+  },
+
+  toJSON: function(object) {
+    var results = [];
+    this.prototype._each.call(object, function(pair) {
+      var value = Object.toJSON(pair.value);
+      if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
+    });
+    return '{' + results.join(', ') + '}';
+  }
+});
+
+Hash.toQueryString.addPair = function(key, value, prefix) {
+  key = encodeURIComponent(key);
+  if (value === undefined) this.push(key);
+  else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
+}
+
+Object.extend(Hash.prototype, Enumerable);
+Object.extend(Hash.prototype, {
+  _each: function(iterator) {
+    for (var key in this) {
+      var value = this[key];
+      if (value && value == Hash.prototype[key]) continue;
+
+      var pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  },
+
+  keys: function() {
+    return this.pluck('key');
+  },
+
+  values: function() {
+    return this.pluck('value');
+  },
+
+  merge: function(hash) {
+    return $H(hash).inject(this, function(mergedHash, pair) {
+      mergedHash[pair.key] = pair.value;
+      return mergedHash;
+    });
+  },
+
+  remove: function() {
+    var result;
+    for(var i = 0, length = arguments.length; i < length; i++) {
+      var value = this[arguments[i]];
+      if (value !== undefined){
+        if (result === undefined) result = value;
+        else {
+          if (result.constructor != Array) result = [result];
+          result.push(value)
+        }
+      }
+      delete this[arguments[i]];
+    }
+    return result;
+  },
+
+  toQueryString: function() {
+    return Hash.toQueryString(this);
+  },
+
+  inspect: function() {
+    return '#<Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}>';
+  },
+
+  toJSON: function() {
+    return Hash.toJSON(this);
+  }
+});
+
+function $H(object) {
+  if (object instanceof Hash) return object;
+  return new Hash(object);
+};
+
+// Safari iterates over shadowed properties
+if (function() {
+  var i = 0, Test = function(value) { this.key = value };
+  Test.prototype.key = 'foo';
+  for (var property in new Test('bar')) i++;
+  return i > 1;
+}()) Hash.prototype._each = function(iterator) {
+  var cache = [];
+  for (var key in this) {
+    var value = this[key];
+    if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
+    cache.push(key);
+    var pair = [key, value];
+    pair.key = key;
+    pair.value = value;
+    iterator(pair);
+  }
+};
+ObjectRange = Class.create();
+Object.extend(ObjectRange.prototype, Enumerable);
+Object.extend(ObjectRange.prototype, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+}
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+}
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (typeof responder[callback] == 'function') {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) {}
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate: function() {
+    Ajax.activeRequestCount++;
+  },
+  onComplete: function() {
+    Ajax.activeRequestCount--;
+  }
+});
+
+Ajax.Base = function() {};
+Ajax.Base.prototype = {
+  setOptions: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   ''
+    }
+    Object.extend(this.options, options || {});
+
+    this.options.method = this.options.method.toLowerCase();
+    if (typeof this.options.parameters == 'string')
+      this.options.parameters = this.options.parameters.toQueryParams();
+  }
+}
+
+Ajax.Request = Class.create();
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+  _complete: false,
+
+  initialize: function(url, options) {
+    this.transport = Ajax.getTransport();
+    this.setOptions(options);
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Hash.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      if (this.options.onCreate) this.options.onCreate(this.transport);
+      Ajax.Responders.dispatch('onCreate', this, this.transport);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous)
+        setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (typeof extras.push == 'function')
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    return !this.transport.status
+        || (this.transport.status >= 200 && this.transport.status < 300);
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState];
+    var transport = this.transport, json = this.evalJSON();
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + this.transport.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(transport, json);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = this.getHeader('Content-type');
+      if (contentType && contentType.strip().
+        match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
+          this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
+      Ajax.Responders.dispatch('on' + state, this, transport, json);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) { return null }
+  },
+
+  evalJSON: function() {
+    try {
+      var json = this.getHeader('X-JSON');
+      return json ? json.evalJSON() : null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Updater = Class.create();
+
+Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
+  initialize: function(container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    }
+
+    this.transport = Ajax.getTransport();
+    this.setOptions(options);
+
+    var onComplete = this.options.onComplete || Prototype.emptyFunction;
+    this.options.onComplete = (function(transport, param) {
+      this.updateContent();
+      onComplete(transport, param);
+    }).bind(this);
+
+    this.request(url);
+  },
+
+  updateContent: function() {
+    var receiver = this.container[this.success() ? 'success' : 'failure'];
+    var response = this.transport.responseText;
+
+    if (!this.options.evalScripts) response = response.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (this.options.insertion)
+        new this.options.insertion(receiver, response);
+      else
+        receiver.update(response);
+    }
+
+    if (this.success()) {
+      if (this.onComplete)
+        setTimeout(this.onComplete.bind(this), 10);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create();
+Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
+  initialize: function(container, url, options) {
+    this.setOptions(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = {};
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(request) {
+    if (this.options.decay) {
+      this.decay = (request.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = request.responseText;
+    }
+    this.timer = setTimeout(this.onTimerEvent.bind(this),
+      this.decay * this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (typeof element == 'string')
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(query.snapshotItem(i));
+    return results;
+  };
+
+  document.getElementsByClassName = function(className, parentElement) {
+    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
+    return document._getElementsByXPath(q, parentElement);
+  }
+
+} else document.getElementsByClassName = function(className, parentElement) {
+  var children = ($(parentElement) || document.body).getElementsByTagName('*');
+  var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
+  for (var i = 0, length = children.length; i < length; i++) {
+    child = children[i];
+    var elementClassName = child.className;
+    if (elementClassName.length == 0) continue;
+    if (elementClassName == className || elementClassName.match(pattern))
+      elements.push(Element.extend(child));
+  }
+  return elements;
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Element) var Element = {};
+
+Element.extend = function(element) {
+  var F = Prototype.BrowserFeatures;
+  if (!element || !element.tagName || element.nodeType == 3 ||
+   element._extended || F.SpecificElementExtensions || element == window)
+    return element;
+
+  var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
+   T = Element.Methods.ByTag;
+
+  // extend methods for all tags (Safari doesn't need this)
+  if (!F.ElementExtensions) {
+    Object.extend(methods, Element.Methods),
+    Object.extend(methods, Element.Methods.Simulated);
+  }
+
+  // extend methods for specific tags
+  if (T[tagName]) Object.extend(methods, T[tagName]);
+
+  for (var property in methods) {
+    var value = methods[property];
+    if (typeof value == 'function' && !(property in element))
+      element[property] = cache.findOrStore(value);
+  }
+
+  element._extended = Prototype.emptyFunction;
+  return element;
+};
+
+Element.extend.cache = {
+  findOrStore: function(value) {
+    return this[value] = this[value] || function() {
+      return value.apply(null, [this].concat($A(arguments)));
+    }
+  }
+};
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, html) {
+    html = typeof html == 'undefined' ? '' : html.toString();
+    $(element).innerHTML = html.stripScripts();
+    setTimeout(function() {html.evalScripts()}, 10);
+    return element;
+  },
+
+  replace: function(element, html) {
+    element = $(element);
+    html = typeof html == 'undefined' ? '' : html.toString();
+    if (element.outerHTML) {
+      element.outerHTML = html.stripScripts();
+    } else {
+      var range = element.ownerDocument.createRange();
+      range.selectNodeContents(element);
+      element.parentNode.replaceChild(
+        range.createContextualFragment(html.stripScripts()), element);
+    }
+    setTimeout(function() {html.evalScripts()}, 10);
+    return element;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $A($(element).getElementsByTagName('*')).each(Element.extend);
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (typeof selector == 'string')
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return expression ? Selector.findElement(ancestors, expression, index) :
+      ancestors[index || 0];
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    var descendants = element.descendants();
+    return expression ? Selector.findElement(descendants, expression, index) :
+      descendants[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return expression ? Selector.findElement(previousSiblings, expression, index) :
+      previousSiblings[index || 0];
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return expression ? Selector.findElement(nextSiblings, expression, index) :
+      nextSiblings[index || 0];
+  },
+
+  getElementsBySelector: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  getElementsByClassName: function(element, className) {
+    return document.getElementsByClassName(className, element);
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      if (!element.attributes) return null;
+      var t = Element._attributeTranslations;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name])  name = t.names[name];
+      var attribute = element.attributes[name];
+      return attribute ? attribute.nodeValue : null;
+    }
+    return element.getAttribute(name);
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    if (elementClassName.length == 0) return false;
+    if (elementClassName == className ||
+        elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
+      return true;
+    return false;
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    Element.classNames(element).add(className);
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    Element.classNames(element).remove(className);
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
+    return element;
+  },
+
+  observe: function() {
+    Event.observe.apply(Event, arguments);
+    return $A(arguments).first();
+  },
+
+  stopObserving: function() {
+    Event.stopObserving.apply(Event, arguments);
+    return $A(arguments).first();
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = Position.cumulativeOffset(element);
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles, camelized) {
+    element = $(element);
+    var elementStyle = element.style;
+
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property])
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
+          (camelized ? property : property.camelize())] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = element.style.overflow || 'auto';
+    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  }
+};
+
+Object.extend(Element.Methods, {
+  childOf: Element.Methods.descendantOf,
+  childElements: Element.Methods.immediateDescendants
+});
+
+if (Prototype.Browser.Opera) {
+  Element.Methods._getStyle = Element.Methods.getStyle;
+  Element.Methods.getStyle = function(element, style) {
+    switch(style) {
+      case 'left':
+      case 'top':
+      case 'right':
+      case 'bottom':
+        if (Element._getStyle(element, 'position') == 'static') return null;
+      default: return Element._getStyle(element, style);
+    }
+  };
+}
+else if (Prototype.Browser.IE) {
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset'+style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  // IE is missing .innerHTML support for TABLE-related elements
+  Element.Methods.update = function(element, html) {
+    element = $(element);
+    html = typeof html == 'undefined' ? '' : html.toString();
+    var tagName = element.tagName.toUpperCase();
+    if (['THEAD','TBODY','TR','TD'].include(tagName)) {
+      var div = document.createElement('div');
+      switch (tagName) {
+        case 'THEAD':
+        case 'TBODY':
+          div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
+          depth = 2;
+          break;
+        case 'TR':
+          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
+          depth = 3;
+          break;
+        case 'TD':
+          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
+          depth = 4;
+      }
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      depth.times(function() { div = div.firstChild });
+      $A(div.childNodes).each(function(node) { element.appendChild(node) });
+    } else {
+      element.innerHTML = html.stripScripts();
+    }
+    setTimeout(function() { html.evalScripts() }, 10);
+    return element;
+  }
+}
+else if (Prototype.Browser.Gecko) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+Element._attributeTranslations = {
+  names: {
+    colspan:   "colSpan",
+    rowspan:   "rowSpan",
+    valign:    "vAlign",
+    datetime:  "dateTime",
+    accesskey: "accessKey",
+    tabindex:  "tabIndex",
+    enctype:   "encType",
+    maxlength: "maxLength",
+    readonly:  "readOnly",
+    longdesc:  "longDesc"
+  },
+  values: {
+    _getAttr: function(element, attribute) {
+      return element.getAttribute(attribute, 2);
+    },
+    _flag: function(element, attribute) {
+      return $(element).hasAttribute(attribute) ? attribute : null;
+    },
+    style: function(element) {
+      return element.style.cssText.toLowerCase();
+    },
+    title: function(element) {
+      var node = element.getAttributeNode('title');
+      return node.specified ? node.nodeValue : null;
+    }
+  }
+};
+
+(function() {
+  Object.extend(this, {
+    href: this._getAttr,
+    src:  this._getAttr,
+    type: this._getAttr,
+    disabled: this._flag,
+    checked:  this._flag,
+    readonly: this._flag,
+    multiple: this._flag
+  });
+}).call(Element._attributeTranslations.values);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    var t = Element._attributeTranslations, node;
+    attribute = t.names[attribute] || attribute;
+    node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = {};
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+ document.createElement('div').__proto__) {
+  window.HTMLElement = {};
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || {});
+  else {
+    if (tagName.constructor == Array) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = {};
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    var cache = Element.extend.cache;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = cache.findOrStore(value);
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = {};
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (typeof klass == "undefined") continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+};
+
+var Toggle = { display: Element.toggle };
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.Insertion = function(adjacency) {
+  this.adjacency = adjacency;
+}
+
+Abstract.Insertion.prototype = {
+  initialize: function(element, content) {
+    this.element = $(element);
+    this.content = content.stripScripts();
+
+    if (this.adjacency && this.element.insertAdjacentHTML) {
+      try {
+        this.element.insertAdjacentHTML(this.adjacency, this.content);
+      } catch (e) {
+        var tagName = this.element.tagName.toUpperCase();
+        if (['TBODY', 'TR'].include(tagName)) {
+          this.insertContent(this.contentFromAnonymousTable());
+        } else {
+          throw e;
+        }
+      }
+    } else {
+      this.range = this.element.ownerDocument.createRange();
+      if (this.initializeRange) this.initializeRange();
+      this.insertContent([this.range.createContextualFragment(this.content)]);
+    }
+
+    setTimeout(function() {content.evalScripts()}, 10);
+  },
+
+  contentFromAnonymousTable: function() {
+    var div = document.createElement('div');
+    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
+    return $A(div.childNodes[0].childNodes[0].childNodes);
+  }
+}
+
+var Insertion = new Object();
+
+Insertion.Before = Class.create();
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
+  initializeRange: function() {
+    this.range.setStartBefore(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment, this.element);
+    }).bind(this));
+  }
+});
+
+Insertion.Top = Class.create();
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
+  initializeRange: function() {
+    this.range.selectNodeContents(this.element);
+    this.range.collapse(true);
+  },
+
+  insertContent: function(fragments) {
+    fragments.reverse(false).each((function(fragment) {
+      this.element.insertBefore(fragment, this.element.firstChild);
+    }).bind(this));
+  }
+});
+
+Insertion.Bottom = Class.create();
+Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
+  initializeRange: function() {
+    this.range.selectNodeContents(this.element);
+    this.range.collapse(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.appendChild(fragment);
+    }).bind(this));
+  }
+});
+
+Insertion.After = Class.create();
+Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
+  initializeRange: function() {
+    this.range.setStartAfter(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment,
+        this.element.nextSibling);
+    }).bind(this));
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create();
+
+Selector.prototype = {
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  compileMatcher: function() {
+    // Selectors with namespaced attributes can't use the XPath version
+    if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e]; return;
+    }
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le,  m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    return this.findElements(document).include(element);
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+};
+
+Object.extend(Selector, {
+  _cache: {},
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: "[@#{1}]",
+    attr: function(m) {
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (typeof h === 'function') return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, m, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
+    className:    'n = h.className(n, r, "#{1}", c); c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+    },
+    pseudo:       function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._counted = true;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._counted) {
+          n._counted = true;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      tagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() == tagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!nodes && root == document) return targetNode ? [targetNode] : [];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr) {
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._counted) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._counted) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._counted) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (typeof expression == 'number') {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    var exprs = expressions.join(','), expressions = [];
+    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, getHash) {
+    var data = elements.inject({}, function(result, element) {
+      if (!element.disabled && element.name) {
+        var key = element.name, value = $(element).getValue();
+        if (value != null) {
+         	if (key in result) {
+            if (result[key].constructor != Array) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return getHash ? data : Hash.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, getHash) {
+    return Form.serializeElements(Form.getElements(form), getHash);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    return $(form).getElements().find(function(element) {
+      return element.type != 'hidden' && !element.disabled &&
+        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || {});
+
+    var params = options.parameters;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (typeof params == 'string') params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(form.readAttribute('action'), options);
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+}
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = {};
+        pair[element.name] = value;
+        return Hash.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+        !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) {}
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element);
+      default:
+        return Form.Element.Serializers.textarea(element);
+    }
+  },
+
+  inputSelector: function(element) {
+    return element.checked ? element.value : null;
+  },
+
+  textarea: function(element) {
+    return element.value;
+  },
+
+  select: function(element) {
+    return this[element.type == 'select-one' ?
+      'selectOne' : 'selectMany'](element);
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = function() {}
+Abstract.TimedObserver.prototype = {
+  initialize: function(element, frequency, callback) {
+    this.frequency = frequency;
+    this.element   = $(element);
+    this.callback  = callback;
+
+    this.lastValue = this.getValue();
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    var value = this.getValue();
+    var changed = ('string' == typeof this.lastValue && 'string' == typeof value
+      ? this.lastValue != value : String(this.lastValue) != String(value));
+    if (changed) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+}
+
+Form.Element.Observer = Class.create();
+Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create();
+Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = function() {}
+Abstract.EventObserver.prototype = {
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback.bind(this));
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+}
+
+Form.Element.EventObserver = Class.create();
+Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create();
+Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) {
+  var Event = new Object();
+}
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+
+  element: function(event) {
+    return $(event.target || event.srcElement);
+  },
+
+  isLeftClick: function(event) {
+    return (((event.which) && (event.which == 1)) ||
+            ((event.button) && (event.button == 1)));
+  },
+
+  pointerX: function(event) {
+    return event.pageX || (event.clientX +
+      (document.documentElement.scrollLeft || document.body.scrollLeft));
+  },
+
+  pointerY: function(event) {
+    return event.pageY || (event.clientY +
+      (document.documentElement.scrollTop || document.body.scrollTop));
+  },
+
+  stop: function(event) {
+    if (event.preventDefault) {
+      event.preventDefault();
+      event.stopPropagation();
+    } else {
+      event.returnValue = false;
+      event.cancelBubble = true;
+    }
+  },
+
+  // find the first node with the given tagName, starting from the
+  // node the event was triggered on; traverses the DOM upwards
+  findElement: function(event, tagName) {
+    var element = Event.element(event);
+    while (element.parentNode && (!element.tagName ||
+        (element.tagName.toUpperCase() != tagName.toUpperCase())))
+      element = element.parentNode;
+    return element;
+  },
+
+  observers: false,
+
+  _observeAndCache: function(element, name, observer, useCapture) {
+    if (!this.observers) this.observers = [];
+    if (element.addEventListener) {
+      this.observers.push([element, name, observer, useCapture]);
+      element.addEventListener(name, observer, useCapture);
+    } else if (element.attachEvent) {
+      this.observers.push([element, name, observer, useCapture]);
+      element.attachEvent('on' + name, observer);
+    }
+  },
+
+  unloadCache: function() {
+    if (!Event.observers) return;
+    for (var i = 0, length = Event.observers.length; i < length; i++) {
+      Event.stopObserving.apply(this, Event.observers[i]);
+      Event.observers[i][0] = null;
+    }
+    Event.observers = false;
+  },
+
+  observe: function(element, name, observer, useCapture) {
+    element = $(element);
+    useCapture = useCapture || false;
+
+    if (name == 'keypress' &&
+      (Prototype.Browser.WebKit || element.attachEvent))
+      name = 'keydown';
+
+    Event._observeAndCache(element, name, observer, useCapture);
+  },
+
+  stopObserving: function(element, name, observer, useCapture) {
+    element = $(element);
+    useCapture = useCapture || false;
+
+    if (name == 'keypress' &&
+        (Prototype.Browser.WebKit || element.attachEvent))
+      name = 'keydown';
+
+    if (element.removeEventListener) {
+      element.removeEventListener(name, observer, useCapture);
+    } else if (element.detachEvent) {
+      try {
+        element.detachEvent('on' + name, observer);
+      } catch (e) {}
+    }
+  }
+});
+
+/* prevent memory leaks in IE */
+if (Prototype.Browser.IE)
+  Event.observe(window, 'unload', Event.unloadCache, false);
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  realOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if(element.tagName=='BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  offsetParent: function(element) {
+    if (element.offsetParent) return element.offsetParent;
+    if (element == document.body) return element;
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return element;
+
+    return document.body;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = this.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = this.realOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = this.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  page: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element,'position')=='absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!window.opera || element.tagName=='BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return [valueL, valueT];
+  },
+
+  clone: function(source, target) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || {})
+
+    // find page position of source
+    source = $(source);
+    var p = Position.page(source);
+
+    // find coordinate system to use
+    target = $(target);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(target,'position') == 'absolute') {
+      parent = Position.offsetParent(target);
+      delta = Position.page(parent);
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
+    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.style.position == 'absolute') return;
+    Position.prepare();
+
+    var offsets = Position.positionedOffset(element);
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.style.position == 'relative') return;
+    Position.prepare();
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+  }
+}
+
+// Safari returns margins on body which is incorrect if the child is absolutely
+// positioned.  For performance reasons, redefine Position.cumulativeOffset for
+// KHTML/WebKit only.
+if (Prototype.Browser.WebKit) {
+  Position.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return [valueL, valueT];
+  }
+}
+
+Element.addMethods();
Index: /branches/wsgi/packages/sipb-xen-www/code/static/style.css
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/static/style.css	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/static/style.css	(revision 571)
@@ -0,0 +1,75 @@
+/*
+  Good style ideas stolen from Debathena.
+  Hey, we use some different fonts.
+  And the colors are unmistakably different.
+*/
+
+/* Basic fonts and sizes. */
+html {
+    font-size: 87.5%;
+}
+body {
+    font-family: Tahoma, Sans, sans-serif;
+}
+pre, code, samp, kbd, tt {
+    font-family: "Bitstream Vera Sans Mono", "Luxi Mono", "Courier New", monospace;
+    font-size: 100%;
+}
+
+/* Blue headings. */
+h1, h2, h3, h4, h5, h6 {
+    color: #13a;
+}
+
+/* Dividing line under the header. */
+h1 {
+    border-bottom: 1px solid black;
+    margin-top: 0;
+    overflow: auto;
+}
+
+pre {
+    background: #f6f6f6;
+    border: 1px solid #ddd;
+    padding: .125em;
+}
+a:link, a:visited {
+    text-decoration: none;
+}
+a:link:hover, a:visited:hover {
+    text-decoration: underline;
+}
+
+/* Keep help-popup links unbolded in e.g. table headings. */
+.helplink {
+  font-weight: normal;
+}
+
+/* Highlight error messages in bright red. */
+.error {
+  color: #FF0000;
+  padding: 0.25em;
+}
+td.error {
+  border: 1px solid red;
+}
+
+/* Navigation bar. */
+.navigation {
+  padding: 0em 1em;
+  font-size: 125%;
+  font-weight: bold;
+  font-family: "Trebuchet MS", Trebuchet, Sans, sans-serif;
+}
+.navigation li {
+  display: inline;
+  padding: .2em;
+}
+
+/* Logged-in welcome message. */
+.loggedin {
+  float: right;
+}
+.loggedin .name {
+  font-weight: bold;
+}
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/Makefile
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/Makefile	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/Makefile	(revision 571)
@@ -0,0 +1,13 @@
+TEMPLATES=$(wildcard *.tmpl)
+OUTPUTS=$(TEMPLATES:.tmpl=.py)
+
+all: ${OUTPUTS}
+
+%.py: %.tmpl
+	cheetah compile $<
+
+#${OUTPUTS}:${TEMPLATES}
+#	cheetah compile $^
+
+clean:
+	@rm -f ${OUTPUTS} *.pyo *.pyc *.py.bak
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/__init__.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/__init__.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/__init__.py	(revision 571)
@@ -0,0 +1,9 @@
+__all__ = 'info command error help invalid list unauth vnc'.split()
+for _name in __all__:
+    try:
+        _module = __import__(_name, globals(), {}, [_name])
+        globals()[_name] = getattr(_module, _name)
+    except ImportError, e:
+        import sys
+        print >> sys.stderr, 'Importing template "%s" raised error: %s' % (_name, e)
+        
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/command.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/command.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/command.tmpl	(revision 571)
@@ -0,0 +1,16 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#def title
+$command $machine.name
+#end def
+
+
+#def body
+<p>$command ${machine.name} was successful.</p>
+#if $command == "Delete VM" or True
+<p><a href="list">Return</a></p>
+#else
+<p><a href="info?machine_id=${machine.machine_id}">Return</a></p>
+#end if
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/create.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/create.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/create.tmpl	(revision 571)
@@ -0,0 +1,15 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#def title
+Created!
+#end def
+
+#def body
+#if $machine
+<p>Congratulations!  You have a new machine named ${machine.name}.</p>
+#else
+<p>Odd... no error, but no machine.</p>
+#end if
+<p><a href="list">Return</a></p>
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/error.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/error.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/error.tmpl	(revision 571)
@@ -0,0 +1,12 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#def title
+ERROR!
+#end def
+
+#def body
+<p>$errorMessage in operation $op.  This shouldn't happen!  Please
+email xvm@mit.edu to explain how it happened.  Stderr:</p>
+<pre>$stderr</pre>
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/functions.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/functions.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/functions.tmpl	(revision 571)
@@ -0,0 +1,63 @@
+#filter WebSafe
+#def databaseList($lst, $default, $onchange, $name, $id, $valueattr, $descattr)
+<select name="$name" id="$id"#slurp
+#if $onchange is not None
+onchange="$onchange"#slurp
+#end if
+>
+  <option #slurp
+#if $default then '' else 'selected'
+ value="">None</option>
+  #for $item in $lst
+  <option #slurp
+#if $default == getattr(item, valueattr) then 'selected' else ''
+ value="${getattr(item, valueattr)}">
+    ${getattr(item, descattr)}
+  </option>
+  #end for
+</select>
+#end def
+
+#def cdromList($default="", $onchange=None)
+#filter None
+$databaseList(sorted($sipb_xen_database.CDROM.select(), key=lambda x: x.description),
+              default, onchange, 'cdrom', 'cdromlist', 'cdrom_id', 'description')
+#end filter
+#end def
+
+#def autoList($default="", $onchange=None)
+#filter None
+$databaseList(sorted($sipb_xen_database.Autoinstall.select(), key=lambda x: x.description),
+              default, onchange, 'autoinstall', 'autoinstalllist', 'autoinstall_id', 'description')
+#end filter
+#end def
+
+#def vmTypeList($default=None)
+#for $vmtype in (('linux-hvm', 'HVM'), ('linux', 'ParaVM'), )
+<label>
+   <input #slurp
+#if $default == $vmtype[0] then 'checked' else ''
+ type="radio" name="vmtype" value="${vmtype[0]}">${vmtype[1]}</input>
+</label>
+#end for
+#end def
+
+#def addError(txt)
+#if $varExists('txt')
+#set global $error_text = $error_text + '----\n' + $txt
+#else
+#set global $error_text = $txt
+#end if
+#end def
+
+#def errorRow($value, $err)
+#if $err and $err.err_field == $value
+<tr>
+<td class="error" colspan="2">${str($err)}</td>
+</tr>
+#end if
+#end def
+#filter None
+$full_body
+#end filter
+#end filter
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/help.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/help.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/help.tmpl	(revision 571)
@@ -0,0 +1,36 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#attr pageclass = 'help'
+
+#def title
+#if len($subjects) == 1
+Help on $subjects[0]
+#else
+Help
+#end if
+#end def
+
+
+#def body
+#if not $simple
+<p>Topics: 
+#for $key in sorted($mapping)
+<a href="help?subject=$key">$key</a>
+#end for
+</p>
+#end if
+#for $subject in $subjects
+#if $subject in $mapping 
+<h2>$subject</h2>
+#filter None
+<p>$mapping[$subject]</p>
+#end filter
+#else
+<p>Unknown subject '$subject'.</p>
+#end if
+#end for
+#if $simple
+<a href="javascript:window.close();">Close</a>
+#end if
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/info.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/info.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/info.tmpl	(revision 571)
@@ -0,0 +1,144 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#def title
+Info on $machine.name
+#end def
+
+#def infoTable()
+<h2>Info</h2>
+<table>
+  #for $key, $value in $fields
+  <tr><td>$key:</td><td>$value</td></tr>
+  #end for
+</table>
+#end def
+
+#def commands()
+<form action="command" method="POST">
+  <input type="hidden" name="back" value="info"/>
+  <input type="hidden" name="machine_id" value="$machine.machine_id"/>
+  <table>
+    
+    <tr><td colspan=3>
+	#if $on
+	#if $has_vnc
+	<strong><a href="vnc?machine_id=$machine.machine_id">Get Console</a></strong>
+	#else
+	VNC console not enabled; still booting?
+	#end if
+	#else
+
+	#end if
+    </td></tr>
+      <tr>
+	#if $on
+	<td><button type="submit" class="button" name="action" value="Power off">Power off (hard)</button></td>
+	<td><button type="submit" class="button" name="action" value="Shutdown">Shut down</button></td>
+	<td><input type="submit" class="button" name="action" value="Reboot"/></td>
+	#else
+	<td><input type="submit" class="button" name="action" value="Power on"/></td>
+	#end if
+      <td>Boot CD:</td>
+      <td>#slurp
+#filter None
+$cdromList()#slurp
+#end filter
+</td>
+  </tr>
+    <tr>
+      <td><input type="submit" class="button" name="action" value="Delete VM" onclick="return confirm('Are you sure that you want to delete this VM?');"/></td>
+    </tr>
+  </table>
+</form>
+#end def
+
+#def modifyForm()
+#if $err
+<p class="error">We had a problem with your request:</p>
+#else if $varExists('new_machine')
+<p>Successfully modified.</p>
+#end if
+#if $on
+(To edit ram, disk size, or machine name, turn off the machine first.)
+#end if
+<form action="modify" method="POST">
+  <input type="hidden" name="machine_id" value="$defaults.machine_id"/>
+  <table>
+    <tr><td>Owner#slurp
+#filter None
+$helppopup("Owner")#slurp
+#end filter
+:</td><td><input type="text" name="owner", value="$defaults.owner"/></td></tr>
+#filter None
+$errorRow('owner', $err)
+#end filter
+    <tr><td>Administrator#slurp
+#filter None
+$helppopup("Administrator")#slurp
+#end filter
+:</td><td><input type="text" name="administrator", value="$defaults.administrator"/></td></tr>
+#filter None
+$errorRow('administrator', $err)
+#end filter
+    <tr><td>Contact email:</td><td><input type="text" name="contact" value="$defaults.contact"/></td></tr>
+#filter None
+$errorRow('contact', $err)
+#end filter
+#if not $on
+    <tr><td>Machine Name:</td><td><input type="text" name="name" value="$defaults.name"/></td></tr>
+#filter None
+$errorRow('name', $err)
+#end filter
+    <tr>
+      <td>HVM/ParaVM#slurp
+#filter None
+$helppopup('HVM/ParaVM')#slurp
+#end filter
+</td>
+      <td>#slurp
+#filter None
+$vmTypeList($defaults.type)#slurp
+#end filter
+</td>
+    </tr>
+    <tr><td>Ram:</td><td><input type="text" size=3 name="memory" value="$defaults.memory"/>MiB (max $max_mem)</td></tr>
+#filter None
+$errorRow('memory', $err)
+#end filter
+    <tr><td>Disk:</td><td><input type="text" size=3 name="disk" value="$defaults.disk"/>GiB (max $max_disk)</td><td>WARNING: Modifying disk size may corrupt your data.</td></tr>
+#filter None
+$errorRow('disk', $err)
+#end filter
+#else
+#filter None
+$errorRow('name', $err)
+$errorRow('memory', $err)
+$errorRow('disk', $err)
+#end filter
+#end if
+    <tr><td><input type="submit" class="button" name="action" value="Change"/></td></tr>
+  </table>
+</form>
+#end def
+
+#def body
+<div id="info">
+#filter None
+  $infoTable()
+#end filter
+</div>
+
+<h2>Commands</h2>
+<div id="commands">
+#filter None
+  $commands()
+#end filter
+</div>
+<h2>Settings</h2>
+<div id="modify">
+#filter None
+  $modifyForm()
+#end filter
+</div>
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/invalid.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/invalid.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/invalid.tmpl	(revision 571)
@@ -0,0 +1,18 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#def title
+Invalid Input
+#end def
+
+#def body
+<p>Your input was bad:</p>
+<table>
+<tr><td>operation</td><td>Field</td><td>value</td><td>reason</td></tr>
+<tr><td>$op</td><td>$err_field</td><td>$err_value</td><td>$errorMessage</td></tr>
+#if $stderr
+<p>Printed to standard error:</p>
+<pre>$stderr</pre>
+#end if
+</table>
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/list.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/list.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/list.tmpl	(revision 571)
@@ -0,0 +1,194 @@
+#from skeleton import skeleton
+#extends skeleton
+#import datetime
+
+
+#def title
+VM List
+#end def
+
+#def createForm()
+#if $cant_add_vm
+<p>$cant_add_vm</p>
+#else
+<h2>Create a new VM</h2>
+#if $err
+<p class="error">We had a problem with your request:</p>
+#else if $varExists('new_machine')
+<p>Congratulations! You successfully created a new VM called $new_machine.</p>
+#end if
+    <form action="create" method="POST">
+    <input type="hidden" name="back" value="list"/>
+      <table>
+#filter None
+      $errorRow('create', $err)
+#end filter
+	<tr>
+	  <td>Name</td>
+	  <td><input type="text" name="name" value="$defaults.name"/></td>
+	</tr>
+#filter None
+$errorRow('name', $err)
+#end filter
+	<tr>
+	  <td>Memory</td>
+	  <td><input type="text" name="memory" value="$defaults.memory" size=3/> MiB ($max_memory max)</td>
+	</tr>
+#filter None
+$errorRow('memory', $err)
+#end filter
+	<tr>
+	  <td>Disk</td>
+	  <td><input type="text" name="disk" value="$defaults.disk" size=3/> GiB (${"%0.1f" % ($max_disk-0.05)} max)</td>
+	</tr>
+#filter None
+$errorRow('disk', $err)
+#end filter
+        <tr>
+          <td>HVM/ParaVM#slurp
+#filter None
+$helppopup('HVM/ParaVM')#slurp
+#end filter
+</td>
+          <td>
+#filter None
+$vmTypeList($defaults.type)
+#end filter
+</td>
+        </tr>
+#filter None
+$errorRow('vmtype', $err)
+#end filter
+        <tr>
+          <td>Clone image?</td>
+#if $can_clone
+          <td><input type="checkbox" name="clone_from" id="clone_from" value="ice3" onchange="onclone(event)"/>
+              (experimental; 1-2 minutes, and you have an etch machine; root pw is 'password'.)
+              <script type='text/javascript'>function onclone(e){ document.getElementById('cdromlist').value = ''; }</script></td>
+#else
+	  <td><input type="checkbox" name="clone_from" id="clone_from" value="ice3" disabled="disabled"/> Image cloning is currently disabled for maintenance</td>
+#end if
+        </tr>
+#filter None
+$errorRow('autoinstall', $err)
+#end filter
+	<!--<tr>
+	  <td>Autoinstall#slurp
+#filter None
+$helppopup('Autoinstall')#slurp
+#end filter
+</td>
+	  <td><input type="radio" name="cd_or_auto" id="cd_or_auto_auto">
+#filter None
+$autoList($defaults.cdrom, "document.getElementById('cd_or_auto_auto').checked = true;document.getElementById('cdromlist').value = ''")
+	      (experimental; 1-2 minutes, and you have a machine; root pw is 'password'.)
+#end filter
+	  </input>
+	</tr>-->
+	<tr>
+	  <td>Boot CD</td>
+	  <td><input type="radio" name="cd_or_auto" id="cd_or_auto_cd" checked>
+#filter None
+$cdromList($defaults.cdrom, "document.getElementById('cd_or_auto_cd').checked = true;document.getElementById('autoinstalllist').value = ''")
+#end filter
+</td>
+	  </input>
+	</tr>
+$errorRow('cdrom', $err)
+$errorRow('cdrom', $err)
+	<tr>
+	  <td>Owner</td>
+	  <td><input type="text" name="owner" value="$defaults.owner"/></td>
+	</tr>
+#filter None
+	$errorRow('owner', $err)
+#end filter
+      </table>
+      <input type="submit" class="button" value="Create it!"/>
+    </form>
+#end if
+#end def
+
+#def machineRow($machine)
+      <tr> 
+	<td><a href="info?machine_id=$machine.machine_id">$machine.name</a></td>
+	<td>${machine.memory}M</td>
+	<td>$machine.owner</td>
+	<td>$machine.administrator</td>
+#if $machine.nics
+#set $nic = $machine.nics[0]
+	<td>$nic.ip</td>
+#else
+	<td></td>
+#end if
+<td>#slurp
+#if $machine.uptime
+${datetime.timedelta(seconds=int(machine.uptime))}#slurp
+#end if
+</td>
+	<td>#slurp
+#if $has_vnc[$machine] == True
+<a href="vnc?machine_id=$machine.machine_id">Console</a>#slurp
+#else if $has_vnc[$machine] != 'Off'
+#filter None
+$has_vnc[$machine]
+#end filter
+#end if
+</td>
+	<td>
+	  <form action="command" method="post">
+	    <input type="hidden" name="back" value="list"/>
+	    <input type="hidden" name="machine_id"
+		   value="$machine.machine_id"/>
+<input type="submit" class="button" name="action" value="#slurp
+#if $machine.uptime then 'Power off' else 'Power on'
+"/>
+	  </form>
+	</td>
+      </tr>
+#end def
+
+#def machineList($machines)
+    <table>
+      <tr>
+	<th>Name</th>
+	<th>Memory</th>
+	<th>Owner#slurp
+#filter None
+$helppopup('Owner')#slurp
+#end filter
+</th>
+        <th>Administrator#slurp
+#filter None
+$helppopup('Administrator')#slurp
+#end filter
+</th>
+	<th>IP</th>
+	<th>Uptime</th>
+	<th>VNC</th>
+	<th></th>
+      </tr>
+      #for $machine in $machines:
+    #filter None
+	$machineRow($machine)
+    #end filter
+      #end for
+    </table>
+#end def
+
+
+#def body
+<p style="font-size: 125%;">SIPB Virtual Servers is an <strong>alpha service</strong>.  <a href="/static/about.html">What does this mean?</a></p>
+#if not $machines
+<p>You don't currently control any VMs.</p>   
+#end if
+    <p><a href="list">refresh</a></p>
+    <div id="machinelist">
+    #filter None
+    $machineList($machines)
+    #end filter
+    </div>
+#filter None
+$createForm()
+#end filter
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/skeleton.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/skeleton.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/skeleton.tmpl	(revision 571)
@@ -0,0 +1,80 @@
+#from functions import functions
+#extends functions
+
+#def full_body
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head><title>$title &mdash; SIPB Virtual Servers</title>
+  <link href="static/favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <link rel="stylesheet" href="static/style.css" type="text/css" />
+  <link rel="stylesheet" href="static/layout.css" type="text/css" media="screen" />
+  <script type="text/javascript" src="static/prototype.js"></script>
+  <script type="text/javascript">
+var helpWin = null;
+function closeWin(){
+	if (helpWin != null){
+		if(!helpWin.closed)
+			helpWin.close();
+	}
+}
+
+function helppopup(name){
+   closeWin()
+   helpWin = window.open("help?simple=true&subject="+encodeURIComponent(name), "Help",
+"status, height = 300, width = 400");
+   if (window.focus){helpWin.focus();}
+   return false;
+}
+</script>
+</head>
+<body id="body"
+#if hasattr($self, 'pageclass'):
+  class="$pageclass"
+#end if
+  >
+
+#if False
+<div>
+<p>We are in the process of modifying the service.  Things likely will not work.</p>
+</div>
+#end if
+
+<div id="err">
+#if $varExists('error_text')
+<p>STDERR:</p><pre>$error_text</pre>
+#end if
+</div>
+
+#if not $varExists('simple') or not $simple
+<p class="loggedin">Welcome, <span class="name">$user</span>.</p>
+
+<ul class="navigation">
+<li><a href="list">List</a></li>
+#if $varExists('machine')
+<li><a href="info?machine_id=$machine.machine_id">Info</a></li>
+<li><a href="vnc?machine_id=$machine.machine_id">Console</a></li>
+#end if
+<li><a href="help">Help</a></ul></li>
+</ul>
+#end if
+<div id="result" class="result">
+#if $varExists('result')
+$result
+#end if
+</div>
+
+#if not $varExists('simple') or not $simple
+<h1>$title &mdash; SIPB Virtual Servers</h1>
+#end if
+#filter None
+$body
+#end filter
+#if not $varExists('simple') or not $simple
+<hr />
+Questions? Contact <a href="mailto:xvm@mit.edu">xvm@mit.edu</a>.
+#end if
+</body>
+</html>
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/unauth.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/unauth.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/unauth.tmpl	(revision 571)
@@ -0,0 +1,40 @@
+#from skeleton import skeleton
+#extends skeleton
+
+
+#def title
+Intro
+#end def
+
+#def body
+<h1>XVM &mdash; Virtual Servers for MIT </h1>
+
+<p><strong>xvm.mit.edu</strong> is a virtualization service for the
+MIT community.  We offer <strong>virtual machines</strong>&mdash;your
+own complete system on which you can start from our <strong>two-minute
+Debian</strong> Linux auto-install or install the operating
+<strong>system of your choice</strong>.  The service is <strong>free
+to any Athena account holder</strong>.  </p>
+
+<p>If you're a member of the MIT community, go <a
+href="https://xvm.mit.edu/"><strong>try out the service</strong>
+now</a>.  You'll need an <a href="http://ca.mit.edu/">MIT personal
+certificate</a>.</p>
+
+<p>The XVM code base is also forthcoming as a <strong>free
+software</strong> project.  We've been too lazy to put in a COPYING
+file yet, but you can look at our source in <a
+href="https://xvm.mit.edu:1111/">our Subversion repo</a>.  If you're
+interested in using <strong>XVM outside MIT</strong>, we'd be happy to
+help you make it work in your environment; mail us at
+<tt>xvm@mit.edu</tt>.</p>
+
+<p>xvm.mit.edu is provided by <a href="http://sipb.mit.edu/">SIPB</a>,
+the <strong>student computing group</strong> at MIT, with generous
+funding from <a href="http://web.mit.edu/ist/">IS&amp;T</a>.  Like all
+SIPB projects, we are independently run by our own team of volunteers,
+and we welcome new contributors.</p>
+
+<p>Questions and <strong>feedback welcome</strong> at <tt>xvm@mit.edu</tt>.</p>
+
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/templates/vnc.tmpl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/templates/vnc.tmpl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/templates/vnc.tmpl	(revision 571)
@@ -0,0 +1,25 @@
+#from skeleton import skeleton
+#extends skeleton
+
+#def title
+Console to $machine.name
+#end def
+
+#def body
+<style type='text/css'>body { max-width: none }</style>
+#if not $on
+<p> Your machine appears to be off.</p>
+#else if not $has_vnc
+<p> Your machine appears to not be accepting VNC connections. Perhaps you have a ParaVM machine?</p>
+#end if
+<p>See <a href="help?subject=Console" target="_blank">tips</a> about framebuffer and other issues.</p>
+<APPLET CODE="VncViewer.class" ARCHIVE="https://$hostname:446/static/VncViewer.jar"
+        WIDTH="100%" HEIGHT="1000">
+<PARAM NAME="PASSWORD" VALUE="moocow">
+<PARAM NAME="PORT" VALUE="10003">
+<PARAM NAME="HOST" VALUE="$hostname">
+<PARAM NAME="VMNAME" VALUE="$machine.name">
+<PARAM NAME="AUTHTOKEN" VALUE="$authtoken">
+<PARAM NAME="SocketFactory" VALUE="VNCProxyConnectSocketFactory">
+</APPLET>
+#end def
Index: /branches/wsgi/packages/sipb-xen-www/code/validation.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/validation.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/validation.py	(revision 571)
@@ -0,0 +1,233 @@
+#!/usr/bin/python
+
+import cache_acls
+import getafsgroups
+import re
+import string
+from sipb_xen_database import Machine, NIC, Type, Disk
+from webcommon import InvalidInput, g
+
+MAX_MEMORY_TOTAL = 512
+MAX_MEMORY_SINGLE = 256
+MIN_MEMORY_SINGLE = 16
+MAX_DISK_TOTAL = 50
+MAX_DISK_SINGLE = 50
+MIN_DISK_SINGLE = 0.1
+MAX_VMS_TOTAL = 10
+MAX_VMS_ACTIVE = 4
+
+def getMachinesByOwner(user, machine=None):
+    """Return the machines owned by the same as a machine.
+
+    If the machine is None, return the machines owned by the same
+    user.
+    """
+    if machine:
+        owner = machine.owner
+    else:
+        owner = user
+    return Machine.select_by(owner=owner)
+
+def maxMemory(user, machine=None, on=True):
+    """Return the maximum memory for a machine or a user.
+
+    If machine is None, return the memory available for a new
+    machine.  Else, return the maximum that machine can have.
+
+    on is whether the machine should be turned on.  If false, the max
+    memory for the machine to change to, if it is left off, is
+    returned.
+    """
+    if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
+        # If they've been blessed, let them have it
+        return machine.memory
+    if not on:
+        return MAX_MEMORY_SINGLE
+    machines = getMachinesByOwner(user, machine)
+    active_machines = [x for x in machines if g.xmlist.get(x)]
+    mem_usage = sum([x.memory for x in active_machines if x != machine])
+    return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
+
+def maxDisk(user, machine=None):
+    """Return the maximum disk that a machine can reach.
+
+    If machine is None, the maximum disk for a new machine. Otherwise,
+    return the maximum that a given machine can be changed to.
+    """
+    if machine is not None:
+        machine_id = machine.machine_id
+    else:
+        machine_id = None
+    disk_usage = Disk.query().filter_by(Disk.c.machine_id != machine_id,
+                                        owner=user).sum(Disk.c.size) or 0
+    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
+
+def cantAddVm(user):
+    machines = getMachinesByOwner(user)
+    active_machines = [x for x in machines if g.xmlist.get(x)]
+    if len(machines) >= MAX_VMS_TOTAL:
+        return 'You have too many VMs to create a new one.'
+    if len(active_machines) >= MAX_VMS_ACTIVE:
+        return ('You already have the maximum number of VMs turned on.  '
+                'To create more, turn one off.')
+    return False
+
+def validAddVm(user):
+    reason = cantAddVm(user)
+    if reason:
+        raise InvalidInput('create', True, reason)
+    return True
+
+def haveAccess(user, machine):
+    """Return whether a user has administrative access to a machine"""
+    return user in cache_acls.accessList(machine)
+
+def owns(user, machine):
+    """Return whether a user owns a machine"""
+    return user in expandLocker(machine.owner)
+
+def validMachineName(name):
+    """Check that name is valid for a machine name"""
+    if not name:
+        return False
+    charset = string.ascii_letters + string.digits + '-_'
+    if name[0] in '-_' or len(name) > 22:
+        return False
+    for x in name:
+        if x not in charset:
+            return False
+    return True
+
+def validMemory(user, memory, machine=None, on=True):
+    """Parse and validate limits for memory for a given user and machine.
+
+    on is whether the memory must be valid after the machine is
+    switched on.
+    """
+    try:
+        memory = int(memory)
+        if memory < MIN_MEMORY_SINGLE:
+            raise ValueError
+    except ValueError:
+        raise InvalidInput('memory', memory,
+                           "Minimum %s MiB" % MIN_MEMORY_SINGLE)
+    if memory > maxMemory(user, machine, on):
+        raise InvalidInput('memory', memory,
+                           'Maximum %s MiB for %s' % (maxMemory(user, machine),
+                                                      user))
+    return memory
+
+def validDisk(user, disk, machine=None):
+    """Parse and validate limits for disk for a given user and machine."""
+    try:
+        disk = float(disk)
+        if disk > maxDisk(user, machine):
+            raise InvalidInput('disk', disk,
+                               "Maximum %s G" % maxDisk(user, machine))
+        disk = int(disk * 1024)
+        if disk < MIN_DISK_SINGLE * 1024:
+            raise ValueError
+    except ValueError:
+        raise InvalidInput('disk', disk,
+                           "Minimum %s GiB" % MIN_DISK_SINGLE)
+    return disk
+
+def validVmType(vm_type):
+    if vm_type is None:
+        return None
+    t = Type.get(vm_type)
+    if t is None:
+        raise CodeError("Invalid vm type '%s'"  % vm_type)
+    return t
+
+def testMachineId(user, machine_id, exists=True):
+    """Parse, validate and check authorization for a given user and machine.
+
+    If exists is False, don't check that it exists.
+    """
+    if machine_id is None:
+        raise InvalidInput('machine_id', machine_id,
+                           "Must specify a machine ID.")
+    try:
+        machine_id = int(machine_id)
+    except ValueError:
+        raise InvalidInput('machine_id', machine_id, "Must be an integer.")
+    machine = Machine.get(machine_id)
+    if exists and machine is None:
+        raise InvalidInput('machine_id', machine_id, "Does not exist.")
+    if machine is not None and not haveAccess(user, machine):
+        raise InvalidInput('machine_id', machine_id,
+                           "You do not have access to this machine.")
+    return machine
+
+def testAdmin(user, admin, machine):
+    """Determine whether a user can set the admin of a machine to this value.
+
+    Return the value to set the admin field to (possibly 'system:' +
+    admin).  XXX is modifying this a good idea?
+    """
+    if admin in (None, machine.administrator):
+        return None
+    if admin == user:
+        return admin
+    if ':' not in admin:
+        if cache_acls.isUser(admin):
+            return admin
+        admin = 'system:' + admin
+    try:
+        if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
+            return admin
+    except getafsgroups.AfsProcessError, e:
+        errmsg = str(e)
+        if errmsg.startswith("pts: User or group doesn't exist"):
+            errmsg = 'The group "%s" does not exist.' % admin
+        raise InvalidInput('administrator', admin, errmsg)
+    #XXX Should we require that user is in the admin group?
+    return admin
+
+def testOwner(user, owner, machine=None):
+    """Determine whether a user can set the owner of a machine to this value.
+
+    If machine is None, this is the owner of a new machine.
+    """
+    if owner == user or machine is not None and owner == machine.owner:
+        return owner
+    if owner is None:
+        raise InvalidInput('owner', owner, "Owner must be specified")
+    try:
+        if user not in cache_acls.expandLocker(owner):
+            raise InvalidInput('owner', owner, 'You do not have access to the '
+                               + owner + ' locker')
+    except getafsgroups.AfsProcessError, e:
+        raise InvalidInput('owner', owner, str(e))
+    return owner
+
+def testContact(user, contact, machine=None):
+    if contact in (None, machine.contact):
+        return None
+    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
+        raise InvalidInput('contact', contact, "Not a valid email.")
+    return contact
+
+def testDisk(user, disksize, machine=None):
+    return disksize
+
+def testName(user, name, machine=None):
+    if name in (None, machine.name):
+        return None
+    if not Machine.select_by(name=name):
+        return name
+    raise InvalidInput('name', name, "Name is already taken.")
+
+def testHostname(user, hostname, machine):
+    for nic in machine.nics:
+        if hostname == nic.hostname:
+            return hostname
+    # check if doesn't already exist
+    if NIC.select_by(hostname=hostname):
+        raise InvalidInput('hostname', hostname,
+                           "Already exists")
+    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
+        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
+                           "must only use number, letters, and dashes.")
+    return hostname
Index: /branches/wsgi/packages/sipb-xen-www/code/webcommon.py
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/webcommon.py	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/webcommon.py	(revision 571)
@@ -0,0 +1,58 @@
+"""Exceptions for the web interface."""
+
+import time
+from sipb_xen_database import Machine, MachineAccess
+
+class MyException(Exception):
+    """Base class for my exceptions"""
+    pass
+
+class InvalidInput(MyException):
+    """Exception for user-provided input is invalid but maybe in good faith.
+
+    This would include setting memory to negative (which might be a
+    typo) but not setting an invalid boot CD (which requires bypassing
+    the select box).
+    """
+    def __init__(self, err_field, err_value, expl=None):
+        MyException.__init__(self, expl)
+        self.err_field = err_field
+        self.err_value = err_value
+
+class CodeError(MyException):
+    """Exception for internal errors or bad faith input."""
+    pass
+
+import controls
+
+def cachedproperty(func):
+    name = '__cache_' + func.__name__ + '_' + str(id(func))
+    def getter(self):
+        try:
+            return getattr(self, name)
+        except AttributeError:
+            value = func(self)
+            setattr(self, name, value)
+            return value
+    return property(getter)
+
+class Global(object):
+    """Global state of the system, to avoid duplicate remctls to get state"""
+    def __init__(self, user):
+        self.user = user
+
+    machines = cachedproperty(lambda self:
+                                  Machine.query().join('acl').select_by(user=self.user))
+    xmlist_raw = cachedproperty(lambda self: controls.getList())
+    xmlist = cachedproperty(lambda self:
+                                dict((m, self.xmlist_raw[m.name])
+                                     for m in self.machines
+                                     if m.name in self.xmlist_raw))
+
+    def clear(self):
+        """Clear the state so future accesses reload it."""
+        for attr in list(self.__dict__):
+            if attr.startswith('__cache_'):
+                delattr(self, attr)
+
+g = Global(None)
Index: /branches/wsgi/packages/sipb-xen-www/code/xen-ips
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/code/xen-ips	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/code/xen-ips	(revision 571)
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+import random
+from sipb_xen_database import *
+import sys
+
+# stolen directly from xend/server/netif.py
+def randomMAC():
+    """Generate a random MAC address.
+
+    Uses OUI (Organizationally Unique Identifier) 00-16-3E, allocated to
+    Xensource, Inc. The OUI list is available at
+    http://standards.ieee.org/regauth/oui/oui.txt.
+
+    The remaining 3 fields are random, with the first bit of the first
+    random field set 0.
+
+    @return: MAC address string
+    """
+    mac = [ 0x00, 0x16, 0x3e,
+            random.randint(0x00, 0x7f),
+            random.randint(0x00, 0xff),
+            random.randint(0x00, 0xff) ]
+    return ':'.join(map(lambda x: "%02x" % x, mac))
+
+# ... and stolen from xend/uuid.py
+def randomUUID():
+    """Generate a random UUID."""
+
+    return [ random.randint(0, 255) for _ in range(0, 16) ]
+
+def uuidToString(u):
+    return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
+                     "%02x" * 6]) % tuple(u)
+
+
+def usage():
+    print >> sys.stderr, "USAGE: " + sys.argv[0] + " <ip>"
+
+def addip(ip):
+    n = NIC(machine_id=None, mac_addr=randomMAC(), ip=ip, hostname=None)
+    ctx.current.save(n)
+    ctx.current.flush()
+
+
+if __name__ == '__main__':
+    if len(sys.argv) == 2:
+        ip = sys.argv[1]
+    else:
+        usage()
+        sys.exit(1)
+    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
+    addip(ip)
Index: /branches/wsgi/packages/sipb-xen-www/debian/changelog
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/changelog	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/changelog	(revision 571)
@@ -0,0 +1,43 @@
+sipb-xen-www (3.4) unstable; urgency=low
+
+  * xvm.mit.edu rather than sipb-xen-dev.mit.edu
+
+ -- Greg Price <price@mit.edu>  Sun, 11 May 2008 00:49:58 -0400
+
+sipb-xen-www (3.3) unstable; urgency=low
+
+  * Fix the SVN server to point to the new AFS mountpoint
+
+ -- Evan Broder <broder@mit.edu>  Fri,  9 May 2008 02:37:21 -0400
+
+sipb-xen-www (3.2) unstable; urgency=low
+
+  * Check in (part of?) the Apache config.
+  * Modify it to allow an informative front page without certs.
+  * Add that front page.
+
+ -- Greg Price <price@mit.edu>  Fri,  9 May 2008 02:11:04 -0400
+
+sipb-xen-www (3.1) unstable; urgency=low
+
+  * Fixed the crontab definition
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Mon, 31 Mar 2008 05:49:32 -0400
+
+sipb-xen-www (3) unstable; urgency=low
+
+  * Refresh the ACL cache every 5 minutes
+
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Mon, 31 Mar 2008 05:38:16 -0400
+
+sipb-xen-www (2) unstable; urgency=low
+
+  * Create sipb-xen group in preinst script.
+
+ -- Eric Price <ecprice@sipb-xen-dev.mit.edu>  Sat, 29 Mar 2008 18:45:02 -0400
+
+sipb-xen-www (1) unstable; urgency=low
+
+  * Initial Release.
+ -- SIPB Xen Project <sipb-xen@mit.edu>  Fri, 28 Mar 2008 22:43:12 -0500
+
Index: /branches/wsgi/packages/sipb-xen-www/debian/compat
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/compat	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/compat	(revision 571)
@@ -0,0 +1,1 @@
+4
Index: /branches/wsgi/packages/sipb-xen-www/debian/control
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/control	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/control	(revision 571)
@@ -0,0 +1,11 @@
+Source: sipb-xen-www
+Section: base
+Priority: extra
+Maintainer: SIPB Xen Project <sipb-xen@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), subversion
+Standards-Version: 3.7.2
+
+Package: sipb-xen-www
+Architecture: all
+Depends: ${misc:Depends}, python-cheetah, python-simplejson, sipb-xen-database-common, sipb-xen-vnc-client
+Description: Install the sipb-xen-dev website
Index: /branches/wsgi/packages/sipb-xen-www/debian/copyright
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/copyright	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/copyright	(revision 571)
@@ -0,0 +1,3 @@
+This package was created for internal use of the SIPB Xen Project of
+the MIT Student Information Processing Board.  Ask sipb-xen@mit.edu if
+you have questions about redistribution.
Index: /branches/wsgi/packages/sipb-xen-www/debian/rules
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/rules	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/rules	(revision 571)
@@ -0,0 +1,6 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+
+binary-fixup/sipb-xen-www::
+	svn co https://sipb-xen-dev.mit.edu:1111/trunk/packages/sipb-xen-www/code/ $(DEB_DESTDIR)/var/www/sipb-xen-www
Index: /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.cron.d
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.cron.d	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.cron.d	(revision 571)
@@ -0,0 +1,8 @@
+#
+# cron-jobs for sipb-xen-www
+# Refresh the ACL cache
+#
+
+MAILTO=root
+
+*/5 * * * * www-data python /var/www/sipb-xen-www/cache_acls.py
Index: /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.install
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.install	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.install	(revision 571)
@@ -0,0 +1,1 @@
+files/* .
Index: /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.postinst
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.postinst	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.postinst	(revision 571)
@@ -0,0 +1,45 @@
+#!/bin/sh
+# postinst script for #PACKAGE#
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+	cd /var/www/sipb-xen-www
+	make clean && make all
+	chgrp -R sipb-xen .
+	chmod -R g+w .
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
Index: /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.preinst
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.preinst	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/debian/sipb-xen-www.preinst	(revision 571)
@@ -0,0 +1,40 @@
+#!/bin/sh
+# preinst script for #PACKAGE#
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <new-preinst> `install'
+#        * <new-preinst> `install' <old-version>
+#        * <new-preinst> `upgrade' <old-version>
+#        * <old-preinst> `abort-upgrade' <new-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    install|upgrade)
+	if ! getent group sipb-xen > /dev/null; then
+	    addgroup --system sipb-xen
+	fi
+    ;;
+
+    abort-upgrade)
+    ;;
+
+    *)
+        echo "preinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/conf.d/sipb-xen-repository
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/conf.d/sipb-xen-repository	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/conf.d/sipb-xen-repository	(revision 571)
@@ -0,0 +1,17 @@
+Alias /sipb-xen /srv/repository
+
+Alias /debian/ /debian/
+
+<Directory /srv/repository>
+		Options Indexes FollowSymLinks MultiViews
+		AllowOverride None
+		Order allow,deny
+		allow from all
+</Directory>
+
+<Directory /debian>
+		Options Indexes FollowSymLinks MultiViews
+		AllowOverride None
+		Order allow,deny
+		allow from all
+</Directory>
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/default
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/default	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/default	(revision 571)
@@ -0,0 +1,48 @@
+NameVirtualHost *:80
+<VirtualHost *:80>
+	ServerAdmin webmaster@localhost
+	
+	DocumentRoot /var/www/sipb-xen-www
+	<Directory /var/www/sipb-xen-www>
+		Options Indexes FollowSymLinks MultiViews ExecCGI
+		AllowOverride None
+		Order allow,deny
+		allow from all
+	</Directory>
+
+	RewriteEngine On
+	RewriteRule ^/static(.*) - [L]
+        RewriteRule ^/trac.fcgi(.*) - [L]
+        RewriteRule ^/trac/chrome/common(.*) /usr/share/trac/htdocs$1 [L]
+	RewriteRule ^/trac/login(.*) https://xvm.mit.edu/trac/login$1 [L]
+        RewriteRule ^/trac(.*) /var/www/trac/trac.fcgi$1 [L]
+	RewriteRule ^/sipb-xen - [L]
+	RewriteRule ^/(.*) /var/www/sipb-xen-www/main.py/$1 [L]
+
+	ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
+	<Directory "/usr/lib/cgi-bin">
+		AllowOverride None
+		Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
+		Order allow,deny
+		Allow from all
+	</Directory>
+
+	ErrorLog /var/log/apache2/error.log
+
+	# Possible values include: debug, info, notice, warn, error, crit,
+	# alert, emerg.
+	LogLevel notice
+
+	CustomLog /var/log/apache2/access.log combined
+	ServerSignature On
+
+    Alias /doc/ "/usr/share/doc/"
+    <Directory "/usr/share/doc/">
+        Options Indexes MultiViews FollowSymLinks
+        AllowOverride None
+        Order deny,allow
+        Deny from all
+        Allow from 127.0.0.0/255.0.0.0 ::1/128
+    </Directory>
+
+</VirtualHost>
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/ssl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/ssl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/ssl	(revision 571)
@@ -0,0 +1,88 @@
+<VirtualHost *:443>
+	ServerAdmin xvm@mit.edu
+	ServerName xvm.mit.edu:443
+	
+	DocumentRoot /var/www/sipb-xen-www
+	<Directory /var/www/sipb-xen-www>
+		Options Indexes FollowSymLinks MultiViews ExecCGI
+		AllowOverride None
+		Order allow,deny
+		allow from all
+	</Directory>
+	<Location />
+		Require valid-user
+		AuthType SSLCert
+		AuthSSLCertVar SSL_CLIENT_S_DN_Email
+		AuthSSLCertStripSuffix "@MIT.EDU"
+	</Location>
+
+	RewriteEngine On
+	RewriteRule ^/static(.*) - [L]
+	RewriteRule ^/trac.fcgi(.*) - [L]
+	RewriteRule ^/trac/chrome/common(.*) /usr/share/trac/htdocs$1 [L]
+	RewriteRule ^/trac(.*) /var/www/trac/trac.fcgi$1 [L]
+	RewriteRule ^/var(.*) - [L]
+	RewriteRule ^/wiki(.*) - [L]
+	RewriteRule ^/(.*) /var/www/sipb-xen-www/main.py/$1 [L]
+
+	RewriteLog /var/log/apache2/rewrite.log
+	RewriteLogLevel 0 
+
+	ErrorLog /var/log/apache2/error.log
+
+	# Possible values include: debug, info, notice, warn, error, crit,
+	# alert, emerg.
+	LogLevel warn
+
+	CustomLog /var/log/apache2/ssl_access.log combined
+	ServerSignature On
+
+	SSLEngine on
+
+	SSLCertificateFile ssl/server.crt
+	SSLCertificateKeyFile ssl/server.key
+	
+	SSLCACertificateFile ssl/mitCAclient.pem
+	SSLVerifyClient optional
+	SSLVerifyDepth 10
+
+	SSLOptions +StdEnvVars
+	
+	SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
+
+
+	Redirect /wiki https://xvm.mit.edu/trac/wiki	
+</VirtualHost>
+
+<VirtualHost *:446>
+	ServerAdmin xvm@mit.edu
+	ServerName xvm.mit.edu:446
+	
+	DocumentRoot /var/www/sipb-xen-www
+	<Directory />
+		Options Indexes FollowSymLinks MultiViews ExecCGI
+		AllowOverride None
+		Order allow,deny
+		allow from all
+	</Directory>
+
+	ErrorLog /var/log/apache2/error.log
+
+	# Possible values include: debug, info, notice, warn, error, crit,
+	# alert, emerg.
+	LogLevel warn
+
+	CustomLog /var/log/apache2/ssl_nocert_access.log combined
+	ServerSignature On
+
+	SSLEngine on
+
+	SSLCertificateFile ssl/server.crt
+	SSLCertificateKeyFile ssl/server.key
+	
+	SSLVerifyClient none
+
+	SSLOptions +StdEnvVars
+	
+	SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0	
+</VirtualHost>
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/svn
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/svn	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-available/svn	(revision 571)
@@ -0,0 +1,35 @@
+<VirtualHost *:1111>
+	ServerAdmin xvm@mit.edu
+	ServerName xvm.mit.edu:1111
+	
+	<Directory />
+		Options FollowSymLinks
+		AllowOverride None
+	</Directory>
+	<Location />
+		DAV svn
+		SVNPath /afs/sipb.mit.edu/project/xvm/svn
+		AuthType Basic
+		AuthName "xvm.mit.edu subversion repository"
+		AuthUserFile /etc/apache2/dav_svn.passwd
+		<LimitExcept GET PROPFIND OPTIONS REPORT>
+			Require valid-user
+		</LimitExcept>
+	</Location>
+
+	ErrorLog /var/log/apache2/error.log
+
+	# Possible values include: debug, info, notice, warn, error, crit,
+	# alert, emerg.
+	LogLevel warn
+
+	CustomLog /var/log/apache2/ssl_access.log combined
+	ServerSignature On
+
+	SSLEngine on
+
+	SSLCertificateFile ssl/server.crt
+	SSLCertificateKeyFile ssl/server.key
+	
+	SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
+</VirtualHost>
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/000-default
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/000-default	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/000-default	(revision 571)
@@ -0,0 +1,1 @@
+link ../sites-available/default
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/ssl
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/ssl	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/ssl	(revision 571)
@@ -0,0 +1,1 @@
+link ../sites-available/ssl
Index: /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/svn
===================================================================
--- /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/svn	(revision 571)
+++ /branches/wsgi/packages/sipb-xen-www/files/etc/apache2/sites-enabled/svn	(revision 571)
@@ -0,0 +1,1 @@
+link ../sites-available/svn
