Index: /branches/htmlform/COPYING
===================================================================
--- /branches/htmlform/COPYING	(revision 446)
+++ /branches/htmlform/COPYING	(revision 446)
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  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
+them 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 prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  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.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  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.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     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
+state 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 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program 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, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU 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.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
Index: /branches/htmlform/INSTALL
===================================================================
--- /branches/htmlform/INSTALL	(revision 446)
+++ /branches/htmlform/INSTALL	(revision 446)
@@ -0,0 +1,129 @@
+$Id$
+
+Requirements
+============
+
+- Any CGI-capable web server that can execute arbitrary files or 
+  files with administrator-defineable extensions
+- PostgreSQL >= 7.4.  It should be possible to (fairly) trivially patch 
+  the code for any other DBMS that supports:
+  - an IP address/CIDR netblock data type
+  - higher/greater, lower/less than, contains, and is-contained-by 
+    operators
+- Perl >= 5.6
+  - Standard modules:
+    These should be included in any base Perl install
+    - File::Path
+    - CGI::Carp
+    - POSIX
+    - Sys::Syslog
+  - Extra modules:
+    - NetAddr::IP >= 4.x.  3.x may work, however 4.x has been out for 
+      more than 4 years.
+    - DBI
+    - DBD::Pg
+    - Sys::SigAction.  This isn't strictly required;  it's used in an 
+      example hook for validating customer IDs against an external 
+      database.  It could arguably be replaced with sigaction() from the 
+      POSIX module when using Perl >= 5.8.2.  See eg Sys::SigAction on 
+      CPAN (http://search.cpan.org/~lbaxter/Sys-SigAction-0.11/lib/Sys/SigAction.pm) 
+      for some thoughts on the gritty details.
+
+Installing the IPDB
+===================
+
+1) Untar in a convenient location.  You should be able to simply use the 
+unpacked tarball as-is, or you can run "make install" to install files 
+in /usr/local/lib/ipdb-#VERSION#, with configuration modules in 
+/usr/local/etc/ipdb-#VERSION#.
+
+The Makefile supports substitution on most standard 
+GNU/FHS-ish paths, so you could also run:
+
+  make install prefix=/opt
+
+to install it under /opt.
+
+The Makefile also supports DESTDIR for packaging, so you can use:
+
+  make install libdir=/usr/lib sysconfdir=/etc DESTDIR=/tmp/ipdbpkgroot
+
+to install for packaging under /tmp/ipdbpkgroot with the core scripts 
+and HTML packaged under /usr/lib/ipdb-#VERSION#, and the configuration 
+modules packaged under /etc/ipdb-#VERSION#.
+
+2) Configuration:  These module files will either be in the cgi-bin/ 
+directory from the unpacked tarball, or /usr/local/etc/ipdb-#VERSION# 
+if installed with 'make install'.
+  a) Edit MyIPDB.pm:  you need to set the database DSN and 
+    company info.  You should probably also set the syslog facility and 
+    default custid.
+  b) Edit CustIDCK.pm as needed to validate customer IDs.
+
+4) Create the database, and the inital tables using cgi-bin/ipdb.psql.
+
+5) Configure your webserver to call the IPDB scripts at an appropriate 
+web path.  A webroot pointing to the HTML files (first level under 
+the ipdb-#VERSION#/ tarball directory, or /usr/local/lib/ipdb-#VERSION#) 
+with a symlink or alias for ip/ -> . should work fine;  a server alias 
+under an existing virtual host should work as well.
+
+Note that all URLs referenced internally currently assume they will be 
+presented at http://host/ip/;  you cannot put the IPDB at 
+http://host/noc/misc/ipdb/.
+
+The directory containing the HTML and scripts must have at least the 
+following Apache directives (or other server equivalent) set:
+
+  Options ExecCGI IncludesNoEXEC FollowSymlinks
+
+6) User lists can be maintained two basic ways:
+
+  a) Use the built-in user manager to add and remove users.  This 
+    requires mod_auth_pgsql, configured with read/write access to the 
+    IPDB users table.  A default user admin, password admin, is created 
+    in step 4 above - make sure to create a new user as an admin, and 
+    remove the default user (or at least change its password).
+
+  b) Maintain an external .htpasswd file of your own, configured and 
+    maintained however you like.  In this case the access-pwd-update.pl 
+    script should edited to match the .htpasswd filename/path and should 
+    be called from cron to make sure new users get added to the 
+    database, and old ones get deleted.  This extra maintenance of user 
+    lists is necessary to support the access controls, which are stored 
+    in the database.
+
+You will have to either temporarily create a user "admin", so that user
+can grant other users priviledges, or run the following on the database:
+
+  UPDATE users SET acl='bacdsA' WHERE username='newadminuser';
+
+Replace 'newadminuser' as appropriate.
+
+If you don't do this, nobody will be able to make any changes;  
+access-pwd-update.pl only grants minimal read access to new users.
+
+7) (optional) Pick a log facility by setting $IPDB::syslog_facility in 
+MyIPDB.pm, and tweak your syslog configuration to direct IPDB logging 
+to a custom log.  Most logging is at the level of "info" or "warn".  
+Full changes are not logged.  Logging verbosity isn't very high, so it 
+may be acceptable to leave the log stream at the defaults.
+
+---
+
+Basic installation should now be complete!  Log in as an admin user, 
+add your ARIN, RIPE, LACNIC, AfriNIC, or APNIC allocations and start 
+documenting your netblock usage.
+
+If you want to export rWHOIS data, see http://www.unixadmin.cc/rwhois/ 
+for a place to start on setting up an rWHOIS server.  Note that 
+db2rwhois.pl creates and maintains the net-<cidr> trees, all you have 
+to do is configure the daemon itself.  Schedule runs of 
+cgi-bin/extras/db2rwhois.pl followed by rwhois_indexer (every hour 
+should be plenty often).  You'll need to fill in correct organization 
+contact info in MyIPDB.pm.
+
+If you're just running from the unpacked tarball directory, you may need 
+to create symlinks in cgi-bin/extras/ for IPDB.pm and MyIPDB.pm, 
+pointing to ../IPDB.pm and ../MyIPDB.pm respectively.  Otherwise 
+db2rwhois.pl won't be able to find these modules.
Index: /branches/htmlform/Makefile
===================================================================
--- /branches/htmlform/Makefile	(revision 446)
+++ /branches/htmlform/Makefile	(revision 446)
@@ -0,0 +1,110 @@
+# $Id$
+# IPDB makefile
+
+PKGNAME=ipdb
+VERSION=2.6
+RELEASE=1
+
+# Include some boilerplate Gnu makefile definitions.
+prefix = /usr/local
+
+exec_prefix = ${prefix}
+bindir = ${exec_prefix}/bin
+libdir = ${exec_prefix}/lib
+infodir = ${prefix}/info
+includedir = ${prefix}/include
+datadir = ${prefix}/share
+localedir = $(datadir)/locale
+sysconfdir = ${prefix}/etc
+mandir = ${prefix}/man
+
+INSTALL = /usr/bin/install -c
+INSTALL_PROGRAM = ${INSTALL}
+INSTALL_SCRIPT = ${INSTALL}
+INSTALL_DATA = ${INSTALL} -m 644
+INSTALLMODE= -m 0755
+INSTALLMODE2 = -m 0555
+
+DESTDIR =
+
+MANIFEST = \
+	INSTALL COPYING Makefile ipdb.spec \
+	\
+	addmaster.html alloctypes.html assign.html compsearch.html \
+	confirm.html confirmRemove.html editDisplay.html fb-assign.html footer.inc \
+	header.inc help.html index.shtml ipdb.css \
+	newcity.html newnode.html nodesearch.html startsn.html updated.html \
+	images/logo.png \
+	\
+	cgi-bin/combineblocks.pl cgi-bin/access-pwd-update.pl \
+	cgi-bin/newnode.cgi cgi-bin/CustIDCK.pm cgi-bin/freespace.pl \
+	cgi-bin/admin.cgi cgi-bin/MyIPDB.pm cgi-bin/IPDB.pm \
+	cgi-bin/main.cgi cgi-bin/checkcusts.pl cgi-bin/newcity.cgi \
+	cgi-bin/allocate.pl cgi-bin/search.cgi \
+	cgi-bin/snCalc.cgi cgi-bin/CommonWeb.pm cgi-bin/ipdb.psql \
+	cgi-bin/consistency-check.pl \
+	\
+	cgi-bin/extras/db2rwhois.pl cgi-bin/extras/rwhois-net-skel.tar.gz cgi-bin/extras/rwhois-config \
+	cgi-bin/extras/network.tmpl
+
+HTML = \
+	addmaster.html alloctypes.html assign.html changes.html compsearch.html \
+	confirm.html confirmRemove.html editDisplay.html fb-assign.html footer.inc \
+	header.inc help.html index.shtml ipdb.css \
+	newcity.html newnode.html nodesearch.html startsn.html updated.html
+
+IMAGES = images/logo.png
+
+SCRIPTS = \
+	cgi-bin/extras/db2rwhois.pl cgi-bin/combineblocks.pl cgi-bin/access-pwd-update.pl \
+	cgi-bin/newnode.cgi cgi-bin/freespace.pl cgi-bin/admin.cgi \
+	cgi-bin/main.cgi cgi-bin/newcity.cgi cgi-bin/allocate.pl \
+	cgi-bin/search.cgi cgi-bin/consistency-check.pl
+
+MODULES = cgi-bin/IPDB.pm
+
+CONFIGMODULES = cgi-bin/MyIPDB.pm cgi-bin/CustIDCK.pm
+
+RWHOIS = \
+	cgi-bin/extras/db2rwhois.pl cgi-bin/extras/rwhois-net-skel.tar.gz cgi-bin/extras/rwhois-config \
+	cgi-bin/extras/network.tmpl
+
+DIRS = images cgi-bin cgi-bin/extras
+
+# hmm.  not sure what do do about you, m'friend...
+#ip@
+
+all:
+	# nullop
+
+install:
+	@for i in $(HTML) $(IMAGES); do \
+		$(INSTALL_DATA) -D $$i $(DESTDIR)${libdir}/ipdb-$(VERSION)/$$i ; \
+	done
+	# munge in necessary 'use lib ...' bits so scripts can find MyIPDB.pm...
+	@for i in $(SCRIPTS) $(MODULES) $(RWHOIS); do \
+		$(INSTALL_SCRIPT) -D $$i $(DESTDIR)${libdir}/ipdb-$(VERSION)/$$i ; \
+		perl -pi -e 's|##uselib##|use lib "${sysconfdir}/ipdb-$(VERSION)/";|;' $(DESTDIR)${libdir}/ipdb-$(VERSION)/$$i ; \
+	done
+	$(INSTALL) -d $(DESTDIR)${sysconfdir}/ipdb-$(VERSION)/
+	@for i in $(CONFIGMODULES) ; do \
+		$(INSTALL_DATA) $$i $(DESTDIR)${sysconfdir}/ipdb-$(VERSION)/ ; \
+	done
+	# and now munge MyIPDB.pm so it can find the core library
+	perl -pi -e 's|##uselib##|use lib "${libdir}/ipdb-$(VERSION)";|;' $(DESTDIR)${sysconfdir}/ipdb-$(VERSION)/MyIPDB.pm
+
+#clean:
+#	@for i in $(DIRS) ; do \
+#		$(MAKE) -C $$i clean ; \
+#	done
+
+dist:
+	mkdir $(PKGNAME)-$(VERSION)
+	tar cf - $(MANIFEST) | (cd $(PKGNAME)-$(VERSION); tar xf -)
+	/usr/bin/perl -p -e 's/#VERSION#/$(VERSION)/;s/#RELEASE#/$(RELEASE)/;s/#BETA#//g' < $(PKGNAME).spec > $(PKGNAME)-$(VERSION)/$(PKGNAME).spec
+	/usr/bin/perl -p -e 's/#VERSION#/$(VERSION)/;s/#RELEASE#/$(RELEASE)/;s/#BETA#//g' < INSTALL > $(PKGNAME)-$(VERSION)/INSTALL
+	perl -pi -e 's/[\d.]+;\s*##VERSION##/$(VERSION);/;' $(PKGNAME)-$(VERSION)/cgi-bin/IPDB.pm
+	tar cf $(PKGNAME)-$(VERSION).tar $(PKGNAME)-$(VERSION)
+	gzip -v -f -9 $(PKGNAME)-$(VERSION).tar
+	rm -rf $(PKGNAME)-$(VERSION)
+	# gpg --detach-sign $(PKGNAME)-$(VERSION).tar.gz
Index: /branches/htmlform/addmaster.html
===================================================================
--- /branches/htmlform/addmaster.html	(revision 446)
+++ /branches/htmlform/addmaster.html	(revision 446)
@@ -0,0 +1,11 @@
+<div class="indent">
+<div class="heading">Add new master IP block</div><br>
+<table class="regular" bgcolor="black" cellspacing="1" cellpadding="1">
+<form method="POST" action="/ip/cgi-bin/main.cgi" class="regular">
+<tr class="color1"><td>Master block to add: (CIDR)&nbsp</td><td><input type=text name=cidr></td></tr> <tr class="color2">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+<input type="hidden" name="action" value="newmaster">
+</form>
+</tr>
+</table>
+</div>
Index: /branches/htmlform/alloctypes.html
===================================================================
--- /branches/htmlform/alloctypes.html	(revision 446)
+++ /branches/htmlform/alloctypes.html	(revision 446)
@@ -0,0 +1,203 @@
+<html><head>
+
+<title>IP Database</title><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+
+<link rel="stylesheet" type="text/css" href="/ip/ipdb.css">
+<link rel="stylesheet" type="text/css" href="/ip/local.css">
+
+</head>
+<body>
+
+<table class="regular">
+
+<tr><td colspan=2 class="heading">Preseeded Allocation Types:</td><tr>
+
+<tr class="color1">
+<td>Customer netblock (default)</td>
+<td>A direct allocation /30 or larger to a customer.  Note that this does NOT 
+include RADIUS-routed "netblocks".  Non-customer systems should use the 
+"End-use netblock" type.</td>
+</tr>
+
+<tr class="color2">
+<td>Static IP - Server pool</td>
+<td>A single IP from a pool designated for servers</td>
+</tr>
+
+<tr class="color1">
+<td>Static IP - Cable</td>
+<td>A single IP address from a designated pool on the cable network.</td>
+</tr>
+
+<tr class="color2">
+<td>Static IP - DSL</td>
+<td>A single IP address from a designated pool on the DSL network.</td>
+</tr>
+
+<tr class="color1">
+<td>Static IP - Dialup</td>
+<td>A single IP address from a designated pool on a dialup RAS.</td>
+</tr>
+
+<tr class="color2">
+<td>Static IP - Wireless</td>
+<td>A single IP address from a designated pool on a wireless connection.</td>
+</tr>
+
+<tr class="color1">
+<td>Static pool - Servers</td>
+<td>A pool of IP addresses available for one-by-one assignment to servers in a 
+POP.</td>
+</tr>
+
+<tr class="color2">
+<td>Static pool - Cable</td>
+<td>A pool of IP addresses available for one-by-one assignment to customers on 
+cable.</td>
+</tr>
+
+<tr class="color1">
+<td>Static pool - DSL</td>
+<td>A pool of IP addresses available for one-by-one assignment to customers on 
+DSL.  Individual IPs will be "assigned" to the customer's city, but the pool 
+will remain assigned to its nominal origin.</td>
+</tr>
+
+<tr class="color2">
+<td>Static pool - Dialup</td>
+<td>A pool of IP addresses available for one-by-one assignment to dialup 
+customers.</td>
+</tr>
+
+<tr class="color1">
+<td>Static pool - Wireless</td>
+<td>A pool of IP addresses available for one-by-one assignment to customers on a 
+wireless connection.</td>
+</tr>
+
+<tr class="color2">
+<td>End-use netblock</td>
+<td>A /30 or larger allocation for arbitrary services - note this should not 
+be used for customer connections.  Use this if exact tracking of IP 
+usage in the block is not required.</td>
+</tr>
+
+<tr class="color1">
+<td>Dialup netblock</td>
+<td>Netblock assigned to one or more RAS units in a POP</td>
+</tr>
+
+<tr class="color2">
+<td>Dynamic DSL block</td>
+<td>Netblock for (mostly residential) PPPoE DSL.</td>
+</tr>
+
+<tr class="color1">
+<td>Dynamic cable block</td>
+<td>Netblock for (mostly residential) DHCP cable.</td>
+</tr>
+
+<tr class="color2">
+<td>Dynamic WiFi block</td>
+<td>Netblock for (mostly residential) (mostly) PPPoE wireless.</td>
+</tr>
+
+<tr class="color1">
+<td>Dynamic VoIP block</td>
+<td>Netblock for DHCP-assigned VoIP services.</td>
+</tr>
+
+<tr class="color2">
+<td>Static IP - LAN/POP</td>
+<td>A single IP address from a designated pool for internal LANs - either at a 
+single POP or for a VPN.</td>
+</tr>
+
+<tr class="color1">
+<td>Static IP - Managment</td>
+<td>A single IP address from a designated pool for managed devices.</td>
+</tr>
+
+<tr class="color2">
+<td>Static IP - Wifi CPE</td>
+<td>A single IP address from a designated pool for wireless CPE devices.</td>
+</tr>
+
+<tr class="color1">
+<td>Static pool - LAN/POP</td>
+<td>A pool of IP addresses available for one-by-one assignment to internal LAN 
+systems or VPNs.</td>
+</tr>
+
+<tr class="color2">
+<td>Static Pool - Managment</td>
+<td>A pool of IP addresses available for assignment to managed devices.</td>
+</tr>
+
+<tr class="color1">
+<td>Static pool - Wifi CPE</td>
+<td>A pool of IP addresses available for assignment to wireless CPE devices.</td>
+</tr>
+
+<tr class="color2">
+<td>Reserve for WAN blocks</td>
+<td>Reserve a chunk of IP space for core routers/etc.</td>
+</tr>
+
+<tr class="color1">
+<td>Reserve for dynamic-route DSL netblocks</td>
+<td>Reserve a chunk of IP space for netblocks configured on the customer end via 
+PPPoE/RADIUS.  Note that this is similar to the static DSL pool, but IP space is 
+to be allocated as /30 and larger netblocks, not single static IPs.</td>
+</tr>
+
+<tr class="color2">
+<td>Reserve for ATM</td>
+<td>Reserve a chunk of IP space for allocation to customers on ATM.</td>
+</tr>
+
+<tr class="color1">
+<td>Reserve for fibre</td>
+<td>Reserve a chunk of IP space for customers on a fibre connection.</td>
+</tr>
+
+<tr class="color2">
+<td>WAN block</td>
+<td>Individual netblock assignment for a core router.  Always taken from a
+block which has previously been reserved for such assignments.</td>
+</tr>
+
+<tr class="color1">
+<td>Dynamic-route DSL netblock</td>
+<td>Customer assignment for a netblock configured on the customer end via 
+PPPoE/RADIUS.  Always taken from a block which has previously been reserved for 
+such assignments.</td>
+</tr>
+
+<tr class="color2">
+<td>ATM block</td>
+<td>Customer assignment for a customer on ATM.  Always taken from a
+block which has previously been reserved for such assignments.</td>
+</tr>
+
+<tr class="color1">
+<td>Fibre</td>
+<td>Customer assignment for a customer on fibre. Always taken from a block 
+which has previously been reserved for such assignments.</td>
+</tr>
+
+<tr class="color2">
+<td>Routing</td>
+<td>Blocks not actually assigned to a service on their own, but which
+have been routed to individual POPs- typically /24, /23, /22</td>
+</tr>
+
+<tr class="color1">
+<td>Master block</td>
+<td>Allocations provided by the regional registry (ARIN, RIPE, LACNIC, AfriNIC, 
+or APNIC)</td>
+</tr>
+
+</table>
+
+</body></html>
Index: /branches/htmlform/assign.html
===================================================================
--- /branches/htmlform/assign.html	(revision 446)
+++ /branches/htmlform/assign.html	(revision 446)
@@ -0,0 +1,54 @@
+<div class="indent">
+<div class="heading">Assign IPs</div><br>
+<table class="regular" bgcolor="black" cellspacing="1" cellpadding="1">
+<form method="POST" action="main.cgi" class="regular">
+<tr class="color1">
+<td>Customer location:&nbsp;</td><td>
+<select name="city"><option selected>-</option>
+$$ALLCITIES$$
+</select>
+&nbsp;<a href="javascript:popNotes('/ip/newcity.html')">Add new location</a>
+</td>
+</tr>
+<tr class="color2">
+<td>Allocation type:</td><td>
+<select name="alloctype">
+$$TYPELIST$$
+</select>
+<input type="button" value=" ? " onclick="helpAllocTypes()" class="regular">
+</td>
+</tr><tr class="color1">
+<td>Subnet CIDR mask length:&nbsp;</td><td valign=top>&nbsp;/&nbsp;<input type="text" name="maskbits" size="3" maxlength="3"></td>
+</tr><tr class="color2">
+<td>Customer ID:&nbsp;</td><td><input type="text" name="custid" size="15" maxlength="15"> (Only required for Customer allocations)</td>
+</tr><tr class="color1">
+<td>Route from/through:&nbsp;</td><td>
+<select name="pop"><option selected value=>-</option>
+$$POPLIST$$
+</select>
+</td>
+</tr>
+<tr class="hack"><td>Wifi tower/Fibre demarc</td><td>
+<select name="node"><option selected>-</option>
+$$NODELIST$$
+</select>
+&nbsp;<a href="javascript:popNotes('/ip/newnode.html')">Add new location</a>
+</td></tr>
+<tr class="color2">
+<td>Route/allocate from this master:&nbsp;</td><td>$$MASTERLIST$$
+Allow automatic allocation from private IP ranges:<input type=checkbox name=allowpriv>
+</td>
+</tr><tr class="color1">
+<td>Circuit ID:&nbsp;</td><td><input name=circid size=40></td>
+</tr><tr class="color2">
+<td>Description/Name:&nbsp;</td><td><input name="desc" size=40></td>
+</tr><tr class="color1">
+<td>Notes:&nbsp;</td><td><textarea name="notes" rows="3" cols="40"></textarea></td>
+</tr>
+$$PRIVDATA$$
+<tr class="$$BUTTONROWCOLOUR$$">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+<input type="hidden" name="action" value="confirm">
+</tr>
+</table>
+</div>
Index: /branches/htmlform/cgi-bin/CommonWeb.pm
===================================================================
--- /branches/htmlform/cgi-bin/CommonWeb.pm	(revision 446)
+++ /branches/htmlform/cgi-bin/CommonWeb.pm	(revision 446)
@@ -0,0 +1,129 @@
+# ipdb/cgi-bin/CommonWeb.pm
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+
+package CommonWeb;
+
+use strict;		
+use warnings;
+use Exporter;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+$VERSION     = 1.00;
+@ISA         = qw(Exporter);
+@EXPORT_OK      = qw(&parse_post &printFooter &printHeader &printError &printAndExit &desanitize &cleanInput &desanitize);
+
+@EXPORT      = (); #export nothing by default
+%EXPORT_TAGS = ( ALL => [qw( &parse_post &printFooter &printHeader &printError
+				&printAndExit &desanitize &cleanInput )],
+                 lean    => [qw( &parse_post &printFooter &printHeader &printError
+				&printAndExit &cleanInput )]
+		);
+
+sub parse_post {
+  my $buffer;
+  if ($ENV{'REQUEST_METHOD'} eq "GET") {
+    $buffer=$ENV{'QUERY_STRING'}
+  } elsif ($ENV{'REQUEST_METHOD'} eq 'POST' && $ENV{'CONTENT_TYPE'} eq "application/x-www-form-urlencoded") {
+    read(STDIN, $buffer, $ENV{CONTENT_LENGTH});
+  } else {
+    $buffer = $ENV{'QUERY_STRING'};
+    $buffer || read(STDIN, $buffer, $ENV{CONTENT_LENGTH});
+  }
+  my @pairs = split(/&/, $buffer);
+  my %webvarLocal;
+  foreach my $pair (@pairs) {
+    my ($name, $value) = split(/=/, $pair);
+    $name  =~ tr/+/ /;
+    $name  =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+    $value =~ tr/+/ /;
+    $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+    $value =~ s/\'/\\\'/g;
+    $webvarLocal{$name} = $value;
+  }
+  return %webvarLocal;
+}
+
+
+sub printHeader {
+  my $title = shift;
+  print "Content-type: text/html\n\n";
+# This doesn't work well.  Must investigate.
+#  my $realm = shift;
+#  print qq(WWW-Authenticate: Basic realm="$realm"\n) if $realm;
+  open FILE, "../header.inc"
+	or carp $!;
+  my $html = join('',<FILE>);
+  close FILE;
+
+  $html =~ s/\$\$TITLE\$\$/$title/;
+# Necessary for mangling arbitrary bits of the header
+  my $i=0;
+  while (defined(my $param = shift)) {
+    $html =~ s/\$\$EXTRA$i\$\$/$param/g;
+    $i++;
+  }
+  print $html;
+}
+
+sub printFooter
+{
+  open FILE, "../footer.inc"
+	or croak $!;
+  while (<FILE>) 
+  {
+    print;
+  }
+  close FILE;
+}
+
+sub printError($)
+{
+	my $errStr = $_[0];
+	print qq(
+	<center><p class="regular"> $errStr </p>
+	<input type="button" value="Back" onclick="history.go(-1)">
+	</center>
+	);
+}
+
+sub printAndExit($)
+{
+	my $errStr = $_[0];
+	print qq(
+	<center><p class="regular"> $errStr </p>
+	<input type="button" value="Back" onclick="history.go(-1)">
+	</center>
+	);
+	printFooter();
+	exit(0);
+}
+
+# needs a reference to the webvar hash.
+# takes out backticks and single quotes
+sub cleanInput($)
+{
+	my $hashRef = $_[0];
+
+	foreach my $key (keys %$hashRef) 
+	{
+		$hashRef->{$key} =~ s/`/\\`/g;
+		$hashRef->{$key} =~ s/'/\'/g;
+	}
+}
+
+# undoes clean input.  takes a string as an arg.
+sub desanitize($)
+{
+	my $string = $_[0];
+	$string =~ s/\\`/`/g;
+	$string =~ s/\\'/'/g;
+	return $string;
+}
+
+# indicate that the module loaded okay.
+1;
Index: /branches/htmlform/cgi-bin/CustIDCK.pm
===================================================================
--- /branches/htmlform/cgi-bin/CustIDCK.pm	(revision 446)
+++ /branches/htmlform/cgi-bin/CustIDCK.pm	(revision 446)
@@ -0,0 +1,87 @@
+# ipdb/cgi-bin/CustIDCK.pm
+# External Customer ID checker stub
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+
+package CustIDCK;
+
+use strict;
+use warnings;
+use Exporter;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+use DBI;
+
+# Supposed to provide cross-Perl-version signal handling.
+# Not part of stock Debian Perl, use dh-make-perl or just
+#  install straight from CPAN.
+# Not part of stock RHEL/CentOS, use cpan2perl, cpanflute,
+#  or just install straight from CPAN.
+use Sys::SigAction;
+
+$VERSION        = 1.00;
+@ISA            = qw(Exporter);
+@EXPORT         = ();
+@EXPORT_OK      = qw ( &custid_exist );
+
+# this is really an example stub, and should be replaced by
+# the local admin on installation
+sub custid_exist {
+  my $custid = shift;
+
+  return 1 if $custid =~ /^STAFF$/;
+  return 1 if $custid =~ /^5554242$/;  # just in case some later change might block this
+  return 1 if $custid =~ /^\d{7}$/;
+  return 1 if $custid =~ /^\d{10}$/;
+
+# some example code for a database check
+  # Try to catch failures to connect.  If the remote server is up but
+  # not responding (this has HAPPENED) we need to break out rather than hanging.
+  my $dbh;
+  eval {
+    my $h = Sys::SigAction::set_sig_handler( 'ALRM',
+      sub { die "failed connection to apex!!"; } );
+
+    alarm 3;	# 3-second timeout.  This may be too aggressive.
+
+    eval {
+      $dbh = DBI->connect ("DBI:Pg:host=billing;dbname=custids", "cidcheck", "c1dch4ck");
+      die "failed connection to billing!!" if !$dbh;
+# Not certain if this is needed here.  It doesn't seem to be.
+#      $dbh->ping;	# Gotta do this to "force" a "failure".  NRGH.
+    };
+    alarm 0;	# cancel the alarm
+    $dbh->ping;	# Gotta do this to "force" a "failure".  NRGH.
+  };
+  alarm 0;	# avoid race conditions.  May not be needed here.  (Hah!)
+  if ($@ && $@ !~ /failed connection to billing!!/) {
+    $CustIDCK::Error = 1;
+    $CustIDCK::ErrMsg = "Failed connection to billing DB host!  Unable to verify CustIDs.";
+    return 0;
+  }
+
+  # We should have a valid DB connection by now.
+  my $sth = $dbh->prepare("SELECT custid FROM custid WHERE custid = '$custid'");
+  $sth->execute;
+  if ($dbh->err) {
+    $CustIDCK::Error = 1;
+    $CustIDCK::ErrMsg = $dbh->errstr();
+    $sth->finish;
+    $dbh->disconnect;
+    return 0;
+  }
+  my $hr = $sth->fetchrow_hashref();
+  my $status = 0;
+  $status = 1 if ( $hr->{custid} );
+  $sth->finish;
+  $dbh->disconnect;
+  return $status;
+
+  return 0;
+  # Stubs for error messages
+  $CustIDCK::Error = 1 if 1 == 0;
+  $CustIDCK::ErrMsg = "bad foo-frob: 1 == 0";
+}
Index: /branches/htmlform/cgi-bin/IPDB.pm
===================================================================
--- /branches/htmlform/cgi-bin/IPDB.pm	(revision 446)
+++ /branches/htmlform/cgi-bin/IPDB.pm	(revision 446)
@@ -0,0 +1,880 @@
+# ipdb/cgi-bin/IPDB.pm
+# Contains functions for IPDB - database access, subnet mangling, block allocation, etc
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+package IPDB;
+
+use strict;
+use warnings;
+use Exporter;
+use DBI;
+use Net::SMTP;
+use NetAddr::IP qw( Compact );
+use POSIX;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+$VERSION	= 2; ##VERSION##
+@ISA		= qw(Exporter);
+@EXPORT_OK    = qw(
+	%disp_alloctypes %list_alloctypes %def_custids @citylist @poplist @masterblocks
+	%allocated %free %routed %bigfree %IPDBacl
+	&initIPDBGlobals &connectDB &finish &checkDBSanity &allocateBlock &addMaster
+	&deleteBlock &getBlockData &mailNotify
+	);
+
+@EXPORT		= (); # Export nothing by default.
+%EXPORT_TAGS	= ( ALL => [qw(
+		%disp_alloctypes %list_alloctypes %def_custids @citylist @poplist
+		@masterblocks %allocated %free %routed %bigfree %IPDBacl
+		&initIPDBGlobals &connectDB &finish &checkDBSanity &allocateBlock
+		&addMaster &deleteBlock &getBlockData &mailNotify
+		)]
+	);
+
+##
+## Global variables
+##
+our %disp_alloctypes;
+our %list_alloctypes;
+our %def_custids;
+our @citylist;
+our @poplist;
+our @masterblocks;
+our %allocated;
+our %free;
+our %routed;
+our %bigfree;
+our %IPDBacl;
+
+our $org_name = 'Example Corp';
+our $smtphost = 'smtp.example.com';
+our $domain = 'example.com';
+our $defcustid = '5554242';
+# mostly for rwhois
+##fixme:  leave these blank by default?
+our $rwhoisDataPath = '/usr/local/rwhoisd/etc/rwhoisd';	# to match ./configure defaults from rwhoisd-1.5.9.6
+our $org_street = '123 4th Street';
+our $org_city = 'Anytown';
+our $org_prov_state = 'ON';
+our $org_pocode = 'H0H 0H0';
+our $org_country = 'CA';
+our $org_phone = '000-555-1234';
+our $org_techhandle = 'ISP-ARIN-HANDLE';
+our $org_email = 'noc@example.com';
+our $hostmaster = 'dns@example.com';
+
+our $syslog_facility = 'local2';
+
+# Let's initialize the globals.
+## IPDB::initIPDBGlobals()
+# Initialize all globals.  Takes a database handle, returns a success or error code
+sub initIPDBGlobals {
+  my $dbh = $_[0];
+  my $sth;
+
+  # Initialize alloctypes hashes
+  $sth = $dbh->prepare("select type,listname,dispname,listorder,def_custid from alloctypes order by listorder");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    $disp_alloctypes{$data[0]} = $data[2];
+    $def_custids{$data[0]} = $data[4];
+    if ($data[3] < 900) {
+      $list_alloctypes{$data[0]} = $data[1];
+    }
+  }
+
+  # City and POP listings
+  $sth = $dbh->prepare("select city,routing from cities order by city");
+  $sth->execute;
+  return (undef,$sth->errstr) if $sth->err;
+  while (my @data = $sth->fetchrow_array) {
+    push @citylist, $data[0];
+    if ($data[1] eq 'y') {
+      push @poplist, $data[0];
+    }
+  }
+
+  # Master block list
+  $sth = $dbh->prepare("select cidr from masterblocks order by cidr");
+  $sth->execute;
+  return (undef,$sth->errstr) if $sth->err;
+  for (my $i=0; my @data = $sth->fetchrow_array(); $i++) {
+    $masterblocks[$i] = new NetAddr::IP $data[0];
+    $allocated{"$masterblocks[$i]"} = 0;
+    $free{"$masterblocks[$i]"} = 0;
+    $bigfree{"$masterblocks[$i]"} = 128; # Larger number means smaller block.
+					# Set to 128 to prepare for IPv6
+    $routed{"$masterblocks[$i]"} = 0;
+  }
+
+  # Load ACL data.  Specific username checks are done at a different level.
+  $sth = $dbh->prepare("select username,acl from users");
+  $sth->execute;
+  return (undef,$sth->errstr) if $sth->err;
+  while (my @data = $sth->fetchrow_array) {
+    $IPDBacl{$data[0]} = $data[1];
+  }
+
+  return (1,"OK");
+} # end initIPDBGlobals
+
+
+## IPDB::connectDB()
+# Creates connection to IPDB.
+# Requires the database name, username, and password.
+# Returns a handle to the db.
+# Set up for a PostgreSQL db;  could be any transactional DBMS with the
+# right changes.
+sub connectDB {
+  my $dbname = shift;
+  my $user = shift;
+  my $pass = shift;
+  my $dbhost = shift;
+
+  my $dbh;
+  my $DSN = "DBI:Pg:".($dbhost ? "host=$dbhost;" : '')."dbname=$dbname";
+
+# Note that we want to autocommit by default, and we will turn it off locally as necessary.
+# We may not want to print gobbledygook errors;  YMMV.  Have to ponder that further.
+  $dbh = DBI->connect($DSN, $user, $pass, {
+	AutoCommit => 1,
+	PrintError => 0
+	})
+    or return (undef, $DBI::errstr) if(!$dbh);
+
+# Return here if we can't select.  Note that this indicates a
+# problem executing the select.
+  my $sth = $dbh->prepare("select type from alloctypes");
+  $sth->execute();
+  return (undef,$DBI::errstr) if ($sth->err);
+
+# See if the select returned anything (or null data).  This should
+# succeed if the select executed, but...
+  $sth->fetchrow();
+  return (undef,$DBI::errstr)  if ($sth->err);
+
+# If we get here, we should be OK.
+  return ($dbh,"DB connection OK");
+} # end connectDB
+
+
+## IPDB::finish()
+# Cleans up after database handles and so on.
+# Requires a database handle
+sub finish {
+  my $dbh = $_[0];
+  $dbh->disconnect;
+} # end finish
+
+
+## IPDB::checkDBSanity()
+# Quick check to see if the db is responding.  A full integrity
+# check will have to be a separate tool to walk the IP allocation trees.
+sub checkDBSanity {
+  my ($dbh) = $_[0];
+
+  if (!$dbh) {
+    print "No database handle, or connection has been closed.";
+    return -1;
+  } else {
+    # it connects, try a stmt.
+    my $sth = $dbh->prepare("select type from alloctypes");
+    my $err = $sth->execute();
+
+    if ($sth->fetchrow()) {
+      # all is well.
+      return 1;
+    } else {
+      print "Connected to the database, but could not execute test statement.  ".$sth->errstr();
+      return -1;
+    }
+  }
+  # Clean up after ourselves.
+#  $dbh->disconnect;
+} # end checkDBSanity
+
+
+## IPDB::addMaster()
+# Does all the magic necessary to sucessfully add a master block
+# Requires database handle, block to add
+# Returns failure code and error message or success code and "message"
+sub addMaster {
+  my $dbh = shift;
+  my $cidr = new NetAddr::IP shift;
+
+  # Allow transactions, and raise an exception on errors so we can catch it later.
+  # Use local to make sure these get "reset" properly on exiting this block
+  local $dbh->{AutoCommit} = 0;
+  local $dbh->{RaiseError} = 1;
+
+  # Wrap all the SQL in a transaction
+  eval {
+    my $sth = $dbh->prepare("select count(*) from masterblocks where cidr <<= '$cidr'");
+    $sth->execute;
+    my @data = $sth->fetchrow_array;
+
+    if ($data[0] eq 0) {
+      # First case - master is brand-spanking-new.
+##fixme: rwhois should be globally-flagable somewhere, much like a number of other things
+## maybe a db table called "config"?
+      $sth = $dbh->prepare("insert into masterblocks (cidr,rwhois) values ('$cidr','y')");
+      $sth->execute;
+
+# Unrouted blocks aren't associated with a city (yet).  We don't rely on this
+# elsewhere though;  legacy data may have traps and pitfalls in it to break this.
+# Thus the "routed" flag.
+
+      $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
+        " values ('$cidr',".$cidr->masklen.",'<NULL>','n')");
+      $sth->execute;
+
+      # If we get here, everything is happy.  Commit changes.
+      $dbh->commit;
+
+    } # new master does not contain existing master(s)
+    else {
+
+      # collect the master(s) we're going to absorb, and snag the longest netmask while we're at it.
+      my $smallmask = $cidr->masklen;
+      $sth = $dbh->prepare("select cidr as mask from masterblocks where cidr <<= '$cidr'");
+      $sth->execute;
+      my @cmasters;
+      while (my @data = $sth->fetchrow_array) {
+	my $master = new NetAddr::IP $data[0];
+	push @cmasters, $master;
+	$smallmask = $master->masklen if $master->masklen > $smallmask;
+      }
+
+      # split the new master, and keep only those blocks not part of an existing master
+      my @blocklist;
+      foreach my $seg ($cidr->split($smallmask)) {
+	my $contained = 0;
+	foreach my $master (@cmasters) {
+	  $contained = 1 if $master->contains($seg);
+	}
+	push @blocklist, $seg if !$contained;
+      }
+
+      # collect the unrouted free blocks within the new master
+      $sth = $dbh->prepare("select cidr from freeblocks where ".
+		"maskbits>=$smallmask and cidr <<= '$cidr' and routed='n'");
+      $sth->execute;
+      while (my @data = $sth->fetchrow_array) {
+	my $freeblock = new NetAddr::IP $data[0];
+	push @blocklist, $freeblock;
+      }
+
+      # combine the set of free blocks we should have now.
+      @blocklist = Compact(@blocklist);
+
+      # and now insert the new data.  Make sure to delete old masters too.
+
+      # freeblocks
+      $sth = $dbh->prepare("delete from freeblocks where cidr <<= ?");
+      my $sth2 = $dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed) values (?,?,'<NULL>','n')");
+      foreach my $newblock (@blocklist) {
+        $sth->execute("$newblock");
+        $sth2->execute("$newblock", $newblock->masklen);
+      }
+
+      # master
+      $sth = $dbh->prepare("delete from masterblocks where cidr <<= '$cidr'");
+      $sth->execute;
+      $sth = $dbh->prepare("insert into masterblocks (cidr,rwhois) values ('$cidr','y')");
+      $sth->execute;
+
+      # *whew*  If we got here, we likely suceeded.
+      $dbh->commit;
+    } # new master contained existing master(s)
+  }; # end eval
+
+  if ($@) {
+    my $msg = $@;
+    eval { $dbh->rollback; };
+    return ('FAIL',$msg);
+  } else {
+    return ('OK','OK');
+  }
+} # end addMaster
+
+
+## IPDB::allocateBlock()
+# Does all of the magic of actually allocating a netblock
+# Requires database handle, block to allocate, custid, type, city,
+#	description, notes, circuit ID, block to allocate from, private data
+# Returns a success code and optional error message.
+sub allocateBlock {
+  my ($dbh,undef,undef,$custid,$type,$city,$desc,$notes,$circid,$privdata,$nodeid) = @_;
+
+  my $cidr = new NetAddr::IP $_[1];
+  my $alloc_from = new NetAddr::IP $_[2];
+  my $sth;
+
+  # Snag the "type" of the freeblock (alloc_from) "just in case"
+  $sth = $dbh->prepare("select routed from freeblocks where cidr='$alloc_from'");
+  $sth->execute;
+  my ($alloc_from_type) = $sth->fetchrow_array;
+
+  # To contain the error message, if any.
+  my $msg = "Unknown error allocating $cidr as '$type'";
+
+  # Enable transactions and error handling
+  local $dbh->{AutoCommit} = 0;	# These need to be local so we don't
+  local $dbh->{RaiseError} = 1;	# step on our toes by accident.
+
+  if ($type =~ /^.i$/) {
+    $msg = "Unable to assign static IP $cidr to $custid";
+    eval {
+      # We have to do this in two parts because otherwise we lose
+      # the ability to return the IP assigned.  Should that change,
+      # the commented SQL statement below may become usable.
+# update poolips set custid='$custid',city='$city',available='n',
+#	description='$desc',notes='$notes',circuitid='$circid'
+#	where ip=(select ip from poolips where pool='$alloc_from'
+#	and available='y' order by ip limit 1);
+
+      $sth = $dbh->prepare("select ip from poolips where pool='$alloc_from'".
+	" and available='y' order by ip");
+      $sth->execute;
+
+      my @data = $sth->fetchrow_array;
+      $cidr = $data[0];  # $cidr is already declared when we get here!
+
+      $sth = $dbh->prepare("update poolips set custid='$custid',".
+	"city='$city',available='n',description='$desc',notes='$notes',".
+	"circuitid='$circid',privdata='$privdata'".
+	" where ip='$cidr'");
+      $sth->execute;
+# node hack
+      if ($nodeid && $nodeid ne '') {
+        $sth = $dbh->prepare("INSERT INTO noderef (block,node_id) VALUES (?,?)");
+        $sth->execute("$cidr",$nodeid);
+      }
+# end node hack
+      $dbh->commit;
+    };
+    if ($@) {
+      $msg .= ": '".$sth->errstr."'";
+      eval { $dbh->rollback; };
+      return ('FAIL',$msg);
+    } else {
+      return ('OK',"$cidr");
+    }
+
+  } else { # end IP-from-pool allocation
+
+    if ($cidr == $alloc_from) {
+      # Easiest case- insert in one table, delete in the other, and go home.  More or less.
+      # insert into allocations values (cidr,custid,type,city,desc) and
+      # delete from freeblocks where cidr='cidr'
+      # For data safety on non-transaction DBs, we delete first.
+
+      eval {
+	$msg = "Unable to allocate $cidr as '$disp_alloctypes{$type}'";
+	if ($type eq 'rm') {
+	  $sth = $dbh->prepare("update freeblocks set routed='y',city='$city'".
+	    " where cidr='$cidr'");
+	  $sth->execute;
+	  $sth = $dbh->prepare("insert into routed (cidr,maskbits,city)".
+		" values ('$cidr',".$cidr->masklen.",'$city')");
+	  $sth->execute;
+	} else {
+	  # common stuff for end-use, dialup, dynDSL, pools, etc, etc.
+
+	  # special case - block is a container/"reserve" block
+	  if ($type =~ /^(.)c$/) {
+	    $sth = $dbh->prepare("update freeblocks set routed='$1' where cidr='$cidr'");
+	    $sth->execute;
+	  } else {
+	    # "normal" case
+	    $sth = $dbh->prepare("delete from freeblocks where cidr='$cidr'");
+	    $sth->execute;
+	  }
+	  $sth = $dbh->prepare("insert into allocations".
+		" (cidr,custid,type,city,description,notes,maskbits,circuitid,privdata)".
+		" values ('$cidr','$custid','$type','$city','$desc','$notes',".
+		$cidr->masklen.",'$circid','$privdata')");
+	  $sth->execute;
+
+	  # And initialize the pool, if necessary
+	  # PPPoE pools (currently dialup, DSL, and WiFi) get all IPs made available
+	  # "DHCP" or "real-subnet" pools have the net, gw, and bcast IPs removed.
+	  if ($type =~ /^.p$/) {
+	    $msg = "Could not initialize IPs in new $disp_alloctypes{$type} $cidr";
+	    my ($code,$rmsg) = initPool($dbh,$cidr,$type,$city,"all");
+	    die $rmsg if $code eq 'FAIL';
+	  } elsif ($type =~ /^.d$/) {
+	    $msg = "Could not initialize IPs in new $disp_alloctypes{$type} $cidr";
+	    my ($code,$rmsg) = initPool($dbh,$cidr,$type,$city,"normal");
+	    die $rmsg if $code eq 'FAIL';
+	  }
+
+	} # routing vs non-routing netblock
+
+# node hack
+      if ($nodeid && $nodeid ne '') {
+        $sth = $dbh->prepare("INSERT INTO noderef (block,node_id) VALUES (?,?)");
+        $sth->execute("$cidr",$nodeid);
+      }
+# end node hack
+	$dbh->commit;
+      }; # end of eval
+      if ($@) {
+	$msg .= ": ".$@;
+	eval { $dbh->rollback; };
+	return ('FAIL',$msg);
+      } else {
+	return ('OK',"OK");
+      }
+
+    } else { # cidr != alloc_from
+
+      # Hard case.  Allocation is smaller than free block.
+      my $wantmaskbits = $cidr->masklen;
+      my $maskbits = $alloc_from->masklen;
+
+      my @newfreeblocks;	# Holds free blocks generated from splitting the source freeblock.
+
+      # This determines which blocks will be left "free" after allocation.  We take the
+      # block we're allocating from, and split it in half.  We see which half the wanted
+      # block is in, and repeat until the wanted block is equal to one of the halves.
+      my $i=0;
+      my $tmp_from = $alloc_from;	# So we don't munge $alloc_from
+      while ($maskbits++ < $wantmaskbits) {
+	my @subblocks = $tmp_from->split($maskbits);
+	$newfreeblocks[$i++] = (($cidr->within($subblocks[0])) ? $subblocks[1] : $subblocks[0]);
+	$tmp_from = ( ($cidr->within($subblocks[0])) ? $subblocks[0] : $subblocks[1] );
+      } # while
+
+      # Begin SQL transaction block
+      eval {
+	$msg = "Unable to allocate $cidr as '$disp_alloctypes{$type}'";
+
+	# Delete old freeblocks entry
+	$sth = $dbh->prepare("delete from freeblocks where cidr='$alloc_from'");
+	$sth->execute();
+
+	# now we have to do some magic for routing blocks
+	if ($type eq 'rm') {
+
+	  # Insert the new freeblocks entries
+	  # Note that non-routed blocks are assigned to <NULL>
+	  # and use the default value for the routed column ('n')
+	  $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city)".
+		" values (?, ?, '<NULL>')");
+	  foreach my $block (@newfreeblocks) {
+ 	    $sth->execute("$block", $block->masklen);
+	  }
+
+	  # Insert the entry in the routed table
+	  $sth = $dbh->prepare("insert into routed (cidr,maskbits,city)".
+		" values ('$cidr',".$cidr->masklen.",'$city')");
+	  $sth->execute;
+	  # Insert the (almost) same entry in the freeblocks table
+	  $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
+		" values ('$cidr',".$cidr->masklen.",'$city','y')");
+	  $sth->execute;
+
+	} else { # done with alloctype == rm
+
+	  # Insert the new freeblocks entries
+	  # Along with some more HairyPerl(TM):
+	  #   if $alloc_type_from is p
+	  #   OR
+	  #   $type matches /^(.)r$/
+	  # inserted value for routed column should match.
+	  # This solves the case of inserting an arbitrary block into a
+	  # "Reserve-for-routed-DSL" block.  Which you really shouldn't
+	  # do in the first place, but anyway...
+	  $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
+		" values (?, ?, (select city from routed where cidr >>= '$cidr'),'".
+		( ( ($alloc_from_type =~ /^(p)$/) || ($type =~ /^(.)r$/) ) ? "$1" : 'y')."')");
+	  foreach my $block (@newfreeblocks) {
+ 	    $sth->execute("$block", $block->masklen);
+	  }
+	  # Special-case for reserve/"container" blocks - generate
+	  # the "extra" freeblocks entry for the container
+	  if ($type =~ /^(.)c$/) {
+	    $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
+		" values ('$cidr',".$cidr->masklen.",'$city','$1')");
+	    $sth->execute;
+	  }
+	  # Insert the allocations entry
+	  $sth = $dbh->prepare("insert into allocations (cidr,custid,type,city,".
+		"description,notes,maskbits,circuitid,privdata)".
+		" values ('$cidr','$custid','$type','$city','$desc','$notes',".
+		$cidr->masklen.",'$circid','$privdata')");
+	  $sth->execute;
+
+	  # And initialize the pool, if necessary
+	  # PPPoE pools (currently dialup, DSL, and WiFi) get all IPs made available
+	  # "DHCP" or "real-subnet" pools have the net, gw, and bcast IPs removed.
+	  if ($type =~ /^.p$/) {
+	    $msg = "Could not initialize IPs in new $disp_alloctypes{$type} $cidr";
+	    my ($code,$rmsg) = initPool($dbh,$cidr,$type,$city,"all");
+	    die $rmsg if $code eq 'FAIL';
+	  } elsif ($type =~ /^.d$/) {
+	    $msg = "Could not initialize IPs in new $disp_alloctypes{$type} $cidr";
+	    my ($code,$rmsg) = initPool($dbh,$cidr,$type,$city,"normal");
+	    die $rmsg if $code eq 'FAIL';
+	  }
+
+	} # done with netblock alloctype != rm
+
+# node hack
+      if ($nodeid && $nodeid ne '') {
+        $sth = $dbh->prepare("INSERT INTO noderef (block,node_id) VALUES (?,?)");
+        $sth->execute("$cidr",$nodeid);
+      }
+# end node hack
+        $dbh->commit;
+      }; # end eval
+      if ($@) {
+	$msg .= ": ".$@;
+	eval { $dbh->rollback; };
+	return ('FAIL',$msg);
+      } else {
+	return ('OK',"OK");
+      }
+
+    } # end fullcidr != alloc_from
+
+  } # end static-IP vs netblock allocation
+
+} # end allocateBlock()
+
+
+## IPDB::initPool()
+# Initializes a pool
+# Requires a database handle, the pool CIDR, type, city, and a parameter
+# indicating whether the pool should allow allocation of literally every
+# IP, or if it should reserve network/gateway/broadcast IPs
+# Note that this is NOT done in a transaction, that's why it's a private
+# function and should ONLY EVER get called from allocateBlock()
+sub initPool {
+  my ($dbh,undef,$type,$city,$class) = @_;
+  my $pool = new NetAddr::IP $_[1];
+
+##fixme Need to just replace 2nd char of type with i rather than capturing 1st char of type
+  $type =~ s/[pd]$/i/;
+  my $sth;
+  my $msg;
+
+  # Trap errors so we can pass them back to the caller.  Even if the
+  # caller is only ever supposed to be local, and therefore already
+  # trapping errors.  >:(
+  local $dbh->{AutoCommit} = 0; # These need to be local so we don't
+  local $dbh->{RaiseError} = 1; # step on our toes by accident.
+
+  eval {
+    # have to insert all pool IPs into poolips table as "unallocated".
+    $sth = $dbh->prepare("insert into poolips (pool,ip,custid,city,type)".
+	" values ('$pool', ?, '$defcustid', '$city', '$type')");
+    my @poolip_list = $pool->hostenum;
+    if ($class eq 'all') { # (DSL-ish block - *all* IPs available
+      if ($pool->addr !~ /\.0$/) {	# .0 causes weirdness.
+	$sth->execute($pool->addr);
+      }
+      for (my $i=0; $i<=$#poolip_list; $i++) {
+	$sth->execute($poolip_list[$i]->addr);
+      }
+      $pool--;
+      if ($pool->addr !~ /\.255$/) {	# .255 can cause weirdness.
+	$sth->execute($pool->addr);
+      }
+    } else { # (real netblock)
+      for (my $i=1; $i<=$#poolip_list; $i++) {
+	$sth->execute($poolip_list[$i]->addr);
+      }
+    }
+  };
+  if ($@) {
+    $msg = "'".$sth->errstr."'";
+    eval { $dbh->rollback; };
+    return ('FAIL',$msg);
+  } else {
+    return ('OK',"OK");
+  }
+} # end initPool()
+
+
+## IPDB::deleteBlock()
+# Removes an allocation from the database, including deleting IPs
+# from poolips and recombining entries in freeblocks if possible
+# Also handles "deleting" a static IP allocation, and removal of a master
+# Requires a database handle, the block to delete, and the type of block
+sub deleteBlock {
+  my ($dbh,undef,$type) = @_;
+  my $cidr = new NetAddr::IP $_[1];
+
+  my $sth;
+
+  # Magic variables used for odd allocation cases.
+  my $container;
+  my $con_type;
+
+  # To contain the error message, if any.
+  my $msg = "Unknown error deallocating $type $cidr";
+  # Enable transactions and exception-on-errors... but only for this sub
+  local $dbh->{AutoCommit} = 0;
+  local $dbh->{RaiseError} = 1;
+
+  # First case.  The "block" is a static IP
+  # Note that we still need some additional code in the odd case
+  # of a netblock-aligned contiguous group of static IPs
+  if ($type =~ /^.i$/) {
+
+    eval {
+      $msg = "Unable to deallocate $disp_alloctypes{$type} $cidr";
+      $sth = $dbh->prepare("update poolips set custid='$defcustid',available='y',".
+	"city=(select city from allocations where cidr >>= '$cidr'".
+	" order by masklen(cidr) desc limit 1),".
+	"description='',notes='',circuitid='' where ip='$cidr'");
+      $sth->execute;
+      $dbh->commit;
+    };
+    if ($@) {
+      eval { $dbh->rollback; };
+      return ('FAIL',$msg);
+    } else {
+      return ('OK',"OK");
+    }
+
+  } elsif ($type eq 'mm') { # end alloctype =~ /.i/
+
+    $msg = "Unable to delete master block $cidr";
+    eval {
+      $sth = $dbh->prepare("delete from masterblocks where cidr='$cidr'");
+      $sth->execute;
+      $sth = $dbh->prepare("delete from freeblocks where cidr <<= '$cidr'");
+      $sth->execute;
+      $dbh->commit;
+    };
+    if ($@) {
+      eval { $dbh->rollback; };
+      return ('FAIL', $msg);
+    } else {
+      return ('OK',"OK");
+    }
+
+  } else { # end alloctype master block case
+
+    ## This is a big block; but it HAS to be done in a chunk.  Any removal
+    ## of a netblock allocation may result in a larger chunk of free
+    ## contiguous IP space - which may in turn be combined into a single
+    ## netblock rather than a number of smaller netblocks.
+
+    eval {
+
+      if ($type eq 'rm') {
+        $msg = "Unable to remove routing allocation $cidr";
+	$sth = $dbh->prepare("delete from routed where cidr='$cidr'");
+	$sth->execute;
+	# Make sure block getting deleted is properly accounted for.
+	$sth = $dbh->prepare("update freeblocks set routed='n',city='<NULL>'".
+		" where cidr='$cidr'");
+	$sth->execute;
+	# Set up query to start compacting free blocks.
+	$sth = $dbh->prepare("select cidr from freeblocks where ".
+		"maskbits<=".$cidr->masklen." and routed='n' order by maskbits desc");
+
+      } else { # end alloctype routing case
+
+	# Magic.  We need to get information about the containing block (if any)
+	# so as to make sure that the freeblocks we insert get the correct "type".
+	$sth = $dbh->prepare("select cidr,type from allocations where cidr >> '$cidr'");
+	$sth->execute;
+	($container, $con_type) = $sth->fetchrow_array;
+
+	# Delete all allocations within the block being deleted.  This is
+	# deliberate and correct, and removes the need to special-case
+	# removal of "container" blocks.
+	$sth = $dbh->prepare("delete from allocations where cidr <<='$cidr'");
+	$sth->execute;
+
+	# Special case - delete pool IPs
+	if ($type =~ /^.[pd]$/) {
+	  # We have to delete the IPs from the pool listing.
+	  $sth = $dbh->prepare("delete from poolips where pool='$cidr'");
+	  $sth->execute;
+	}
+
+	# Set up query for compacting free blocks.
+	if ($con_type && $con_type eq 'pc') {
+	  # Clean up after "bad" allocations (blocks that are not formally
+	  # contained which have nevertheless been allocated from a container block)
+	  # We want to make certain that the freeblocks are properly "labelled"
+	  $sth = $dbh->prepare("select cidr from freeblocks where cidr <<= '$container' order by maskbits desc");
+	} else {
+	  # Standard deallocation.
+	  $sth = $dbh->prepare("select cidr from freeblocks where cidr <<= ".
+		"(select cidr from routed where cidr >>= '$cidr') ".
+		" and maskbits<=".$cidr->masklen.
+		" and routed='".(($type =~ /^(.)r$/) ? "$1" : 'y').
+		"' order by maskbits desc");
+	}
+
+      } # end alloctype general case
+
+      ## Deallocate legacy blocks stashed in the middle of a static IP pool
+      ## This may be expandable to an even more general case of contained netblock, or other pool types.
+
+      # Find out if the block we're deallocating is within a DSL pool
+      my $sth2 = $dbh->prepare("SELECT cidr,city,type FROM allocations WHERE type LIKE '_p' AND cidr >>= ?");
+      $sth2->execute("$cidr");
+      my ($pool,$poolcity,$pooltype) = $sth2->fetchrow_array;
+
+      if ($pool || $sth2->rows) {
+	# We've already deleted the block, now we have to stuff its IPs into the pool.
+	$pooltype =~ s/p$/i/;	# change type to static IP
+	$sth2 = $dbh->prepare("INSERT INTO poolips (pool,ip,city,type,custid) values ".
+		"('$pool',?,'$poolcity','$pooltype','$defcustid')");
+##fixme:  need to not insert net, gateway, and bcast on "real netblock" pools (DHCPish)
+	# don't insert .0
+	$sth2->execute($cidr->addr) unless $cidr->addr =~ m|\.0$|;
+	foreach my $ip ($cidr->hostenum) {
+	  $sth2->execute("$ip");
+	}
+	$cidr--;
+	# don't insert .255
+	$sth2->execute($cidr->addr) unless $cidr->addr =~ m|\.255$|;
+      } else {	# done returning IPs from a block to a static DSL pool
+
+	# Now we look for larger-or-equal-sized free blocks in the same master (routed)
+	# (super)block. If there aren't any, we can't combine blocks anyway.  If there
+	# are, we check to see if we can combine blocks.
+	# Execute the statement prepared in the if-else above.
+
+	$sth->execute;
+
+# NetAddr::IP->compact() attempts to produce the smallest inclusive block
+# from the caller and the passed terms.
+# EG:  if you call $cidr->compact($ip1,$ip2,$ip3) when $cidr, $ip1, $ip2,
+#	and $ip3 are consecutive /27's starting on .0 (.0-.31, .32-.63,
+#	.64-.95, and .96-.128), you will get an array containing a single
+#	/25 as element 0 (.0-.127).  Order is not important;  you could have
+#	$cidr=.32/27, $ip1=.96/27, $ip2=.0/27, and $ip3=.64/27.
+
+	my (@together, @combinelist);
+	my $i=0;
+	while (my @data = $sth->fetchrow_array) {
+	  my $testIP = new NetAddr::IP $data[0];
+	  @together = $testIP->compact($cidr);
+	  my $num = @together;
+	  if ($num == 1) {
+	    $cidr = $together[0];
+	    $combinelist[$i++] = $testIP;
+	  }
+	}
+
+	# Clear old freeblocks entries - if any.  They should all be within
+	# the $cidr determined above.
+	$sth = $dbh->prepare("delete from freeblocks where cidr <<='$cidr'");
+	$sth->execute;
+
+	# insert "new" freeblocks entry
+	if ($type eq 'rm') {
+	  $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city)".
+		" values ('$cidr',".$cidr->masklen.",'<NULL>')");
+	} else {
+	  # Magic hackery to insert "correct" data for deallocation of
+	  # non-contained blocks allocated from within a container.
+	  $type = 'pr' if $con_type && $con_type eq 'pc';
+
+	  $sth = $dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
+		" values ('$cidr',".$cidr->masklen.
+		",(select city from routed where cidr >>= '$cidr'),'".
+		(($type =~ /^(.)r$/) ? "$1" : 'y')."')");
+	}
+	$sth->execute;
+
+      } # done returning IPs to the appropriate place
+
+      # If we got here, we've succeeded.  Whew!
+      $dbh->commit;
+    }; # end eval
+    if ($@) {
+      $msg = $@;
+      eval { $dbh->rollback; };
+      return ('FAIL', $msg);
+    } else {
+      return ('OK',"OK");
+    }
+
+  } # end alloctype != netblock
+
+} # end deleteBlock()
+
+
+## IPDB::getBlockData()
+# Return custid, type, city, and description for a block
+sub getBlockData {
+  my $dbh = shift;
+  my $block = shift;
+
+  my $sth = $dbh->prepare("select cidr,custid,type,city,description from searchme".
+	" where cidr='$block'");
+  $sth->execute();
+  return $sth->fetchrow_array();
+} # end getBlockData()
+
+
+## IPDB::mailNotify()
+# Sends notification mail to recipients regarding an IPDB operation
+sub mailNotify {
+  my $dbh = shift;
+  my ($action,$subj,$message) = @_;
+
+##fixme: need to redesign the breakdown/processing for $action for proper handling of all cases
+
+# split action into parts for fiddlement.  nb: there are almost certainly better ways to do this.
+  my @actionbits = split //, $action;
+
+  # want to notify anyone who has specifically requested notify on *this* type ($action as passed),
+  # on "all static IP types" or "all pool types" (and other last-char-in-type groupings), on eg "all DSL types",
+  # and "all events with this action"
+  my @actionsets = ($action);
+##fixme: ick, eww.  really gotta find a better way to handle this...
+  push @actionsets, ($actionbits[0].'.'.$actionbits[2],
+	$actionbits[0].$actionbits[1].'.', $actionbits[0].'a') if $action =~ /^.{3}$/;
+
+  my $mailer = Net::SMTP->new($smtphost, Hello => "ipdb.$domain");
+
+  # get recip list from db
+  my $sth = $dbh->prepare("SELECT reciplist FROM notify WHERE action=?");
+
+  my %reciplist;
+  foreach (@actionsets) {
+    $sth->execute($_);
+##fixme - need to handle db errors
+    my ($recipsub) = $sth->fetchrow_array;
+    next if !$recipsub;
+    foreach (split(/,/, $recipsub)) {
+      $reciplist{$_}++;
+    }
+  }
+
+  return if !%reciplist;
+
+  foreach my $recip (keys %reciplist) {
+    $mailer->mail("ipdb\@$domain");
+    $mailer->to($recip);
+    $mailer->data("From: \"$org_name IP Database\" <ipdb\@$domain>\n",
+	"To: $recip\n",
+	"Date: ".strftime("%a, %d %b %Y %H:%M:%S %z",localtime)."\n",
+	"Subject: {IPDB} $subj\n",
+	"X-Mailer: IPDB Notify v".sprintf("%.1d",$IPDB::VERSION)."\n",
+	"Organization: $org_name\n",
+	"\n$message\n");
+  }
+  $mailer->quit;
+}
+
+# Indicates module loaded OK.  Required by Perl.
+1;
Index: /branches/htmlform/cgi-bin/MyIPDB.pm
===================================================================
--- /branches/htmlform/cgi-bin/MyIPDB.pm	(revision 446)
+++ /branches/htmlform/cgi-bin/MyIPDB.pm	(revision 446)
@@ -0,0 +1,58 @@
+# ipdb/cgi-bin/MyIPDB.pm
+# Contains site-specific functions for IPDB
+# May override some functions from IPDB.pm, wraps others
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use IPDB 2.0 qw(:ALL);
+
+
+# DSN, user, and password for database.  These **MUST** be set.
+my $dbname = 'ipdb';
+my $dbuser = 'ipdb';
+my $dbpass = 'ipdbpwd';
+# DB host is optional.
+my $dbhost = 'ipdb-db';
+
+# Set some globals declared in IPDB.pm.  Most of these only affect mailNotify().
+# Note that while you *can* leave these at defaults, it's probably a Really Bad Idea.
+#$IPDB::org_name = "Bob's Big Bonaza";
+#$IPDB::smtphost = '127.0.0.1';
+#$IPDB::domain = 'bob.com';
+#$IPDB::defcustid = '5554242';
+# Globals for db2rwhois.pl
+#$IPDB::rwhoisDataPath = '/usr/local/rwhoisd/etc/rwhoisd';
+#$IPDB::org_street = '123 4th Street';
+#$IPDB::org_city = 'Anytown';
+#$IPDB::org_prov_state = 'ON';
+#$IPDB::org_pocode = 'H0H 0H0';
+#$IPDB::org_country = 'CA';
+#$IPDB::org_phone = '000-555-1234';
+# note: following may also just be a bare email address
+#$IPDB::org_techhandle = 'ISP-ARIN-HANDLE';
+#$IPDB::org_email = 'noc@example.com';
+#$IPDB::hostmaster = 'dns@example.com';
+
+# Logging destination.  Defaults to local2.  See your local syslog docs for valid facilities.
+# Note that the value here should have the LOG_ prefix removed, and convert to lower-case.
+# local0 through local7 and user make the most sense.
+#$IPDB::syslog_facility = 'daemon';
+
+
+## connectDB_My()
+# Wrapper for IPDB::connectDB
+# Takes no arguments, returns whatever IPDB::connectDB returns.
+sub connectDB_My {
+  return connectDB($dbname, $dbuser, $dbpass, $dbhost);
+} # end connectDB_My()
+
+# Keep Perl from complaining.
+1;
Index: /branches/htmlform/cgi-bin/access-pwd-update.pl
===================================================================
--- /branches/htmlform/cgi-bin/access-pwd-update.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/access-pwd-update.pl	(revision 446)
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+# Update IPDB users table with user/password data from 5-minute
+# cron'ed push from billing
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2007-2010 - Kris Deugau
+
+use strict;
+use warnings;
+use DBI;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+my $ip_dbh;
+my $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  die "Database error: $errstr\n";
+}
+initIPDBGlobals($ip_dbh);
+
+my %userhash;
+my $passfile = "/var/www/ipdb.example.com/ip/.htpasswd";
+
+die ".htpasswd error:  file is empty!\n"
+	if -z $passfile;
+
+die ".htpasswd error:  file seems too small: ".(-s $passfile)."\n"
+	if (-s $passfile <3000);
+
+open HTPASS, "<$passfile";
+
+my $sth = $ip_dbh->prepare("select count(*) from users where username=?");
+my $sth2;
+while (<HTPASS>) {
+  chomp;
+  my ($user,$pass) = split /:/;
+  $sth->execute($user);
+  my @data = $sth->fetchrow_array();
+  my $sql;
+  if ($data[0] == 0) {
+    $sql = "insert into users (username,password) values ('$user','$pass')";
+    print "new user: $user\n";
+  } else {
+    $sql = "update users set password='$pass' where username='$user'";
+  }
+  $sth2 = $ip_dbh->prepare($sql);
+  $sth2->execute or print "error executing $sql: ".$DBI::errstr."\n";
+  $userhash{$user} = $pass;
+}
+
+# and now to delete users that have been removed
+$sth = $ip_dbh->prepare("select username,acl from users order by username");
+$sth2 = $ip_dbh->prepare("delete from users where username=?");
+$sth->execute;
+while (my @data = $sth->fetchrow_array()) {
+  if (!$userhash{$data[0]}) {
+    print "deleting $data[0] (acl $data[1])\n";
+    $sth2->execute($data[0])
+	or print "error deleting $data[0]: ".$DBI::errstr."\n";
+  }
+}
Index: /branches/htmlform/cgi-bin/admin.cgi
===================================================================
--- /branches/htmlform/cgi-bin/admin.cgi	(revision 446)
+++ /branches/htmlform/cgi-bin/admin.cgi	(revision 446)
@@ -0,0 +1,831 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/admin.cgi
+# Hack interface to make specific changes to IPDB that (for one reason
+# or another) can't be made through the main interface.
+#
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+use strict;
+use warnings;
+use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use CommonWeb qw(:ALL);
+use CustIDCK;
+#use POSIX qw(ceil);
+use NetAddr::IP;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+openlog "IPDB-admin","pid","$IPDB::syslog_facility";
+
+# Collect the username from HTTP auth.  If undefined, we're in a test environment.
+my $authuser;
+if (!defined($ENV{'REMOTE_USER'})) {
+  $authuser = '__temptest';
+} else {
+  $authuser = $ENV{'REMOTE_USER'};
+}
+
+syslog "debug", "$authuser active";
+
+# Why not a global DB handle?  (And a global statement handle, as well...)
+# Use the connectDB function, otherwise we end up confusing ourselves
+my $ip_dbh;
+my $sth;
+my $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  printAndExit("Database error: $errstr\n");
+}
+initIPDBGlobals($ip_dbh);
+
+if ($IPDBacl{$authuser} !~ /A/) {
+  print "Content-Type: text/html\n\n".
+	"<html>\n<head>\n\t<title>Access denied</title>\n".
+	qq(\t<link rel="stylesheet" type="text/css" href="/ip/ipdb.css">\n).
+	qq(\t<link rel="stylesheet" type="text/css" href="/ip/local.css">\n).
+	"</head>\n<body>\n".
+	qq(Access to this tool is restricted.  Contact the <a href="mailto:ipdbadmin\@example.com">IPDB administrator</a> \n).
+	"for more information.\n</body>\n</html>\n";
+  exit;
+}
+
+my %webvar = parse_post();
+cleanInput(\%webvar);
+
+print "Content-type: text/html\n\n".
+	"<html>\n<head>\n\t<title>[IPDB admin tools]</title>\n".
+	qq(\t<link rel="stylesheet" type="text/css" href="/ip/ipdb.css">\n).
+	qq(\t<link rel="stylesheet" type="text/css" href="/ip/local.css">\n).
+	"</head>\n<body>\n".
+	"<h2>IPDB - Administrative Tools</h2>\n<hr>\n";
+
+if(!defined($webvar{action})) {
+  $webvar{action} = "<NULL>";   #shuts up the warnings.
+
+  my $typelist = '';
+  $sth = $ip_dbh->prepare("select type,listname from alloctypes where listorder < 900 order by listorder");
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+  $typelist .= "<option value='$data[0]' selected>$data[1]</option>\n";
+  while (my @data = $sth->fetchrow_array) {
+    $typelist .= "<option value='$data[0]'>$data[1]</option>\n";
+  }
+
+  my $masterlist = '';
+  $sth = $ip_dbh->prepare("select cidr,mtime from masterblocks order by cidr");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    $masterlist .= "<option value='$data[0]'>$data[0] ($data[1])</option>\n";
+  }
+
+  print qq(WARNING:  There are FAR fewer controls on what you can do here.  Use the
+main interface if at all possible.
+<hr>
+<form action="admin.cgi" method="POST">
+<input type=hidden name=action value=alloc>
+Allocate block/IP: <input name=cidr> as <select name=alloctype>$typelist</select> to <input name=custid>
+<input type=submit value=" GIMME!! "></form>
+<hr><form action="admin.cgi" method="POST">
+<input type=hidden name=action value=alloctweak>
+Manually update allocation data in this /24: <input name=allocfrom>
+<input type=submit value="Show allocations">
+</form>
+
+<hr>rWHOIS tools:
+<form action="admin.cgi" method="POST">
+<input type=hidden name=action value=touch>
+Bump "last updated" timestamp on this master: <select name=whichmaster>$masterlist</select>
+<input type=submit value="Update timestamp"> (Sets timestamp to "now")</form>
+<a href="admin.cgi?action=listcust">Edit customer data for rWHOIS</a> - data used for
+blocks with the SWIP box checkmarked.  Links to edit/add data are on this page.
+
+<hr><a href="admin.cgi?action=showpools">List IP Pools</a> for manual tweaking and updates
+
+<hr><a href="admin.cgi?action=showusers">Manage users</a> (add/remove users;  change
+internal access controls - note that this does NOT include IP-based limits)<br>
+<a href="admin.cgi?action=emailnotice">Manage email notice options</a> (pick which events
+and allocation types cause notifications;  configure recipient lists for notices)
+
+<hr>Consistency check tools<br>
+<a href="consistency-check.pl">General</a>:  Check general netblock consistency.<br>
+<a href="freespace.pl">Free space</a>:  List total and aggregate free space.  Does not 
+include private networks (192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8)
+);
+} else {
+  print '<a href="/ip/cgi-bin/admin.cgi">Back</a> to main<hr>';
+}
+
+
+## Possible actions.
+if ($webvar{action} eq 'alloc') {
+  # OK, we know what we're allocating.
+
+  if ($webvar{cidr} !~ /^\s*(\d{1,3}\.){3}\d{1,3}(\/\d{2})?\s*$/) {
+    printAndExit("Can't allocate something that's not a netblock/ip");
+  }
+
+  $sth = $ip_dbh->prepare("select def_custid from alloctypes where type='$webvar{alloctype}'");
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+  my $custid = $data[0];
+  if ($custid eq '') {
+    if ($webvar{custid} !~ /^(?:\d{10}|\d{7}|STAFF)(?:-\d\d?)?$/) {
+      # Force uppercase for now...
+      $webvar{custid} =~ tr/a-z/A-Z/;
+      # Crosscheck with billing.
+      my $status = CustIDCK->custid_exist($webvar{custid});
+      if ($CustIDCK::Error) {
+	printError("Error verifying customer ID: ".$CustIDCK::ErrMsg);
+	return;
+      }
+      if (!$status) {
+	printError("Customer ID not valid.  Make sure the Customer ID ".
+	  "is correct.<br>\nUse STAFF for staff static IPs, and $IPDB::defcustid for any other ".
+	  "non-customer assignments.");
+	return;
+      }
+    }
+    # Type that doesn't have a default custid
+    $custid = $webvar{custid};
+  }
+
+  my $cidr = new NetAddr::IP $webvar{cidr};
+  my @data;
+  if ($webvar{alloctype} eq 'rm') {
+    $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr >>='$cidr' and routed='n'");
+    $sth->execute;
+    @data = $sth->fetchrow_array;
+# User deserves errors if user can't be bothered to find the free block first.
+    printAndExit("Can't allocate from outside a free block!!\n")
+        if !$data[0];
+  } elsif ($webvar{alloctype} =~ /^(.)i$/) {
+    $sth = $ip_dbh->prepare("select cidr from allocations where cidr >>='$cidr' and (type like '_d' or type like '_p')");
+    $sth->execute;
+    @data = $sth->fetchrow_array;
+# User deserves errors if user can't be bothered to find the pool and a free IP first.
+    printAndExit("Can't allocate static IP from outside a pool!!\n")
+	if !$data[0];
+  } else {
+    $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr >>='$cidr' and not (routed='n')");
+    $sth->execute;
+    @data = $sth->fetchrow_array;
+# User deserves errors if user can't be bothered to find the free block first.
+    printAndExit("Can't allocate from outside a routed block!!\n")
+        if !$data[0];
+  }
+
+  my $alloc_from = new NetAddr::IP $data[0];
+  $sth->finish;
+
+  my $cities = '';
+  foreach my $city (@citylist) {
+    $cities .= "<option>$city</option>\n";
+  }
+
+  print qq(<table class=regular>
+<form method=POST action=admin.cgi>
+<tr class=color1>
+<td>Allocating:</td>
+<td>$cidr<input type=hidden name=cidr value="$cidr"></td>
+</tr><tr class=color2>
+<td>Type:</td><td>$disp_alloctypes{$webvar{alloctype}}
+<input type=hidden name=alloctype value="$webvar{alloctype}"></td>
+</tr><tr class=color1>
+<td>Allocated from:</td>
+<td>$alloc_from<input type=hidden name=alloc_from value="$alloc_from"></td>
+</tr><tr class="color2">
+<td>Customer ID:</td><td>$custid<input type=hidden name=custid value="$custid"></td>
+</tr><tr class=color1>
+<td>Customer location:</td><td>
+<select name="city"><option selected>-</option>
+$cities
+</select>
+&nbsp;<a href="javascript:popNotes('/ip/newcity.html')">Add new location</a>
+</td>
+</tr>
+<tr class="color2">
+<td>Circuit ID:</td><td><input name=circid size=40></td>
+</tr><tr class="color1">
+<td>Description/Name:</td><td><input name="desc" size=40></td>
+</tr><tr class="color2">
+<td>Notes:</td><td><textarea name="notes" rows="3" cols="40"></textarea></td>
+</tr><tr class="warning">
+<td colspan=2><center>WARNING:  This will IMMEDIATELY assign this block!!</center></td>
+</tr><tr class="color2">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+<input type="hidden" name="action" value="confirm">
+</form>
+</tr>
+</table>
+);
+
+
+} elsif ($webvar{action} eq 'confirm') {
+
+  print "Assigning $webvar{cidr} to $webvar{custid} (\"$webvar{desc}\") as ".
+	"$disp_alloctypes{$webvar{alloctype}}...<br>\n";
+  # Only need to check city here.
+  if ($webvar{city} eq '-') {
+    printError("Invalid customer location!  Go back and select customer's location.");
+  } else {
+    if ($webvar{alloctype} =~ /^.i$/) {
+      $sth = $ip_dbh->prepare("update poolips set available='n', custid='$webvar{custid}', ".
+	"city='$webvar{city}', description='$webvar{desc}', notes='$webvar{notes}' ".
+	"where ip='$webvar{cidr}'");
+      $sth->execute;
+      if ($sth->err) {
+	print "Allocation failed!  DBI said:\n".$sth->errstr."\n";
+        syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
+		"'$webvar{alloctype}' failed: '".$sth->errstr."'";
+      } else {
+	print "Allocation OK!\n";
+	syslog "notice", "$authuser allocated '$webvar{cidr}' to '$webvar{custid}' as ".
+		"'$webvar{alloctype}'";
+	mailNotify($ip_dbh, "a$webvar{alloctype}",
+	  "$disp_alloctypes{$webvar{alloctype}} $webvar{cidr} allocated to customer".
+	  " $webvar{custid}\n".
+	  "Description: $webvar{desc}\n\nAllocated by: $authuser\n");
+      }
+    } else {
+      my ($retcode,$msg) = allocateBlock($ip_dbh, $webvar{cidr}, $webvar{alloc_from},
+	$webvar{custid}, $webvar{alloctype}, $webvar{city}, $webvar{desc}, $webvar{notes},
+	$webvar{circid});
+      if ($retcode eq 'OK') {
+	print "Allocation OK!\n";
+	syslog "notice", "$authuser allocated '$webvar{cidr}' to '$webvar{custid}' as ".
+		"'$webvar{alloctype}'";
+      } else {
+	print "Allocation failed!  IPDB::allocateBlock said:\n$msg\n";
+        syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
+		"'$webvar{alloctype}' failed: '$msg'";
+      }
+    } # static IP vs netblock
+
+  } # done city check
+
+} elsif ($webvar{action} eq 'alloctweak') {
+  fix_allocfrom();
+  showAllocs($webvar{allocfrom});
+} elsif ($webvar{action} eq 'update') {
+  update();
+} elsif ($webvar{action} eq 'assign') {
+  # Display a list of possible blocks within the requested block.
+  open (HTML, "../admin_alloc.html")
+	or croak "Could not open admin_alloc.html :$!";
+  my $html = join('', <HTML>);
+  $html =~ s/\$\$MASK\$\$/$webvar{masklen}/g;
+  $html =~ s/\$\$ALLOCFROM\$\$/$webvar{allocfrom}/g;
+
+  my $from = new NetAddr::IP $webvar{allocfrom};
+  my @blocklist = $from->split($webvar{masklen});
+  my $availblocks;
+  foreach (@blocklist) {
+    $availblocks .= qq(<tr><td colspan=2 align=center><input type=radio name=block value="$_">$_</td></tr>\n);
+  }
+  $html =~ s/\$\$BLOCKLIST\$\$/$availblocks/g;
+
+  print $html;
+} elsif ($webvar{action} eq 'touch') {
+  print "Touching master $webvar{whichmaster}\n";
+  $sth = $ip_dbh->prepare("update masterblocks set mtime=now() where cidr='$webvar{whichmaster}'");
+  $sth->execute;
+  if ($sth->err) {
+    print "<p>Error updating modified timestamp on master $webvar{whichmaster}: ".$sth->errstr."\n";
+  }
+} elsif ($webvar{action} eq 'listcust') {
+  print qq(Add new entry:\n
+<form action=admin.cgi method=POST>
+<table border=1><tr>
+<input type=hidden name=action value=edcust>
+<input type=hidden name=newcust value=1>
+<td>CustID:</td><td><input name=custid></td>
+<td align=center><input type=submit value="Go to edit page for this custid"></td></tr>
+</form></table>
+);
+  print "<p>Click CustID to edit existing customer contact data:\n".
+	"<table border=1>\n<tr><td>CustID</td><td>Name</td><td>Tech handle</td></tr>\n";
+  $sth = $ip_dbh->prepare("select custid,name,tech_handle from customers order by custid");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    print qq(<tr><td><a href="admin.cgi?action=edcust&custid=$data[0]">$data[0]</td>).
+	"<td>$data[1]</td><td>$data[2]</td></tr>\n";
+  }
+  print "</table>\n";
+} elsif ($webvar{action} eq 'edcust') {
+  if ($webvar{newcust}) {
+    print "got here?\n";
+    $sth = $ip_dbh->prepare("INSERT INTO customers (custid) VALUES (?)");
+    $sth->execute($webvar{custid});
+  }
+  $sth = $ip_dbh->prepare("select custid,name,street,city,province,".
+	"country,pocode,phone,tech_handle,abuse_handle,admin_handle,special ".
+	"from customers where custid='$webvar{custid}'");
+  $sth->execute;
+  my ($custid, $name, $street, $city, $prov, $country, $pocode, $phone, $tech, $abuse, $admin, $special) =
+	$sth->fetchrow_array;
+  print qq(<form action=admin.cgi method=POST>
+<table border=1><tr>
+<input type=hidden name=action value=updcust>
+<td>CustID:</td><td>$custid<input type=hidden name=custid value=$custid></td>
+<td>Name:</td><td><input name=name value="$name"></td></tr>
+<tr><td>Street:</td><td><input name=street value="$street"></td>
+<!-- <td>Street2:</td><td><input name=street2></td> -->
+<td>City:</td><td><input name=city value="$city"></td></tr>
+<tr><td>Province/State: (2-letter code)</td><td><input name=province value="$prov" length=2 size=2></td>
+<td>Country: (2-letter code)</td><td><input name=country value="$country" length=2 size=2></td></tr>
+<tr><td>Postal/ZIP Code:</td><td><input name=pocode value="$pocode"></td>
+<td>Phone:</td><td><input name=phone value="$pocode"></td></tr>
+<!-- <td>Default rDNS:</td><td><input name=def_rdns></td></tr>
+<td>Description:</td><td><input name=description></td> -->
+<tr><td>Contacts/ARIN Handles:</td><td>
+ Tech: <input name=tech_handle value="$tech"><br>
+ Abuse: <input name=abuse_handle value="$abuse"><br>
+ Admin: <input name=admin_handle value="$admin"><br>
+Note:  Only tech is required at the moment.
+</td>
+<td>"Special":</td><td><textarea name=special rows=4 cols=50>$special</textarea></td>
+</tr>
+<tr><td colspan=4 align=center><input type=submit value="Update"></td></tr>
+</form></table>
+<div style="margin-left:5px">
+<h3>Explanation for "Special" field:</h3>
+This is a temporary place to define the WHOIS "net name" for a block.
+It may be removed later, more likely migrated elsewhere.
+<p>It's formatted like this, one line for each custom net name:
+<pre>NetName[CIDR block]: NET-NAME</pre>
+Example:
+<pre>NetName192.168.236.0/24: MEGAWIDGET-1</pre>
+Note:
+<ul style="margin-top: 0px;">
+<li>Spacing is important - there should only be ONE space, in between the colon and the net name.
+<li>The CIDR block name nust include all four octets - no short forms are accepted.
+<li>Net names must be all uppercase, and consist only of A-Z, 0-9, and - (same as for SWIPed net names).
+</ul>
+</div>
+);
+
+} elsif ($webvar{action} eq 'updcust') {
+  $sth = $ip_dbh->prepare("UPDATE customers SET".
+	" name=?, street=?, city=?, province=?, country=?, pocode=?,".
+	" phone=?, tech_handle=?, abuse_handle=?, admin_handle=?, special=?".
+	" WHERE custid=?");
+  $sth->execute($webvar{name}, $webvar{street}, $webvar{city}, $webvar{province}, 
+	$webvar{country}, $webvar{pocode}, $webvar{phone}, $webvar{tech_handle}, 
+	$webvar{abuse_handle}, $webvar{admin_handle}, $webvar{special}, $webvar{custid});
+  print "Updated $webvar{custid}<br>\n".
+	qq(<table border=1>
+<tr><td>CustID:</td><td>$webvar{custid}</td></tr>
+<tr><td>Name:</td><td>$webvar{name}</td></tr>
+<tr><td>Street:</td><td>$webvar{street}</td></tr>
+<tr><td>City:</td><td>$webvar{city}</td></tr>
+<tr><td>Province/State:</td><td>$webvar{province}</td></tr>
+<tr><td>Country:</td><td>$webvar{country}</td></tr>
+<tr><td>Postal/ZIP Code:</td><td>$webvar{pocode}</td></tr>
+<tr><td>Phone:</td><td>$webvar{phone}</td></tr>
+<!-- <td>Default rDNS:</td><td>$webvar{def_rdns}</td></tr> -->
+<tr><td>Contacts/ARIN Handles:</td><td>
+ Tech: $webvar{tech_handle}<br>
+ Abuse: $webvar{abuse_handle}<br>
+ Admin: $webvar{admin_handle}<br>
+</td></tr>
+<tr><td>"Special":</td><td><pre>$webvar{special}</pre></td></tr>
+</table>
+<a href="admin.cgi?action=listcust">Back</a> to rWHOIS customer list<br>\n);
+
+} elsif ($webvar{action} eq 'showpools') {
+  print "IP Pools currently allocated:\n".
+	"<table border=1>\n<tr><td>Pool</td><td># of free IPs</td></tr>\n";
+  $sth = $ip_dbh->prepare("select cidr from allocations where type like '%p' or type like '%d' order by cidr");
+  $sth->execute;
+  my %poolfree;
+  while (my @data = $sth->fetchrow_array) {
+    $poolfree{$data[0]} = 0;
+  }
+  $sth = $ip_dbh->prepare("select pool,ip from poolips where available='y' order by ip");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    $poolfree{$data[0]}++;
+  }
+  foreach my $key (keys %poolfree) {
+    print qq(<tr><td><a href="admin.cgi?action=tweakpool&pool=$key">$key</a></td>).
+	"<td>$poolfree{$key}</td></tr>\n";
+  }
+  print "</table>\n";
+} elsif ($webvar{action} eq 'tweakpool') {
+  showPool($webvar{pool});
+} elsif ($webvar{action} eq 'updatepool') {
+
+  $sth = $ip_dbh->prepare("update poolips set custid='$webvar{custid}', ".
+	"city='$webvar{city}', type='$webvar{type}', available='".
+	(($webvar{available} eq 'y') ? 'y' : 'n').
+	"', notes='$webvar{notes}', description='$webvar{desc}' ".
+	"where ip='$webvar{ip}'");
+  $sth->execute;
+  if ($sth->err) {
+    print "Error updating pool IP $webvar{ip}: $@<hr>\n";
+    syslog "err", "$authuser could not update pool IP $webvar{ip}: $@";
+  } else {  
+    $sth = $ip_dbh->prepare("select pool from poolips where ip='$webvar{ip}'");
+    $sth->execute;
+    my @data = $sth->fetchrow_array;
+    print "$webvar{ip} in $data[0] updated\n<hr>\n";
+    syslog "notice", "$authuser updated pool IP $webvar{ip}";
+  }
+} elsif ($webvar{action} eq 'showusers') {
+  print "Notes:<br>\n".
+	"<li>Admin users automatically get all other priviledges.\n".
+	"<li>Everyone has basic read access.\n".
+	"<hr>Add new user:<form action=admin.cgi method=POST>\n".
+	"Username: <input name=username><br>\n".
+	"Password: <input name=password> <input type=checkbox name=preenc>Password is pre-encrypted (MUST be crypt() encrypted)<br>\n".
+	"<input type=submit value='Add user'><input type=hidden name=action value=newuser></form>\n";
+
+  print "<hr>Users with access:\n<table border=1>\n";
+  print "<tr><td></td><td align=center colspan=3>General access</td></tr>\n";
+  print "<tr><td>Username</td><td>Add new</td><td>Change</td>".
+	"<td>Delete</td><td>Systems/Networking</td><td>Admin user</td></tr>\n".
+	"<form action=admin.cgi method=POST>\n";
+  $sth = $ip_dbh->prepare("select username,acl from users order by username");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    print "<form action=admin.cgi method=POST><input type=hidden name=action value=updacl>".
+	qq(<tr><td>$data[0]<input type=hidden name=username value="$data[0]"></td><td>).
+    # Now for the fun bit.  We have to pull apart the ACL field and
+    # output a bunch of checkboxes.
+    	"<input type=checkbox name=add".($data[1] =~ /a/ ? ' checked=y' : '').
+	"></td><td><input type=checkbox name=change".($data[1] =~ /c/ ? ' checked=y' : '').
+	"></td><td><input type=checkbox name=del".($data[1] =~ /d/ ? ' checked=y' : '').
+	"></td><td><input type=checkbox name=sysnet".($data[1] =~ /s/ ? ' checked=y' : '').
+	"></td><td><input type=checkbox name=admin".($data[1] =~ /A/ ? ' checked=y' : '').
+	qq(></td><td><input type=submit value="Update"></td></form>\n).
+	"<form action=admin.cgi method=POST><td><input type=hidden name=action value=deluser>".
+	"<input type=hidden name=username value=$data[0]>".
+	qq(<input type=submit value="Delete user"></tr></form>\n);
+
+  }
+  print "</table>\n";
+} elsif ($webvar{action} eq 'updacl') {
+  print "Updating ACL for $webvar{username}:<br>\n";
+  my $acl = 'b';
+  if ($webvar{admin} eq 'on') {
+    $acl .= "acdsA";
+  } else {
+    $acl .= ($webvar{add} eq 'on' ? 'a' : '').
+	($webvar{change} eq 'on' ? 'c' : '').
+	($webvar{del} eq 'on' ? 'd' : '').
+	($webvar{sysnet} eq 'on' ? 's' : '');
+  }
+  print "New ACL: $acl<br>\n";
+
+  $sth = $ip_dbh->prepare("update users set acl='$acl' where username='$webvar{username}'");
+  $sth->execute;
+  print "OK\n" if !$sth->err;
+
+  print qq(<hr><a href="admin.cgi?action=showusers">Back</a> to user listing\n);
+
+} elsif ($webvar{action} eq 'newuser') {
+  print "Adding user $webvar{username}...\n";
+  my $cr_pass = ($webvar{preenc} ? $webvar{password} :
+	crypt $webvar{password}, join('',('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64]));
+  $sth = $ip_dbh->prepare("insert into users (username,password,acl) values ".
+	"('$webvar{username}','$cr_pass','b')");
+  $sth->execute;
+  if ($sth->err) {
+    print "<br>Error adding user: ".$sth->errstr;
+  } else {
+    print "OK\n";
+  }
+
+  print qq(<hr><a href="admin.cgi?action=showusers">Back</a> to user listing\n);
+
+} elsif ($webvar{action} eq 'deluser') {
+  print "Deleting user $webvar{username}.<br>\n";
+  $sth = $ip_dbh->prepare("delete from users where username='$webvar{username}'");
+  $sth->execute;
+  print "OK\n" if !$sth->err;
+
+  print qq(<hr><a href="admin.cgi?action=showusers">Back</a> to user listing\n);
+
+} elsif ($webvar{action} eq 'emailnotice') {
+  print "<h4>Email notice management:</h4>\nClick the email addresses to edit that list.";
+  $sth = $ip_dbh->prepare("SELECT action,reciplist FROM notify");
+  $sth->execute;
+
+  print "<table border=1>\n";
+  while (my ($notice_code,$reciplist) = $sth->fetchrow_array() ) {
+##fixme: hairy mess, only a few things call mailNotify() anyway, so many possible notices won't work.
+    my $action_out = dispNoticeCode($notice_code);
+    print "<tr><td>$action_out</td>".
+	qq(<td><a href="admin.cgi?action=ednotice&code=$notice_code">$reciplist</a></td>).
+	qq(<td><a href="admin.cgi?action=delnotice&code=$notice_code">Delete</a></tr>\n);
+  }
+  print qq(<tr><td colspan=2>Known "special" codes:<br>
+<ul style="margin-top: 0px; margin-bottom: 0px;">
+	<li>swi: Notify if block being updated has SWIP flag set</li>
+</ul></td></tr>
+</table>
+);
+
+# add new entries from this tangle:
+  print "<h4>Add new notification:</h4>\n".
+	"Note:  Failure notices on most conditions are not yet supported.\n";
+
+  print qq(<table border=1><form action=admin.cgi method="POST">
+<input type=hidden name=action value=addnotice>
+<tr>
+<td>Recipients</td><td colspan=3><textarea name=reciplist cols=50 rows=5></textarea></td></tr>
+<tr><td>Action</td><td>
+	<table><tr>
+		<td><input type=radio name=msgaction value=a>Add &nbsp;
+		<input type=radio name=msgaction value=u>Update &nbsp;
+		<input type=radio name=msgaction value=d>Delete &nbsp;
+		<input type=radio name=msgaction value=n>New listitem</td>
+	</tr><tr>
+		<td>
+		<input type=radio name=msgaction value=s:>Special: <input name=special>(requires code changes)
+	</td></tr></table>
+</td>
+<td>Failure?</td><td><input type=checkbox name=onfail></td></tr>
+<tr><td>Event/Allocation type:</td><td colspan=3>
+	<table>
+	<tr>
+		<td><input type=radio name=alloctype value=a>All allocations</td>
+		<td><input type=radio name=alloctype value=.i>All static IPs</td>
+		<td><input type=radio name=alloctype value=ci>New city</td>
+		<td><input type=radio name=alloctype value=no>New node</td>
+	</tr>
+	<tr>
+);
+
+  $sth = $ip_dbh->prepare("SELECT type,dispname FROM alloctypes WHERE listorder < 500 ".
+	"ORDER BY listorder");
+  $sth->execute;
+  my $i=0;
+  while (my ($type,$disp) = $sth->fetchrow_array) {
+    print "		<td><input type=radio name=alloctype value=$type>$disp</td>";
+    $i++;
+    print "	</tr>\n\t<tr>"
+	if ($i % 4 == 0);
+  }
+
+  print qq(	</tr>
+	</table>
+</tr>
+<tr><td colspan=4 align=center><input type=submit value="Add notice"></td></tr>
+</table>
+</form>
+);
+  ## done spitting out add-new-spam-me-now table
+
+} elsif ($webvar{action} eq 'addnotice') {
+  $webvar{alloctype} = $webvar{special} if $webvar{msgaction} eq 's:';
+  if ($webvar{msgaction} && $webvar{alloctype} && $webvar{reciplist}) {
+    $webvar{reciplist} =~ s/[\r\n]+/,/g;
+    $webvar{msgaction} = "f:$webvar{msgaction}" if $webvar{onfail};
+    print "Adding notice to $webvar{reciplist} for ".dispNoticeCode($webvar{msgaction}.$webvar{alloctype}).":\n";
+    $sth = $ip_dbh->prepare("INSERT INTO notify (action, reciplist) VALUES (?,?)");
+##fixme:  automagically merge reciplists iff action already exists
+    $sth->execute($webvar{msgaction}.$webvar{alloctype}, $webvar{reciplist});
+    if ($sth->err) {
+      print "Failed:  DB error: ".$sth->errstr."\n";
+    } else {
+      print "OK!<br>\n"
+    }
+  } else {
+    print "Need to specify at least one recipient, an action, and an allocation type. ".
+	qq{("Special" content is considered an allocation type).  Hit the Back button and try again.<br>\n};
+  }
+  print qq(<a href="admin.cgi?action=emailnotice">Back to email notice list</a>\n);
+
+} elsif ($webvar{action} eq 'delnotice') {
+  print "Deleting notices on ".dispNoticeCode($webvar{code}.$webvar{alloctype}).":\n";
+  $sth = $ip_dbh->prepare("DELETE FROM notify WHERE action=?");
+  $sth->execute($webvar{code});
+  if ($sth->err) {
+    print "Failed:  DB error: ".$sth->errstr."\n";
+  } else {
+    print "OK!<br>\n"
+  }
+  print qq(<a href="admin.cgi?action=emailnotice">Back to email notice list</a>\n);
+
+} elsif ($webvar{action} eq 'ednotice') {
+  print "<h4>Editing recipient list for '".dispNoticeCode($webvar{code})."':</h4>\n";
+  $sth = $ip_dbh->prepare("SELECT reciplist FROM notify WHERE action=?");
+  $sth->execute($webvar{code});
+  my ($reciplist) = $sth->fetchrow_array;
+  $reciplist =~ s/,/\n/g;
+  print qq(<form action=admin.cgi method=POST><input type=hidden name=code value="$webvar{code}">\n).
+	qq(<input type=hidden name=action value="updnotice"><table border=1><tr><td>).
+	qq(<textarea cols="40" rows="5" name=reciplist>$reciplist</textarea></td><td><input type=submit value="Update">\n).
+	"</td></tr></table></form>\n";
+} elsif ($webvar{action} eq 'updnotice') {
+  print "<h4>Updating recipient list for '".dispNoticeCode($webvar{code})."':</h4>\n";
+  $sth = $ip_dbh->prepare("UPDATE notify SET reciplist=? WHERE action=?");
+  $webvar{reciplist} =~ s/[\r\n]+/,/g;
+  $sth->execute($webvar{reciplist}, $webvar{code});
+  if ($sth->err) {
+    print "Failed:  DB error: ".$sth->errstr."\n";
+  } else {
+    print "OK!<br>\n"
+  }
+  print qq(<a href="admin.cgi?action=emailnotice">Back to email notice list</a>\n);
+} elsif ($webvar{action} ne '<NULL>') {
+  print "webvar{action} check failed: Don't know how to $webvar{action}";
+}
+
+# Hokay.  This is a little different.  We have a few specific functions here:
+#  -> Assign arbitrary subnet from arbitrary free space
+#  -> Tweak individual DB fields
+#
+
+print qq(<hr><a href="/ip/">Back</a> to main interface</a>\n);
+
+printFooter;
+
+$ip_dbh->disconnect;
+
+exit;
+
+
+# Tweak allocfrom into shape.
+sub fix_allocfrom {
+  if ($webvar{allocfrom} =~ /^(\d+\.){2}\d+$/) {
+    # 3-octet class C specified
+    $webvar{allocfrom} .= ".0/24";
+  } elsif ($webvar{allocfrom} =~ /^(\d+\.){3}\d+$/) {
+    # 4-octet IP specified;  
+    $webvar{allocfrom} .= "/24";
+  }
+}
+
+
+# List free blocks in a /24 for arbitrary manual allocation
+sub showfree($) {
+  my $cidr = new NetAddr::IP $_[0];
+  print "Showing free blocks in $cidr<br>\n".
+	"<table border=1>\n";
+  $sth = $ip_dbh->prepare("select * from freeblocks where cidr <<= '$cidr' order by cidr");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    my $temp = new NetAddr::IP $data[0];
+    print "<tr><form action=admin.cgi method=POST><input type=hidden name=action value=assign>\n".
+	qq(<td>$temp<input type=hidden name=allocfrom value="$temp"></td>\n).
+	"<td>".
+	(($temp->masklen == 30) ? '<input type=hidden name=masklen value=30>30'
+	  : "<select name=masklen><option>30</option>\n<option>29</option>\n") .
+	(($temp->masklen < 29) ? "<option>28</option>\n" : '') .
+	(($temp->masklen < 28) ? "<option>27</option>\n" : '') .
+	(($temp->masklen < 27) ? "<option>26</option>\n" : '') .
+	(($temp->masklen < 26) ? "<option>25</option>\n" : '') .
+	(($temp->masklen < 25) ? "<option>24</option>\n" : '') .
+	"</td>".
+	qq(<td>$data[2]</td><td><input type=submit value="Allocate from here"></td>).
+	"\n</form></tr>\n";
+  }
+  print "</table>\n";
+}
+
+
+# Show allocations to allow editing.
+sub showAllocs($) {
+  my $cidr = new NetAddr::IP $_[0];
+  print "Edit custID, allocation type, city for allocations in ".
+	"$cidr:\n<table border=1>";
+  $sth = $ip_dbh->prepare("select * from allocations where cidr <<= '$cidr' order by cidr");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    print "<tr><form action=admin.cgi method=POST><input type=hidden name=action value=update>\n".
+	qq(<td>$data[0]<input type=hidden value="$data[0]" name=block></td>\n).
+	qq(<td><input name=custid value="$data[1]"></td>\n).
+	"<td><select name=alloctype>";
+
+    my $sth2 = $ip_dbh->prepare("select type,listname from alloctypes".
+	" where listorder < 500 and not (type like '_i') order by listorder");
+    $sth2->execute;
+    while (my @types = $sth2->fetchrow_array) {
+      print "<option". (($data[2] eq $types[0]) ? ' selected' : '') .
+	" value='$types[0]'>$types[1]</option>\n";
+    }
+
+    print qq(<td><input name=city value="$data[3]"></td>\n).
+	"<td>$data[4]</td><td>$data[5]</td>".
+	qq(<td><input type=submit value="Update"></td></form></tr>\n);
+  }
+  print "</table>\n";
+
+  # notes
+  print "<hr><b>Notes:</b>\n".
+	"<ul>\n<li>Use the main interface to update description and notes fields\n".
+	"<li>Changing the allocation type here will NOT affect IP pool data.\n".
+	"</ul>\n";
+}
+
+
+# Stuff updates into DB
+sub update {
+  eval {
+    # Relatively simple SQL transaction here.  Note that we're deliberately NOT
+    # updating notes/desc here as it's available through the main interface.
+    $sth = $ip_dbh->prepare("update allocations set custid='$webvar{custid}',".
+	"city='$webvar{city}',type='$webvar{alloctype}' where cidr='$webvar{block}'");
+    $sth->execute;
+    $ip_dbh->commit;
+  };
+  if ($@) {
+    carp "Transaction aborted because $@";
+    eval { $ip_dbh->rollback; };
+    syslog "err", "$authuser could not update block '$webvar{block}': '$@'";
+  } else {
+    # If we get here, the operation succeeded.
+    syslog "notice", "$authuser updated $webvar{block}";
+    print "Allocation $webvar{block} updated<hr>\n";
+  }
+  # need to get /24 that block is part of
+  my @bits = split /\./, $webvar{block};
+  $bits[3] = "0/24";
+  showAllocs((join ".", @bits));
+}
+
+
+# showPool()
+# List all IPs in a pool, and allow arbitrary admin changes to each
+# Allow changes to ALL fields
+sub showPool($) {
+  my $pool = new NetAddr::IP $_[0];
+  print qq(Listing pool $pool:\n<table border=1>
+<form action=admin.cgi method=POST>
+<input type=hidden name=action value=updatepool>
+<tr><td align=right>Customer ID:</td><td><input name=custid></td></tr>
+<tr><td align=right>Customer location:</td><td><input name=city></td></tr>
+<tr><td align=right>Type:</td><td><select name=type><option selected>-</option>\n);
+
+  $sth = $ip_dbh->prepare("select type,listname from alloctypes where type like '_i' order by listorder");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    print "<option value='$data[0]'>$data[1]</option>\n";
+  }
+
+  print qq(</select></td></tr>
+<tr><td align=right>Available?</td><td><input type=checkbox value=y></td></tr>
+<tr><td align=right>Description/name:</td><td><input name=desc size=40></td></tr>
+<tr><td align=right>Notes:</td><td><textarea name=notes rows=3 cols=40></textarea></td></tr>
+<tr><td colspan=2 align=center><input type=submit value="Update"></td></tr>
+).
+	"</table>Update the following record:<table border=1>\n";
+  $sth = $ip_dbh->prepare("select pool,ip,custid,city,type,available,description,notes from poolips where pool='$pool' order by ip");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    print qq(<tr><td><input type=radio name=ip value="$data[1]">$data[1]</td>).
+	"<td>$data[2]</td><td>$data[3]</td><td>$data[4]</td>".
+	"<td>$data[5]</td><td>$data[6]</td><td>$data[7]</td></tr>\n";
+  }
+  print "</form></table>\n";
+}
+
+
+# interpret the notify codes
+sub dispNoticeCode {
+  my $code = shift;
+  my $action_out = '';
+
+  if ($code =~ /^s:/) {
+    $code =~ s/^s:/Special: /;
+    return $code;
+  }
+  if ($code =~ /^f:(.+)$/) {
+    $code =~ s/^f://;
+    $action_out = "Failure on ";
+  }
+  if (my $target = $code =~ /^n(.+)/) {
+    $action_out .= "New ";
+    if ($1 eq 'ci') { $action_out .= "city"; }
+    elsif ($1 eq 'no') { $action_out .= "node"; }
+    else { $action_out .= '&lt;unknown&gt;'; }
+  } else {
+    my ($action,$target) = ($code =~ /^(.)(.+)$/);
+    if ($action eq 'a')      { $action_out .= 'Add '; }
+    elsif ($action eq 'u')   { $action_out .= 'Update '; }
+    elsif ($action eq 'd')   { $action_out .= 'Delete '; }
+##fixme:  what if we get something funky?
+# What about the eleventy-billion odd combinations possible?
+# this should give an idea of the structure tho
+    if ($target eq 'a') { $action_out .= "all"; }
+    elsif ($target eq '.i') {
+      $action_out .= "all static IPs";
+    }
+    else { $action_out .= $disp_alloctypes{$target}; }
+  }
+  return $action_out;
+}
Index: /branches/htmlform/cgi-bin/allocate.pl
===================================================================
--- /branches/htmlform/cgi-bin/allocate.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/allocate.pl	(revision 446)
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+# Shell-based script to allocate arbitrary block
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+
+use strict;
+use warnings;
+use DBI;
+use CommonWeb qw(:ALL);
+use NetAddr::IP;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+openlog "IPDBshell","pid","$IPDB::syslog_facility";
+
+# Collect the username from the environment.  If undefined, something
+# is Officially Hosed.
+my $authuser;
+if (!defined($ENV{'USER'})) {
+  die "Bad environment!  USER not defined.\n";
+} else {
+  $authuser = $ENV{'USER'};
+}
+
+# Why not a global DB handle?  (And a global statement handle, as well...)
+# Use the connectDB function, otherwise we end up confusing ourselves
+my $ip_dbh;
+my $sth;
+my $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  printAndExit("Failed to connect to database: $errstr\n");
+}
+checkDBSanity($ip_dbh);
+initIPDBGlobals($ip_dbh);
+
+# Hokay, now we can start to handle the allocation.
+
+my ($cidr, $type, $custid, $city, $desc, $alloc_from);
+# Check ARGV.  We need some information to determine what to allocate.
+if (!$ARGV[1]) {
+  # Usage message
+  print "Usage:  allocate.pl [IP/subnet] [Type] [CustID] [City] [\"Description\"]\n".
+	"	Further information can be entered via the web interface\n";
+  exit;
+} else {
+  $cidr = new NetAddr::IP "$ARGV[0]";
+  $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr >>='$cidr'");
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+# User deserves errors if user can't be bothered to find the free block first.
+  die "Can't allocate from outside a free block!!\n"
+	if !$data[0];
+  $alloc_from = new NetAddr::IP $data[0];
+  $sth->finish;
+  $type = $ARGV[1];
+  if (!$ARGV[4]) {
+    # Default desc
+    $desc = "DEFAULT: $disp_alloctypes{$type}";
+  } else {
+    $desc = $ARGV[4];
+  }
+  if (!$ARGV[3]) {
+    # Default city
+    $sth = $ip_dbh->prepare("select city from routed where cidr >>='$cidr'");
+    $sth->execute;
+    my @data = $sth->fetchrow_array;
+    $city = $data[0];
+    $sth->finish;
+  } else {
+    $city = $ARGV[3];
+  }
+  if (!$ARGV[2]) {
+    # Default custid - make it REAL obvious.
+    $custid = "FIXME";
+  } else {
+    $custid = $ARGV[2];
+  }
+}
+
+print "Allocating $cidr as $type to $custid in $city: '$desc'\n";
+
+my ($code,$msg) = allocateBlock($ip_dbh, $cidr, $alloc_from, $custid, $type, $city,
+	$desc, '', '');
+
+if ($code eq 'OK') {
+  print "Allocation OK!\n";
+  syslog "notice", "($authuser) Allocated '$cidr' to '$custid' as '$type'";
+} else {
+  print "Allocation failed!  IPDB::allocateBlock said:\n$msg\n";
+  syslog "err", "($authuser) Allocation of '$cidr' to '$custid' as '$type' failed: '$msg'";
+}
+
+# Close it down.
+finish($ip_dbh);
Index: /branches/htmlform/cgi-bin/checkcusts.pl
===================================================================
--- /branches/htmlform/cgi-bin/checkcusts.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/checkcusts.pl	(revision 446)
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+# Check all customer IDs to see which are invalid
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 Kris Deugau
+
+use DBI;
+use IPDB 2.0 qw(:ALL);
+
+# We'll be hosing the server with several thousand queries.  We
+# REALLY don't want the overhead and load of opening a new connection
+# for each query.
+#use CustIDCK;
+
+use NetAddr::IP;
+
+$priv1 = new NetAddr::IP '10.0.0.0/8';
+$priv2 = new NetAddr::IP '172.16.0.0/12';
+$priv3 = new NetAddr::IP '192.168.0.0/16';
+
+print "Content-type: text/plain\n\n";
+
+($dbh,$errstr) = connectDB("ipdb", "ipdb", "ipdbpwd");
+$IDH = DBI->connect ("DBI:Pg:host=billing;dbname=custids", "cidcheck", "c1dch4ck");
+
+$sth = $dbh->prepare("select distinct def_custid from alloctypes where listorder >=40");
+$sth->execute;
+while (@data = $sth->fetchrow_array) {
+  push @def_custids, $data[0];
+}
+$sth = $dbh->prepare("select cidr,custid from searchme where not (custid='$IPDB::defcustid') ".
+	"and not (custid='STAFF') order by cidr");
+#$sth = $dbh->prepare("select cidr,custid from searchme order by cidr");
+$sth->execute;
+
+$IDS = $IDH->prepare("select custid from custid where custid=?");
+
+$count = $bad = 0;
+while (@data = $sth->fetchrow_array) {
+  $cidr = new NetAddr::IP $data[0];
+  if ($cidr->within($priv1) or $cidr->within($priv2) or $cidr->within($priv3) or
+	(grep /$data[1]/, @def_custids)) {
+    # no-op.  we ignore these.
+  } else {
+    $count++;
+    $IDS->execute($data[1]);
+    $hr = $IDS->fetchrow_hashref();
+    if (!$hr->{custid}) {
+      print "  $data[0]\thas invalid CustID '$data[1]'\n";
+      $bad++;
+    }
+    $IDS->finish;
+  }
+}
+
+$IDH->disconnect;
+$dbh->disconnect;
+
+print "$count customer blocks, $bad bad.\n";
+exit 0;
+
+
+### Ported subs of sorts from CustIDCK.pm
Index: /branches/htmlform/cgi-bin/combineblocks.pl
===================================================================
--- /branches/htmlform/cgi-bin/combineblocks.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/combineblocks.pl	(revision 446)
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/combineblocks.pl
+# Quick hack to clean up mangled deallocations
+###
+# Revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+
+use strict;
+use warnings;
+#use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+#use CommonWeb qw(:ALL);
+#use POSIX qw(ceil);
+use NetAddr::IP;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+my $null = new NetAddr::IP "255.255.255.255/32";
+
+my @fbtypes;
+
+my ($dbh,$ret) = connectDB_My;
+initIPDBGlobals($dbh);
+my $sth;
+
+# Get the types of freeblocks
+$sth = $dbh->prepare("select distinct routed from freeblocks");
+$sth->execute;
+while (my @data = $sth->fetchrow_array) {
+  push @fbtypes, @data;
+}
+
+foreach my $type (@fbtypes) {
+
+  my $i=0;
+  my @compacted;
+
+  my $sth = $dbh->prepare("select cidr from freeblocks where routed='$type'");
+  $sth->execute;
+  while (my @data = $sth->fetchrow_array) {
+    my $temp = new NetAddr::IP $data[0];
+    @compacted = $temp->compact(@compacted);
+    $i++;
+  }
+
+#print $compacted[0];
+my $numcomp = @compacted;
+#print " $numcomp";
+
+  if ($i == $numcomp) {
+    print "No compactable blocks for type $type ($i free)\n";
+    next;
+  }
+  print "Type $type: $i free blocks to start, $numcomp to finish\n";
+
+  $sth = $dbh->prepare("select cidr from freeblocks where cidr << ?");
+  foreach my $cip (@compacted) {
+    $sth->execute("$cip");
+    if ($sth->rows > 0) {
+      print "  $cip combines from:\n";
+      while (my @data = $sth->fetchrow_array) {
+        print "    $data[0]\n";
+      }
+#    } else {
+#      print "  $cip does not compact\n";
+    }
+  }
+
+} # each @fbtype
+
+finish($dbh);
Index: /branches/htmlform/cgi-bin/consistency-check.pl
===================================================================
--- /branches/htmlform/cgi-bin/consistency-check.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/consistency-check.pl	(revision 446)
@@ -0,0 +1,241 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/consistency-check.pl
+# Does full check to see if the data in the db is consistent and complete.
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+use DBI;
+use NetAddr::IP;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+print "Content-type: text/plain\n\n";
+
+($dbh,$errstr) = connectDB_My;
+die $errstr if !$dbh;
+# May as well.  We need a number of globals.
+initIPDBGlobals($dbh);
+
+print "Checking master containment...\n";
+
+# First check - make sure ALL routes and allocated blocks are part
+# of one of the master blocks.
+print "  Checking routed blocks:";
+$sth = $dbh->prepare("select cidr from routed");
+$sth->execute;
+$flag = '';
+ROUTED: while (@data = $sth->fetchrow_array) {
+  $cidr = new NetAddr::IP $data[0];
+  foreach $master (@masterblocks) {
+    if ($master->contains($cidr)) { next ROUTED; }
+  }
+  print "\n    $cidr not mastered";
+}
+print "$flag done.\n";
+
+# Next test:  All allocations must be part of a master.
+print "  Checking allocations:";
+$sth = $dbh->prepare("select cidr from allocations");
+$sth->execute;
+$flag = '';
+ALLOCATED: while (@data = $sth->fetchrow_array) {
+  $cidr = new NetAddr::IP $data[0];
+  foreach $master (@masterblocks) {
+    if ($master->contains($cidr)) { next ALLOCATED; }
+  }
+  print "\n    $cidr not mastered";
+  $flag = "\n ";
+}
+print "$flag done.\n";
+
+# Next:  free blocks
+print "  Checking freeblocks:";
+$sth = $dbh->prepare("select cidr from freeblocks order by cidr");
+$sth->execute;
+$flag = '';
+FREEBLOCK: while (@data = $sth->fetchrow_array) {
+  $cidr = new NetAddr::IP $data[0];
+  foreach $master (@masterblocks) {
+    if ($master->contains($cidr)) { next FREEBLOCK; }
+  }
+  print "\n    $cidr not mastered";
+  $flag = "\n ";
+}
+print "$flag done.\n";
+
+print "Done checking master containment.\n\n";
+
+print "Checking pool containment...\n";
+
+$sth = $dbh->prepare("select pool,ip from poolips order by ip");
+$sth->execute;
+while (@data = $sth->fetchrow_array) {
+  $pool = new NetAddr::IP $data[0];
+  $ip = new NetAddr::IP $data[1];
+  print "  IP $ip listed with incorrect pool $pool\n"
+    if !$pool->contains($ip);
+}
+$sth = $dbh->prepare("select distinct pool from poolips order by pool");
+$sth->execute;
+while (@data = $sth->fetchrow_array) {
+  $sth2 = $dbh->prepare("select cidr from allocations where cidr='$data[0]'");
+  $sth2->execute;
+  print "  Pool $data[0] does not exist in allocations table\n"
+    if (($sth2->fetchrow_array)[0] eq '');
+}
+
+print "Done checking pool containment.\n\n";
+
+print "Checking block-alignment consistency...\n";
+
+# Block alignment consistency:  All allocated+free blocks within a master
+# must NOT overlap, and they must show no gaps.
+# eg, if we have blocks:
+#  master is 192.168.2.0/24
+#  allocated 192.168.2.0/29, 192.168.2.8/29, 192.168.2.64/26
+#  free 192.168.2.16/28, 192.168.2.32/27, 192.168.2.128/25
+# then we're OK, but if any one of the allocated or free blocks is missing,
+# something b0rked.
+
+# (select cidr from allocations where cidr <<= '$master') union
+#  (select cidr from freeblocks where cidr <<= '$master')
+#  order by cidr
+
+foreach $master (@masterblocks) {
+  print "  Master $master:\n";
+  $prev = $master;
+  $sth = $dbh->prepare("(select network(cidr) as net, broadcast(cidr) as bcast ".
+	"from allocations where cidr <<= '$master' and type not like '_c') ".
+	"union (select network(cidr) as net, broadcast(cidr) as bcast ".
+	"from freeblocks where cidr <<= '$master' and not (routed='c')) order by net");
+  $sth->execute;
+
+  while (@data = $sth->fetchrow_array) {
+    $cur = new NetAddr::IP $data[0];
+
+    if ($master->numeric == $prev->numeric) {
+      # check if cur starts with master
+      if ($cur->numeric > $prev->numeric) {
+        print "    Gap from start of master $master to first block $cur\n";
+      } elsif ($cur->numeric < $prev->numeric) {
+        print "    BIG problem!  Current block $cur begins before master $master!\n";
+      }
+    } else {
+      if ($cur->numeric < ($prev->numeric + 1)) {
+        print "    Block ".$prev->network." overlaps block $cur\n";
+      } elsif ($cur->numeric > ($prev->numeric + 1)) {
+        print "    Gap between end of block ".$prev->network." and block $cur\n";
+      }
+    }
+
+    $prev = $cur;
+    $prev--;
+
+  } # while (@data...)
+
+  $cur--;
+  $master--;
+  if ($cur->numeric ne $master->numeric) {
+    print "    Gap from $cur to end of master at $master\n";
+  }
+  $master++;
+  print "  done $master.\n";
+}
+
+print "Done checking block alignment.\n\n";
+
+print "Checking containment on container blocks...\n";
+# First, we need a list of containers.
+# Then, we check all of the contained blocks to see if they're within
+# the proper container.
+$sth = $dbh->prepare("select cidr from allocations where type like '_c' order by cidr");
+$sth->execute;
+$i=0;
+while (@data = $sth->fetchrow_array) {
+  $containers[$i++] = new NetAddr::IP $data[0];
+}
+print "  Checking general containment:";
+$sth = $dbh->prepare("select cidr from allocations where type like '_r' order by cidr");
+$sth->execute;
+$flag = '';
+CONTAINED: while (@data = $sth->fetchrow_array) {
+  $cidr = new NetAddr::IP $data[0];
+  foreach $container (@containers) {
+    next CONTAINED if $container->contains($cidr);
+  }
+  print "\n    $cidr not contained";
+  $flag = "\n ";
+}
+print "$flag done.\n";
+print "  Checking alignment:\n";
+foreach $container (@containers) {
+  print "    Container $container:\n";
+  $prev = $container;
+  $sth = $dbh->prepare("(select network(cidr) as net, broadcast(cidr) as bcast ".
+        "from allocations where cidr <<= '$container' and type like '_r') ".
+        "union (select network(cidr) as net, broadcast(cidr) as bcast ".
+        "from freeblocks where cidr <<= '$container' and not (routed='y' or routed='n')) ".
+	"order by net");
+    $sth->execute;
+
+  while (@data = $sth->fetchrow_array) {
+    $cur = new NetAddr::IP $data[0];
+
+    if ($container->numeric == $prev->numeric) {
+      # check if cur starts with master
+      if ($cur->numeric > $prev->numeric) {
+        print "      Gap from start of container $container to first block $cur\n";
+      } elsif ($cur->numeric < $prev->numeric) {
+        print "      BIG problem!  Current block $cur begins before container $container!\n";
+      }
+    } else {
+      if ($cur->numeric < ($prev->numeric + 1)) {
+        print "      Block ".$prev->network." overlaps block $cur\n";
+      } elsif ($cur->numeric > ($prev->numeric + 1)) {
+        print "      Gap between end of block ".$prev->network." and block $cur\n";
+      }
+    }
+
+    $prev = $cur;
+    $prev--;
+
+  } # while (@data...)
+
+  $cur--;
+  $container--;
+  if ($cur->numeric ne $container->numeric) {
+    print "      Gap from $cur to end of container at $container\n";
+  }
+  $container++;
+  print "    done $container.\n";
+
+}
+print "  done container alignment.\n";
+print "Done checking container containment.\n\n";
+
+print "Checking for correctness on 'defined' CustIDs:\n";
+# New check:  Make sure "defined" CustIDs are correct.
+$sth = $dbh->prepare("select cidr,type,custid from allocations where not (type='cn' or type like '_r') order by cidr");
+$sth->execute;
+while (@data = $sth->fetchrow_array) {
+  print "$data[0] ($disp_alloctypes{$data[1]}) has incorrect CustID $data[2]\n"
+	if $data[2] ne $def_custids{$data[1]};
+}
+print "Done predefined CustID correctness check.\n\n";
+
+print "Checking for customer blocks with 'bad' CustIDs:\n";
+# Make sure cn-type ("customer netblock") blocks have "real" CustIDs.
+$sth = $dbh->prepare("select cidr,type,custid from allocations where type='cn' and (custid='$IPDB::defcustid' or custid='STAFF') order by cidr");
+$sth->execute;
+while (@data = $sth->fetchrow_array) {
+  print "cn block $data[0] has incorrect CustID $data[2]\n";
+}
+print "Done checking customer blocks\n";
Index: /branches/htmlform/cgi-bin/extras/db2rwhois.pl
===================================================================
--- /branches/htmlform/cgi-bin/extras/db2rwhois.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/extras/db2rwhois.pl	(revision 446)
@@ -0,0 +1,349 @@
+#!/usr/bin/perl
+# -T
+# ipdb/cgi-bin/extras/db2rwhois.pl
+# Pull data from ipdb and mangle it into RWHOIS
+# Initial version 03/26/2004 kdeugau against IPDB v1
+###
+# Revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+use strict;
+use warnings;
+use DBI;
+use NetAddr::IP;
+use File::Path 'rmtree';
+use POSIX qw(strftime);
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+#$ENV{"PATH"} = "/bin;/usr/bin";
+
+my @autharea;
+my $authrw;
+# Use the template file to allow us to keep persistent nodes aside from netblock data
+open AUTHTEMPLATE, "<$IPDB::rwhoisDataPath/rwhoisd.auth_template";
+my $template_persist;
+while (<AUTHTEMPLATE>) {
+  next if /^##/;
+  $template_persist = 1 if /^[a-z]/i;
+  $autharea[0] .= $_;
+}
+
+my ($dbh,$msg) = connectDB_My;
+
+# For WHOIS purposes this may not be very useful.  YMMV, we'll see.
+#initIPDBGlobals($dbh);
+
+my @masterblocks;
+my %netnameprefix;
+
+# Get the list of live directories for potential deletion
+opendir RWHOISROOT, $IPDB::rwhoisDataPath;
+my %rwhoisdirs;
+foreach (readdir RWHOISROOT) {
+  $rwhoisdirs{$_} = 1 if /^net-/;
+}
+closedir RWHOISROOT;
+
+# prefetch alloctype data
+my $sth = $dbh->prepare("select type,def_custid,arin_netname from alloctypes");
+$sth->execute;
+while (my @data = $sth->fetchrow_array) {
+  $netnameprefix{$data[0]} = $data[2];
+}
+
+# Get the list of masters to export
+my $msth = $dbh->prepare("select cidr,ctime,mtime from masterblocks where rwhois='y'");
+$msth->execute;
+
+# Prepare to select subblocks for each master
+# Make sure to remove the private netblocks from this,
+# no use or point in broadcasting our use of them.
+# Also remove the details of our "reserved CORE/WAN" blocks;  they're not critical.
+my $ssth = $dbh->prepare("select cidr,custid,type,city,description,createstamp,modifystamp,swip ".
+	"from allocations where ".
+	"not (cidr <<= '192.168.0.0/16') and ".
+	"not (cidr <<= '172.16.0.0/12') and ".
+	"not (cidr <<= '10.0.0.0/8') and ".
+	"not (type = 'wr') and ".
+	"masklen(cidr) <=30 and ".
+	"cidr <<= ?");
+
+# Customer data, for those rare blocks we really need to delegate.
+my $custsth = $dbh->prepare("select name,street,city,province,country,pocode,phone,tech_handle,special ".
+	"from customers where custid=?");
+
+# Fill in data about our master blocks as allocated from ARIN
+# We open separate files for each of these as appropriate.
+# Changes in master blocks are treated as complete new masters - since we're exporting
+# all data every time, this isn't so terrible as it might seem.
+my $i=0;
+while (my @data = $msth->fetchrow_array()) {
+
+  $masterblocks[$i] = new NetAddr::IP $data[0];
+  my ($ctime,undef) = split /\s/, $data[1];
+  my ($mtime,undef) = split /\s/, $data[2];
+
+  print "$masterblocks[$i] $ctime $mtime\n";
+
+  my $date = strftime("%Y-%m-%d", localtime);
+
+  my $rwnet = "net-".$masterblocks[$i]->addr."-".$masterblocks[$i]->masklen;
+
+  # unflag the directory for deletion.  Whee!  Roundabout!
+  delete $rwhoisdirs{$rwnet};
+
+# Hokay.  Gonna do checks *here* to see if we need to create new master trees
+  my $netdatadir = "$IPDB::rwhoisDataPath/$rwnet";
+  if (! -e $netdatadir) {
+    print " New master $masterblocks[$i]!\n";
+    print "  Creating directories...\n";
+    mkdir $netdatadir;
+    mkdir "$netdatadir/attribute_defs";
+    mkdir "$netdatadir/data";
+    mkdir "$netdatadir/data/network";
+    mkdir "$netdatadir/data/org";
+    mkdir "$netdatadir/data/referral";
+
+    my $serial = strftime("%Y%m%d%H%M%S000", localtime);
+
+##fixme: SOA should be different every time data changes, therefore need to rewrite this ~~ every export :(
+    print "  Creating SOA...\n";
+    open SOAFILE, ">$netdatadir/soa";
+    print SOAFILE qq(Serial-Number: $serial
+Refresh-Interval: 3600
+Increment-Interval: 1800
+Retry-Interval: 1800
+Time-To-Live: 86400
+Primary-Server: rwhois.$IPDB::domain:4321
+Hostmaster: $IPDB::hostmaster
+);
+    close SOAFILE;
+
+    print "  Creating Schema...\n";
+    open SCHEMAFILE, ">$netdatadir/schema";
+    print SCHEMAFILE qq(name: network
+attributedef: $rwnet/attribute_defs/network.tmpl
+dbdir: $rwnet/data/network
+Schema-Version: $serial
+---
+name: organization
+attributedef: $rwnet/attribute_defs/org.tmpl
+dbdir: $rwnet/data/org
+description: Organization object
+Schema-Version: $serial
+---
+name: referral
+attributedef:$rwnet/attribute_defs/referral.tmpl
+dbdir:$rwnet/data/referral
+Schema-Version: $serial
+);
+    close SCHEMAFILE;
+
+    print "  Copying template files...\n";
+##fixme: find a way to do this without a shell (or functional equivalent)
+    qx { /bin/cp $IPDB::rwhoisDataPath/skel/attribute_defs/* $netdatadir/attribute_defs/ };
+
+##fixme: not sure if this is even necessary, since it's not referenced anywhere I can recall...
+    print "  Creating org data...\n";
+    open ORGDATAFILE, ">$netdatadir/data/org/ourorg.txt";
+    print ORGDATAFILE qq(ID: NETBLK-ISP.$masterblocks[$i]
+Auth-Area: $masterblocks[$i]
+Org-Name: $IPDB::org_name
+Street-Address: $IPDB::org_street
+City: $IPDB::org_city
+State: $IPDB::org_prov_state
+Postal-Code: $IPDB::org_pocode
+Country-Code: $IPDB::org_country
+Phone: $IPDB::org_phone
+Created: 20040308
+Updated: 20040308
+);
+    close ORGDATAFILE;
+
+    # Generate auth_area record, and add it to the array.
+    $authrw = 1;	# Flag for rewrite and daemon reload/restart
+
+  } # new master
+
+  # do this for all masters, so that we can use this array to export the data
+  # to rwhoisd.auth_area later if we need to
+  push @autharea, qq(type:master
+name:$masterblocks[$i]
+data-dir: $rwnet/data
+schema-file: $rwnet/schema
+soa-file: $rwnet/soa
+);
+
+  # Recreate the net-nnn.nnn.nnn.nnn-nn.txt data file
+  my $masterfilename = "$rwnet/data/network/".$masterblocks[$i]->addr."-".$masterblocks[$i]->masklen.".txt";
+
+  open MASTERFILE,">$IPDB::rwhoisDataPath/$masterfilename";
+
+  print MASTERFILE "ID: NETBLK-ISP.$masterblocks[$i]\n".
+  	"Auth-Area: $masterblocks[$i]\n".
+	"Network-Name: ISP-".$masterblocks[$i]->network."\n".
+	"IP-Network: $masterblocks[$i]\n".
+	"IP-Network-Block: ".$masterblocks[$i]->range."\n".
+	"Org-Name: $IPDB::org_name\n".
+	"Street-Address: $IPDB::org_street\n".
+	"City: $IPDB::org_city\n".
+	"StateProv: $IPDB::org_prov_state\n".
+	"Postal-Code: $IPDB::org_pocode\n".
+	"Country-Code: $IPDB::org_country\n".
+	"Tech-Contact: $IPDB::org_techhandle\n".
+	"Created: $ctime\n".
+	"Updated: $mtime\n".
+	"Updated-By: $IPDB::org_email\n";
+
+  # And now the subblocks
+  $ssth->execute("$masterblocks[$i]");
+  while (my ($cidr, $custid, $type, $city, $desc, $ctime, $mtime, $swip) = $ssth->fetchrow_array) {
+
+# We get master block info from @masterblocks.
+ # ID: NETBLK-ISP.10.0.0.0/8
+ # Auth-Area: 10.0.0.0/8
+ # Network-Name: ISP-10.0.2.144
+ # IP-Network: 10.0.2.144.144/29
+ # IP-Network-Block: 10.0.2.144 - 10.0.2.151
+ # Organization: WidgetCorp
+ # Tech-Contact: bob@widgetcorp.com
+ # Admin-Contact: ISP-ARIN-HANDLE
+ # Created: 20040314
+ # Updated: 20040314
+ # Updated-By: noc@example.com
+
+    # Get the "full" network number
+    my $net = new NetAddr::IP $cidr;
+
+# Assumptions:  All data in ipdb is public
+# If not, we need another field to indicate "public/private".
+
+# cidr custid type city description notes maskbits
+
+# Fill in a generic entry for nameless allocations
+if ($desc =~ /^\s*$/) { $desc = $IPDB::org_name; }
+
+    # Fix up datestamps.  We don't *really* need sub-microsecond resolution on our exports...
+    ($ctime) = ($ctime =~ /^(\d+-\d+-\d+)\s+/);
+    ($mtime) = ($mtime =~ /^(\d+-\d+-\d+)\s+/);
+
+# Notes:
+# Network-name should contain some component of "description"
+# Cust address/contact data should be included;  NB, no phone for ARIN!
+#  network:ID: NET-WIDGET
+#  network:Network-Name: WIDGET			[IPDB description, sort of]
+#  network:IP-Network: 10.1.1.0/24
+#  network:Org-Name: Widget Corp		[Cust name;  from billing?]
+#  network:Street-Address: 211 Oak Drive	[May need more than one line, OR...]
+#  network:City: Pineville			[...this line...]
+#  network:StateProv: WI			[...and this line...]
+#  network:Postal-Code: 48888			[...and this line]
+#  network:Country-Code: US
+#  network:Tech-Contact: BZ142-MYRWHOIS		[ARIN handle?]
+#  network:Updated: 19991221			[timestamp from db]
+#  network:Updated-By: jo@myrwhois.net		[noc@example, since that's our POC for IP netspace issues]
+#  network:Class-Name:network			[Provided by rWHOIS protocol]
+
+    my $netname = $netnameprefix{$type};
+
+    if ($swip eq 'n') {
+      print MASTERFILE "---\nID: NETBLK-ISP.$masterblocks[$i]\n".
+	"Auth-Area: $masterblocks[$i]\n".
+	"Network-Name: $netname-".$net->network."\n".
+	"IP-Network: $net\n".
+	"IP-Network-Block: ".$net->range."\n".
+	"Org-Name: $IPDB::org_name\n".
+	"Street-Address: $IPDB::org_street\n".
+	"City: $IPDB::org_city\n".
+	"StateProv: $IPDB::org_prov_state\n".
+	"Postal-Code: $IPDB::org_pocode\n".
+	"Country-Code: $IPDB::org_country\n".
+	"Tech-Contact: $IPDB::org_techhandle\n".
+	"Created: $ctime\n".
+	"Updated: $mtime\n".
+	"Updated-By: $IPDB::org_email\n";
+    } else {
+      $custsth->execute($custid);
+      my ($name, $street, $city, $prov, $country, $pocode, $phone, $tech, $special) = $custsth->fetchrow_array;
+      $custsth->finish;
+      if ($special && $special =~ /NetName/ && $special =~ /$cidr/) {
+	($netname) = ($special =~ /NetName$cidr: ([A-Z0-9_-]+)/);
+      } else {
+	$netname .= "-".$net->network;
+      }
+      print MASTERFILE "---\nID: NETBLK-ISP.$masterblocks[$i]\n".
+  	"Auth-Area: $masterblocks[$i]\n".
+	"Network-Name: $netname\n".
+	"IP-Network: $net\n".
+	"IP-Network-Block: ".$net->range."\n".
+	"Org-Name: ".($name ? $name : $IPDB::org_name)."\n".
+	"Street-Address: ".($street ? $street : $IPDB::org_street)."\n".
+	"City: ".($city ? $city : $IPDB::org_city)."\n".
+	"StateProv: ".($prov ? $prov : $IPDB::org_prov_state)."\n".
+	"Postal-Code: ".($pocode ? $pocode : $IPDB::org_pocode)."\n".
+	"Country-Code: ".($country ? $country : $IPDB::org_country)."\n".
+	"Tech-Contact: ".($tech ? $tech : $IPDB::org_techhandle)."\n".
+	"Created: $ctime\n".
+	"Updated: $mtime\n".
+	"Updated-By: $IPDB::org_email\n";
+    } # swip
+
+  } # while $ssth->fetchrow_array()
+
+  close MASTERFILE;
+
+  $i++;
+} # while $msth->fetchrow_array()
+
+# Now we see if there's obsolete netdata directories to be deleted,
+# and therefore an auth-area file to regenerate
+foreach my $netdir (keys %rwhoisdirs) {
+  print "deleting obsolete directory $netdir...\n";
+  rmtree ( "$IPDB::rwhoisDataPath/$netdir", { verbose => 1, error => \my $errlist } );
+  for my $diag (@$errlist) {
+    my ($file, $message) = each %$diag;
+    if ($file eq '') {
+      print "general error: $message\n";
+    }
+  }
+  $authrw = 1;	# there's probably a more efficient place to put this.  Feh.
+}
+
+# Regenerate rwhoisd.auth_area if needed
+if ($authrw) {
+  print "Regenerating auth_area\n";
+  open RWHOISDAUTH, ">$IPDB::rwhoisDataPath/rwhoisd.auth_area";
+  print RWHOISDAUTH "# WARNING: This file is autogenerated!  Any static nodes should\n".
+		"# be entered in /etc/rwhoisd/rwhoisd.auth_template\n";
+  if ($template_persist) {
+    print RWHOISDAUTH shift @autharea;
+    print RWHOISDAUTH "---\n";
+  }
+  # feh.  we need to know when we're at the end of the loop, because then
+  # we DON'T want to write the separator...
+  for (;@autharea;) {	# my head hurts.
+    print RWHOISDAUTH shift @autharea;
+    print RWHOISDAUTH "---\n" if @autharea;
+  }
+  close RWHOISDAUTH;
+
+  # restart/reload rwhoisd
+  if (-e "$IPDB::rwhoisDataPath/rwhoisd.pid") {	# no pidfile, no restart.
+    print "Restarting rwhoisd\n";
+    open PIDFILE, "<$IPDB::rwhoisDataPath/rwhoisd.pid";
+    my ($rwpid) = (<PIDFILE> =~ /^(\d+)/);
+    close PIDFILE;
+    kill 'HUP', $rwpid;
+  }
+}
+
+# and finally
+$dbh->disconnect;
Index: /branches/htmlform/cgi-bin/extras/network.tmpl
===================================================================
--- /branches/htmlform/cgi-bin/extras/network.tmpl	(revision 446)
+++ /branches/htmlform/cgi-bin/extras/network.tmpl	(revision 446)
@@ -0,0 +1,306 @@
+attribute:       Network-Name
+attribute-alias: NN
+description:     Network name
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           EXACT
+type:            TEXT
+---
+attribute:       IP-Network
+attribute-alias: IPN
+description:     IP network in CIDR notation
+is-primary-key:  TRUE
+is-required:     TRUE
+is-repeatable:   TRUE
+is-multi-line:   FALSE
+is-hierarchical: TRUE
+index:           CIDR
+type:            TEXT
+---
+attribute:       IP-Network-Block
+attribute-alias: IPNB
+description:     IP address range
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Org-Name
+attribute-alias: ORG
+description:     Organization
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Street-Address
+attribute-alias: SA
+description:     Street address
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       City
+attribute-alias: CITY
+description:     City
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       StateProv
+attribute-alias: SP
+description:     State or province
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Postal-Code
+attribute-alias: ZIP
+description:     ZIP or postal code
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Country-Code
+attribute-alias: CC
+description:     Two-letter country code
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Admin-Contact
+attribute-alias: AC
+description:     Administrative contact
+is-primary-key:  FALSE
+is-required:     FALSE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            ID
+---
+attribute:       Tech-Contact
+attribute-alias: TC
+description:     Technical contact
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            ID
+---
+attribute:       Abuse-Contact
+attribute-alias: NA
+description:     Network Abuse contact
+is-primary-key:  FALSE
+is-required:     FALSE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            ID
+---
+attribute:       Created
+attribute-alias: CR
+description:     Creation date
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Updated-By
+attribute-alias: UB
+description:     Updated by
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+attribute:       Network-Name
+attribute-alias: NN
+description:     Network name
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           EXACT
+type:            TEXT
+---
+attribute:       IP-Network
+attribute-alias: IPN
+description:     IP network in CIDR notation
+is-primary-key:  TRUE
+is-required:     TRUE
+is-repeatable:   TRUE
+is-multi-line:   FALSE
+is-hierarchical: TRUE
+index:           CIDR
+type:            TEXT
+---
+attribute:       IP-Network-Block
+attribute-alias: IPNB
+description:     IP address range
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Org-Name
+attribute-alias: ORG
+description:     Organization
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Street-Address
+attribute-alias: SA
+description:     Street address
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       City
+attribute-alias: CITY
+description:     City
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       StateProv
+attribute-alias: SP
+description:     State or province
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Postal-Code
+attribute-alias: ZIP
+description:     ZIP or postal code
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Country-Code
+attribute-alias: CC
+description:     Two-letter country code
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Admin-Contact
+attribute-alias: AC
+description:     Administrative contact
+is-primary-key:  FALSE
+is-required:     FALSE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            ID
+---
+attribute:       Tech-Contact
+attribute-alias: TC
+description:     Technical contact
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            ID
+---
+attribute:       Abuse-Contact
+attribute-alias: NA
+description:     Network Abuse contact
+is-primary-key:  FALSE
+is-required:     FALSE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            ID
+---
+attribute:       Created
+attribute-alias: CR
+description:     Creation date
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
+---
+attribute:       Updated-By
+attribute-alias: UB
+description:     Updated by
+is-primary-key:  FALSE
+is-required:     TRUE
+is-repeatable:   FALSE
+is-multi-line:   FALSE
+is-hierarchical: FALSE
+index:           NONE
+type:            TEXT
Index: /branches/htmlform/cgi-bin/extras/rwhois-config
===================================================================
--- /branches/htmlform/cgi-bin/extras/rwhois-config	(revision 446)
+++ /branches/htmlform/cgi-bin/extras/rwhois-config	(revision 446)
@@ -0,0 +1,40 @@
+Notes on requirements for "proper" rWHOIS configuration
+
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+
+- Each "master" block has its own auth area
+- Only the network records are used;  referral records may be useful
+  someday but not yet.
+- "Org" data is stored in the network records directly.  Doing
+  otherwise more or less works, but it's tedious and headache-ful
+  to retrieve it.  This according to an ARIN staffer who gets
+  paid to check rWHOIS servers for "correctness".
+- Suitable templates and scripts for adding a fresh new master
+  (manually) are included in this directory.  Note that adding a
+  master automagically is unlikely to happen soon.  (As of 2006-04-06)
+- The "register" directive should be disabled;  we're not using it.
+Notes on requirements for "proper" rWHOIS configuration
+
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+
+- Each "master" block has its own auth area
+- Only the network records are used;  referral records may be useful
+  someday but not yet.
+- "Org" data is stored in the network records directly.  Doing
+  otherwise more or less works, but it's tedious and headache-ful
+  to retrieve it.  This according to an ARIN staffer who gets
+  paid to check rWHOIS servers for "correctness".
+- Suitable templates and scripts for adding a fresh new master
+  (manually) are included in this directory.  Note that adding a
+  master automagically is unlikely to happen soon.  (As of 2006-04-06)
+- The "register" directive should be disabled;  we're not using it.
Index: /branches/htmlform/cgi-bin/freespace.pl
===================================================================
--- /branches/htmlform/cgi-bin/freespace.pl	(revision 446)
+++ /branches/htmlform/cgi-bin/freespace.pl	(revision 446)
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/freespace.pl
+# Quick hack to calculate aggregate free IP space, and statistic-ify the blocks.
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+use DBI;
+use NetAddr::IP;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+($dbh,$errstr) = connectDB_My;
+
+print "Content-type: text/plain\n\n";
+
+$tnumfree = $bigrfree = $bigufree = 0;
+
+$sql = 'select * from freeblocks where ';
+# General counts first
+if ($ARGV[0]) {
+  $sql .= 'maskbits >= $ARGV[0] and ';
+}
+$sql .= "not (cidr <<= '192.168.0.0/16') ".
+		"and not (cidr <<= '172.16.0.0/12') ".
+		"and not (cidr <<= '10.0.0.0/8') ";
+$sql .= "order by maskbits desc";
+
+$sth = $dbh->prepare($sql);
+$sth->execute;
+while (@data = $sth->fetchrow_array) {
+  # cidr,maskbits,city,routed
+  $tnumfree++;
+  $numfree{"$data[1]"}++;
+}
+
+print "Free block counts:\n";
+foreach $size (sort {$a cmp $b} keys %numfree) {
+  print "/$size: $numfree{$size}\n";
+}
+print "\n";
+
+for ($i=30; $i>16; $i--) {
+  $j = $i-1;
+  $numfree{"$j"} += int $numfree{"$i"}/2;
+  $numfree{"$i"} -= (int $numfree{"$i"}/2)*2;
+}
+
+print "Aggregate free space:\n";
+foreach $size (sort {$a cmp $b} keys %numfree) {
+  print "/$size: $numfree{$size}\n";
+}
Index: /branches/htmlform/cgi-bin/ipdb.psql
===================================================================
--- /branches/htmlform/cgi-bin/ipdb.psql	(revision 446)
+++ /branches/htmlform/cgi-bin/ipdb.psql	(revision 446)
@@ -0,0 +1,233 @@
+DROP DATABASE ipdb;
+
+CREATE USER ipdb WITH PASSWORD 'ipdbpwd';
+
+CREATE DATABASE ipdb;
+
+-- Need to do this or our triggers don't work.  Why do we need to do this?
+CREATE FUNCTION "plpgsql_call_handler" () RETURNS language_handler AS '$libdir/plpgsql' LANGUAGE C;
+CREATE TRUSTED LANGUAGE "plpgsql" HANDLER "plpgsql_call_handler";
+
+UPDATE pg_database SET datdba=(SELECT usesysid FROM pg_shadow WHERE usename='ipdb')
+	WHERE datname='ipdb';
+
+\connect ipdb ipdb
+
+CREATE TABLE "customers" (
+	"custid" character varying(16) DEFAULT '' NOT NULL,
+	"name" character varying(64),
+	"street" character varying(25),
+	"street2" character varying(25),
+	"city" character varying(30),
+	"province" character(2),
+	"country" character(2),
+	"pocode" character varying(7),
+	"phone" character varying(15),
+	"tech_handle" character varying(50),
+	"abuse_handle" character varying(50),
+	"admin_handle" character varying(50),
+	"def_rdns" character varying(40),
+	"special" text,
+	Constraint "customers_pkey" Primary Key ("custid")
+);
+
+REVOKE ALL on "customers" from PUBLIC;
+GRANT ALL on "customers" to "ipdb";
+
+CREATE TABLE "masterblocks" (
+	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"ctime" timestamp DEFAULT now(),
+	"mtime" timestamp DEFAULT now(),
+	"rwhois" character(1) DEFAULT 'n' NOT NULL
+);
+
+REVOKE ALL on "masterblocks" from PUBLIC;
+GRANT ALL on "masterblocks" to "ipdb";
+
+CREATE TABLE "routed" (
+	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"maskbits" integer DEFAULT 128,
+	"city" character varying(30) DEFAULT '',
+	"ctime" timestamp DEFAULT now()
+);
+
+REVOKE ALL on "routed" from PUBLIC;
+GRANT ALL on "routed" to "ipdb";
+GRANT SELECT on "routed" to "ipdb";
+
+CREATE TABLE "temp" (
+	"ofs" integer
+);
+
+REVOKE ALL on "temp" from PUBLIC;
+GRANT ALL on "temp" to "ipdb";
+
+CREATE TABLE "freeblocks" (
+	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"maskbits" integer DEFAULT 128,
+	"city" character varying(30) DEFAULT '',
+	"routed" character(1) DEFAULT 'n'
+);
+
+REVOKE ALL on "freeblocks" from PUBLIC;
+GRANT ALL on "freeblocks" to "ipdb";
+
+CREATE TABLE "poolips" (
+	"pool" cidr DEFAULT '255.255.255.255/32' NOT NULL,
+	"ip" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"oldcustid" character varying(16) DEFAULT '' NOT NULL,
+	"city" character varying(30) DEFAULT '' NOT NULL,
+	"type" character(2) DEFAULT '' NOT NULL,
+	"available" character(1) DEFAULT 'y' NOT NULL,
+	"notes" text DEFAULT '' NOT NULL,
+	"description" character varying(64) DEFAULT '' NOT NULL,
+	"circuitid" character varying(128) DEFAULT '' NOT NULL,
+	"privdata" text DEFAULT '' NOT NULL,
+	"custid" character varying(16) DEFAULT '',
+	"createstamp" timestamp DEFAULT now(),
+	"modifystamp" timestamp DEFAULT now(),
+	CHECK (((available = 'y'::bpchar) OR (available = 'n'::bpchar)))
+);
+
+REVOKE ALL on "poolips" from PUBLIC;
+GRANT ALL on "poolips" to "ipdb";
+
+CREATE TABLE "allocations" (
+	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"oldcustid" character varying(16) DEFAULT '',
+	"type" character(2) DEFAULT '',
+	"city" character varying(30) DEFAULT '',
+	"description" character varying(64) DEFAULT '',
+	"notes" text DEFAULT '',
+	"maskbits" integer DEFAULT 128,
+	"circuitid" character varying(128) DEFAULT '',
+	"createstamp" timestamp DEFAULT now(),
+	"modifystamp" timestamp DEFAULT now(),
+	"privdata" text DEFAULT '' NOT NULL,
+	"custid" character varying(16) DEFAULT '',
+	swip character(1) DEFAULT 'n'
+);
+
+REVOKE ALL on "allocations" from PUBLIC;
+GRANT ALL on "allocations" to "ipdb";
+
+CREATE VIEW "searchme" as SELECT allocations.cidr, allocations.custid, allocations."type", allocations.city, allocations.description, allocations.notes, allocations.oldcustid, allocations.circuitid FROM allocations UNION SELECT poolips.ip, poolips.custid, poolips.type, poolips.city, poolips.description, poolips.notes, poolips.oldcustid, poolips.circuitid FROM poolips;
+
+REVOKE ALL on "searchme" from PUBLIC;
+GRANT ALL on "searchme" to "ipdb";
+
+CREATE TABLE "alloctypes" (
+	"type" character(2) DEFAULT '' NOT NULL PRIMARY KEY,
+	"listname" character varying(40) DEFAULT '',
+	"dispname" character varying(40) DEFAULT '',
+	"listorder" integer DEFAULT 0,
+	"def_custid" character varying(16) DEFAULT '',
+	"arin_netname" character varying(20) DEFAULT 'ISP'
+);
+
+--
+-- Name: alloctypes; Type: TABLE DATA; Schema: public; Owner: ipdb
+--
+
+COPY alloctypes ("type", listname, dispname, listorder, def_custid, arin_netname) FROM stdin;
+cn	Customer netblock	Customer netblock	0		ISPCUST
+si	Static IP - Server pool	Server pool IP	20		ISP
+ci	Static IP - Cable	Static cable IP	21		ISP
+di	Static IP - DSL	Static DSL IP	22		ISP
+mi	Static IP - Dialup	Static dialup IP	23		ISP
+wi	Static IP - Wireless	Static wireless IP	24		ISP
+sd	Static Pool - Servers	Server pool	40	5554242	ISP
+cd	Static Pool - Cable	Cable pool	41	CBL-BUS	ISP-STATIC-CABLE
+dp	Static Pool - DSL	DSL pool	42	DSL-BUS	ISP-STATIC-DSL
+mp	Static Pool - Dialup	Static dialup pool	43	DIAL-BUS	ISP-STATIC-DIAL
+wp	Static Pool - Wireless	Static wireless pool	44	WL-BUS	ISP-STATIC-WIFI
+en	End-use netblock	End-use netblock	100	5554242	ISP
+me	Dialup netblock	Dialup netblock	101	DIAL-RES	ISP-DIAL
+de	Dynamic DSL block	Dynamic DSL block	102	DSL-RES	ISP-DSL
+ce	Dynamic cable block	Dynamic cable block	103	CBL-RES	ISP-CABLE
+we	Dynamic WiFi block	Dynamic WiFi block	104	WL-RES	ISP-WIFI
+ve	Dynamic VoIP block	Dynamic VoIP block	105	DYN-VOIP	ISP
+li	Static IP - LAN/POP	Static LAN/POP IP	190	NOC-VPN	ISP
+ai	Static IP - Management	Static management IP	192	NOC-VPN	ISP
+bi	Static IP - Wifi CPE	Wifi CPE IP	193		ISP
+ld	Static Pool - LAN/POP	LAN pool	195	NOC-VPN	ISP
+ad	Static Pool - Management	Management pool	196	NOC-VPN	ISP
+bd	Static pool - Wifi CPE	Wifi CPE pool	197		ISP
+in	Internal netblock	Internal netblock	199	5554242	ISP
+wc	Reserve for CORE/WAN blocks	CORE/WAN blocks	200	5554242	ISP
+pc	Reserve for dynamic-route DSL netblocks	Dynamic-route netblocks	201	5554242	ISP-STATIC-DSL
+ac	Reserve for ATM	ATM blocks	202	5554242	ISP
+fc	Reserve for fibre	Fibre blocks	203	5554242	ISP
+wr	CORE/WAN block	CORE/WAN block	220	5554242	ISP
+pr	Dynamic-route DSL netblock (cust)	Dynamic-route DSL (cust)	221		ISPCUST
+ar	ATM block	ATM block	222		ISP
+fr	Fibre	Fibre	223		ISP
+rm	Routing	Routed netblock	500	5554242	ISP
+mm	Master block	Master block	999	5554242	ISP
+\.
+
+REVOKE ALL on "alloctypes" from PUBLIC;
+GRANT ALL on "alloctypes" to "ipdb";
+
+CREATE TABLE "cities" (
+	"id" serial NOT NULL PRIMARY KEY,
+	"city" character varying(30) DEFAULT '' NOT NULL,
+	"routing" character(1) DEFAULT 'n' NOT NULL
+);
+
+REVOKE ALL on "cities" from PUBLIC;
+GRANT ALL on "cities" to "ipdb";
+
+--
+-- Trigger and matching function to update modifystamp on allocations, poolips
+--
+CREATE FUNCTION up_modtime () RETURNS OPAQUE AS '
+    BEGIN
+	NEW.modifystamp := ''now'';
+	RETURN NEW;
+    END;
+' LANGUAGE 'plpgsql';
+
+CREATE TRIGGER up_modtime BEFORE UPDATE ON allocations
+    FOR EACH ROW EXECUTE PROCEDURE up_modtime();
+
+CREATE TRIGGER up_modtime BEFORE UPDATE ON poolips
+    FOR EACH ROW EXECUTE PROCEDURE up_modtime();
+
+--
+-- User data table - required for proper ACLs
+--
+
+CREATE TABLE "users" (
+	"username" varchar(16) NOT NULL PRIMARY KEY,
+	"password" varchar(16) DEFAULT '',
+	"acl" varchar(16) DEFAULT 'b'
+);
+
+-- Default password is admin
+INSERT INTO users VALUES ('admin','luef5C4XumqIs','bacdsA');
+
+CREATE TABLE "dns" (
+	"ip" inet NOT NULL PRIMARY KEY,
+	"hostname" character varying(128),
+	"auto" character(1) DEFAULT 'y'
+);
+
+-- Network nodes - allows finding customers affected by a broken <x> quickly
+CREATE TABLE noderef (
+    block inet NOT NULL PRIMARY KEY,
+    node_id integer
+);
+
+CREATE TABLE nodes (
+    node_id serial NOT NULL PRIMARY KEY,
+    node_type character varying(2),
+    node_name character varying(40),
+    node_ip inet
+);
+
+-- Email notifications on <action>
+CREATE TABLE notify (
+    action varchar(5) NOT NULL PRIMARY KEY,
+    reciplist varchar(500)
+);
Index: /branches/htmlform/cgi-bin/main.cgi
===================================================================
--- /branches/htmlform/cgi-bin/main.cgi	(revision 446)
+++ /branches/htmlform/cgi-bin/main.cgi	(revision 446)
@@ -0,0 +1,1419 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/main.cgi
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use CommonWeb qw(:ALL);
+use CustIDCK;
+use POSIX qw(ceil);
+use NetAddr::IP;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+openlog "IPDB","pid","$IPDB::syslog_facility";
+
+# Collect the username from HTTP auth.  If undefined, we're in
+# a test environment, or called without a username.
+my $authuser;
+if (!defined($ENV{'REMOTE_USER'})) {
+  $authuser = '__temptest';
+} else {
+  $authuser = $ENV{'REMOTE_USER'};
+}
+
+syslog "debug", "$authuser active, $ENV{'REMOTE_ADDR'}";
+
+# Why not a global DB handle?  (And a global statement handle, as well...)
+# Use the connectDB function, otherwise we end up confusing ourselves
+my $ip_dbh;
+my $sth;
+my $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  exitError("Database error: $errstr\n");
+}
+initIPDBGlobals($ip_dbh);
+
+# Headerize!  Make sure we replace the $$EXTRA0$$ bit as needed.
+printHeader('', ($IPDBacl{$authuser} =~ /a/ ?
+	'<td align=right><a href="/ip/cgi-bin/main.cgi?action=assign">Add new assignment</a>' : ''
+	));
+
+
+# Global variables
+my %webvar = parse_post();
+cleanInput(\%webvar);
+
+
+#main()
+
+if(!defined($webvar{action})) {
+  $webvar{action} = "<NULL>";	#shuts up the warnings.
+}
+
+if($webvar{action} eq 'index') {
+  showSummary();
+} elsif ($webvar{action} eq 'addmaster') {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+  } else {
+    open HTML, "<../addmaster.html";
+    print while <HTML>;
+  }
+} elsif ($webvar{action} eq 'newmaster') {
+
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+  } else {
+
+    my $cidr = new NetAddr::IP $webvar{cidr};
+
+    print "<div type=heading align=center>Adding $cidr as master block....</div>\n";
+
+    my ($code,$msg) = addMaster($ip_dbh, $webvar{cidr});
+
+    if ($code eq 'FAIL') {
+      carp "Transaction aborted because $msg";
+      syslog "err", "Could not add master block '$webvar{cidr}' to database: '$msg'";
+      printError("Could not add master block $webvar{cidr} to database: $msg");
+    } else {
+      print "<div type=heading align=center>Success!</div>\n";
+      syslog "info", "$authuser added master block $webvar{cidr}";
+    }
+
+  } # ACL check
+
+} # end add new master
+
+elsif($webvar{action} eq 'showmaster') {
+  showMaster();
+}
+elsif($webvar{action} eq 'showrouted') {
+  showRBlock();
+}
+elsif($webvar{action} eq 'listpool') {
+  listPool();
+}
+
+# Not modified or added;  just shuffled
+elsif($webvar{action} eq 'assign') {
+  assignBlock();
+}
+elsif($webvar{action} eq 'confirm') {
+  confirmAssign();
+}
+elsif($webvar{action} eq 'insert') {
+  insertAssign();
+}
+elsif($webvar{action} eq 'edit') {
+  edit();
+}
+elsif($webvar{action} eq 'update') {
+  update();
+}
+elsif($webvar{action} eq 'delete') {
+  remove();
+}
+elsif($webvar{action} eq 'finaldelete') {
+  finalDelete();
+}
+elsif ($webvar{action} eq 'nodesearch') {
+  open HTML, "<../nodesearch.html";
+  my $html = join('',<HTML>);
+  close HTML;
+
+  $sth = $ip_dbh->prepare("SELECT node_id, node_name FROM nodes ORDER BY node_type,node_id");
+  $sth->execute() or print "DEBUG: failed retrieval from nodes: ".$sth->errstr,"<br>\n";
+  my $nodes = '';
+  while (my ($nid,$nname) = $sth->fetchrow_array()) {
+    $nodes .= "<option value='$nid'>$nname</option>\n";
+  }
+  $html =~ s/\$\$NODELIST\$\$/$nodes/;
+
+  print $html;
+}
+
+# Default is an error.  It shouldn't be possible to easily get here.
+# The only way I can think of offhand is to just call main.cgi bare-
+# which is not in any way guaranteed to provide anything useful.
+else {
+  my $rnd = rand 500;
+  my $boing = sprintf("%.2f", rand 500);
+  my @excuses = ("Aether cloudy.  Ask again later.","The gods are unhappy with your sacrifice.",
+	"Because one of it's legs are both the same", "*wibble*",
+	"Hey! Stop pushing my buttons!", "I ain't done nuttin'", "9",
+	"8", "9", "10", "11", "12", "13", "14", "15", "16", "17");
+  printAndExit("Error $boing:  ".$excuses[$rnd/30.0]);
+}
+## Finally! Done with that NASTY "case" emulation!
+
+
+
+# Clean up IPDB globals, DB handle, etc.
+finish($ip_dbh);
+
+print qq(<div align=right style="position: absolute; right: 30px;">).
+	qq(<a href="/ip/cgi-bin/admin.cgi">Admin tools</a></div><br>\n)
+	if $IPDBacl{$authuser} =~ /A/;
+
+# We print the footer here, so we don't have to do it elsewhere.
+printFooter;
+# Just in case something waaaayyy down isn't in place
+# properly... we exit explicitly.
+exit;
+
+
+
+# args are: a reference to an array with the row to be printed and the 
+# class(stylesheet) to use for formatting.
+# if ommitting the class - call the sub as &printRow(\@array)
+sub printRow {
+  my ($rowRef,$class) = @_;
+
+  if (!$class) {
+    print "<tr>\n";
+  } else {
+    print "<tr class=\"$class\">\n";
+  }
+
+ELEMENT:  foreach my $element (@$rowRef) {
+    if (!defined($element)) {
+      print "<td></td>\n";
+      next ELEMENT;
+    }
+    $element =~ s|\n|</br>|g;
+    print "<td>$element</td>\n";
+  }
+  print "</tr>";
+} # printRow
+
+
+# Prints table headings.  Accepts any number of arguments;
+# each argument is a table heading.
+sub startTable {
+  print qq(<center><table width="98%" cellspacing="0" class="center"><tr>);
+
+  foreach(@_) {
+    print qq(<td class="heading">$_</td>);
+  }
+  print "</tr>\n";
+} # startTable
+
+
+# Initial display:  Show master blocks with total allocated subnets, total free subnets
+sub showSummary {
+
+  startTable('Master netblock', 'Routed netblocks', 'Allocated netblocks',
+	'Free netblocks', 'Largest free block');
+
+  my %allocated;
+  my %free;
+  my %routed;
+  my %bigfree;
+
+  # Count the allocations.
+  $sth = $ip_dbh->prepare("select count(*) from allocations where cidr <<= ?");
+  foreach my $master (@masterblocks) {
+    $sth->execute("$master");
+    $sth->bind_columns(\$allocated{"$master"});
+    $sth->fetch();
+  }
+
+  # Count routed blocks
+  $sth = $ip_dbh->prepare("select count(*) from routed where cidr <<= ?");
+  foreach my $master (@masterblocks) {
+    $sth->execute("$master");
+    $sth->bind_columns(\$routed{"$master"});
+    $sth->fetch();
+  }
+
+  # Count the free blocks.
+  $sth = $ip_dbh->prepare("select count(*) from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n')");
+  foreach my $master (@masterblocks) {
+    $sth->execute("$master");
+    $sth->bind_columns(\$free{"$master"});
+    $sth->fetch();
+  }
+
+  # Find the largest free block in each master
+  $sth = $ip_dbh->prepare("select maskbits from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n') order by maskbits limit 1");
+  foreach my $master (@masterblocks) {
+    $sth->execute("$master");
+    $sth->bind_columns(\$bigfree{"$master"});
+    $sth->fetch();
+  }
+
+  # Print the data.
+  my $count=0;
+  foreach my $master (@masterblocks) {
+    my @row = ("<a href=\"/ip/cgi-bin/main.cgi?action=showmaster&block=$master\">$master</a>",
+	$routed{"$master"}, $allocated{"$master"}, $free{"$master"}, 
+	( ($bigfree{"$master"} eq '') ? ("&lt;NONE&gt;") : ("/".$bigfree{"$master"}) )
+	);
+
+    printRow(\@row, 'color1' ) if($count%2==0);
+    printRow(\@row, 'color2' ) if($count%2!=0);
+    $count++;
+  }
+  print "</table>\n";
+  if ($IPDBacl{$authuser} =~ /a/) {
+    print qq(<a href="/ip/cgi-bin/main.cgi?action=addmaster">Add new master block</a><br><br>\n);
+  }
+  print "Note:  Free blocks noted here include both routed and unrouted blocks.\n";
+
+} # showSummary
+
+
+# Display detail on master
+# Alrighty then!  We're showing routed blocks within a single master this time.
+# We should be able to steal code from showSummary(), and if I'm really smart
+# I'll figger a way to munge the two together.  (Once I've done that, everything
+# else should follow.  YMMV.)
+sub showMaster {
+
+  print qq(<center><div class="heading">Summarizing routed blocks for ).
+	qq($webvar{block}:</div></center><br>\n);
+
+  my %allocated;
+  my %free;
+  my %routed;
+  my %bigfree;
+
+  my $master = new NetAddr::IP $webvar{block};
+  my @localmasters;
+
+  # Fetch only the blocks relevant to this master
+  $sth = $ip_dbh->prepare("select cidr,city from routed where cidr <<= '$master' order by cidr");
+  $sth->execute();
+
+  my $i=0;
+  while (my @data = $sth->fetchrow_array()) {
+    my $cidr = new NetAddr::IP $data[0];
+    $localmasters[$i++] = $cidr;
+    $free{"$cidr"} = 0;
+    $allocated{"$cidr"} = 0;
+    $bigfree{"$cidr"} = 128;
+    # Retain the routing destination
+    $routed{"$cidr"} = $data[1];
+  }
+
+  # Check if there were actually any blocks routed from this master
+  if ($i > 0) {
+    startTable('Routed block','Routed to','Allocated blocks',
+	'Free blocks','Largest free block');
+
+    # Count the allocations
+    $sth = $ip_dbh->prepare("select count(*) from allocations where cidr <<= ?");
+    foreach my $master (@localmasters) {
+      $sth->execute("$master");
+      $sth->bind_columns(\$allocated{"$master"});
+      $sth->fetch();
+    }
+
+    # Count the free blocks.
+    $sth = $ip_dbh->prepare("select count(*) from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n')");
+    foreach my $master (@localmasters) {
+      $sth->execute("$master");
+      $sth->bind_columns(\$free{"$master"});
+      $sth->fetch();
+    }
+
+    # Get the size of the largest free block
+    $sth = $ip_dbh->prepare("select maskbits from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n') order by maskbits limit 1");
+    foreach my $master (@localmasters) {
+      $sth->execute("$master");
+      $sth->bind_columns(\$bigfree{"$master"});
+      $sth->fetch();
+    }
+
+    # Print the data.
+    my $count=0;
+    foreach my $master (@localmasters) {
+      my @row = ("<a href=\"/ip/cgi-bin/main.cgi?action=showrouted&block=$master\">$master</a>",
+	$routed{"$master"}, $allocated{"$master"},
+	$free{"$master"},
+	( ($bigfree{"$master"} eq 128) ? ("&lt;NONE&gt;") : ("/".$bigfree{"$master"}) )
+      );
+      printRow(\@row, 'color1' ) if($count%2==0);
+      printRow(\@row, 'color2' ) if($count%2!=0);
+      $count++;
+    }
+  } else {
+    # If a master block has no routed blocks, then by definition it has no
+    # allocations, and can be deleted.
+    print qq(<hr width="60%"><center><div class="heading">No allocations in ).
+        qq($master.</div>\n).
+	($IPDBacl{$authuser} =~ /d/ ?
+	        qq(<form action="/ip/cgi-bin/main.cgi" method=POST>\n).
+	        qq(<input type=hidden name=action value="delete">\n).
+	        qq(<input type=hidden name=block value="$master">\n).
+	        qq(<input type=hidden name=alloctype value="mm">\n).
+	        qq(<input type=submit value=" Remove this master ">\n).
+	        qq(</form></center>\n) :
+		'');
+
+  } # end check for existence of routed blocks in master
+
+  print qq(</table>\n<hr width="60%">\n).
+	qq(<center><div class="heading">Unrouted blocks in $master:</div></center><br>\n);
+
+  startTable('Netblock','Range');
+
+  # Snag the free blocks.
+  my $count = 0;
+  $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr <<='$master' and ".
+	"routed='n' order by cidr");
+  $sth->execute();
+  while (my @data = $sth->fetchrow_array()) {
+    my $cidr = new NetAddr::IP $data[0];
+    my @row = ("$cidr", $cidr->range);
+    printRow(\@row, 'color1' ) if($count%2==0);
+    printRow(\@row, 'color2' ) if($count%2!=0);
+    $count++;
+  }
+
+  print "</table>\n";
+} # showMaster
+
+
+# Display details of a routed block
+# Alrighty then!  We're showing allocations within a routed block this time.
+# We should be able to steal code from showSummary() and showMaster(), and if
+# I'm really smart I'll figger a way to munge all three together.  (Once I've
+# done that, everything else should follow.  YMMV.
+# This time, we check the database before spewing, because we may
+# not have anything useful to spew.
+sub showRBlock {
+
+  my $master = new NetAddr::IP $webvar{block};
+
+  $sth = $ip_dbh->prepare("select city from routed where cidr='$master'");
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+
+  print qq(<center><div class="heading">Summarizing allocated blocks for ).
+	qq($master ($data[0]):</div></center><br>\n);
+
+  startTable('CIDR allocation','Customer Location','Type','CustID','SWIPed?','Description/Name');
+
+  # Snag the allocations for this block
+  $sth = $ip_dbh->prepare("select cidr,city,type,custid,swip,description".
+	" from allocations where cidr <<= '$master' order by cidr");
+  $sth->execute();
+
+  # hack hack hack
+  # set up to flag swip=y records if they don't actually have supporting data in the customers table
+  my $custsth = $ip_dbh->prepare("select count(*) from customers where custid=?");
+
+  my $count=0;
+  while (my @data = $sth->fetchrow_array()) {
+    # cidr,city,type,custid,swip,description, as per the SELECT
+    my $cidr = new NetAddr::IP $data[0];
+
+    # Clean up extra spaces that are borking things.
+#    $data[2] =~ s/\s+//g;
+
+    $custsth->execute($data[3]);
+    my ($ncust) = $custsth->fetchrow_array();
+
+    # Prefix subblocks with "Sub "
+    my @row = ( (($data[2] =~ /^.r$/) ? 'Sub ' : '').
+	qq(<a href="/ip/cgi-bin/main.cgi?action=edit&block=$data[0]">$data[0]</a>),
+	$data[1], $disp_alloctypes{$data[2]}, $data[3], 
+	($data[4] eq 'y' ? ($ncust == 0 ? 'Yes<small>*</small>' : 'Yes') : 'No'), $data[5]);
+    # If the allocation is a pool, allow listing of the IPs in the pool.
+    if ($data[2] =~ /^.[pd]$/) {
+      $row[0] .= ' &nbsp; <a href="/ip/cgi-bin/main.cgi?action=listpool'.
+	"&pool=$data[0]\">List IPs</a>";
+    }
+
+    printRow(\@row, 'color1') if ($count%2 == 0);
+    printRow(\@row, 'color2') if ($count%2 != 0);
+    $count++;
+  }
+
+  print "</table>\n";
+
+  # If the routed block has no allocations, by definition it only has
+  # one free block, and therefore may be deleted.
+  if ($count == 0) {
+    print qq(<hr width="60%"><center><div class="heading">No allocations in ).
+	qq($master.</div></center>\n).
+	($IPDBacl{$authuser} =~ /d/ ?
+		qq(<form action="/ip/cgi-bin/main.cgi" method=POST>\n).
+		qq(<input type=hidden name=action value="delete">\n).
+		qq(<input type=hidden name=block value="$master">\n).
+		qq(<input type=hidden name=alloctype value="rm">\n).
+		qq(<input type=submit value=" Remove this block ">\n).
+		qq(</form>\n) :
+		'');
+  }
+
+  print qq(<hr width="60%">\n<center><div class="heading">Free blocks within routed ).
+	qq(submaster $master</div></center>\n);
+
+  startTable('CIDR block','Range');
+
+  # Snag the free blocks.  We don't really *need* to be pedantic about avoiding
+  # unrouted free blocks, but it's better to let the database do the work if we can.
+  $count = 0;
+  $sth = $ip_dbh->prepare("select cidr,routed from freeblocks where cidr <<= '$master'".
+	" order by cidr");
+  $sth->execute();
+  while (my @data = $sth->fetchrow_array()) {
+    # cidr,routed
+    my $cidr = new NetAddr::IP $data[0];
+    # Include some HairyPerl(TM) to prefix subblocks with "Sub "
+    my @row = ((($data[1] ne 'y' && $data[1] ne 'n') ? 'Sub ' : '').
+	($IPDBacl{$authuser} =~ /a/ ? qq(<a href="/ip/cgi-bin/main.cgi?action=assign&block=$cidr&fbtype=$data[1]">$cidr</a>) : $cidr),
+	$cidr->range);
+    printRow(\@row, 'color1') if ($count%2 == 0);
+    printRow(\@row, 'color2') if ($count%2 != 0);
+    $count++;
+  }
+
+  print "</table>\n";
+} # showRBlock
+
+
+# List the IPs used in a pool
+sub listPool {
+
+  my $cidr = new NetAddr::IP $webvar{pool};
+
+  my ($pooltype,$poolcity);
+
+  # Snag pool info for heading
+  $sth = $ip_dbh->prepare("select type,city from allocations where cidr='$cidr'");
+  $sth->execute;
+  $sth->bind_columns(\$pooltype, \$poolcity);
+  $sth->fetch() || carp $sth->errstr;
+
+  print qq(<center><div class="heading">Listing pool IPs for $cidr<br>\n).
+	qq(($disp_alloctypes{$pooltype} in $poolcity)</div></center><br>\n);
+  # Only display net/gw/bcast if it's a "real" netblock and not a PPP(oE) lunacy
+  if ($pooltype =~ /^.d$/) {
+    print qq(<div class="indent"><b>Reserved IPs:</b><br>\n);
+    print qq(<div class="indent"><table><tr class=color1><td>Network IP:</td><td>).
+	$cidr->addr."</td></tr>\n";
+    $cidr++;
+    print "<tr class=color2><td>Gateway:</td><td>".$cidr->addr."</td></tr>\n";
+    $cidr--;  $cidr--;
+    print "<tr class=color1><td>Broadcast:</td><td>".$cidr->addr."</td></tr>\n".
+	"<tr><td>Netmask:</td><td>".$cidr->mask."</td></tr>\n".
+	"</table></div></div>\n";
+  }
+
+# probably have to add an "edit IP allocation" link here somewhere.
+
+  startTable('IP','Customer ID','Available?','Description','');
+  $sth = $ip_dbh->prepare("select ip,custid,available,description,type".
+	" from poolips where pool='$webvar{pool}' order by ip");
+  $sth->execute;
+  my $count = 0;
+  while (my @data = $sth->fetchrow_array) {
+    # pool,ip,custid,city,ptype,available,notes,description,circuitid
+    # ip,custid,available,description,type
+    # If desc is "null", make it not null.  <g>
+    if ($data[3] eq '') {
+      $data[3] = '&nbsp;';
+    }
+    # Some nice hairy Perl to decide whether to allow unassigning each IP
+    #   -> if $data[2] (aka poolips.available) == 'n' then we print the unassign link
+    #	   else we print a blank space
+    my @row = ( qq(<a href="/ip/cgi-bin/main.cgi?action=edit&block=$data[0]">$data[0]</a>),
+	$data[1],$data[2],$data[3],
+	( (($data[2] eq 'n') && ($IPDBacl{$authuser} =~ /d/)) ?
+	  ("<a href=\"/ip/cgi-bin/main.cgi?action=delete&block=$data[0]&".
+	   "alloctype=$data[4]\">Unassign this IP</a>") :
+	  ("&nbsp;") )
+	);
+    printRow(\@row, 'color1') if($count%2==0);
+    printRow(\@row, 'color2') if($count%2!=0);
+    $count++;
+  }
+  print "</table>\n";
+
+} # end listPool
+
+
+# Show "Add new allocation" page.  Note that the actual page may
+# be one of two templates, and the lists come from the database.
+sub assignBlock {
+
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+
+  my $html;
+
+  # New special case- block to assign is specified
+  if ($webvar{block} ne '') {
+    open HTML, "../fb-assign.html"
+	or croak "Could not open fb-assign.html: $!";
+    $html = join('',<HTML>);
+    close HTML;
+    my $block = new NetAddr::IP $webvar{block};
+    $html =~ s|\$\$BLOCK\$\$|$block|g;
+    $html =~ s|\$\$MASKBITS\$\$|$block->masklen|;
+    my $typelist = '';
+
+    # This is a little dangerous, as it's *theoretically* possible to
+    # get fbtype='n' (aka a non-routed freeblock).  However, should
+    # someone manage to get there, they get what they deserve.
+    if ($webvar{fbtype} ne 'y') {
+      # Snag the type of the block from the database.  We have no
+      # convenient way to pass this in from the calling location.  :/
+      $sth = $ip_dbh->prepare("select type from allocations where cidr >>='$block'");
+      $sth->execute;
+      my @data = $sth->fetchrow_array;
+      $data[0] =~ s/c$/r/;	# Munge the type into the correct form
+      $typelist = "$list_alloctypes{$data[0]}<input type=hidden name=alloctype value=$data[0]>\n";
+    } else {
+      $typelist .= qq(<select name="alloctype">\n);
+      $sth = $ip_dbh->prepare("select type,listname from alloctypes where listorder < 500 ".
+	"and type not like '_i' and type not like '_r' order by listorder");
+      $sth->execute;
+      my @data = $sth->fetchrow_array;
+      $typelist .= "<option value='$data[0]' selected>$data[1]</option>\n";
+      while (my @data = $sth->fetchrow_array) {
+        $typelist .= "<option value='$data[0]'>$data[1]</option>\n";
+      }
+      $typelist .= "</select>\n";
+    }
+    $html =~ s|\$\$TYPELIST\$\$|$typelist|g;
+  } else {
+    open HTML, "../assign.html"
+	or croak "Could not open assign.html: $!";
+    $html = join('',<HTML>);
+    close HTML;
+    my $masterlist = "<select name=allocfrom><option selected>-</option>\n";
+    foreach my $master (@masterblocks) {
+      $masterlist .= "<option>$master</option>\n";
+    }
+    $masterlist .= "</select>\n";
+    $html =~ s|\$\$MASTERLIST\$\$|$masterlist|g;
+    my $pops = '';
+    foreach my $pop (@poplist) {
+      $pops .= "<option>$pop</option>\n";
+    }
+    $html =~ s|\$\$POPLIST\$\$|$pops|g;
+    my $typelist = '';
+    $sth = $ip_dbh->prepare("select type,listname from alloctypes where listorder < 900 order by listorder");
+    $sth->execute;
+    my @data = $sth->fetchrow_array;
+    $typelist .= "<option value='$data[0]' selected>$data[1]</option>\n";
+    while (my @data = $sth->fetchrow_array) {
+      $typelist .= "<option value='$data[0]'>$data[1]</option>\n";
+    }
+    $html =~ s|\$\$TYPELIST\$\$|$typelist|g;
+  }
+  my $cities = '';
+  foreach my $city (@citylist) {
+    $cities .= "<option>$city</option>\n";
+  }
+  $html =~ s|\$\$ALLCITIES\$\$|$cities|g;
+
+## node hack
+  $sth = $ip_dbh->prepare("SELECT node_id, node_name FROM nodes ORDER BY node_type,node_id");
+  $sth->execute() or print "DEBUG: failed retrieval from nodes: ".$sth->errstr,"<br>\n";
+  my $nodes = '';
+  while (my ($nid,$nname) = $sth->fetchrow_array()) {
+    $nodes .= "<option value='$nid'>$nname</option>\n";
+  }
+  $html =~ s/\$\$NODELIST\$\$/$nodes/;
+## end node hack
+
+  my $i = 0;
+  $i++ if $webvar{fbtype} eq 'y';
+  # Check to see if user is allowed to do anything with sensitive data
+  my $privdata = '';
+  if ($IPDBacl{$authuser} =~ /s/) {
+    $privdata = qq(<tr class="color).($i%2).qq("><td>Restricted data:</td>).
+        qq(<td class=regular><textarea rows="3" cols="64" name="privdata" class="regular">).
+        qq(</textarea></td></tr>\n);
+    $i++;
+  }
+  $html =~ s/\$\$PRIVDATA\$\$/$privdata/g;
+
+  $i = $i % 2;
+  $html =~ s/\$\$BUTTONROWCOLOUR\$\$/color$i/;
+
+  print $html;
+
+} # assignBlock
+
+
+# Take info on requested IP assignment and see what we can provide.
+sub confirmAssign {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+
+  my $cidr;
+  my $alloc_from;
+
+  # Going to manually validate some items.
+  # custid and city are automagic.
+  return if !validateInput();
+
+# Several different cases here.
+# Static IP vs netblock
+#  + Different flavours of static IP
+#  + Different flavours of netblock
+
+  if ($webvar{alloctype} =~ /^.i$/) {
+    my ($base,undef) = split //, $webvar{alloctype};	# split into individual chars
+
+# Ewww.  But it works.
+    $sth = $ip_dbh->prepare("SELECT (SELECT city FROM allocations WHERE cidr=poolips.pool), ".
+	"poolips.pool, COUNT(*) FROM poolips,allocations WHERE poolips.available='y' AND ".
+	"poolips.pool=allocations.cidr AND allocations.city='$webvar{pop}' AND poolips.type LIKE '".$base."_' ".
+	"GROUP BY pool");
+    $sth->execute;
+    my $optionlist;
+    while (my @data = $sth->fetchrow_array) {
+      # city,pool cidr,free IP count
+      if ($data[2] > 0) {
+        $optionlist .= "<option value='$data[1]'>$data[1] [$data[2] free IP(s)] in $data[0]</option>\n";
+      }
+    }
+    $cidr = "Single static IP";
+    $alloc_from = "<select name=alloc_from>".$optionlist."</select>\n";
+
+  } else { # end show pool options
+
+    if ($webvar{fbassign} eq 'y') {
+      $cidr = new NetAddr::IP $webvar{block};
+      $webvar{maskbits} = $cidr->masklen;
+    } else { # done with direct freeblocks assignment
+
+      if (!$webvar{maskbits}) {
+        printError("Please specify a CIDR mask length.");
+	return;
+      }
+      my $sql;
+      my $city;
+      my $failmsg;
+      my $extracond = '';
+      if ($webvar{allocfrom} eq '-') {
+	$extracond = ($webvar{allowpriv} eq 'on' ? '' : 
+		" and not (cidr <<= '192.168.0.0/16'".
+			" or cidr <<= '10.0.0.0/8'".
+			" or cidr <<= '172.16.0.0/12')");
+      }
+      my $sortorder;
+      if ($webvar{alloctype} eq 'rm') {
+        if ($webvar{allocfrom} ne '-') {
+	  $sql = "select * from freeblocks where maskbits<=$webvar{maskbits} and routed='n'".
+		" and cidr <<= '$webvar{allocfrom}'";
+	  $sortorder = "maskbits desc";
+	} else {
+	  $sql = "select * from freeblocks where maskbits<=$webvar{maskbits} and routed='n'";
+	  $sortorder = "maskbits desc";
+	}
+	$failmsg = "No suitable free block found.<br>\nWe do not have a free".
+	  " routeable block of that size.<br>\nYou will have to either route".
+	  " a set of smaller netblocks or a single smaller netblock.";
+      } else {
+##fixme
+# This section needs serious Pondering.
+	# Pools of most types get assigned to the POP they're "routed from"
+	# This includes WAN blocks and other netblock "containers"
+	# This does NOT include cable pools.
+	if ($webvar{alloctype} =~ /^.[pc]$/) {
+	  $city = $webvar{city};
+	  $failmsg = "No suitable free block found.<br>\nYou will have to route another".
+	    " superblock from one of the<br>\nmaster blocks or chose a smaller".
+	    " block size for the pool.";
+	} else {
+	  $city = $webvar{pop};
+	  $failmsg = "No suitable free block found.<br>\nYou will have to route another".
+	    " superblock to $webvar{pop}<br>\nfrom one of the master blocks or".
+	    " chose a smaller blocksize.";
+	}
+	if (defined $webvar{allocfrom} && $webvar{allocfrom} ne '-') {
+	  $sql = "select cidr from freeblocks where city='$city' and maskbits<=$webvar{maskbits}".
+		" and cidr <<= '$webvar{allocfrom}' and routed='".
+		(($webvar{alloctype} =~ /^(.)r$/) ? "$1" : 'y')."'";
+	  $sortorder = "maskbits desc,cidr";
+	} else {
+	  $sql = "select cidr from freeblocks where city='$city' and maskbits<=$webvar{maskbits}".
+		" and routed='".(($webvar{alloctype} =~ /^(.)r$/) ? "$1" : 'y')."'";
+	  $sortorder = "maskbits desc,cidr";
+	}
+      }
+      $sql = $sql.$extracond." order by ".$sortorder;
+      $sth = $ip_dbh->prepare($sql);
+      $sth->execute;
+      my @data = $sth->fetchrow_array();
+      if ($data[0] eq "") {
+	printError($failmsg);
+	return;
+      }
+      $cidr = new NetAddr::IP $data[0];
+    } # check for freeblocks assignment or IPDB-controlled assignment
+
+    $alloc_from = qq($cidr<input type=hidden name=alloc_from value="$cidr">);
+
+    # If the block to be allocated is smaller than the one we found,
+    # figure out the "real" block to be allocated.
+    if ($cidr->masklen() ne $webvar{maskbits}) {
+      my $maskbits = $cidr->masklen();
+      my @subblocks;
+      while ($maskbits++ < $webvar{maskbits}) {
+	@subblocks = $cidr->split($maskbits);
+      }
+      $cidr = $subblocks[0];
+    }
+  } # if ($webvar{alloctype} =~ /^.i$/)
+
+  open HTML, "../confirm.html"
+	or croak "Could not open confirm.html: $!";
+  my $html = join '', <HTML>;
+  close HTML;
+
+## node hack
+  if ($webvar{node} && $webvar{node} ne '-') {
+    $sth = $ip_dbh->prepare("SELECT node_name FROM nodes WHERE node_id=?");
+    $sth->execute($webvar{node});
+    my ($nodename) = $sth->fetchrow_array();
+    $html =~ s/\$\$NODENAME\$\$/$nodename/;
+    $html =~ s/\$\$NODEID\$\$/$webvar{node}/;
+  } else {
+    $html =~ s/\$\$NODENAME\$\$//;
+    $html =~ s/\$\$NODEID\$\$//;
+  }
+## end node hack
+
+### gotta fix this in final
+  # Stick in customer info as necessary - if it's blank, it just ends
+  # up as blank lines ignored in the rendering of the page
+	my $custbits;
+  $html =~ s|\$\$CUSTBITS\$\$|$custbits|g;
+###
+
+  # Stick in the allocation data
+  $html =~ s|\$\$ALLOC_TYPE\$\$|$webvar{alloctype}|g;
+  $html =~ s|\$\$TYPEFULL\$\$|$disp_alloctypes{$webvar{alloctype}}|g;
+  $html =~ s|\$\$ALLOC_FROM\$\$|$alloc_from|g;
+  $html =~ s|\$\$CIDR\$\$|$cidr|g;
+  $webvar{city} = desanitize($webvar{city});
+  $html =~ s|\$\$CITY\$\$|$webvar{city}|g;
+  $html =~ s|\$\$CUSTID\$\$|$webvar{custid}|g;
+  $webvar{circid} = desanitize($webvar{circid});
+  $html =~ s|\$\$CIRCID\$\$|$webvar{circid}|g;
+  $webvar{desc} = desanitize($webvar{desc});
+  $html =~ s|\$\$DESC\$\$|$webvar{desc}|g;
+  $webvar{notes} = desanitize($webvar{notes});
+  $html =~ s|\$\$NOTES\$\$|$webvar{notes}|g;
+  $html =~ s|\$\$ACTION\$\$|insert|g;
+
+  my $i=1;
+  # Check to see if user is allowed to do anything with sensitive data
+  my $privdata = '';
+  if ($IPDBacl{$authuser} =~ /s/) {
+    $privdata = qq(<tr class="color).($i%2).qq("><td>Restricted data:</td>).
+        qq(<td class=regular>$webvar{privdata}).
+	qq(<input type=hidden name=privdata value="$webvar{privdata}"></td></tr>\n);
+    $i++;
+  }
+# We're going to abuse $$PRIVDATA$$ to stuff in some stuff for billing.
+  $privdata .= "<input type=hidden name=billinguser value=$webvar{userid}>\n"
+	if $webvar{userid};
+  $html =~ s/\$\$PRIVDATA\$\$/$privdata/g;
+
+  $i = $i % 2;
+  $html =~ s/\$\$BUTTONROWCOLOUR\$\$/color$i/;
+
+  print $html;
+
+} # end confirmAssign
+
+
+# Do the work of actually inserting a block in the database.
+sub insertAssign {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+  # Some things are done more than once.
+  return if !validateInput();
+
+  if (!defined($webvar{privdata})) {
+    $webvar{privdata} = '';
+  }
+  # $code is "success" vs "failure", $msg contains OK for a
+  # successful netblock allocation, the IP allocated for static
+  # IP, or the error message if an error occurred.
+  my ($code,$msg) = allocateBlock($ip_dbh, $webvar{fullcidr}, $webvar{alloc_from},
+	$webvar{custid}, $webvar{alloctype}, $webvar{city}, $webvar{desc}, $webvar{notes},
+	$webvar{circid}, $webvar{privdata}, $webvar{node});
+
+  if ($code eq 'OK') {
+    if ($webvar{alloctype} =~ /^.i$/) {
+      $msg =~ s|/32||;
+      print qq(<div class="center"><div class="heading">The IP $msg has been allocated to customer $webvar{custid}</div>).
+	( ($webvar{alloctype} eq 'di' && $webvar{billinguser}) ?
+		qq(<div><a href="https://billing.example.com/radius.pl?).
+		"action=new_radius_user&custid=$webvar{custid}&userid=$webvar{billinguser}".
+		qq(&ipdb=1&ip=$msg">Add this IP to RADIUS user table</a></div>)
+	: "</div>");
+      mailNotify($ip_dbh, "a$webvar{alloctype}", "ADDED: $disp_alloctypes{$webvar{alloctype}} allocation",
+	"$disp_alloctypes{$webvar{alloctype}} $msg allocated to customer $webvar{custid}\n".
+	"Description: $webvar{desc}\n\nAllocated by: $authuser\n");
+    } else {
+      my $netblock = new NetAddr::IP $webvar{fullcidr};
+      print qq(<div class="center"><div class="heading">The block $webvar{fullcidr} was ).
+	"sucessfully added as: $disp_alloctypes{$webvar{alloctype}}</div>".
+	( ($webvar{alloctype} eq 'pr' && $webvar{billinguser}) ?
+		qq(<div><a href="https://billing.example.com/radius.pl?).
+		"action=new_radius_user&custid=$webvar{custid}&userid=$webvar{billinguser}".
+		"&route_subnet=".$netblock->addr."&subnet_slash=".$netblock->masklen.
+		"&include_routed_subnet=1&ipdb=1".
+		qq(">Add this netblock to RADIUS user table</a></div>)
+	: "</div>");
+      mailNotify($ip_dbh, "a$webvar{alloctype}", "ADDED: $disp_alloctypes{$webvar{alloctype}} allocation",
+	"$disp_alloctypes{$webvar{alloctype}} $webvar{fullcidr} allocated to customer $webvar{custid}\n".
+	"Description: $webvar{desc}\n\nAllocated by: $authuser\n");
+    }
+    syslog "notice", "$authuser allocated '$webvar{fullcidr}' to '$webvar{custid}' as ".
+	"'$webvar{alloctype}' ($msg)";
+  } else {
+    syslog "err", "Allocation of '$webvar{fullcidr}' to '$webvar{custid}' as ".
+	"'$webvar{alloctype}' by $authuser failed: '$msg'";
+    printError("Allocation of $webvar{fullcidr} as '$disp_alloctypes{$webvar{alloctype}}'".
+	" failed:<br>\n$msg\n");
+  }
+
+} # end insertAssign()
+
+
+# Does some basic checks on common input data to make sure nothing
+# *really* weird gets in to the database through this script.
+# Does NOT do complete input validation!!!
+sub validateInput {
+  if ($webvar{city} eq '-') {
+    printError("Please choose a city.");
+    return;
+  }
+
+  # Alloctype check.
+  chomp $webvar{alloctype};
+  if (!grep /$webvar{alloctype}/, keys %disp_alloctypes) {
+    # Danger! Danger!  alloctype should ALWAYS be set by a dropdown.  Anyone
+    # managing to call things in such a way as to cause this deserves a cryptic error.
+    printError("Invalid alloctype");
+    return;
+  }
+
+  # CustID check
+  # We have different handling for customer allocations and "internal" or "our" allocations
+  if ($def_custids{$webvar{alloctype}} eq '') {
+    if (!$webvar{custid}) {
+      printError("Please enter a customer ID.");
+      return;
+    }
+    if ($webvar{custid} !~ /^(?:\d{10}|\d{7}|STAFF)(?:-\d\d?)?$/) {
+      # Force uppercase for now...
+      $webvar{custid} =~ tr/a-z/A-Z/;
+      # Crosscheck with billing.
+      my $status = CustIDCK->custid_exist($webvar{custid});
+      if ($CustIDCK::Error) {
+	printError("Error verifying customer ID: ".$CustIDCK::ErrMsg);
+	return;
+      }
+      if (!$status) {
+	printError("Customer ID not valid.  Make sure the Customer ID ".
+	  "is correct.<br>\nUse STAFF for staff static IPs, and $IPDB::defcustid for any other ".
+	  "non-customer assignments.");
+	return;
+      }
+    }
+#    print "<!-- [ In validateInput().  Insert customer ID cross-check here. ] -->\n";
+  } else {
+    # New!  Improved!  And now Loaded From The Database!!
+    if ((!$webvar{custid}) || ($webvar{custid} ne 'STAFF')) {
+      $webvar{custid} = $def_custids{$webvar{alloctype}};
+    }
+  }
+
+  # Check POP location
+  my $flag;
+  if ($webvar{alloctype} eq 'rm') {
+    $flag = 'for a routed netblock';
+    foreach (@poplist) {
+      if (/^$webvar{city}$/) {
+	$flag = 'n';
+	last;
+      }
+    }
+  } else {
+    $flag = 'n';
+##fixme:  hook to force-set POP or city on certain alloctypes
+# if ($webvar{alloctype =~ /foo,bar,bz/ { $webvar{pop} = 'blah'; }
+    if ($webvar{pop} =~ /^-$/) {
+      $flag = 'to route the block from/through';
+    }
+  }
+  if ($flag ne 'n') {
+    printError("Please choose a valid POP location $flag.  Valid ".
+	"POP locations are currently:<br>\n".join (" - ", @poplist));
+    return;
+  }
+
+  return 'OK';
+} # end validateInput
+
+
+# Displays details of a specific allocation in a form
+# Allows update/delete
+# action=edit
+sub edit {
+
+  my $sql;
+
+  # Two cases:  block is a netblock, or block is a static IP from a pool
+  # because I'm lazy, we'll try to make the SELECT's bring out identical)ish) data
+  if ($webvar{block} =~ /\/32$/) {
+    $sql = "select ip,custid,type,city,circuitid,description,notes,modifystamp,privdata,oldcustid from poolips where ip='$webvar{block}'";
+  } else {
+    $sql = "select cidr,custid,type,city,circuitid,description,notes,modifystamp,privdata,oldcustid,swip from allocations where cidr='$webvar{block}'"
+  }
+
+  # gotta snag block info from db
+  $sth = $ip_dbh->prepare($sql);
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+
+  # Clean up extra whitespace on alloc type
+  $data[2] =~ s/\s//;
+
+  open (HTML, "../editDisplay.html")
+	or croak "Could not open editDisplay.html :$!";
+  my $html = join('', <HTML>);
+
+  # We can't let the city be changed here;  this block is a part of
+  # a larger routed allocation and therefore by definition can't be moved.
+  # block and city are static.
+##fixme
+# Needs thinking.  Have to allow changes to city to correct errors, no?
+  $html =~ s/\$\$BLOCK\$\$/$webvar{block}/g;
+
+  if ($IPDBacl{$authuser} =~ /c/) {
+    $html =~ s/\$\$CUSTID\$\$/<input type=text name=custid value="$data[1]" maxlength=15 class="regular">/;
+
+# Screw it.  Changing allocation types gets very ugly VERY quickly- especially
+# with the much longer list of allocation types.
+# We'll just show what type of block it is.
+
+# this has now been Requested, so here goes.
+
+##fixme The check here should be built from the database
+    if ($data[2] =~ /^.[ne]$/) {
+      # Block that can be changed
+      my $blockoptions = "<select name=alloctype><option".
+	(($data[2] eq 'me') ? ' selected' : '') ." value='me'>Dialup netblock</option>\n<option".
+	(($data[2] eq 'de') ? ' selected' : '') ." value='de'>Dynamic DSL netblock</option>\n<option".
+	(($data[2] eq 'ce') ? ' selected' : '') ." value='ce'>Dynamic cable netblock</option>\n<option".
+	(($data[2] eq 'we') ? ' selected' : '') ." value='we'>Dynamic wireless netblock</option>\n<option".
+	(($data[2] eq 'cn') ? ' selected' : '') ." value='cn'>Customer netblock</option>\n<option".
+	(($data[2] eq 'en') ? ' selected' : '') ." value='en'>End-use netblock</option>\n<option".
+	(($data[2] eq 'in') ? ' selected' : '') ." value='in'>Internal netblock</option>\n".
+	"</select>\n";
+      $html =~ s/\$\$TYPESELECT\$\$/$blockoptions/g;
+    } else {
+      $html =~ s/\$\$TYPESELECT\$\$/$disp_alloctypes{$data[2]}<input type=hidden name=alloctype value="$data[2]">/g;
+    }
+## node hack
+  $sth = $ip_dbh->prepare("SELECT node_id FROM noderef WHERE block='$webvar{block}'");
+  $sth->execute;
+  my ($nodeid) = $sth->fetchrow_array();
+  if ($nodeid) {
+    $sth = $ip_dbh->prepare("SELECT node_id, node_name FROM nodes ORDER BY node_type,node_id");
+    $sth->execute() or print "DEBUG: failed retrieval from nodes: ".$sth->errstr,"<br>\n";
+    my $nodes = "<select name=node>\n";
+    while (my ($nid,$nname) = $sth->fetchrow_array()) {
+      $nodes .= "<option".($nodeid == $nid ? ' selected' : '')." value='$nid'>$nname</option>\n";
+    }
+    $nodes .= "</select>\n";
+    $html =~ s/\$\$NODE\$\$/$nodes/;
+  } else {
+    if ($data[2] eq 'fr' || $data[2] eq 'bi') {
+      $sth = $ip_dbh->prepare("SELECT node_id, node_name FROM nodes ORDER BY node_type,node_id");
+      $sth->execute() or print "DEBUG: failed retrieval from nodes: ".$sth->errstr,"<br>\n";
+      my $nodes = "<select name=node>\n<option value=>--</option>\n";
+      while (my ($nid,$nname) = $sth->fetchrow_array()) {
+        $nodes .= "<option value='$nid'>$nname</option>\n";
+      }
+      $nodes .= "</select>\n";
+      $html =~ s/\$\$NODE\$\$/$nodes/;
+    } else {
+      $html =~ s|\$\$NODE\$\$|N/A|;
+    }
+  }
+## end node hack
+    $html =~ s/\$\$OLDCUSTID\$\$/$data[9]/g;
+    $html =~ s/\$\$CITY\$\$/<input type=text name=city value="$data[3]">/g;
+    $html =~ s/\$\$CIRCID\$\$/<input type="text" name="circid" value="$data[4]" maxlength=64 size=64 class="regular">/g;
+    $html =~ s/\$\$DESC\$\$/<input type="text" name="desc" value="$data[5]" maxlength=64 size=64 class="regular">/g;
+    $html =~ s|\$\$NOTES\$\$|<textarea rows="8" cols="64" name="notes" class="regular">$data[6]</textarea>|g;
+  } else {
+## node hack
+    if ($data[2] eq 'fr' || $data[2] eq 'bi') {
+      $sth = $ip_dbh->prepare("SELECT node_name FROM nodes INNER JOIN noderef".
+	" ON nodes.node_id=noderef.node_id WHERE noderef.block='$webvar{block}'");
+      $sth->execute() or print "DEBUG: failed retrieval from nodes: ".$sth->errstr,"<br>\n";
+      my ($node) = $sth->fetchrow_array;
+      $html =~ s/\$\$NODE\$\$/$node/;
+    } else {
+      $html =~ s|\$\$NODE\$\$|N/A|;
+    }
+## end node hack
+    $html =~ s/\$\$CUSTID\$\$/$data[1]/g;
+    $html =~ s/\$\$OLDCUSTID\$\$/$data[9]/g;
+    $html =~ s/\$\$TYPESELECT\$\$/$disp_alloctypes{$data[2]}/g;
+    $html =~ s/\$\$CITY\$\$/$data[3]/g;
+    $html =~ s/\$\$CIRCID\$\$/$data[4]/g;
+    $html =~ s/\$\$DESC\$\$/$data[5]/g;
+    $html =~ s/\$\$NOTES\$\$/$data[6]/g;
+  }
+  my ($lastmod,undef) = split /\s+/, $data[7];
+  $html =~ s/\$\$LASTMOD\$\$/$lastmod/g;
+
+## Hack time!  SWIP isn't going to stay, so I'm not going to integrate it with ACLs.
+if ($data[2] =~ /.i/) {
+  $html =~ s/\$\$SWIP\$\$/N\/A/;
+} else {
+  my $tmp = (($data[10] eq 'n') ? '<input type=checkbox name=swip>' : 
+	'<input type=checkbox name=swip checked=yes>');
+  $html =~ s/\$\$SWIP\$\$/$tmp/;
+}
+
+  # Allows us to "correctly" colour backgrounds in table
+  my $i=1;
+
+  # Check to see if we can display sensitive data
+  my $privdata = '';
+  if ($IPDBacl{$authuser} =~ /s/) {
+    $privdata = qq(<tr class="color).($i%2).qq("><td class=heading>Restricted data:</td>).
+	qq(<td class=regular><textarea rows="3" cols="64" name="privdata" class="regular">).
+	qq($data[8]</textarea></td></tr>\n);
+    $i++;
+  }
+  $html =~ s/\$\$PRIVDATA\$\$/$privdata/g;
+
+  # More ACL trickery - we can live with forms that don't submit,
+  # but we can't leave the extra table rows there, and we *really*
+  # can't leave the submit buttons there.
+  my $updok = '';
+  if ($IPDBacl{$authuser} =~ /c/) {
+    $updok = qq(<tr class="color).($i%2).qq("><td colspan=2><div class="center">).
+	qq(<input type="submit" value=" Update this block " class="regular">).
+	"</div></td></tr></form>\n";
+    $i++;
+  }
+  $html =~ s/\$\$UPDOK\$\$/$updok/g;
+
+  my $delok = '';
+  if ($IPDBacl{$authuser} =~ /d/) {
+    $delok = qq(<form method="POST" action="main.cgi">
+	<tr class="color).($i%2).qq("><td colspan=2 class="regular"><div class=center>
+	<input type="hidden" name="action" value="delete">
+	<input type="hidden" name="block" value="$webvar{block}">
+	<input type="hidden" name="alloctype" value="$data[2]">
+	<input type=submit value=" Delete this block ">
+	</div></td></tr>);
+  }
+  $html =~ s/\$\$DELOK\$\$/$delok/;
+
+  print $html;
+
+} # edit()
+
+
+# Stuff new info about a block into the db
+# action=update
+sub update {
+  if ($IPDBacl{$authuser} !~ /c/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+
+  # Check to see if we can update restricted data
+  my $privdata = '';
+  if ($IPDBacl{$authuser} =~ /s/) {
+    $privdata = ",privdata='$webvar{privdata}'";
+  }
+
+  # Make sure incoming data is in correct format - custID among other things.
+  return if !validateInput;
+
+  # SQL transaction wrapper
+  eval {
+    # Relatively simple SQL transaction here.
+    my $sql;
+    if (my $pooltype = ($webvar{alloctype} =~ /^(.)i$/) ) {
+      $sql = "update poolips set custid='$webvar{custid}',notes='$webvar{notes}',".
+	"circuitid='$webvar{circid}',description='$webvar{desc}',city='$webvar{city}'".
+	"$privdata where ip='$webvar{block}'";
+    } else {
+      $sql = "update allocations set custid='$webvar{custid}',".
+	"description='$webvar{desc}',notes='$webvar{notes}',city='$webvar{city}',".
+	"type='$webvar{alloctype}',circuitid='$webvar{circid}'$privdata,".
+	"swip='".($webvar{swip} eq 'on' ? 'y' : 'n')."' ".
+	"where cidr='$webvar{block}'";
+    }
+    # Log the details of the change.
+    syslog "debug", $sql;
+    $sth = $ip_dbh->prepare($sql);
+    $sth->execute;
+## node hack
+    if ($webvar{node}) {
+      $ip_dbh->do("DELETE FROM noderef WHERE block='$webvar{block}'");
+      $sth = $ip_dbh->prepare("INSERT INTO noderef (block,node_id) VALUES (?,?)");
+      $sth->execute($webvar{block},$webvar{node});
+    }
+## end node hack
+    $ip_dbh->commit;
+  };
+  if ($@) {
+    my $msg = $@;
+    carp "Transaction aborted because $msg";
+    eval { $ip_dbh->rollback; };
+    syslog "err", "$authuser could not update block/IP '$webvar{block}': '$msg'";
+    printError("Could not update block/IP $webvar{block}: $msg");
+    return;
+  }
+
+  # If we get here, the operation succeeded.
+  syslog "notice", "$authuser updated $webvar{block}";
+##fixme:  need to wedge something in to allow "update:field" notifications
+## hmm.  how to tell what changed?  O_o
+mailNotify($ip_dbh, 's:swi', "SWIPed: $disp_alloctypes{$webvar{alloctype}} $webvar{block}",
+	"$webvar{block} had SWIP status changed to \"Yes\" by $authuser") if $webvar{swip} eq 'on';
+  open (HTML, "../updated.html")
+	or croak "Could not open updated.html :$!";
+  my $html = join('', <HTML>);
+
+  # Link back to browse-routed or list-pool page on "Update complete" page.
+  my $backlink = "/ip/cgi-bin/main.cgi?action=";
+  my $cblock;	# to contain the CIDR of the container block we're retrieving.
+  my $sql;
+  if (my $pooltype = ($webvar{alloctype} =~ /^(.)i$/) ) {
+    $sql = "select pool from poolips where ip='$webvar{block}'";
+    $backlink .= "listpool&pool=";
+  } else {
+    $sql = "select cidr from routed where cidr >>= '$webvar{block}'";
+    $backlink .= "showrouted&block=";
+  }
+  # I define there to be no errors on this operation...  so we don't need to check for them.
+  $sth = $ip_dbh->prepare($sql);
+  $sth->execute;
+  $sth->bind_columns(\$cblock);
+  $sth->fetch();
+  $sth->finish;
+  $backlink .= $cblock;
+
+my $swiptmp = ($webvar{swip} eq 'on' ? 'Yes' : 'No');
+  $html =~ s/\$\$BLOCK\$\$/$webvar{block}/g;
+  $webvar{city} = desanitize($webvar{city});
+  $html =~ s/\$\$CITY\$\$/$webvar{city}/g;
+  $html =~ s/\$\$ALLOCTYPE\$\$/$webvar{alloctype}/g;
+  $html =~ s/\$\$TYPEFULL\$\$/$disp_alloctypes{$webvar{alloctype}}/g;
+  $html =~ s/\$\$CUSTID\$\$/$webvar{custid}/g;
+  $html =~ s/\$\$SWIP\$\$/$swiptmp/g;
+  $webvar{circid} = desanitize($webvar{circid});
+  $html =~ s/\$\$CIRCID\$\$/$webvar{circid}/g;
+  $webvar{desc} = desanitize($webvar{desc});
+  $html =~ s/\$\$DESC\$\$/$webvar{desc}/g;
+  $webvar{notes} = desanitize($webvar{notes});
+  $html =~ s/\$\$NOTES\$\$/$webvar{notes}/g;
+  $html =~ s/\$\$BACKLINK\$\$/$backlink/g;
+  $html =~ s/\$\$BACKBLOCK\$\$/$cblock/g;
+
+  if ($IPDBacl{$authuser} =~ /s/) {
+    $privdata = qq(<tr class="color2"><td valign="top">Restricted data:</td>).
+	qq(<td class="regular">).desanitize($webvar{privdata}).qq(</td></tr>\n);
+  }
+  $html =~ s/\$\$PRIVDATA\$\$/$privdata/g;
+
+  print $html;
+
+} # update()
+
+
+# Delete an allocation.
+sub remove {
+  if ($IPDBacl{$authuser} !~ /d/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+
+  #show confirm screen.
+  open HTML, "../confirmRemove.html"
+	or croak "Could not open confirmRemove.html :$!";
+  my $html = join('', <HTML>);
+  close HTML;
+
+  # Serves'em right for getting here...
+  if (!defined($webvar{block})) {
+    printError("Error 332");
+    return;
+  }
+
+  my ($cidr, $custid, $type, $city, $circid, $desc, $notes, $alloctype, $privdata);
+
+  if ($webvar{alloctype} eq 'rm') {
+    $sth = $ip_dbh->prepare("select cidr,city from routed where cidr='$webvar{block}'");
+    $sth->execute();
+
+# This feels...  extreme.
+    croak $sth->errstr() if($sth->errstr());
+
+    $sth->bind_columns(\$cidr,\$city);
+    $sth->execute();
+    $sth->fetch || croak $sth->errstr();
+    $custid = "N/A";
+    $alloctype = $webvar{alloctype};
+    $circid = "N/A";
+    $desc = "N/A";
+    $notes = "N/A";
+
+  } elsif ($webvar{alloctype} eq 'mm') {
+    $cidr = $webvar{block};
+    $city = "N/A";
+    $custid = "N/A";
+    $alloctype = $webvar{alloctype};
+    $circid = "N/A";
+    $desc = "N/A";
+    $notes = "N/A";
+  } elsif ($webvar{alloctype} =~ /^.i$/) { # done with alloctype=[rm]m
+
+    # Unassigning a static IP
+    my $sth = $ip_dbh->prepare("select ip,custid,city,type,notes,circuitid,privdata".
+	" from poolips where ip='$webvar{block}'");
+    $sth->execute();
+#  croak $sth->errstr() if($sth->errstr());
+
+    $sth->bind_columns(\$cidr, \$custid, \$city, \$alloctype, \$notes, \$circid,
+	\$privdata);
+    $sth->fetch() || croak $sth->errstr;
+
+  } else { # done with alloctype=~ /^.i$/
+
+    my $sth = $ip_dbh->prepare("select cidr,custid,type,city,circuitid,description,notes,privdata".
+	" from allocations where cidr='$webvar{block}'");
+    $sth->execute();
+#	croak $sth->errstr() if($sth->errstr());
+
+    $sth->bind_columns(\$cidr, \$custid, \$alloctype, \$city, \$circid, \$desc,
+	\$notes, \$privdata);
+    $sth->fetch() || carp $sth->errstr;
+  } # end cases for different alloctypes
+
+  # Munge everything into HTML
+  $html =~ s|Please confirm|Please confirm <b>removal</b> of|;
+  $html =~ s|\$\$BLOCK\$\$|$cidr|g;
+  $html =~ s|\$\$TYPEFULL\$\$|$disp_alloctypes{$alloctype}|g;
+  $html =~ s|\$\$ALLOCTYPE\$\$|$alloctype|g;
+  $html =~ s|\$\$CITY\$\$|$city|g;
+  $html =~ s|\$\$CUSTID\$\$|$custid|g;
+  $html =~ s|\$\$CIRCID\$\$|$circid|g;
+  $html =~ s|\$\$DESC\$\$|$desc|g;
+  $html =~ s|\$\$NOTES\$\$|$notes|g;
+
+  $html =~ s|\$\$ACTION\$\$|finaldelete|g;
+
+  # Set the warning text.
+  if ($alloctype =~ /^.[pd]$/) {
+    $html =~ s|<!--warn-->|<tr bgcolor="black"><td colspan="2"><div class="red">Warning: clicking confirm will remove this record entirely.<br>Any IPs allocated from this pool will also be removed!</div></td></tr>|;
+  } else {
+    $html =~ s|<!--warn-->|<tr bgcolor="black"><td colspan="2"><div class="red">Warning: clicking confirm will remove this record entirely.</div></td></tr>|;
+  }
+
+  my $i = 1;
+  # Check to see if user is allowed to do anything with sensitive data
+  if ($IPDBacl{$authuser} =~ /s/) {
+    $privdata = qq(<tr class="color).($i%2).qq("><td>Restricted data:</td>).
+	qq(<td class=regular>$privdata</td></tr>\n);
+    $i++;
+  }
+  $html =~ s/\$\$PRIVDATA\$\$/$privdata/g;
+
+  $i = ++$i % 2;
+  $html =~ s/\$\$BUTTONROWCOLOUR\$\$/color$i/;
+
+  print $html;
+} # end edit()
+
+
+# Delete an allocation.  Return it to the freeblocks table;  munge
+# data as necessary to keep as few records as possible in freeblocks
+# to prevent weirdness when allocating blocks later.
+# Remove IPs from pool listing if necessary
+sub finalDelete {
+  if ($IPDBacl{$authuser} !~ /d/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+
+  # need to retrieve block data before deleting so we can notify on that
+  my ($cidr,$custid,$type,$city,$description) = getBlockData($ip_dbh, $webvar{block});
+
+  my ($code,$msg) = deleteBlock($ip_dbh, $webvar{block}, $webvar{alloctype});
+
+  if ($code eq 'OK') {
+    print "<div class=heading align=center>Success!  $webvar{block} deallocated.</div>\n";
+    syslog "notice", "$authuser deallocated '$webvar{alloctype}'-type netblock $webvar{block}".
+	" $custid, $city, desc='$description'";
+    mailNotify($ip_dbh, 'da', "REMOVED: $disp_alloctypes{$webvar{alloctype}} $webvar{block}",
+	"$disp_alloctypes{$webvar{alloctype}} $webvar{block} deallocated by $authuser\n".
+	"CustID: $custid\nCity: $city\nDescription: $description\n");
+  } else {
+    if ($webvar{alloctype} =~ /^.i$/) {
+      syslog "err", "$authuser could not deallocate static IP '$webvar{block}': '$msg'";
+      printError("Could not deallocate static IP $webvar{block}: $msg");
+    } else {
+      syslog "err", "$authuser could not deallocate netblock '$webvar{block}': '$msg'";
+      printError("Could not deallocate netblock $webvar{block}: $msg");
+    }
+  }
+
+} # finalDelete
+
+
+sub exitError {
+  my $errStr = $_[0];
+  printHeader('','');
+  print qq(<center><p class="regular"> $errStr </p>
+<input type="button" value="Back" onclick="history.go(-1)">
+</center>
+);
+  printFooter();
+  exit;
+} # errorExit
+
+
+# Just in case we manage to get here.
+exit 0;
Index: /branches/htmlform/cgi-bin/newcity.cgi
===================================================================
--- /branches/htmlform/cgi-bin/newcity.cgi	(revision 446)
+++ /branches/htmlform/cgi-bin/newcity.cgi	(revision 446)
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/newcity.cgi
+# Add new city to database
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2010 - Kris Deugau
+
+use strict;
+use warnings;
+#use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use CommonWeb qw(:ALL);
+#use POSIX qw(ceil);
+use NetAddr::IP;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+openlog "IPDB","pid","$IPDB::syslog_facility";
+
+# Collect the username from HTTP auth.  If undefined, we're in a test environment.
+my $authuser;
+if (!defined($ENV{'REMOTE_USER'})) {
+  $authuser = '__temptest';
+} else {
+  $authuser = $ENV{'REMOTE_USER'};
+}
+
+my %webvar = parse_post();
+cleanInput(\%webvar);
+
+my ($dbh,$errstr) = connectDB_My;
+my $sth;
+
+print "Content-type: text/html\n\n";
+
+if ($webvar{pop} eq 'on') {
+  $sth = $dbh->prepare("insert into cities (city,routing) values ('$webvar{city}','y')");
+} else {
+  $sth = $dbh->prepare("insert into cities (city,routing) values ('$webvar{city}','n')");
+}
+$sth->execute;
+
+if ($sth->err) {
+  print "Error adding city to database: ".$sth->errstr;
+  mailNotify($dbh, 'f:nci', "IPDB city add failure",
+	"$authuser could not add city '$webvar{city}' to database: ".$sth->errstr);
+  syslog "err", "$authuser could not add city '$webvar{city}' to database: ".$sth->errstr;
+} else {
+  print "City added.  Closing this window should refresh the page.";
+  syslog "notice", "$authuser added city/location '$webvar{pop}'".
+	(($webvar{pop} eq 'on') ? ' as POP location' : '');
+}
+
+finish($dbh);
Index: /branches/htmlform/cgi-bin/newnode.cgi
===================================================================
--- /branches/htmlform/cgi-bin/newnode.cgi	(revision 446)
+++ /branches/htmlform/cgi-bin/newnode.cgi	(revision 446)
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/newnode.cgi
+# Add new wifi tower or fibre demarc switch ("node") to database
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2010 - Kris Deugau
+
+use strict;
+use warnings;
+#use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use CommonWeb qw(:ALL);
+#use POSIX qw(ceil);
+use NetAddr::IP;
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+openlog "IPDB","pid","$IPDB::syslog_facility";
+
+# Collect the username from HTTP auth.  If undefined, we're in a test environment.
+my $authuser;
+if (!defined($ENV{'REMOTE_USER'})) {
+  $authuser = '__temptest';
+} else {
+  $authuser = $ENV{'REMOTE_USER'};
+}
+
+my %webvar = parse_post();
+cleanInput(\%webvar);
+
+my ($dbh,$errstr) = connectDB_My;
+my $sth;
+
+print "Content-type: text/html\n\n";
+
+$sth = $dbh->prepare("insert into nodes (node_type,node_name,node_ip)".
+	" values ('$webvar{type}','$webvar{nodename}','$webvar{nodeip}')");
+$sth->execute;
+
+if ($sth->err) {
+  print "Error adding node to database: ".$sth->errstr;
+  mailNotify($dbh, 'f:nno', "IPDB node add failure",
+	"$authuser could not add node '$webvar{nodename}','$webvar{type}' to database: ".$sth->errstr);
+  syslog "err", "$authuser could not add node '$webvar{nodename}','$webvar{type}' to database: ".$sth->errstr;
+} else {
+  print "Node added.  Closing this window should refresh the page.";
+  syslog "notice", "$authuser added node '$webvar{nodename}'";
+}
+
+finish($dbh);
Index: /branches/htmlform/cgi-bin/search.cgi
===================================================================
--- /branches/htmlform/cgi-bin/search.cgi	(revision 446)
+++ /branches/htmlform/cgi-bin/search.cgi	(revision 446)
@@ -0,0 +1,518 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/search.cgi
+# Started splitting search functions (quick and otherwise) from
+# main IPDB interface 03/11/2005
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright 2005-2010 - Kris Deugau
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use CommonWeb qw(:ALL);
+use POSIX qw(ceil);
+use NetAddr::IP;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+# Don't formally need a username or syslog here.  syslog left active for debugging.
+use Sys::Syslog;
+openlog "IPDBsearch","pid","$IPDB::syslog_facility";
+
+# ... but we do *use* the username on ACLs now.
+# Collect the username from HTTP auth.  If undefined, we're in
+# a test environment, or called without a username.
+my $authuser;
+if (!defined($ENV{'REMOTE_USER'})) {
+  $authuser = '__temptest';
+} else {
+  $authuser = $ENV{'REMOTE_USER'};
+}
+
+# Why not a global DB handle?  (And a global statement handle, as well...)
+# Use the connectDB function, otherwise we end up confusing ourselves
+my $ip_dbh;
+my $sth;
+my $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  printAndExit("Failed to connect to database: $errstr\n");
+}
+checkDBSanity($ip_dbh);
+initIPDBGlobals($ip_dbh);
+
+# Global variables
+my $RESULTS_PER_PAGE = 25;
+my %webvar = parse_post();
+cleanInput(\%webvar);
+
+if (defined($webvar{rpp})) {
+  ($RESULTS_PER_PAGE) = ($webvar{rpp} =~ /(\d+)/);
+}
+
+if (!defined($webvar{stype})) {
+  $webvar{stype} = "<NULL>";   #shuts up the warnings.
+}
+
+# Headerize!  Make sure we replace the $$EXTRA0$$ bit as needed.
+printHeader('', ($IPDBacl{$authuser} =~ /a/ ?
+	'<td align=right><a href="/ip/cgi-bin/main.cgi?action=assign">Add new assignment</a></td>' : ''
+	));
+
+if ($webvar{stype} eq 'q') {
+  # Quick search.
+
+  if (!$webvar{input}) {
+    # No search term.  Display everything.
+    viewBy('all', '');
+  } else {
+    # Search term entered.  Display matches.
+    # We should really sanitize $webvar{input}, no?
+    my $searchfor;
+    # Chew up leading and trailing whitespace
+    $webvar{input} =~ s/^\s+//;
+    $webvar{input} =~ s/\s+$//;
+    if ($webvar{input} =~ /^\d+$/) {
+      # All-digits, new custID
+      $searchfor = "cust";
+    } elsif ($webvar{input} =~ /^[\d\.]+(\/\d{1,3})?$/) {
+      # IP addresses should only have numbers, digits, and maybe a slash+netmask
+      $searchfor = "ipblock";
+    } else {
+      # Anything else.
+      $searchfor = "desc";
+    }
+    viewBy($searchfor, $webvar{input});
+  }
+
+} elsif ($webvar{stype} eq 'c') {
+  # Complex search.
+
+  # Several major cases, and a whole raft of individual cases.
+  # -> Show all types means we do not need to limit records retrieved by type
+  # -> Show all cities means we do not need to limit records retrieved by city
+  # Individual cases are for the CIDR/IP, CustID, Description, Notes, and individual type
+  # requests.
+
+  my $sqlconcat;
+  if ($webvar{which} eq 'all') {
+    # Must match *all* specified criteria.	## use INTERSECT or EXCEPT
+    $sqlconcat = "INTERSECT";
+  } elsif ($webvar{which} eq 'any') {
+    # Match on any specified criteria		## use UNION
+    $sqlconcat = "UNION";
+  } else {
+    # We can't get here.  PTHBTT!
+    printAndExit "PTHBTT!!  Your search has been rejected due to Microsoft excuse #4432: ".
+	"Not enough mana";
+  }
+
+# We actually construct a monster SQL statement for all criteria.
+# Iff something has been entered, it will be used as a filter.
+# Iff something has NOT been entered, we still include it but in
+# such a way that it does not actually filter anything out.
+
+  # Columns actually returned.  Slightly better than hardcoding it
+  # in each (sub)select
+  my $cols = "cidr,custid,type,city,description";
+
+  # First chunk of SQL.  Filter on custid, description, and notes as necessary.
+  my $sql = "(select $cols from searchme where".
+	" $webvar{custexclude} (custid ilike '%$webvar{custid}%'".
+	" OR $webvar{custexclude} oldcustid ilike '%$webvar{custid}%'))".
+	" $sqlconcat (select $cols from searchme where $webvar{descexclude} description ilike '%$webvar{desc}%')".
+	" $sqlconcat (select $cols from searchme where $webvar{notesexclude} notes ilike '%$webvar{notes}%')";
+
+  # If we're not supposed to search for all types, search for the selected types.
+  if ($webvar{alltypes} ne 'on') {
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{typeexclude} type in (";
+    foreach my $key (keys %webvar) {
+      $sql .= "'$1'," if $key =~ /type\[(..)\]/;
+    }
+    chop $sql;
+    $sql .= "))";
+  }
+
+  # If we're not supposed to search for all cities, search for the selected cities.
+  # This could be vastly improved with proper foreign keys in the database.
+  if ($webvar{allcities} ne 'on') {
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{cityexclude} city in (";
+    $sth = $ip_dbh->prepare("select city from cities where id=?");
+    foreach my $key (keys %webvar) {
+      if ($key =~ /city\[(\d+)\]/) {
+        $sth->execute($1);
+        my $city;
+        $sth->bind_columns(\$city);
+        $sth->fetch;
+        $city =~ s/'/''/;
+        $sql .= "'$city',";
+      }
+    }
+    chop $sql;
+    $sql .= "))";
+  }
+
+  ## CIDR query options.
+  $webvar{cidr} =~ s/\s+//;	# Hates the nasty spaceseseses we does.
+  if ($webvar{cidr} eq '') { # We has a blank CIDR.  Ignore it.
+  } elsif ($webvar{cidr} =~ /\//) {
+    # 192.168.179/26 should show all /26 subnets in 192.168.179
+    my ($net,$maskbits) = split /\//, $webvar{cidr};
+    if ($webvar{cidr} =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
+      # /0->/9 are silly to worry about right now.  I don't think
+      # we'll be getting a class A anytime soon.  <g>
+      $sql .= " $sqlconcat (select $cols from searchme where ".
+	"$webvar{cidrexclude} cidr<<='$webvar{cidr}')";
+    } else {
+      # Partial match;  beginning of subnet and maskbits are provided
+      # Show any blocks with the leading octet(s) and that masklength
+      # Need some more magic for bare /nn searches:
+      my $condition = ($net eq '' ?
+	"masklen(cidr)=$maskbits" : "text(cidr) like '$net%' and masklen(cidr)=$maskbits");
+      $sql .= " $sqlconcat (select $cols from searchme where $webvar{cidrexclude} ".
+	"($condition))";
+    }
+  } elsif ($webvar{cidr} =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
+    # Specific IP address match.  Will show either a single netblock,
+    # or a static pool plus an IP.
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{cidrexclude} ".
+	"cidr >>= '$webvar{cidr}')";
+  } elsif ($webvar{cidr} =~ /^\d{1,3}(\.(\d{1,3}(\.(\d{1,3}\.?)?)?)?)?$/) {
+    # Leading octets in CIDR
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{cidrexclude} ".
+	"text(cidr) like '$webvar{cidr}%')";
+  } else {
+    # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+    printAndExit("Invalid netblock query.");
+  } # done with CIDR query options.
+
+  # Find the offset for multipage results
+  my $offset = ($webvar{page}-1)*$RESULTS_PER_PAGE;
+
+  # Find out how many rows the "core" query will return.
+  my $count = countRows($sql);
+
+  if ($count == 0) {
+    printError "No matches found.  Try eliminating one of the criteria,".
+	" or making one or more criteria more general.";
+  } else {
+    # Add the limit/offset clauses
+    $sql .= " order by cidr";
+    $sql .= " limit $RESULTS_PER_PAGE offset $offset" if $RESULTS_PER_PAGE != 0;
+    # And tell the user.
+    print "<div class=heading>Searching...............</div>\n";
+    queryResults($sql, $webvar{page}, $count);
+  }
+
+} elsif ($webvar{stype} eq 'n') {
+  # Node search.
+
+  my $sql = "SELECT cidr,custid,type,city,description FROM searchme".
+	" WHERE cidr IN (SELECT block FROM noderef WHERE node_id=$webvar{node})";
+
+  # Find the offset for multipage results
+  my $offset = ($webvar{page}-1)*$RESULTS_PER_PAGE;
+
+  # Find out how many rows the "core" query will return.
+  my $count = countRows($sql);
+
+  if ($count == 0) {
+    printError "No customers currently listed as connected through this node.";
+  } else {
+    # Add the limit/offset clauses
+    $sql .= " order by cidr";
+    $sql .= " limit $RESULTS_PER_PAGE offset $offset" if $RESULTS_PER_PAGE != 0;
+    # And tell the user.
+    print "<div class=heading>Searching...............</div>\n";
+    queryResults($sql, $webvar{page}, $count);
+  }
+
+} else { # how script was called.  General case is to show the search criteria page.
+
+  # Display search page.  We have to do this here, because otherwise
+  # we can't retrieve data from the database for the types and cities.  >:(
+  my $html;
+  open HTML,"<../compsearch.html";
+  $html = join('',<HTML>);
+  close HTML;
+
+# Generate table of types
+  my $typetable = "<table class=regular cellspacing=0>\n<tr>";
+  $sth = $ip_dbh->prepare("select type,dispname from alloctypes where listorder <500 ".
+	"order by listorder");
+  $sth->execute;
+  my $i=0;
+  while (my @data = $sth->fetchrow_array) {
+    $typetable .= "<td><input type=checkbox name=type[$data[0]]>$data[1]</td>";
+    $i++;
+    $typetable .= "</tr>\n<tr>"
+	if ($i % 4 == 0);
+  }
+  if ($i %4 == 0) {
+    $typetable =~ s/<tr>$//;
+  } else {
+    $typetable .= "</tr>\n";
+  }
+  $typetable .= "</table>\n";
+
+# Generate table of cities
+  my $citytable = "<table class=regular cellspacing=0>\n<tr>";
+  $sth = $ip_dbh->prepare("select id,city from cities order by city");
+  $sth->execute;
+  my $i=0;
+  while (my @data = $sth->fetchrow_array) {
+    $citytable .= "<td><input type=checkbox name=city[$data[0]]>$data[1]</td>";
+    $i++;
+    $citytable .= "</tr>\n<tr>"
+	if ($i % 5 == 0);
+  }
+  if ($i %5 == 0) {
+    $citytable =~ s/<tr>$//;
+  } else {
+    $citytable .= "</tr>\n";
+  }
+  $citytable .= "</table>\n";
+
+  $html =~ s/\$\$TYPELIST\$\$/$typetable/;
+  $html =~ s/\$\$CITYLIST\$\$/$citytable/;
+
+  print $html;
+}
+
+# Shut down and clean up.
+finish($ip_dbh);
+printFooter;
+# We shouldn't need to directly execute any code below here;  it's all subroutines.
+exit 0;
+
+
+# viewBy()
+# The quick search
+# Takes a category descriptor and a query string
+# Creates appropriate SQL to run the search and display the results
+# with queryResults()
+sub viewBy($$) {
+  my ($category,$query) = @_;
+
+  # Local variables
+  my $sql;
+
+  # Calculate start point for LIMIT clause
+  my $offset = ($webvar{page}-1)*$RESULTS_PER_PAGE;
+
+# Possible cases:
+# 1) Partial IP/subnet.  Treated as "octet-prefix".
+# 2a) CIDR subnet.  Exact match.
+# 2b) CIDR netmask.  YMMV but it should be octet-prefix-with-netmask
+#	(ie, all matches with the octet prefix *AND* that netmask)
+# 3) Customer ID.  "Match-any-segment"
+# 4) Description.  "Match-any-segment"
+# 5) Invalid data which might be interpretable as an IP or something, but
+#    which probably shouldn't be for reasons of sanity.
+
+  my $cols = "cidr,custid,type,city,description";
+
+  if ($category eq 'all') {
+
+    print qq(<div class="heading">Showing all netblock and static-IP allocations</div><br>\n);
+    $sql = "select $cols from searchme";
+    my $count = countRows($sql);
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    queryResults($sql, $webvar{page}, $count);
+
+  } elsif ($category eq 'cust') {
+
+    print qq(<div class="heading">Searching for Customer IDs containing '$query'</div><br>\n);
+
+    # Query for a customer ID.  Note that we can't restrict to "numeric-only"
+    # as we have non-numeric custIDs in the legacy data.  :/
+    $sql = "select $cols from searchme where custid ilike '%$query%' or oldcustid ilike '%$query%' or description like '%$query%'";
+    my $count = countRows($sql);
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    queryResults($sql, $webvar{page}, $count);
+
+  } elsif ($category eq 'desc') {
+
+    print qq(<div class="heading">Searching for descriptions containing '$query'</div><br>\n);
+    # Query based on description (includes "name" from old DB).
+    $sql = "select $cols from searchme where description ilike '%$query%'".
+	" or custid ilike '%$query%'";
+    my $count = countRows($sql);
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    queryResults($sql, $webvar{page}, $count);
+
+  } elsif ($category =~ /ipblock/) {
+
+    # Query is for a partial IP, a CIDR block in some form, or a flat IP.
+    print qq(<div class="heading">Searching for IP-based matches on '$query'</div><br>\n);
+
+    $query =~ s/\s+//g;
+    if ($query =~ /\//) {
+      # 192.168.179/26 should show all /26 subnets in 192.168.179
+      my ($net,$maskbits) = split /\//, $query;
+      if ($query =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
+	# /0->/9 are silly to worry about right now.  I don't think
+	# we'll be getting a class A anytime soon.  <g>
+        $sql = "select $cols from searchme where cidr='$query'";
+	queryResults($sql, $webvar{page}, 1);
+      } else {
+	#print "Finding all blocks with netmask /$maskbits, leading octet(s) $net<br>\n";
+	# Partial match;  beginning of subnet and maskbits are provided
+	$sql = "select $cols from searchme where text(cidr) like '$net%' and ".
+		"text(cidr) like '%$maskbits'";
+	my $count = countRows($sql);
+	$sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+	queryResults($sql, $webvar{page}, $count);
+      }
+    } elsif ($query =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
+      # Specific IP address match
+      #print "4-octet pattern found;  finding netblock containing IP $query<br>\n";
+      my ($net,$ip) = ($query =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})/);
+      my $sfor = new NetAddr::IP $query;
+      $sth = $ip_dbh->prepare("select $cols from searchme where text(cidr) like '$net%'");
+      $sth->execute;
+      while (my @data = $sth->fetchrow_array()) {
+        my $cidr = new NetAddr::IP $data[0];
+	if ($cidr->contains($sfor)) {
+	  queryResults("select $cols from searchme where cidr='$cidr'", $webvar{page}, 1);
+	}
+      }
+    } elsif ($query =~ /^(\d{1,3}\.){1,3}\d{1,3}\.?$/) {
+      #print "Finding matches with leading octet(s) $query<br>\n";
+      $sql = "select $cols from searchme where text(cidr) like '$query%'";
+      my $count = countRows($sql);
+      $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+      queryResults($sql, $webvar{page}, $count);
+    } else {
+      # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+      printError("Invalid query.");
+    }
+  } else {
+    # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+    printError("Invalid searchfor.");
+  }
+} # viewBy
+
+
+# args are: a reference to an array with the row to be printed and the 
+# class(stylesheet) to use for formatting.
+# if ommitting the class - call the sub as &printRow(\@array)
+sub printRow {
+  my ($rowRef,$class) = @_;
+
+  if (!$class) {
+    print "<tr>\n";
+  } else {
+    print "<tr class=\"$class\">\n";
+  }
+
+ELEMENT:  foreach my $element (@$rowRef) {
+    if (!defined($element)) {
+      print "<td></td>\n";
+      next ELEMENT;
+    }
+    $element =~ s|\n|</br>|g;
+    print "<td>$element</td>\n";
+  }
+  print "</tr>";
+} # printRow
+
+
+# queryResults()
+# Display search queries based on the passed SQL.
+# Takes SQL, page number (for multipage search results), and a total count.
+sub queryResults($$$) {
+  my ($sql, $pageNo, $rowCount) = @_;
+  my $offset = 0;
+  $offset = $1 if($sql =~ m/.*limit\s+(.*),.*/);
+
+  my $sth = $ip_dbh->prepare($sql);
+  $sth->execute();
+
+  startTable('Allocation','CustID','Type','City','Description/Name');
+  my $count = 0;
+
+  while (my @data = $sth->fetchrow_array) {
+
+    # cidr,custid,type,city,description,notes
+    # Another bit of HairyPerl(TM) to prefix subblocks with "Sub"
+    my @row = (($data[2] =~ /^.r$/ ? 'Sub ' : '').
+	qq(<a href="/ip/cgi-bin/main.cgi?action=edit&block=$data[0]">$data[0]</a>),
+	$data[1], $disp_alloctypes{$data[2]}, $data[3], $data[4]);
+    # Allow listing of pool if desired/required.
+    if ($data[2] =~ /^.[pd]$/) {
+      $row[0] .= ' &nbsp; <a href="/ip/cgi-bin/main.cgi?action=listpool'.
+	"&pool=$data[0]\">List IPs</a>";
+    }
+    printRow(\@row, 'color1', 1) if ($count%2==0); 
+    printRow(\@row, 'color2', 1) if ($count%2!=0);
+    $count++;
+  }
+
+  # Have to think on this call, it's primarily to clean up unfetched rows from a select.
+  # In this context it's probably a good idea.
+  $sth->finish();
+
+  my $upper = $offset+$count;
+  print "<tr><td colspan=10 bgcolor=white class=regular>Records found: $rowCount<br><i>Displaying: ".($offset+1)." - $upper</i></td></tr>\n";
+  print "</table></center>\n";
+
+  # print the page thing..
+  if ($RESULTS_PER_PAGE > 0 && $rowCount > $RESULTS_PER_PAGE) {
+    my $pages = ceil($rowCount/$RESULTS_PER_PAGE);
+    print qq(<div class="center"> Page: );
+    for (my $i = 1; $i <= $pages; $i++) {
+      if ($i == $pageNo) {
+	print "<b>$i&nbsp;</b>\n";
+      } else {
+	print qq(<a href="/ip/cgi-bin/search.cgi?page=$i&stype=$webvar{stype}&);
+	if ($webvar{stype} eq 'c') {
+	  print "cidr=$webvar{cidr}&custid=$webvar{custid}&desc=$webvar{desc}&".
+		"notes=$webvar{notes}&which=$webvar{which}&alltypes=$webvar{alltypes}&".
+		"allcities=$webvar{allcities}&";
+	  foreach my $key (keys %webvar) {
+	    if ($key =~ /^(?:type|city)\[/ || $key =~ /exclude$/) {
+	      print "$key=$webvar{$key}&";
+	    }
+	  }
+	} else {
+	  print "input=$webvar{input}&";
+	}
+	print qq(">$i</a>&nbsp;\n);
+      }
+    }
+    print "</div>";
+  }
+} # queryResults
+
+
+# Prints table headings.  Accepts any number of arguments;
+# each argument is a table heading.
+sub startTable {
+  print qq(<center><table width="98%" cellspacing="0" class="center"><tr>);
+
+  foreach(@_) {
+    print qq(<td class="heading">$_</td>);
+  }
+  print "</tr>\n";
+} # startTable
+
+
+# Return count of rows to be returned in a "real" query
+# with the passed SQL statement
+sub countRows($) {
+  # Note that the "as foo" is required
+  my $sth = $ip_dbh->prepare("select count(*) from ($_[0]) as foo");
+  $sth->execute();
+  my @a = $sth->fetchrow_array();
+  $sth->finish();
+  return $a[0];
+}
Index: /branches/htmlform/cgi-bin/snCalc.cgi
===================================================================
--- /branches/htmlform/cgi-bin/snCalc.cgi	(revision 446)
+++ /branches/htmlform/cgi-bin/snCalc.cgi	(revision 446)
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use NetAddr::IP;
+use CommonWeb qw(:ALL);;
+
+#file snCalc.cgi	little subnet calculator app
+
+my %webvar = parse_post();
+my $input;
+
+print "Content-Type: text/html\n\n";
+
+open(HTML, "../startsn.html")|| die "Could not open startsn.html :$!";
+my $start = join('', <HTML>);
+close(HTML);
+print $start;
+
+# Clean up input so we don't divide by zero or something equally silly
+if ($webvar{input} =~ m/(\d+)/) {
+  $input = 1*$1;
+  $input = 3 if $input < 3;
+  $input = 29 if $input > 29;	# Not doing IPv6 yet...
+} else {
+  $input = 29;
+}
+
+my $ltinput = $input - 1;
+my $gtinput = $input + 1;
+
+my $prenet = new NetAddr::IP "0.0.0.0/$ltinput";
+my $net = new NetAddr::IP "0.0.0.0/$input";
+my $postnet = new NetAddr::IP "0.0.0.0/$gtinput";
+
+print qq(<div class="center">
+<table align="center" cellspacing="3" cellpadding="3">
+<tr>
+	<td class="heading" align="center">Results for /$ltinput</td>
+	<td class="heading" align="center">Results for /$input</td>
+	<td class="heading" align="center">Results for /$gtinput</td>
+</tr>
+);
+
+print qq(<tr><td valign="top">\n).
+	qq(	<div class="mask">).$prenet->mask."</div>\n".
+	qq(	<div class="wildcard">).$prenet->wildcard."</div>\n".
+	getranges($ltinput).
+	qq(</td>\n<td valign="top" bgcolor="#d0e0e0">\n).
+	qq(	<div class="mask">).$net->mask."</div>\n".
+	qq(	<div class="wildcard">).$net->wildcard."</div>\n".
+	getranges($input).
+	qq(</td>\n<td valign="top">).
+	qq(	<div class="mask">).$postnet->mask."</div>\n".
+	qq(	<div class="wildcard">).$postnet->wildcard."</div>\n".
+	getranges($gtinput);
+
+print "</td></tr>\n</table>\n";
+
+print qq(<input type="button" value="Back" onclick="history.go(-1)" class="heading">
+</div>
+</body>
+</html>
+);
+	
+# Just In Case
+exit 0;
+
+# subs
+sub xrange {
+  my $block = shift;
+  my $masklen = shift;
+  my $data = $block->range;
+  if ($masklen >= 24) {
+    $data =~ s/\b0\.0\.0\./x.x.x./g;
+  } elsif ($masklen >=16) {
+    $data =~ s/\b0\.0\.(\d+\.\d+)/x.x.$1/g;
+  } elsif ($masklen >=8) {
+    $data =~ s/\b0\.(\d+\.\d+\.\d+)/x.$1/g;
+  }
+  return $data;
+} # xrange()
+
+
+sub getranges {
+  my $masklen = shift;
+  my $ret = '';
+  my $super;
+  if ($masklen < 8) {
+    $super = new NetAddr::IP "0.0.0.0/0";
+  } elsif ($masklen < 16) {
+    $super = new NetAddr::IP "0.0.0.0/8";
+  } elsif ($masklen < 24) {
+    $super = new NetAddr::IP "0.0.0.0/16";
+  } else {
+    $super = new NetAddr::IP "0.0.0.0/24";
+  }
+  foreach my $net ($super->split($masklen)) {
+    $ret .= "\t".xrange($net,$masklen)."<br />\n";
+  }
+  return $ret;
+} # getranges()
Index: /branches/htmlform/compsearch.html
===================================================================
--- /branches/htmlform/compsearch.html	(revision 446)
+++ /branches/htmlform/compsearch.html	(revision 446)
@@ -0,0 +1,42 @@
+<div class="indent">
+<div class="heading">Complex Search</div>
+<form action="/ip/cgi-bin/search.cgi" method=POST>
+<table bgcolor="black" cellspacing="1" cellpadding="2">
+<tr class="color1">
+<td colspan=2>Match on:<input type=radio checked name=which value="all">All
+<input type=radio name=which value="any">Any
+<td>Results per page: <select name=rpp>
+<option value='25'>25</option>
+<option value='50'>50</option>
+<option value='100'>100</option>
+<option value='200'>200</option>
+<option value='0'>Show all</option>
+</select>
+<td align=right><input type=submit value="Search Now"></td>
+</td>
+<tr class="color1">
+<td>Exclude?<input type=checkbox name=cidrexclude value='not'></td>
+<td>IP/netblock:</td><td colspan=2><input name=cidr></td>
+</tr><tr class="color2">
+<td>Exclude?<input type=checkbox name=custexclude value='not'></td>
+<td>CustID:</td><td colspan=2><input name=custid></td>
+</tr><tr class="color1">
+<td>Exclude?<input type=checkbox name=descexclude value='not'></td>
+<td>Description:</td><td colspan=2><input name=desc></td>
+</tr><tr class="color2">
+<td>Exclude?<input type=checkbox name=notesexclude value='not'></td>
+<td>Notes:</td><td colspan=2><input name=notes></td>
+</tr><tr class="color1">
+<td>Exclude?<input type=checkbox name=typeexclude value='not'></td>
+<td>Types:</td><td colspan=2><input type=checkbox name=alltypes checked>Show all types
+$$TYPELIST$$</td>
+</tr><tr class="color2">
+<td>Exclude?<input type=checkbox name=cityexclude value='not'></td>
+<td>Cities:</td><td colspan=2><input type=checkbox name=allcities checked>Show all cities
+$$CITYLIST$$</td>
+</tr>
+<input type=hidden name=stype value=c>
+<input type=hidden name=page value=1>
+</table>
+</form>
+</div>
Index: /branches/htmlform/confirm.html
===================================================================
--- /branches/htmlform/confirm.html	(revision 446)
+++ /branches/htmlform/confirm.html	(revision 446)
@@ -0,0 +1,34 @@
+<div class="heading">Please confirm</div>
+<div class="indent">
+<table class="regular" bgcolor="white" cellspacing="1" cellpadding="1">
+<form method="POST" action="main.cgi" class="regular">
+$$CUSTBITS$$
+<tr class="color1">
+<td>CIDR block to be allocated:&nbsp;</td><td>&nbsp;$$CIDR$$<input type=hidden name="fullcidr" value="$$CIDR$$"></td>
+</tr><tr class="color2">
+<td>Block allocation is taken from:&nbsp;</td><td>&nbsp;$$ALLOC_FROM$$</td>
+</tr><tr class="color1">
+<td>City:&nbsp;</td><td>&nbsp;$$CITY$$<input type="hidden" name="city" value="$$CITY$$"></td>
+</tr><tr class="hack">
+<td>Demarc switch/wifi tower:&nbsp;</td><td>&nbsp;$$NODENAME$$<input type="hidden" name="node" value="$$NODEID$$"></td>
+</tr><tr class="color2">
+<td>Allocation type:&nbsp;</td><td>&nbsp;$$TYPEFULL$$</td>
+</tr><tr class="color1">
+<td>Customer ID:&nbsp;</td><td>&nbsp;$$CUSTID$$<input type="hidden" name="custid" value="$$CUSTID$$"></td>
+</tr><tr class="color2">
+<td valign="top">Cicruit ID:&nbsp;</td><td>&nbsp;$$CIRCID$$<input type="hidden" name="circid" value="$$CIRCID$$"></td>
+</tr><tr class="color1">
+<td valign="top">Description/name:&nbsp;</td><td>&nbsp;$$DESC$$<input type="hidden" name="desc" value="$$DESC$$"></td>
+</tr><tr class="color2">
+<td valign="top">Notes:&nbsp;</td><td>&nbsp;$$NOTES$$<input type="hidden" name="notes" value="$$NOTES$$"></td>
+</tr>
+$$PRIVDATA$$
+<!-- warn -->
+<input type="hidden" name="alloctype" value="$$ALLOC_TYPE$$">
+<input type="hidden" name="action" value="$$ACTION$$">
+<tr class="$$BUTTONROWCOLOUR$$">
+<td class="center" colspan="2">
+<input type="button" value="Back" onclick="history.go(-1)"><input type="submit" value="Confirm">
+</td></tr>
+</table>
+</div>
Index: /branches/htmlform/confirmRemove.html
===================================================================
--- /branches/htmlform/confirmRemove.html	(revision 446)
+++ /branches/htmlform/confirmRemove.html	(revision 446)
@@ -0,0 +1,19 @@
+<div class="heading">Please confirm:</div>
+<div class="indent">
+<table class="regular" bgcolor="white" cellspacing="1" cellpadding="1">
+<form action="main.cgi" method="POST" class="regular">
+<tr class="color1"><td>IP block:</td><td>$$BLOCK$$<input type=hidden name=block value="$$BLOCK$$"></td></tr>
+<tr class="color2"><td>City:</td><td>$$CITY$$<input type=hidden name=city value="$$CITY$$"></td></tr>
+<tr class="color1"><td>Type:</td><td>$$TYPEFULL$$<input type=hidden name=alloctype value="$$ALLOCTYPE$$"></td></tr>
+<tr class="color2"><td>Customer ID:</td><td>$$CUSTID$$</td></tr>
+<tr class="color2"><td>Circuit ID:</td><td>$$CIRCID$$</td></tr>
+<tr class="color1"><td valign="top">Description/Name:</td><td>$$DESC$$</td></tr>
+<tr class="color2"><td valign="top">Notes:</td><td>$$NOTES$$</td></tr>
+$$PRIVDATA$$
+<!--warn-->
+<input type="hidden" name="action" value="$$ACTION$$">
+<tr class="$$BUTTONROWCOLOUR$$"><td class="center" colspan=2>
+<input type="button" value="Back" onclick="history.go(-1)"><input type="submit" value="Confirm">
+</td></tr>
+</table>
+</div>
Index: /branches/htmlform/editDisplay.html
===================================================================
--- /branches/htmlform/editDisplay.html	(revision 446)
+++ /branches/htmlform/editDisplay.html	(revision 446)
@@ -0,0 +1,35 @@
+<div class="heading"><div class="indent">Edit the information below</div></div>
+
+<table class="indent" size="33%" cellspacing=1 bgcolor="black">
+<form method="POST" action="main.cgi">
+<input type="hidden" name="action" value="update">
+<input type="hidden" name="block" value="$$BLOCK$$">
+
+<tr class="color1"><td class=heading>IP block:</td><td class="regular">$$BLOCK$$</td></tr>
+
+<tr class="color2"><td class=heading>City:</td><td class="regular">$$CITY$$</td></tr>
+
+<tr class="color1"><td class=heading>Type:</td><td class=regular>$$TYPESELECT$$</td></tr>
+
+<tr class="hack"><td class=heading>Demarc/tower:</td><td class=regular>$$NODE$$</td></tr>
+
+<tr class="color2"><td class=heading>CustID:</td><td class="regular">$$CUSTID$$</td></tr>
+<tr class="color2"><td class=heading>Old CustID:</td><td class="regular">$$OLDCUSTID$$</td></tr>
+
+<tr class="color2"><td class=heading>SWIPed?:</td><td class=regular>$$SWIP$$</td></tr>
+
+<tr class="color1"><td class=heading>Last modified:</td><td class=regular>$$LASTMOD$$</td></tr>
+
+<tr class="color2"><td class="heading">Circuit ID:</td><td class="regular">$$CIRCID$$</td></tr>
+
+<tr class="color1"><td class="heading">Description/Name:</td><td class="regular">$$DESC$$</td></tr>
+
+<tr class="color2"><td class="heading" valign="top">Notes:</td><td class="regular">$$NOTES$$</td></tr>
+
+$$PRIVDATA$$
+$$UPDOK$$
+$$DELOK$$
+</form>
+
+</table>
+</br>
Index: /branches/htmlform/fb-assign.html
===================================================================
--- /branches/htmlform/fb-assign.html	(revision 446)
+++ /branches/htmlform/fb-assign.html	(revision 446)
@@ -0,0 +1,39 @@
+<div class="indent">
+<div class="heading">Assign IPs</div><br>
+<table class="regular" bgcolor="black" cellspacing="1" cellpadding="1">
+<form method="POST" action="main.cgi" class="regular">
+<tr class="color1"><td>Free block selected for assignment:&nbsp;</td><td>$$BLOCK$$<input type=hidden name=block value="$$BLOCK$$"></td></tr>
+<tr class="color2">
+<td>City:&nbsp;</td><td>
+<select name="city"><option selected>-</option>
+$$ALLCITIES$$
+</select>
+</td>
+</tr>
+<tr class="color1">
+<td>Allocation type:</td><td>
+$$TYPELIST$$
+<input type="button" value=" ? " onclick="helpAllocTypes()" class="regular">
+</td>
+</tr><tr class="color2">
+<td>Customer ID:&nbsp;</td><td><input type="text" name="custid" size="15" maxlength="15"> (Only required for Customer allocations)</td>
+</tr>
+<tr class="hack"><td>Wifi tower/Fibre demarc</td><td>
+<select name="node"><option selected>-</option>
+$$NODELIST$$
+</select>
+&nbsp;<a href="javascript:popNotes('/ip/newnode.html')">Add new location</a>
+</td></tr>
+<tr class="color1">
+<td valign="top">Description/Name:&nbsp;</td><td><input name="desc" size=40></td>
+</tr><tr class="color2">
+<td>Notes:&nbsp;</td><td><textarea name="notes" rows="3" cols="40"></textarea></td>
+</tr>
+$$PRIVDATA$$
+<tr class="$$BUTTONROWCOLOUR$$">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+<input type="hidden" name="action" value="confirm">
+<input type="hidden" name=fbassign value=y>
+</tr>
+</table>
+</div>
Index: /branches/htmlform/footer.inc
===================================================================
--- /branches/htmlform/footer.inc	(revision 446)
+++ /branches/htmlform/footer.inc	(revision 446)
@@ -0,0 +1,20 @@
+<br><br>
+<table width="100%" border="0" cellspacing="0" cellpadding="0" height="1" bgcolor="#000000">
+  <tr>
+    <td></td>
+  </tr>
+</table>
+<center>
+<table width="98%" border="0" cellspacing="0" cellpadding="1">
+  <tr>
+    <td nowrap="" width="0" height="0" valign="top"> 
+      <div align="right"><font face="Arial, Helvetica, sans-serif" size="1"> DeepNet
+ <a href="http://projects.deepnet.cx/trac/ipdb" target="_blank">IP Database</a> |
+ Copyright 2004-2010 <a href="mailto:kdeugau@deepnet.cx">Kris Deugau</a><br>
+Written for standards-based browsers (eg <a href="http://www.mozilla.org">Mozilla</a>)</font></div>
+    </td>
+  </tr>
+</table>
+</center>
+</body>
+</html>
Index: /branches/htmlform/header.inc
===================================================================
--- /branches/htmlform/header.inc	(revision 446)
+++ /branches/htmlform/header.inc	(revision 446)
@@ -0,0 +1,66 @@
+<html>
+<head>
+	<title>[IP Database v2]</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> 
+	<link rel="stylesheet" type="text/css" href="/ip/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="/ip/local.css">
+
+<script language=javascript>
+function openHelp() {
+	window.open("/ip/help.html", "help_window", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width=700,height=400")
+}
+function openTables() {
+	window.open("/ip/tables.html", "subnet_tables", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width=600,height=250")	
+}
+function helpAllocTypes() {
+	window.open("/ip/alloctypes.html", "alloc_window", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width=750,height=550")
+}
+function popNotes(page) {
+	window.open(page, "IPDB_notes", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width=400,height=300");
+}
+</script>
+
+</head>
+<body>
+
+<table width="98%" border="0" cellspacing="0" cellpadding="0" height="0">
+  <tbody><tr>
+      <td nowrap=""><a href="/ip/index.shtml" target="_blank"><img src="/ip/images/logo.png" width="167" height="60" border="0" alt="[ Logo ]"></a></td>
+    <td nowrap="" valign="bottom"> 
+      <div align="right"><b><font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+	  <a href="/ip/index.shtml">
+	  [IP Database v2 Home]</a></font></b></div>
+    </td>
+  </tr>
+</tbody></table>
+
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+<tr bgcolor="#000000"><td></td></tr></table>
+<!-- end line and top. -->
+<table width="100%" cellspacing="0">
+<tr class="color1">
+<td width=10></td>
+<form method="POST" action="/ip/cgi-bin/search.cgi">
+<td width=390>Quick Search:
+<input type="text" name="input" size="20" maxlength="50" class="regular">
+<input type=hidden name=page value="1">
+<input type=hidden name=stype value="q">
+<input type=submit value="Go!" class="heading">
+<input type="button" value=" Help? " onclick="openHelp()" class="regular">
+</td><td width=10></td><td><a href="/ip/cgi-bin/search.cgi">Complex Search</a></td>
+$$EXTRA0$$
+<td width=60></td>
+</form>
+</tr>
+</table>
+<!-- start another line -->
+<!-- There has GOT to be a better way to do this...  Ugh.  -->
+<table width="100%" border="0" cellspacing="0" cellpadding="0" height="1" bgcolor="#000000">
+  <tbody><tr>
+   <td></td>
+  </tr>
+</tbody></table>
+<table width="98%" border=0><tr><td align=right>
+<a href="/ip/cgi-bin/main.cgi?action=nodesearch">Find by connection point</a>
+</td></tr></table>
+<br>
Index: /branches/htmlform/help.html
===================================================================
--- /branches/htmlform/help.html	(revision 446)
+++ /branches/htmlform/help.html	(revision 446)
@@ -0,0 +1,64 @@
+<html><head>
+
+<title>IP Database</title><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+
+<link rel="stylesheet" type="text/css" href="/ip/ipdb.css" />
+<link rel="stylesheet" type="text/css" href="/ip/local.css" />
+
+</head>
+<body>
+
+<table class="regular">
+
+<tr><td class="heading">Quick Searches:</td><tr>
+
+<tr class="color1">
+<td>IP blocks</td><td>192.168.28 or 192.</td>
+<td>Lists all alloctions starting with that set of octets.  Note that matches on the
+first octet MUST include the period to be considered an IP search.</td>
+</tr>
+<tr class="color2">
+<td>CIDR blocks</td><td>192.168.28/30</br>or
+192.168.28.0/30</td><td>Lists all /30's beginning with 192.168.28 or checks for an
+exact match for 192.168.28.0/30 respectively</td>
+</tr>
+<tr class="color1">
+<td>IP address</td><td>192.168.28.30</td><td>Finds the alloction that IP is a
+part of (if any)</td>
+</tr>
+<tr class="color2">
+<td>Customer ID:</td><td>123456</td><td>Find all alloctions to that
+customer.  Customer IDs are assumed to be numeric for this search.
+</td>
+</tr>
+<tr class="color1">
+<td>Description:</td><td>cable or BigCustomer</td><td>Find all allocations with the search term in
+the description.  Note that searches for CustIDs with letters will fall under this category
+unless CustIDs are all-numeric.</td>
+</tr>
+
+<tr class="regular"><!-- blank row --><td></td></tr>
+<tr><td colspan="3">A blank query will "show all"</td></tr>
+<tr><td colspan="3">The title in the top right hand corner is a link home.</td></tr>
+
+<tr class="regular"><!-- blank row --><td></td></tr>
+<tr class="regular"><!-- blank row --><td></td></tr>
+<tr class="regular"><!-- blank row --><td></td></tr>
+
+<tr class="color1"><td>Subnet Calculator</td>
+<td colspan="2">
+<form method="POST" action="cgi-bin/snCalc.cgi">
+ / <input type="text" size="5" maxlength="10" name="input" class="regular">
+<input type="submit" value="Calculate" class="heading">&nbsp; Show
+<span class="mask">subnet mask</span>, <span class="wildcard">wildcard mask</span>, 
+and possible subnet ranges for the entered mask length.
+</form>
+</tr>
+
+<tr><td>
+
+
+</table>
+
+
+</body></html>
Index: /branches/htmlform/index.shtml
===================================================================
--- /branches/htmlform/index.shtml	(revision 446)
+++ /branches/htmlform/index.shtml	(revision 446)
@@ -0,0 +1,1 @@
+<!--#include virtual="/ip/cgi-bin/main.cgi?action=index" -->
Index: /branches/htmlform/ipdb.css
===================================================================
--- /branches/htmlform/ipdb.css	(revision 446)
+++ /branches/htmlform/ipdb.css	(revision 446)
@@ -0,0 +1,89 @@
+body {
+  background-color: #ffffff;
+  text-color: #000000;
+  font-family: helvetica;
+  margin: 0;
+}
+
+a { text-decoration: underline; }
+a:link          { color:#3333ff; }      /* unvisited link */
+a:visited       { color:#663366; }      /* visited link */
+a:hover         { color:#dd00dd; }      /* mouse over link */
+a:active        { color:#cc0000; }      /* selected link */
+
+tr.color0 {
+	background-color: #A8C4D0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 90%;
+}
+
+tr.color1 {
+	background-color: #d0e0e0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 90%;
+}
+
+tr.color2 {
+	background-color: #A8C4D0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 90%;
+}
+
+tr.hack {
+	background-color: #E4EEE8;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 90%;
+}
+
+tr.warning {
+	background-color: #000000;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-weight: bold;
+	color: red;
+}
+
+tr.header {
+	background-color: #CCCCCC;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+.indent {
+	margin-left: 5%;
+	font-size: 90%;
+}
+
+.regular { 
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 100%;
+}
+
+.heading {
+	font-size: 110%;
+	font-weight: bold;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+.small {
+	font-size: 60%;
+}
+
+.center {
+	text-align: center;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 100%;
+}
+
+.red {
+	font-weight: bold;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size:100%;
+	color: red;
+}
+
+.mask {
+	background: #00ff00;
+}
+
+.wildcard {
+	background: #ffff00;
+}
Index: /branches/htmlform/ipdb.spec
===================================================================
--- /branches/htmlform/ipdb.spec	(revision 446)
+++ /branches/htmlform/ipdb.spec	(revision 446)
@@ -0,0 +1,87 @@
+# spec file for IPDB
+# $Id$
+
+%define errata 99
+
+# A collection of magic to set the release "number" such that dist upgrades will, erm, upgrade.
+%if %{?debdist:0}%{?!debdist:1}
+%define debdist etch
+%endif
+%if "%{debdist}" == "sarge"
+%define errata 0
+%endif
+%if "%{debdist}" == "dapper"
+%define errata 1
+%endif
+%if "%{debdist}" == "etch"
+%define errata 2
+%endif
+%if "%{debdist}" == "lenny"
+%define errata 3
+%endif
+%if "%{debdist}" == "squeeze"
+%define errata 4
+%endif
+%if %{?relnum:0}%{?!relnum:1}
+%define relnum 1
+%endif
+
+%define release %{relnum}.%{errata}%{debdist}
+
+Summary: IP Database
+Name: ipdb
+Version: #VERSION#
+Release: %{release}
+Group: Applications/System
+Source: ipdb-#VERSION#.tar.gz
+Packager: Kris Deugau <kdeugau@deepnet.cx>
+BuildRoot: /var/tmp/%{name}-%{version}
+License: GPL 3+
+BuildArch: noarch
+Requires: perl(NetAddr::IP) >= 4, perl(DBD:Pg)
+%if %{_vendor} == "debbuild"
+Recommends: perl(Sys::SigAction)
+%endif
+
+%description
+A web-based IP address allocation management tool
+
+%prep
+%setup
+
+%build
+
+%install
+# le sigh.  rpm's makeinstall macro includes the buildroot in the dirs, so it will break here.  :/
+make \
+        prefix=%{_prefix} \
+        exec_prefix=%{_exec_prefix} \
+        bindir=%{_bindir} \
+        sbindir=%{_sbindir} \
+        sysconfdir=%{_sysconfdir} \
+        datadir=%{_datadir} \
+        includedir=%{_includedir} \
+        libdir=%{_libdir} \
+        libexecdir=%{_libexecdir} \
+        localstatedir=%{_localstatedir} \
+        sharedstatedir=%{_sharedstatedir} \
+        mandir=%{_mandir} \
+        infodir=%{_infodir} \
+  install DESTDIR=$RPM_BUILD_ROOT
+
+%clean
+if [ "$RPM_BUILD_ROOT" != "/" ]; then
+  rm -rf $RPM_BUILD_ROOT
+fi
+
+%files
+%{_libdir}/ipdb
+%attr(-,-,0755) %{_libdir}/ipdb/cgi-bin/*.cgi
+%attr(-,-,0755) %{_libdir}/ipdb/cgi-bin/*.pl
+%attr(-,-,0755) %{_libdir}/ipdb/cgi-bin/extras/*.pl
+%config %{_sysconfdir}/ipdb/MyIPDB.pm
+%{_sysconfdir}/ipdb
+
+%changelog
+* Mon Jun 28 2010  Kris Deugau <kdeugau@deepnet.cx> 2.6-1
+- Initial package
Index: /branches/htmlform/newcity.html
===================================================================
--- /branches/htmlform/newcity.html	(revision 446)
+++ /branches/htmlform/newcity.html	(revision 446)
@@ -0,0 +1,27 @@
+<html>
+<head>
+	<title>IP Database - Add new city</title>
+	<link rel="stylesheet" type="text/css" href="/ip/ipdb.css" />
+	<link rel="stylesheet" type="text/css" href="/ip/local.css" />
+
+	<SCRIPT language="JavaScript" type="text/javascript">
+	<!-- Hide From Non-Supporting Browsers
+	// snagged from http://www.webxpertz.net/forums/showthread.php?t=24650
+	function redoParent() {
+		opener.location.reload(true);
+	}
+
+	// Done Hiding -->
+	</script>
+
+</head>
+
+<body onUnload="redoParent()">
+<table>
+<form action="cgi-bin/newcity.cgi" method="POST">
+<tr class="color1"><td>Enter new city:</td><td><input name="city"></td></tr>
+<tr class="color2"><td>POP site?</td><td><input type=checkbox name="pop"></td></tr>
+<tr class="color1"><td colspan=2 align=center><input type=submit value="Add city"></td></tr>
+</table>
+</body>
+</html>
Index: /branches/htmlform/newnode.html
===================================================================
--- /branches/htmlform/newnode.html	(revision 446)
+++ /branches/htmlform/newnode.html	(revision 446)
@@ -0,0 +1,31 @@
+<html>
+<head>
+	<title>IP Database - Add new tower/demarc switch</title>
+	<link rel="stylesheet" type="text/css" href="/ip/ipdb.css" />
+	<link rel="stylesheet" type="text/css" href="/ip/local.css" />
+
+	<SCRIPT language="JavaScript" type="text/javascript">
+	<!-- Hide From Non-Supporting Browsers
+	// snagged from http://www.webxpertz.net/forums/showthread.php?t=24650
+	function redoParent() {
+		opener.location.reload(true);
+	}
+
+	// Done Hiding -->
+	</script>
+
+</head>
+
+<body onUnload="redoParent()">
+<table>
+<form action="cgi-bin/newnode.cgi" method="POST">
+<tr class="color1"><td>Enter new AP/switch name/location:</td><td><input name="nodename"></td></tr>
+<tr class="color2"><td>Enter management IP:</td><td><input name="nodeip"></td></tr>
+<tr class="color1"><td>Wifi tower or fibre demarc switch?</td>
+<td><input type=radio name="type" value="bi">Wifi tower<br>
+<input type=radio name="type" value="fr">Fibre demarc
+</td></tr>
+<tr class="color2"><td colspan=2 align=center><input type=submit value="Add"></td></tr>
+</table>
+</body>
+</html>
Index: /branches/htmlform/nodesearch.html
===================================================================
--- /branches/htmlform/nodesearch.html	(revision 446)
+++ /branches/htmlform/nodesearch.html	(revision 446)
@@ -0,0 +1,20 @@
+<div class="indent">
+<div class="heading">Switch/tower search</div>
+<form action="/ip/cgi-bin/search.cgi" method=POST>
+<table bgcolor="black" cellspacing="1" cellpadding="2">
+<tr class="color1">
+<td>Find customers connected through to:</td>
+<td><select name=node>
+$$NODELIST$$
+</select></td>
+</tr>
+
+<tr class="color2">
+<td colspan=2 align=center><input type=submit value="Find customers"></td>
+</tr>
+<input type=hidden name=stype value=n>
+<input type=hidden name=page value=1>
+<input type=hidden name=rpp value=50>
+</table>
+</form>
+</div>
Index: /branches/htmlform/startsn.html
===================================================================
--- /branches/htmlform/startsn.html	(revision 446)
+++ /branches/htmlform/startsn.html	(revision 446)
@@ -0,0 +1,10 @@
+<html>
+<head>
+
+<title>IP Database</title>
+
+<link rel="stylesheet" type="text/css" href="/ip/ipdb.css" />
+<link rel="stylesheet" type="text/css" href="/ip/local.css" />
+
+</head>
+<body>
Index: /branches/htmlform/updated.html
===================================================================
--- /branches/htmlform/updated.html	(revision 446)
+++ /branches/htmlform/updated.html	(revision 446)
@@ -0,0 +1,18 @@
+<div class="heading">Allocation info updated:</div>
+<div class="indent">
+<table class="regular" bgcolor="white" cellspacing="1" cellpadding="1">
+<tr class="color1"><td>IP block:</td><td>$$BLOCK$$</td></tr>
+<tr class="color2"><td>City:</td><td>$$CITY$$</td></tr>
+<tr class="color1"><td>Type:</td><td>$$TYPEFULL$$</td></tr> 
+<tr class="color2"><td>Customer ID:</td><td>$$CUSTID$$</td></tr>
+<tr class="color2"><td>SWIPed?:</td><td>$$SWIP$$</td></tr>
+<tr class="color1"><td>Circuit ID:</td><td>$$CIRCID$$</td></tr>
+<tr class="color2"><td valign="top">Description/Name:</td><td>$$DESC$$</td></tr>
+<tr class="color1"><td valign="top">Notes:</td><td>$$NOTES$$</td></tr>
+$$PRIVDATA$$
+</table>
+</div>
+<p>
+<div name="backlink">
+<a href="$$BACKLINK$$">Back to $$BACKBLOCK$$</a>
+</div>
