Index: tags/2.8.2/COPYING
===================================================================
--- tags/2.8.2/COPYING	(revision 617)
+++ tags/2.8.2/COPYING	(revision 617)
@@ -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: tags/2.8.2/INSTALL
===================================================================
--- tags/2.8.2/INSTALL	(revision 617)
+++ tags/2.8.2/INSTALL	(revision 617)
@@ -0,0 +1,149 @@
+$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) As a Postgres superuser, create a database user and the database:
+
+shell> psql template1
+pg# create user ipdb with password "ipdbpwd";
+pg# create database ipdb owner ipdb;
+
+Add the PL/pgSQL language to the database.  This is not strictly
+necessary but there are triggers on the poolips and allocations table
+to automatically update a last-modified column.
+
+pg# \c ipdb
+pg# create language plpgsql
+
+Create the inital tables using cgi-bin/ipdb.psql:
+
+shell> psql -U ipdb ipdb <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#)
+should work fine;  a server alias under an existing virtual host should
+work as well.
+
+Set $IPDB::webpath (the web path to your IPDB install) in MyIPDB.pm.
+Straight out of the tarball it should work at the webroot, but if you
+want it in a subdirectory, you'll need to set this variable to get all
+of the internal links to behave properly.
+
+The directory containing the HTML and scripts must have at least the 
+following Apache directives (or other server equivalent) set:
+
+  Options ExecCGI IncludesNoEXEC
+
+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) Use the built-in user manager as in a) but create a short script to
+    export the user list to a standard .htpasswd file.  This may be
+    useful if mod_auth_pgsql isn't easily available.
+
+  c) 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: tags/2.8.2/Makefile
===================================================================
--- tags/2.8.2/Makefile	(revision 617)
+++ tags/2.8.2/Makefile	(revision 617)
@@ -0,0 +1,116 @@
+# $Id$
+# IPDB makefile
+
+PKGNAME=ipdb
+VERSION=2.8.2
+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 =
+
+HTML = \
+	alloctypes.html help.html index.shtml ipdb.css
+
+JS = templates/widgets.js
+
+IMAGES = images/logo.png
+
+TEMPLATES = \
+	templates/aclerror.tmpl templates/addmaster.tmpl templates/assign.tmpl templates/confirm.tmpl \
+	templates/dberr.tmpl templates/delete.tmpl templates/dunno.tmpl templates/edit.tmpl \
+	templates/finaldelete.tmpl templates/footer.tmpl templates/header.tmpl templates/index.tmpl \
+	templates/insert.tmpl templates/listpool.tmpl templates/newcity.tmpl templates/newmaster.tmpl \
+	templates/newnode.tmpl templates/nodesearch.tmpl templates/showmaster.tmpl templates/showrouted.tmpl \
+	templates/subnet-calc.tmpl templates/update.tmpl \
+	\
+	templates/admin/aclerr.tmpl templates/admin/addnotice.tmpl templates/admin/alloc.tmpl \
+	templates/admin/alloctweak.tmpl templates/admin/confirm.tmpl templates/admin/dberr.tmpl \
+	templates/admin/delnotice.tmpl templates/admin/deluser.tmpl templates/admin/dunno.tmpl \
+	templates/admin/edcust.tmpl templates/admin/ednotice.tmpl templates/admin/emailnotice.tmpl \
+	templates/admin/header.tmpl templates/admin/listcust.tmpl templates/admin/main.tmpl \
+	templates/admin/newuser.tmpl templates/admin/showallocs.tmpl templates/admin/showpools.tmpl \
+	templates/admin/showusers.tmpl templates/admin/touch.tmpl templates/admin/tweakpool.tmpl \
+	templates/admin/updacl.tmpl templates/admin/updatepool.tmpl templates/admin/update.tmpl \
+	templates/admin/updcust.tmpl templates/admin/updnotice.tmpl \
+	\
+	templates/search/compsearch.tmpl templates/search/sresults.tmpl
+
+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/ipdb-rpc.cgi cgi-bin/newcity.cgi cgi-bin/allocate.pl \
+	cgi-bin/search.cgi cgi-bin/consistency-check.pl cgi-bin/snCalc.cgi
+
+MODULES = cgi-bin/IPDB.pm
+
+CONFIGMODULES = cgi-bin/MyIPDB.pm cgi-bin/CustIDCK.pm
+
+RWHOIS = \
+	cgi-bin/extras/rwhois-net-skel.tar.gz cgi-bin/extras/rwhois-config \
+	cgi-bin/extras/network.tmpl
+
+DIRS = images templates cgi-bin cgi-bin/extras
+
+MANIFEST = \
+	$(HTML) \
+	$(JS) \
+	$(IMAGES) \
+	$(TEMPLATES) \
+	$(SCRIPTS) \
+	$(MODULES) \
+	$(CONFIGMODULES) \
+	$(RWHOIS)
+
+all:
+	# nullop
+
+install:
+	@for i in $(HTML) $(IMAGES) $(JS) $(TEMPLATES); 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)/g;s/#RELEASE#/$(RELEASE)/g;s/#BETA#//g' < $(PKGNAME).spec > $(PKGNAME)-$(VERSION)/$(PKGNAME).spec
+	/usr/bin/perl -p -e 's/#VERSION#/$(VERSION)/g;s/#RELEASE#/$(RELEASE)/g;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 -a $(PKGNAME)-$(VERSION).tar.gz
Index: tags/2.8.2/alloctypes.html
===================================================================
--- tags/2.8.2/alloctypes.html	(revision 617)
+++ tags/2.8.2/alloctypes.html	(revision 617)
@@ -0,0 +1,204 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>IP Database - Allocation Types</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+	<link rel="stylesheet" type="text/css" href="ipdb.css">
+	<link rel="stylesheet" type="text/css" href="local.css">
+</head>
+
+<body>
+
+<table class="regular">
+
+<tr><td colspan=2 class="heading">Preseeded Allocation Types:</td></tr>
+
+<tr class="row0">
+<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="row1">
+<td>Static IP - Server pool</td>
+<td>A single IP from a pool designated for servers</td>
+</tr>
+
+<tr class="row0">
+<td>Static IP - Cable</td>
+<td>A single IP address from a designated pool on the cable network.</td>
+</tr>
+
+<tr class="row1">
+<td>Static IP - DSL</td>
+<td>A single IP address from a designated pool on the DSL network.</td>
+</tr>
+
+<tr class="row0">
+<td>Static IP - Dialup</td>
+<td>A single IP address from a designated pool on a dialup RAS.</td>
+</tr>
+
+<tr class="row1">
+<td>Static IP - Wireless</td>
+<td>A single IP address from a designated pool on a wireless connection.</td>
+</tr>
+
+<tr class="row0">
+<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="row1">
+<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="row0">
+<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="row1">
+<td>Static pool - Dialup</td>
+<td>A pool of IP addresses available for one-by-one assignment to dialup 
+customers.</td>
+</tr>
+
+<tr class="row0">
+<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="row1">
+<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="row0">
+<td>Dialup netblock</td>
+<td>Netblock assigned to one or more RAS units in a POP</td>
+</tr>
+
+<tr class="row1">
+<td>Dynamic DSL block</td>
+<td>Netblock for (mostly residential) PPPoE DSL.</td>
+</tr>
+
+<tr class="row0">
+<td>Dynamic cable block</td>
+<td>Netblock for (mostly residential) DHCP cable.</td>
+</tr>
+
+<tr class="row1">
+<td>Dynamic WiFi block</td>
+<td>Netblock for (mostly residential) (mostly) PPPoE wireless.</td>
+</tr>
+
+<tr class="row0">
+<td>Dynamic VoIP block</td>
+<td>Netblock for DHCP-assigned VoIP services.</td>
+</tr>
+
+<tr class="row1">
+<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="row0">
+<td>Static IP - Managment</td>
+<td>A single IP address from a designated pool for managed devices.</td>
+</tr>
+
+<tr class="row1">
+<td>Static IP - Wifi CPE</td>
+<td>A single IP address from a designated pool for wireless CPE devices.</td>
+</tr>
+
+<tr class="row0">
+<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="row1">
+<td>Static Pool - Managment</td>
+<td>A pool of IP addresses available for assignment to managed devices.</td>
+</tr>
+
+<tr class="row0">
+<td>Static pool - Wifi CPE</td>
+<td>A pool of IP addresses available for assignment to wireless CPE devices.</td>
+</tr>
+
+<tr class="row1">
+<td>Reserve for WAN blocks</td>
+<td>Reserve a chunk of IP space for core routers/etc.</td>
+</tr>
+
+<tr class="row0">
+<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="row1">
+<td>Reserve for ATM</td>
+<td>Reserve a chunk of IP space for allocation to customers on ATM.</td>
+</tr>
+
+<tr class="row0">
+<td>Reserve for fibre</td>
+<td>Reserve a chunk of IP space for customers on a fibre connection.</td>
+</tr>
+
+<tr class="row1">
+<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="row0">
+<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="row1">
+<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="row0">
+<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="row1">
+<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="row0">
+<td>Master block</td>
+<td>Allocations provided by the regional registry (ARIN, RIPE, LACNIC, AfriNIC, 
+or APNIC)</td>
+</tr>
+
+</table>
+
+</body></html>
Index: tags/2.8.2/cgi-bin/CustIDCK.pm
===================================================================
--- tags/2.8.2/cgi-bin/CustIDCK.pm	(revision 617)
+++ tags/2.8.2/cgi-bin/CustIDCK.pm	(revision 617)
@@ -0,0 +1,88 @@
+# 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 $self = shift;
+  my $custid = shift;
+
+  # hardcoded "OK" custids.
+  return 1 if $custid =~ /^STAFF(?:-\d\d?)?$/;
+  return 1 if $custid =~ /^5554242(?:-\d\d?)?$/;  # just in case some later change might block this
+  return 1 if $custid =~ /^\d{7}(?:-\d\d?)?$/;
+  return 1 if $custid =~ /^\d{10}(?:-\d\d?)?$/;
+
+  # Force uppercase for now...
+  $custid =~ tr/a-z/A-Z/;
+
+# 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 $hr = $dbh->selectrow_hashref("SELECT custid FROM custid WHERE custid = ?", undef, ($custid) );
+  my $status = 0;
+  if ($dbh->err) {
+    $CustIDCK::Error = 1;
+    $CustIDCK::ErrMsg = $dbh->errstr();
+  } else {
+    $status = 1 if ( $hr->{custid} );
+  }
+  $dbh->disconnect;
+  return $status;
+
+  return 0;
+  # Stubs for error messages
+  $CustIDCK::Error = 1 if 1 == 0;
+  $CustIDCK::ErrMsg = "bad foo-frob: 1 == 0";
+}
Index: tags/2.8.2/cgi-bin/IPDB.pm
===================================================================
--- tags/2.8.2/cgi-bin/IPDB.pm	(revision 617)
+++ tags/2.8.2/cgi-bin/IPDB.pm	(revision 617)
@@ -0,0 +1,1404 @@
+# 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-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+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
+	%IPDBacl %aclmsg %rpcacl $maxfcgi
+	&initIPDBGlobals &connectDB &finish &checkDBSanity
+	&addMaster &touchMaster
+	&listSummary &listMaster &listRBlock &listFree &listPool
+	&getMasterList &getTypeList &getPoolSelect &findAllocateFrom
+	&ipParent &subParent &blockParent &getRoutedCity
+	&allocateBlock &updateBlock &deleteBlock &getBlockData
+	&getNodeList &getNodeName &getNodeInfo
+	&mailNotify
+	);
+
+@EXPORT		= (); # Export nothing by default.
+%EXPORT_TAGS	= ( ALL => [qw(
+		%disp_alloctypes %list_alloctypes %def_custids @citylist @poplist
+		%IPDBacl %aclmsg %rpcacl $maxfcgi
+		&initIPDBGlobals &connectDB &finish &checkDBSanity
+		&addMaster &touchMaster
+		&listSummary &listMaster &listRBlock &listFree &listPool
+		&getMasterList &getTypeList &getPoolSelect &findAllocateFrom
+		&ipParent &subParent &blockParent &getRoutedCity
+		&allocateBlock &updateBlock &deleteBlock &getBlockData
+		&getNodeList &getNodeName &getNodeInfo
+		&mailNotify
+		)]
+	);
+
+##
+## Global variables
+##
+our %disp_alloctypes;
+our %list_alloctypes;
+our %def_custids;
+our @citylist;
+our @poplist;
+our %IPDBacl;
+
+# mapping table for functional-area => error message
+our %aclmsg = (
+	addmaster	=> 'add a master block',
+	addblock	=> 'add an allocation',
+	updateblock	=> 'update a block',
+	delblock	=> 'delete an allocation',
+	);
+
+our %rpcacl;
+our $maxfcgi = 3;
+
+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';
+
+# Allow allocations to come from private IP ranges by default?
+# Set to 'y' or 'on' to enable.
+our $allowprivrange = '';
+
+# 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];
+    }
+  }
+
+  # 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];
+  }
+
+##fixme:  initialize HTML::Template env var for template path
+# something like $self->path().'/templates' ?
+#  $ENV{HTML_TEMPLATE_ROOT} = 'foo/bar';
+
+  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 if $dbh;
+} # 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 ($mexist) = $dbh->selectrow_array("SELECT cidr FROM masterblocks WHERE cidr <<= ?", undef, ($cidr) );
+
+    if (!$mexist) {
+      # 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"?
+      $dbh->do("INSERT INTO masterblocks (cidr,rwhois) VALUES (?,?)", undef, ($cidr,'y') );
+
+# 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.
+      $dbh->do("INSERT INTO freeblocks (cidr,maskbits,city,routed) VALUES (?,?,?,?)", undef,
+	($cidr, $cidr->masklen, '<NULL>', 'n') );
+
+      # If we get here, everything is happy.  Commit changes.
+      $dbh->commit;
+
+    } # done 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;
+      my $sth = $dbh->prepare("SELECT cidr FROM masterblocks WHERE cidr <<= ?");
+      $sth->execute($cidr);
+      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 <= ? AND cidr <<= ? AND routed = 'n'");
+      $sth->execute($smallmask, $cidr);
+      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
+      $dbh->do("DELETE FROM masterblocks WHERE cidr <<= ?", undef, ($cidr) );
+      $dbh->do("INSERT INTO masterblocks (cidr,rwhois) VALUES (?,?)", undef, ($cidr, 'y') );
+
+      # *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::touchMaster()
+# Update last-changed timestamp on a master block.
+sub touchMaster {
+  my $dbh = shift; 
+  my $master = shift;
+
+  local $dbh->{AutoCommit} = 0;
+  local $dbh->{RaiseError} = 1;
+
+  eval {
+    $dbh->do("UPDATE masterblocks SET mtime=now() WHERE cidr = ?", undef, ($master));
+    $dbh->commit;
+  };
+
+  if ($@) {
+    my $msg = $@;
+    eval { $dbh->rollback; };
+    return ('FAIL',$msg);
+  }
+  return ('OK','OK');
+} # end touchMaster()
+
+
+## IPDB::listSummary()
+# Get summary list of all master blocks
+# Returns an arrayref to a list of hashrefs containing the master block, routed count,
+# allocated count, free count, and largest free block masklength
+sub listSummary {
+  my $dbh = shift;
+
+  my $mlist = $dbh->selectall_arrayref("SELECT cidr AS master FROM masterblocks ORDER BY cidr", { Slice => {} });
+
+  foreach (@{$mlist}) {
+    my ($rcnt) = $dbh->selectrow_array("SELECT count(*) FROM routed WHERE cidr <<= ?", undef, ($$_{master}));
+    $$_{routed} = $rcnt;
+    my ($acnt) = $dbh->selectrow_array("SELECT count(*) FROM allocations WHERE cidr <<= ?", undef, ($$_{master}));
+    $$_{allocated} = $acnt;
+    my ($fcnt) = $dbh->selectrow_array("SELECT count(*) FROM freeblocks WHERE cidr <<= ?".
+	" AND (routed='y' OR routed='n')", undef, ($$_{master}));
+    $$_{free} = $fcnt;
+    my ($bigfree) = $dbh->selectrow_array("SELECT maskbits FROM freeblocks WHERE cidr <<= ?".
+	" AND (routed='y' OR routed='n') ORDER BY maskbits LIMIT 1", undef, ($$_{master}));
+##fixme:  should find a way to do this without having to HTMLize the <>
+    $bigfree = "/$bigfree" if $bigfree;
+    $bigfree = '<NONE>' if !$bigfree;
+    $$_{bigfree} = $bigfree;
+  }
+  return $mlist;
+} # end listSummary()
+
+
+## IPDB::listMaster()
+# Get list of routed blocks in the requested master
+# Returns an arrayref to a list of hashrefs containing the routed block, POP/city the block is routed to,
+# allocated count, free count, and largest free block masklength
+sub listMaster {
+  my $dbh = shift;
+  my $master = shift;
+
+  my $rlist = $dbh->selectall_arrayref("SELECT cidr AS block,city FROM routed WHERE cidr <<= ? ORDER BY cidr",
+	{ Slice => {} }, ($master) );
+
+  foreach (@{$rlist}) {
+    my ($acnt) = $dbh->selectrow_array("SELECT count(*) FROM allocations WHERE cidr <<= ?", undef, ($$_{block}));
+    $$_{nsubs} = $acnt;
+    my ($fcnt) = $dbh->selectrow_array("SELECT count(*) FROM freeblocks WHERE cidr <<= ?".
+	" AND (routed='y' OR routed='n')", undef, ($$_{block}));
+    $$_{nfree} = $fcnt;
+    my ($bigfree) = $dbh->selectrow_array("SELECT maskbits FROM freeblocks WHERE cidr <<= ?".
+	" AND (routed='y' OR routed='n') ORDER BY maskbits LIMIT 1", undef, ($$_{block}));
+##fixme:  should find a way to do this without having to HTMLize the <>
+    $bigfree = "/$bigfree" if $bigfree;
+    $bigfree = '<NONE>' if !$bigfree;
+    $$_{lfree} = $bigfree;
+  }
+  return $rlist;
+} # end listMaster()
+
+
+## IPDB::listRBlock()
+# Gets a list of free blocks in the requested parent/master in both CIDR and range notation
+# Takes a parent/master and an optional flag to look at routed or unrouted blocks, depending
+# on whether the master is a direct master or a routed block
+# Returns an arrayref to a list of hashrefs containing the CIDR and range-notation blocks
+sub listRBlock {
+  my $dbh = shift;
+  my $routed = shift;
+
+  # Snag the allocations for this block
+  my $sth = $dbh->prepare("SELECT cidr,city,type,custid,swip,description,vrf".
+        " FROM allocations WHERE cidr <<= ? ORDER BY cidr");
+  $sth->execute($routed);
+
+  # hack hack hack
+  # set up to flag swip=y records if they don't actually have supporting data in the customers table
+  my $custsth = $dbh->prepare("SELECT count(*) FROM customers WHERE custid = ?");
+
+  my @blocklist;
+  while (my ($cidr,$city,$type,$custid,$swip,$desc,$vrf) = $sth->fetchrow_array()) {
+    $desc .= " - vrf:$vrf" if $desc && $vrf;
+    $desc = "vrf:$vrf" if !$desc && $vrf;
+    $custsth->execute($custid);
+    my ($ncust) = $custsth->fetchrow_array();
+    my %row = (
+	block => $cidr,
+	city => $city,
+	type => $disp_alloctypes{$type},
+	custid => $custid,
+	swip => ($swip eq 'y' ? 'Yes' : 'No'),
+	partswip => ($swip eq 'y' && $ncust == 0 ? 1 : 0),
+	desc => $desc
+	);
+    $row{subblock} = ($type =~ /^.r$/);         # hmf.  wonder why these won't work in the hash declaration...
+    $row{listpool} = ($type =~ /^.[pd]$/);
+    push (@blocklist, \%row);
+  }
+  return \@blocklist;
+} # end listRBlock()
+
+
+## IPDB::listFree()
+# Gets a list of free blocks in the requested parent/master in both CIDR and range notation
+# Takes a parent/master and an optional "routed or unrouted" flag that defaults to unrouted.
+# Returns an arrayref to a list of hashrefs containing the CIDR and range-notation blocks
+# Returns some extra flags in the hashrefs for routed blocks, since those can have several subtypes
+sub listFree {
+  my $dbh = shift;
+  my $master = shift;
+  my $routed = shift || 'n';
+
+  # do it this way so we can waste a little less time iterating
+  my $sth = $dbh->prepare("SELECT cidr,routed FROM freeblocks WHERE cidr <<= ? AND ".
+	($routed eq 'n' ? '' : 'NOT')." routed = 'n' ORDER BY cidr");
+  $sth->execute($master);
+  my @flist;
+  while (my ($cidr,$rtype) = $sth->fetchrow_array()) {
+    $cidr = new NetAddr::IP $cidr;
+    my %row = (
+	fblock => "$cidr",
+	frange => $cidr->range,
+	);
+    if ($routed eq 'y') {
+      $row{subblock} = ($rtype ne 'y' && $rtype ne 'n');
+      $row{fbtype} = $rtype;
+    }
+    push @flist, \%row;
+  }
+  return \@flist;
+} # end listFree()
+
+
+## IPDB::listPool()
+#
+sub listPool {
+  my $dbh = shift;
+  my $pool = shift;
+
+  my $sth = $dbh->prepare("SELECT ip,custid,available,description,type,vrf".
+        " FROM poolips WHERE pool = ? ORDER BY ip");
+  $sth->execute($pool);
+  my @poolips;
+  while (my ($ip,$custid,$available,$desc,$type,$vrf) = $sth->fetchrow_array) {
+    $desc .= " - vrf:$vrf" if $desc && $vrf;
+    $desc = "vrf:$vrf" if !$desc && $vrf;
+    my %row = (
+        ip => $ip,
+        custid => $custid,
+        available => $available,
+        desc => $desc,
+        delme => $available eq 'n'
+        );
+    push @poolips, \%row;
+  }
+  return \@poolips;
+} # end listPool()
+
+
+## IPDB::getMasterList()
+# Get a list of master blocks, optionally including last-modified timestamps
+# Takes an optional flag to indicate whether to include timestamps;
+#  'm' includes ctime, all others (suggest 'c') do not.
+# Returns an arrayref to a list of hashrefs
+sub getMasterList {
+  my $dbh = shift;
+  my $stampme = shift || 'm';	# optional but should be set by caller for clarity
+
+  my $mlist = $dbh->selectall_arrayref("SELECT cidr AS master".($stampme eq 'm' ? ',mtime' : '').
+	" FROM masterblocks ORDER BY cidr", { Slice => {} });
+  return $mlist;
+} # end getMasterList()
+
+
+## IPDB::getTypeList()
+# Get an alloctype/description pair list suitable for dropdowns
+# Takes a flag to determine which general groups of types are returned
+# Returns an reference to an array of hashrefs
+sub getTypeList {
+  my $dbh = shift;
+  my $tgroup = shift || 'a';	# technically optional, like this, but should
+				# really be specified in the call for clarity
+  my $tlist;
+  if ($tgroup eq 'n') {
+    # grouping 'n' - all netblock types.  These include routed blocks, containers (_c)
+    # and contained (_r) types, dynamic-allocation ranges (_e), static IP pools (_d and _p),
+    # and the "miscellaneous" cn, in, and en types.
+    $tlist = $dbh->selectall_arrayref("SELECT type,listname FROM alloctypes WHERE listorder < 500 ".
+        "AND type NOT LIKE '_i' ORDER BY listorder", { Slice => {} });
+  } elsif ($tgroup eq 'p') {
+    # grouping 'p' - primary allocation types.  As with 'n' above but without the _r contained types.
+    $tlist = $dbh->selectall_arrayref("SELECT type,listname FROM alloctypes WHERE listorder <= 500 ".
+        "AND type NOT LIKE '_i' AND type NOT LIKE '_r' ORDER BY listorder", { Slice => {} });
+  } elsif ($tgroup eq 'c') {
+    # grouping 'c' - contained types.  These include all static IPs and all _r types.
+    $tlist = $dbh->selectall_arrayref("SELECT type,listname FROM alloctypes WHERE listorder <= 500 ".
+        " AND (type LIKE '_i' OR type LIKE '_r') ORDER BY listorder", { Slice => {} });
+  } elsif ($tgroup eq 'i') {
+    # grouping 'i' - static IP types.
+    $tlist = $dbh->selectall_arrayref("SELECT type,listname FROM alloctypes WHERE listorder <= 500 ".
+        " AND type LIKE '_i' ORDER BY listorder", { Slice => {} });
+  } else {
+    # grouping 'a' - all standard allocation types.  This includes everything
+    # but mm (present only as a formality).  Make this the default.
+    $tlist = $dbh->selectall_arrayref("SELECT type,listname FROM alloctypes WHERE listorder <= 500 ".
+        " ORDER BY listorder", { Slice => {} });
+  }
+  return $tlist;
+}
+
+
+## IPDB::getPoolSelect()
+# Get a list of pools matching the passed city and type that have 1 or more free IPs
+# Returns an arrayref to a list of hashrefs
+sub getPoolSelect {
+  my $dbh = shift;
+  my $iptype = shift;
+  my $pcity = shift;
+
+  my ($ptype) = ($iptype =~ /^(.)i$/);
+  return if !$ptype;
+  $ptype .= '_';
+
+  my $plist = $dbh->selectall_arrayref(
+	"SELECT (SELECT city FROM allocations WHERE cidr=poolips.pool) AS poolcit, ".
+	"poolips.pool AS poolblock, COUNT(*) AS poolfree FROM poolips,allocations ".
+	"WHERE poolips.available='y' AND poolips.pool=allocations.cidr ".
+	"AND allocations.city = ? AND poolips.type LIKE ? ".
+	"GROUP BY pool", { Slice => {} }, ($pcity, $ptype) );
+  return $plist;
+} # end getPoolSelect()
+
+
+## IPDB::findAllocateFrom()
+# Find free block to add a new allocation from.  (CIDR block version of pool select above, more or less)
+# Takes
+#  - mask length
+#  - allocation type
+#  - POP city "parent"
+#  - optional master-block restriction
+#  - optional flag to allow automatic pick-from-private-network-ranges
+# Returns a string with the first CIDR block matching the criteria, if any
+sub findAllocateFrom {
+  my $dbh = shift;
+  my $maskbits = shift;
+  my $type = shift;
+  my $city = shift;
+  my $pop = shift;
+  my %optargs = @_;
+
+  my $failmsg = "No suitable free block found\n";
+
+## Set up the SQL to find out what freeblock we can (probably) use for an allocation.
+## Very large systems will require development of a reserve system (possibly an extension
+## of the reserve-for-expansion concept in https://secure.deepnet.cx/trac/ipdb/ticket/24?)
+## Also populate a value list for the DBI call.
+
+  my @vallist = ($maskbits, ($type eq 'rm' ? 'n' : ($type =~ /^(.)r$/ ? "$1" : 'y')) );
+  my $sql = "SELECT cidr FROM freeblocks WHERE maskbits <= ? AND routed = ?";
+
+  # for PPP(oE) and container types, the POP city is the one attached to the pool.
+  # individual allocations get listed with the customer city site.
+  ##fixme:  chain cities to align roughly with a full layer-2 node graph
+  $city = $pop if $type !~ /^.[pc]$/;
+  if ($type ne 'rm' && $city) {
+    $sql .= " AND city = ?";
+    push @vallist, $city;
+  }
+  # Allow specifying an arbitrary full block, instead of a master
+  if ($optargs{gimme}) {
+    $sql .= " AND cidr >>= ?";
+    push @vallist, $optargs{gimme};
+  }
+  # if a specific master was requested, allow the requestor to self->shoot(foot)
+  if ($optargs{master} && $optargs{master} ne '-') {
+    $sql .= " AND cidr <<= ?" if $optargs{master} ne '-';
+    push @vallist, $optargs{master};
+  } else {
+    # if a specific master was NOT requested, filter out the RFC 1918 private networks
+    if (!$optargs{allowpriv}) {
+      $sql .= " AND NOT (cidr <<= '192.168.0.0/16' OR cidr <<= '10.0.0.0/8' OR cidr <<= '172.16.0.0/12')";
+    }
+  }
+  # Sorting and limiting, since we don't (currently) care to provide a selection of
+  # blocks to carve up.  This preserves something resembling optimal usage of the IP
+  # space by forcing contiguous allocations and free blocks as much as possible.
+  $sql .= " ORDER BY maskbits DESC,cidr LIMIT 1";
+
+  my ($fbfound) = $dbh->selectrow_array($sql, undef, @vallist);
+  return $fbfound;
+} # end findAllocateFrom()
+
+
+## IPDB::ipParent()
+# Get an IP's parent pool's details
+# Takes a database handle and IP
+# Returns a hashref to the parent pool block, if any
+sub ipParent {
+  my $dbh = shift;
+  my $block = shift;
+
+  my $pinfo = $dbh->selectrow_hashref("SELECT cidr,custid,type,city,description FROM allocations".
+	" WHERE cidr >>= ?", undef, ($block) );
+  return $pinfo;
+} # end ipParent()
+
+
+## IPDB::subParent()
+# Get a block's parent's details
+# Takes a database handle and CIDR block
+# Returns a hashref to the parent container block, if any
+sub subParent {
+  my $dbh = shift;
+  my $block = shift;
+
+  my $pinfo = $dbh->selectrow_hashref("SELECT cidr,custid,type,city,description FROM allocations".
+	" WHERE cidr >>= ?", undef, ($block) );
+  return $pinfo;
+} # end subParent()
+
+
+## IPDB::blockParent()
+# Get a block's parent's details
+# Takes a database handle and CIDR block
+# Returns a hashref to the parent container block, if any
+sub blockParent {
+  my $dbh = shift;
+  my $block = shift;
+
+  my $pinfo = $dbh->selectrow_hashref("SELECT cidr,city FROM routed".
+	" WHERE cidr >>= ?", undef, ($block) );
+  return $pinfo;
+} # end blockParent()
+
+
+## IPDB::getRoutedCity()
+# Get the city for a routed block.
+sub getRoutedCity {
+  my $dbh = shift;
+  my $block = shift;
+
+  my ($rcity) = $dbh->selectrow_array("SELECT city FROM routed WHERE cidr = ?", undef, ($block) );
+  return $rcity;
+} # end getRoutedCity()
+
+
+## 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,$vrf) = @_;
+  $privdata = '' if !defined($privdata);
+  $vrf = '' if !defined($vrf);
+
+  my $cidr = new NetAddr::IP $_[1];
+  my $alloc_from = new NetAddr::IP $_[2];
+  my $sth;
+
+  $desc = '' if !$desc;
+  $notes = '' if !$notes;
+  $circid = '' if !$circid;
+  $privdata = '' if !$privdata;
+
+  # 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);
+
+      if ($cidr) {
+	my ($isavail) = $dbh->selectrow_array("SELECT available FROM poolips WHERE ip=?", undef, ($cidr) );
+	if ($isavail eq 'n') {
+	  die "IP already allocated.  Deallocate and reallocate, or update the entry\n";
+	}
+	if (!$isavail) {
+	  die "IP is not in an IP pool.\n";
+	}
+      } else {
+	($cidr) = $dbh->selectrow_array("SELECT ip FROM poolips WHERE pool=? AND available='y' ORDER BY ip",
+		undef, ($alloc_from) );
+      }
+      $dbh->do("UPDATE poolips SET custid=?,city=?,available='n',description=?,".
+	"notes=?,circuitid=?,privdata=?,vrf=? ".
+	"WHERE ip=?", undef, ($custid, $city, $desc, $notes, $circid, $privdata, $vrf, $cidr) );
+
+# 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 .= ": $@";
+      eval { $dbh->rollback; };
+      return ('FAIL',$msg);
+    } else {
+      return ('OK',"$cidr");
+    }
+
+  } else { # end IP-from-pool allocation
+
+    my $errcode = 'OK';
+    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,vrf)".
+		" values (?,?,?,?,?,?,?,?,?,?)");
+	  $sth->execute("$cidr", $custid, $type, $city, $desc, $notes, $cidr->masklen, $circid, $privdata, $vrf);
+
+	  # 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';
+	    $msg = $rmsg;
+	    $errcode = $code;
+	  } 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';
+	    $msg = $rmsg;
+	    $errcode = $code;
+	  }
+
+	} # 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 ($errcode,($type =~ /^.[pd]$/ ? $msg : "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,vrf)".
+		" values (?,?,?,?,?,?,?,?,?,?)");
+	  $sth->execute("$cidr", $custid, $type, $city, $desc, $notes, $cidr->masklen, $circid, $privdata, $vrf);
+
+	  # 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 ($errcode,($type =~ /^.[pd]$/ ? $msg : "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];
+
+  return ('WARN','Refusing to melt server with IPv6 IP pool') if $pool->bits == 128;
+
+##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', ?, '$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, $city);
+      }
+      for (my $i=0; $i<=$#poolip_list; $i++) {
+	$sth->execute($poolip_list[$i]->addr, $city);
+      }
+      $pool--;
+      if ($pool->addr !~ /\.255$/) {	# .255 can cause weirdness.
+	$sth->execute($pool->addr, $city);
+      }
+    } else { # (real netblock)
+      for (my $i=1; $i<=$#poolip_list; $i++) {
+	$sth->execute($poolip_list[$i]->addr, $city);
+      }
+    }
+  };
+  if ($@) {
+    $msg = $@." '".$sth->errstr."'";
+    eval { $dbh->rollback; };
+    return ('FAIL',$msg);
+  } else {
+    return ('OK',"OK");
+  }
+} # end initPool()
+
+
+## IPDB::updateBlock()
+# Update an allocation
+# Takes all allocation fields in a hash
+sub updateBlock {
+  my $dbh = shift;
+  my %args = @_;
+
+  return ('FAIL', 'Missing block to update') if !$args{block};
+
+  # do it all in a transaction
+  local $dbh->{AutoCommit} = 0;
+  local $dbh->{RaiseError} = 1;
+
+  my @fieldlist;
+  my @vallist;
+  foreach ('custid', 'city', 'description', 'notes', 'circuitid', 'privdata', 'vrf') {
+    if ($args{$_}) {
+      push @fieldlist, $_;
+      push @vallist, $args{$_};
+    }
+  }
+
+  my $updtable = 'allocations';
+  my $keyfield = 'cidr';
+  if ($args{type} =~ /^(.)i$/) {
+    $updtable = 'poolips';
+    $keyfield = 'ip';
+  } else {
+## fixme:  there's got to be a better way...
+    if ($args{swip}) {
+      if ($args{swip} eq 'on' || $args{swip} eq '1' || $args{swip} eq 'y') {
+        $args{swip} = 'y';
+      } else {
+        $args{swip} = 'n';
+      }
+    }
+    foreach ('type', 'swip') {
+      if ($args{$_}) {
+        push @fieldlist, $_;
+        push @vallist, $args{$_};
+      }
+    }
+  }
+
+  return ('FAIL', 'No fields to update') if !@fieldlist;
+
+  push @vallist, $args{block};
+  my $sql = "UPDATE $updtable SET ";
+  $sql .= join " = ?, ", @fieldlist;
+  $sql .= " = ? WHERE $keyfield = ?";
+
+  eval {
+    # do the update
+    $dbh->do($sql, undef, @vallist);
+
+    if ($args{node} && $args{node} ne '--') {
+      # done with delete/insert so we don't have to worry about funkyness updating a node ref that isn't there
+      $dbh->do("DELETE FROM noderef WHERE block = ?", undef, ($args{block}) );
+      $dbh->do("INSERT INTO noderef (block,node_id) VALUES (?,?)", undef, ($args{block}, $args{node}) );
+    }
+
+    $dbh->commit;
+  };
+  if ($@) {
+    my $msg = $@;
+    $dbh->rollback;
+    return ('FAIL', $msg);
+  }
+  return 0;
+} # end updateBlock()
+
+
+## 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=?,available='y',".
+	"city=(select city from allocations where cidr >>= ?".
+	" order by masklen(cidr) desc limit 1),".
+	"description='',notes='',circuitid='' where ip=?");
+      $sth->execute($defcustid, "$cidr", "$cidr");
+      $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=?");
+      $sth->execute($cidr);
+      $sth = $dbh->prepare("delete from freeblocks where cidr <<= ?");
+      $sth->execute($cidr);
+      $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()
+# Get CIDR or IP, custid, type, city, circuit ID, description, notes, modification time, private/restricted 
+# data, and VRF tag, for a CIDR block or pool IP
+# Also returns SWIP status flag for CIDR blocks
+# Takes the block/IP to look up
+# Returns an arrayref to a list of hashrefs
+sub getBlockData {
+  my $dbh = shift;
+  my $block = shift;
+
+  my $cidr = new NetAddr::IP $block;
+
+  my $keycol = 'cidr';
+  my $blocktable = 'allocations';
+  my $poolip = 0;
+
+my $binfo;
+## hack ptui
+# the current DB does not lend itself well to /32 netblock allocations.
+# this hack should work around that lack so that such allocations can be manipulated:
+# instead of inspecting $block/$cidr to see if it's an IPv4 /32,
+# we brute-force it and just see if there's anything in the allocations
+# table first, then if nothing was found check the poolips table
+$binfo = $dbh->selectrow_hashref("SELECT $keycol AS block, custid, type, city, circuitid, description,".
+	" notes, modifystamp AS lastmod, privdata, vrf".($poolip ? '' : ', swip')." FROM $blocktable".
+	" WHERE $keycol = ?", undef, ($block) );
+
+## this only works if blocks are never /32.  *sigh*
+  # Pool IP and IPv6 check all in one!  Still needs to be tightened
+  # up a little for the as-yet-unhandled case of IPv6 IP pools
+#  if ($cidr->bits == 32 && $cidr->masklen == 32) {
+  if (!$binfo) {
+    $poolip = 1;
+    $keycol = 'ip';
+    $blocktable = 'poolips';
+    $binfo = $dbh->selectrow_hashref("SELECT $keycol AS block, custid, type, city, circuitid, description,".
+	" notes, modifystamp AS lastmod, privdata, vrf".($poolip ? '' : ', swip')." FROM $blocktable".
+	" WHERE $keycol = ?", undef, ($block) );
+  }
+  return $binfo;
+} # end getBlockData()
+
+
+## IPDB::getNodeList()
+# Gets a list of node ID+name pairs as an arrayref to a list of hashrefs
+sub getNodeList {
+  my $dbh = shift;
+  
+  my $ret = $dbh->selectall_arrayref("SELECT node_id, node_name FROM nodes ORDER BY node_type,node_id",
+	{ Slice => {} });
+  return $ret;
+} # end getNodeList()
+
+
+## IPDB::getNodeName()
+# Get node name from the ID
+sub getNodeName {
+  my $dbh = shift;
+  my $nid = shift;
+
+  my ($nname) = $dbh->selectrow_array("SELECT node_name FROM nodes WHERE node_id = ?", undef, ($nid) );
+  return $nname;
+} # end getNodeName()
+
+
+## IPDB::getNodeInfo()
+# Get node name and ID associated with a block
+sub getNodeInfo {
+  my $dbh = shift;
+  my $block = shift;
+
+  my ($nid, $nname) = $dbh->selectrow_array("SELECT nodes.node_id,node_name FROM nodes INNER JOIN noderef".
+	" ON nodes.node_id=noderef.node_id WHERE noderef.block = ?", undef, ($block) );
+  return ($nid, $nname);
+} # end getNodeInfo()
+
+
+## IPDB::mailNotify()
+# Sends notification mail to recipients regarding an IPDB operation
+sub mailNotify {
+  my $dbh = shift;
+  my ($action,$subj,$message) = @_;
+
+  return if $smtphost eq 'smtp.example.com';   # do nothing if still using default SMTP host.
+
+##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$IPDB::VERSION\n",
+	"Organization: $org_name\n",
+	"\n$message\n");
+  }
+  $mailer->quit;
+}
+
+# Indicates module loaded OK.  Required by Perl.
+1;
Index: tags/2.8.2/cgi-bin/MyIPDB.pm
===================================================================
--- tags/2.8.2/cgi-bin/MyIPDB.pm	(revision 617)
+++ tags/2.8.2/cgi-bin/MyIPDB.pm	(revision 617)
@@ -0,0 +1,75 @@
+# 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-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+# 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';
+
+# ACL for RPC shim.  A hash of arrays;  the hash keys are the remote system
+# names, the array contents are the IPs allowed to claim that system name.
+#%IPDB::rpcacl = (
+#	cli_test => ['192.168.99.115','209.91.179.62'],
+#	devel => ['10.0.10.5'],
+#	);
+# Only you can prevent memory leaks!
+#$IPDB::maxfcgi = 100;
+
+# Quick workaround for fixed web path.  Set this to the absolute web path to
+# your IPDB install, or leave blank for installation at the webroot.
+$IPDB::webpath = '';
+
+# 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.
+# Also note that mail will silently not happen if at least smtphost and domain are not set.
+#$IPDB::org_name = "Example Corp";
+#$IPDB::smtphost = '127.0.0.1';
+#$IPDB::domain = 'example.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';
+
+# Allow allocations to come from private IP ranges by default?
+# Set to 'y' or 'on' to enable.
+#$IPDB::allowprivrange = '';
+
+## 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: tags/2.8.2/cgi-bin/access-pwd-update.pl
===================================================================
--- tags/2.8.2/cgi-bin/access-pwd-update.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/access-pwd-update.pl	(revision 617)
@@ -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: tags/2.8.2/cgi-bin/admin.cgi
===================================================================
--- tags/2.8.2/cgi-bin/admin.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/admin.cgi	(revision 617)
@@ -0,0 +1,644 @@
+#!/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-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;
+use warnings;
+use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
+use DBI;
+#use POSIX qw(ceil);
+use NetAddr::IP;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use CustIDCK;
+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";
+
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+# anyone got a better name?  :P
+my $thingroot = $ENV{SCRIPT_FILENAME};
+$thingroot =~ s|cgi-bin/admin.cgi||;
+
+# Set up some globals
+$ENV{HTML_TEMPLATE_ROOT} = $thingroot."templates";
+
+# 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) {
+  $webvar{action} = "dberr";
+} else {
+  initIPDBGlobals($ip_dbh);
+}
+
+if(!defined($webvar{action})) {
+  $webvar{action} = "main";   #shuts up the warnings.
+}
+
+# handle DB error output
+if ($webvar{action} eq 'dberr') {
+  my $page = HTML::Template->new(filename => "admin/dberr.tmpl");
+  $page->param(errmsg => $errstr);
+  print "Content-Type: text/html\n\n".$page->output;
+  exit;
+}
+
+if ($IPDBacl{$authuser} !~ /A/) {
+  my $page = HTML::Template->new(filename => "admin/aclerr.tmpl");
+##fixme:  need params for IPDB admin email and name
+  $page->param(ipdbadmin_email => 'ipdbadmin@example.com');
+  $page->param(ipdbadmin_name => 'the IPDB administrator');
+  print "Content-Type: text/html\n\n".$page->output;
+  exit;
+}
+
+my $header = HTML::Template->new(filename => "admin/header.tmpl");
+
+my $page;
+if (-e "$ENV{HTML_TEMPLATE_ROOT}/admin/$webvar{action}.tmpl") {
+  $page = HTML::Template->new(filename => "admin/$webvar{action}.tmpl");
+} else {
+  $page = HTML::Template->new(filename => "admin/dunno.tmpl");
+}
+
+# handle index page
+if ($webvar{action} eq 'main') {
+  $header->param(mainpage => 1);
+
+  my $tlist = getTypeList($ip_dbh, 'a');
+  $tlist->[0]->{sel} = 1;
+  $page->param(typelist => $tlist);
+
+  my $mlist = getMasterList($ip_dbh, 'm');
+  $page->param(masterlist => $mlist);
+}
+
+## Non-default actions.
+
+elsif ($webvar{action} eq 'alloc') {
+
+  my $cidr = new NetAddr::IP $webvar{cidr};
+  if (!$cidr || "$cidr" =~ /^0/) {
+    $page->param(errmsg => "Can't allocate something that's not a netblock/ip");
+    goto ERRJUMP;
+  }
+
+  my $custid = $def_custids{$webvar{alloctype}};
+  if ($custid eq '') {
+    # Crosscheck with billing.
+    my $status = CustIDCK->custid_exist($webvar{custid});
+    if ($CustIDCK::Error) {
+      $page->param(errmsg => "Error verifying customer ID: ".$CustIDCK::ErrMsg);
+      goto ERRJUMP;
+    }
+    if (!$status) {
+      $page->param(errmsg => "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.");
+      goto ERRJUMP;
+    }
+    # Type that doesn't have a default custid
+    $custid = $webvar{custid};
+  }
+
+  my $maskbits = $cidr->masklen;
+  my $fbtmp = findAllocateFrom($ip_dbh, $maskbits, $webvar{alloctype}, '','',
+	(gimme => "$cidr", allowpriv => 1));
+
+  if ($webvar{alloctype} eq 'rm') {
+    if (!$fbtmp) {
+      $page->param(errmsg => "Can't allocate from outside a free block!!");
+      goto ERRJUMP;
+    }
+  } elsif ($webvar{alloctype} =~ /^(.)i$/) {
+    my $iptype = $1;
+    my $ptmp = ipParent($ip_dbh, "$cidr");
+    if ($ptmp->{type} =~ /^(.)[dp]$/) {
+      my $newiptype = "$1i";
+      $fbtmp = $ptmp->{cidr};
+      if ($ptmp->{type} !~ /^$iptype./) {
+	$page->param(warnmsg => "Warning:  Allocating IP as '".$disp_alloctypes{$newiptype}."' instead of '".
+		$disp_alloctypes{$webvar{alloctype}}."' to match pool $fbtmp\n");
+	$webvar{alloctype} = $newiptype;
+      }
+    }
+    if (!$fbtmp) {
+      $page->param(errmsg => "Can't allocate static IP from outside a pool!!");
+      goto ERRJUMP;
+    }
+  } else {
+    if (!$fbtmp) {
+      $page->param(errmsg => "Can't allocate from outside a routed block!!");
+      goto ERRJUMP;
+    }
+  }
+
+  my $alloc_from = new NetAddr::IP $fbtmp;
+
+  my @cities;
+  foreach my $city (@citylist) {
+     my %row = (city => $city);
+     push @cities, \%row;
+  }
+  $page->param(
+	cidr => $cidr,
+	disptype => $disp_alloctypes{$webvar{alloctype}},
+	type => $webvar{alloctype},
+	alloc_from => $alloc_from,
+	custid => $custid,
+	citylist => \@cities
+	);
+
+} elsif ($webvar{action} eq 'confirm') {
+
+  $page->param(
+	cidr => $webvar{cidr},
+	custid => $webvar{custid},
+	desc => $webvar{desc},
+	disptype => $disp_alloctypes{$webvar{alloctype}}
+	);
+  # Only need to check city here.
+  if ($webvar{city} eq '-') {
+    $page->param(locerr => "Invalid customer location!  Go back and select customer's location.");
+    goto ERRJUMP;
+  }
+
+  my ($retcode,$msg) = allocateBlock($ip_dbh, $webvar{cidr}, $webvar{alloc_from},
+	$webvar{custid}, $webvar{alloctype}, $webvar{city}, $webvar{desc}, $webvar{notes},
+	$webvar{circid}, undef, undef, $webvar{vrf});
+  if ($retcode eq 'OK') {
+    syslog "notice", "$authuser allocated '$webvar{cidr}' to '$webvar{custid}' as ".
+	"'$webvar{alloctype}'";
+    if ($webvar{alloctype} =~ /^.i$/) {
+      mailNotify($ip_dbh, "a$webvar{alloctype}", "ADDED: $disp_alloctypes{$webvar{alloctype}} allocation",
+	"$disp_alloctypes{$webvar{alloctype}} $webvar{cidr} allocated to customer $webvar{custid}\n".
+	"Description: $webvar{desc}\n\nAllocated by: $authuser\n");
+    }
+  } else {
+    $page->param(errmsg => $msg);
+    syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
+	"'$webvar{alloctype}' failed: '$msg'";
+  }
+
+} elsif ($webvar{action} eq 'alloctweak') {
+
+  fix_allocfrom();
+  showAllocs($webvar{allocfrom});
+
+} elsif ($webvar{action} eq 'update') {
+
+  update();
+
+} elsif ($webvar{action} eq 'touch') {
+
+  my ($code,$msg) = touchMaster($ip_dbh, $webvar{whichmaster});
+  $page->param(errmsg => $msg) if $code eq 'FAIL';
+
+} elsif ($webvar{action} eq 'listcust') {
+
+  $sth = $ip_dbh->prepare("select custid,name,tech_handle from customers order by custid");
+  $sth->execute;
+  my @custlist;
+  while (my @data = $sth->fetchrow_array) {
+    my %row = (
+	custid => $data[0],
+	custname => $data[1],
+	tech => $data[2]
+	);
+    push @custlist, \%row;
+  }
+  $page->param(custlist => \@custlist);
+
+} elsif ($webvar{action} eq 'edcust') {
+
+  if ($webvar{newcust}) {
+    $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;
+
+  $page->param(
+	custid => $custid,
+	name => $name,
+	street => $street,
+	city => $city,
+	prov => $prov,
+	country => $country,
+	pocode => $pocode,
+	phone => $phone,
+	tech => $tech,
+	abuse => $abuse,
+	admin => $admin,
+	special => $special
+	);
+
+} 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});
+  $page->param(
+	custid => $webvar{custid},
+	name => $webvar{name},
+	street => $webvar{street},
+	city => $webvar{city},
+	prov => $webvar{province},
+	country => $webvar{country},
+	pocode => $webvar{pocode},
+	phone => $webvar{phone},
+	tech => $webvar{tech_handle},
+	abuse => $webvar{abuse_handle},
+	admin => $webvar{admin_handle},
+	special => $webvar{special}
+	);
+
+} elsif ($webvar{action} eq 'showpools') {
+
+  $sth = $ip_dbh->prepare("select pool, count(*) from poolips where available='y' group by pool order by pool");
+  $sth->execute;
+  my @poollist;
+  while (my ($pool,$free) = $sth->fetchrow_array) {
+    my %row = (
+	pool => $pool,
+	free => $free
+	);
+    push @poollist, \%row;
+  }
+  $page->param(poollist => \@poollist);
+
+} elsif ($webvar{action} eq 'tweakpool') {
+
+  showPool($webvar{pool});
+
+} elsif ($webvar{action} eq 'updatepool') {
+
+  $sth = $ip_dbh->prepare("update poolips set custid='$webvar{custid}', ".
+	"city=?, type='$webvar{type}', available='".
+	(($webvar{available} eq 'y') ? 'y' : 'n').
+	"', notes=?, description=? ".
+	"where ip='$webvar{ip}'");
+  $sth->execute($webvar{city},$webvar{notes},$webvar{desc});
+  $page->param(ip => $webvar{ip});
+  if ($sth->err) {
+    $page->param(errmsg => $sth->errstr);
+    syslog "err", "$authuser could not update pool IP $webvar{ip}: ".$sth->errstr;
+  } else {
+    syslog "notice", "$authuser updated pool IP $webvar{ip}";
+  }
+  $sth = $ip_dbh->prepare("select pool from poolips where ip='$webvar{ip}'");
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+  $page->param(pool => $data[0]);
+
+} elsif ($webvar{action} eq 'showusers') {
+
+  $sth = $ip_dbh->prepare("select username,acl from users order by username");
+  $sth->execute;
+  my @userlist;
+  while (my ($username,$acl) = $sth->fetchrow_array) {
+##fixme: funky things happening with HTML::Template here;  shouldn't need the "logic ? iftrue : iffalse" structure
+    my %row = (
+	username => $username,
+	can_add => ($acl =~ /a/ ? 1 : 0),
+	can_change => ($acl =~ /c/ ? 1 : 0),
+	can_del => ($acl =~ /d/ ? 1 : 0),
+	sysnet => ($acl =~ /s/ ? 1 : 0),
+	is_admin => ($acl =~ /A/ ? 1 : 0),
+	acl => $acl
+	);
+    push @userlist, \%row;
+  }
+  $page->param(userlist => \@userlist);
+
+} elsif ($webvar{action} eq 'updacl') {
+
+  $page->param(username => $webvar{username});
+  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' : '');
+  }
+  $page->param(acl => $acl);
+
+  $sth = $ip_dbh->prepare("update users set acl='$acl' where username='$webvar{username}'");
+  $sth->execute;
+  $page->param(errmsg => $sth->errstr) if $sth->err;
+
+} elsif ($webvar{action} eq 'newuser') {
+
+  $page->param(username => $webvar{username});
+  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;
+  $page->param(errmsg => $sth->errstr) if $sth->err;
+
+} elsif ($webvar{action} eq 'deluser') {
+
+  $page->param(username => $webvar{username});
+  $sth = $ip_dbh->prepare("delete from users where username='$webvar{username}'");
+  $sth->execute;
+  $page->param(errmsg => $sth->errstr) if $sth->err;
+
+} elsif ($webvar{action} eq 'emailnotice') {
+
+  $sth = $ip_dbh->prepare("SELECT action,reciplist FROM notify");
+  $sth->execute;
+  my @spamlist;
+  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);
+    my %row = (
+	action => $action_out,
+	code => $notice_code,
+	recips => $reciplist
+	);
+    push @spamlist, \%row;
+  }
+  $page->param(spamlist => \@spamlist);
+
+  $sth = $ip_dbh->prepare("SELECT type,dispname FROM alloctypes WHERE listorder < 500 ".
+	"ORDER BY listorder");
+  $sth->execute;
+  my $i=0;
+  my @typelist;
+  while (my ($type,$disp) = $sth->fetchrow_array) {
+    my %row = (
+	type => $type,
+	disptype => $disp,
+# ahh, off-by-one counts, how we do love thee...  NOT!
+	newrow => ($i+2 > $sth->rows ? 1 : (++$i % 4)),
+	);
+    push @typelist, \%row;
+  }
+  $page->param(typelist => \@typelist);
+
+} elsif ($webvar{action} eq 'addnotice') {
+
+  $webvar{alloctype} = $webvar{special} if $webvar{msgaction} eq 's:';
+  if ($webvar{msgaction} && $webvar{alloctype} && $webvar{reciplist}) {
+    $page->param(cantry => 1);
+    $webvar{reciplist} =~ s/[\r\n]+/,/g;
+    $webvar{msgaction} = "f:$webvar{msgaction}" if $webvar{onfail};
+    $page->param(reciplist => $webvar{reciplist});
+    $page->param(dispnotice => dispNoticeCode($webvar{msgaction}.$webvar{alloctype}));
+    $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});
+    $page->param(addfailed => $sth->errstr) if $sth->err;
+  }
+
+} elsif ($webvar{action} eq 'delnotice') {
+
+  $page->param(dispnotice => dispNoticeCode($webvar{code}.$webvar{alloctype}));
+  $sth = $ip_dbh->prepare("DELETE FROM notify WHERE action=?");
+  $sth->execute($webvar{code});
+  $page->param(delfailed => $sth->errstr) if $sth->err;
+
+} elsif ($webvar{action} eq 'ednotice') {
+
+  $page->param(dispnotice => dispNoticeCode($webvar{code}));
+  $page->param(code => $webvar{code});
+  $sth = $ip_dbh->prepare("SELECT reciplist FROM notify WHERE action=?");
+  $sth->execute($webvar{code});
+  my ($reciplist) = $sth->fetchrow_array;
+  $reciplist =~ s/,/\n/g;
+  $page->param(reciplist => $reciplist);
+
+} elsif ($webvar{action} eq 'updnotice') {
+
+  $page->param(dispnotice => dispNoticeCode($webvar{code}));
+  $sth = $ip_dbh->prepare("UPDATE notify SET reciplist=? WHERE action=?");
+  $webvar{reciplist} =~ s/[\r\n]+/,/g;
+  $sth->execute($webvar{reciplist}, $webvar{code});
+  $page->param(updfailed => $sth->errstr) if $sth->err;
+
+} elsif ($webvar{action} ne '<NULL>') {
+  $page->param(dunno => $webvar{action});
+}
+
+ERRJUMP:
+
+$header->param(version => $IPDB::VERSION);
+#$header->param(addperm => $IPDBacl{$authuser} =~ /a/);
+$header->param(webpath => $IPDB::webpath);
+
+print "Content-type: text/html\n\n".$header->output;
+
+print $page->output;
+
+##fixme:  make me a footer param!
+print qq(<hr><div><a href="$IPDB::webpath/">Back</a> to main interface</div>\n);
+
+# We print the footer here, so we don't have to do it elsewhere.
+my $footer = HTML::Template->new(filename => "footer.tmpl");
+# we're already in the admin tools, no need to provide a bottom link.  maybe.
+#$footer->param(adminlink => ($IPDBacl{$authuser} =~ /A/));
+
+print $footer->output;
+
+$ip_dbh->disconnect;
+
+exit;
+
+
+# Hokay.  This is a little different.  We have a few specific functions here:
+#  -> Assign arbitrary subnet from arbitrary free space
+#  -> Tweak individual DB fields
+#
+
+
+# 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";
+  }
+}
+
+
+# Show allocations to allow editing.
+sub showAllocs {
+
+  my $within = new NetAddr::IP $_[0];
+  $page->param(within => $within);
+
+  $sth = $ip_dbh->prepare("select cidr,custid,type,city,description from allocations where cidr <<= '$within' order by cidr");
+  $sth->execute;
+  my @blocklist;
+  while (my ($cidr,$custid,$type,$city,$desc) = $sth->fetchrow_array) {
+    my %row = (
+	cidr => $cidr,
+	custid => $custid,
+	city => $city,
+	desc => $desc,
+	);
+
+##fixme:  don't wanna retrieve the whole type list *every time around the outer loop*
+    my $sth2 = $ip_dbh->prepare("select type,listname from alloctypes".
+	" where listorder < 500 and not (type like '_i') order by listorder");
+    $sth2->execute;
+    my @typelist;
+    while (my ($listtype,$dispname) = $sth2->fetchrow_array) {
+      my %subrow = (
+	type => $listtype,
+	dispname => $dispname,
+	selected => ($listtype eq $type)
+	);
+      push @typelist, \%subrow;
+    }
+    $row{typelist} = \@typelist;
+    push @blocklist, \%row;
+  }
+  $page->param(blocklist => \@blocklist);
+} # end showAllocs()
+
+
+# Stuff updates into DB
+sub update {
+  # 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=?,type='$webvar{alloctype}' where cidr='$webvar{block}'");
+  $sth->execute($webvar{city});
+
+  $page->param(block => $webvar{block});
+  if ($sth->err) {
+    $page->param(updfailed => $sth->errstr);
+    syslog "err", "$authuser could not update block '$webvar{block}': '".$sth->errstr."'";
+  } else {
+    syslog "notice", "$authuser updated $webvar{block}";
+  }
+  # need to get /24 that block is part of
+  my @bits = split /\./, $webvar{block};
+  $bits[3] = "0/24";
+  showAllocs((join ".", @bits));
+} # end update()
+
+
+# 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];
+
+  $sth = $ip_dbh->prepare("select type,listname from alloctypes where type like '_i' order by listorder");
+  $sth->execute;
+  my @typelist;
+  while (my ($type,$dispname) = $sth->fetchrow_array) {
+    my %row = (
+	type => $type,
+	dispname => $dispname
+	);
+    push @typelist, \%row;
+  }
+  $page->param(typelist => \@typelist);
+
+  $sth = $ip_dbh->prepare("SELECT ip,custid,city,type,available,description,notes from poolips".
+	" WHERE pool=? ORDER BY ip");
+  $sth->execute($pool);
+  my @iplist;
+  while (my ($ip,$custid,$city,$type,$avail,$desc,$notes) = $sth->fetchrow_array) {
+    my %row = (
+	ip => $ip,
+	custid => $custid,
+	city => $city,
+	type => $type,
+	avail => $avail,
+	desc => $desc,
+	notes => $notes
+	);
+    push @iplist, \%row;
+  }
+  $page->param(iplist => \@iplist);
+} # end showPool()
+
+
+# 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: tags/2.8.2/cgi-bin/allocate.pl
===================================================================
--- tags/2.8.2/cgi-bin/allocate.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/allocate.pl	(revision 617)
@@ -0,0 +1,103 @@
+#!/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 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;
+die "Failed to connect to database: $errstr\n"
+	if !$ip_dbh;
+
+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: tags/2.8.2/cgi-bin/checkcusts.pl
===================================================================
--- tags/2.8.2/cgi-bin/checkcusts.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/checkcusts.pl	(revision 617)
@@ -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: tags/2.8.2/cgi-bin/combineblocks.pl
===================================================================
--- tags/2.8.2/cgi-bin/combineblocks.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/combineblocks.pl	(revision 617)
@@ -0,0 +1,76 @@
+#!/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 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: tags/2.8.2/cgi-bin/consistency-check.pl
===================================================================
--- tags/2.8.2/cgi-bin/consistency-check.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/consistency-check.pl	(revision 617)
@@ -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: tags/2.8.2/cgi-bin/extras/db2rwhois.pl
===================================================================
--- tags/2.8.2/cgi-bin/extras/db2rwhois.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/extras/db2rwhois.pl	(revision 617)
@@ -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-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+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 family(cidr)=4) or (masklen(cidr) <=56 and family(cidr)=6)) 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: tags/2.8.2/cgi-bin/extras/network.tmpl
===================================================================
--- tags/2.8.2/cgi-bin/extras/network.tmpl	(revision 617)
+++ tags/2.8.2/cgi-bin/extras/network.tmpl	(revision 617)
@@ -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: tags/2.8.2/cgi-bin/extras/rwhois-config
===================================================================
--- tags/2.8.2/cgi-bin/extras/rwhois-config	(revision 617)
+++ tags/2.8.2/cgi-bin/extras/rwhois-config	(revision 617)
@@ -0,0 +1,20 @@
+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: tags/2.8.2/cgi-bin/freespace.pl
===================================================================
--- tags/2.8.2/cgi-bin/freespace.pl	(revision 617)
+++ tags/2.8.2/cgi-bin/freespace.pl	(revision 617)
@@ -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: tags/2.8.2/cgi-bin/ipdb-rpc.cgi
===================================================================
--- tags/2.8.2/cgi-bin/ipdb-rpc.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/ipdb-rpc.cgi	(revision 617)
@@ -0,0 +1,210 @@
+#!/usr/bin/perl
+# XMLRPC interface to manipulate most DNS DB entities
+###
+# $Id$
+###
+# Copyright (C) 2013 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;
+use warnings;
+
+use DBI;
+use CustIDCK;
+use NetAddr::IP;
+use FCGI;
+use Frontier::Responder;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use MyIPDB;
+
+openlog "IPDB-rpc","pid","$IPDB::syslog_facility";
+
+##fixme:  username source?  can we leverage some other auth method?
+# we don't care except for logging here, and Frontier::Client needs
+# a patch that's not well-distributed to use HTTP AUTH.
+
+# 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;
+initIPDBGlobals($ip_dbh);
+
+my $methods = {
+#	'ipdb.getCityList'	=> \&rpc_getCityList,
+	'ipdb.getAvailableStatics'	=> \&rpc_getAvailableStatics,
+	'ipdb.allocateBlock'	=>	\&rpc_allocateBlock,
+};
+
+my $reqcnt = 0;
+
+# main FCGI loop.
+while (FCGI::accept >= 0) {
+  # done here to a) prevent $ENV{'REMOTE_ADDR'} from being empty and b) to collect
+  # the right user for the individual call (since we may be running with FCGI)
+  syslog "debug", "$authuser active, $ENV{'REMOTE_ADDR'}";
+
+  # don't *think* we need any of these...
+  # %disp_alloctypes, %def_custids, %list_alloctypes
+  # @citylist, @poplist
+  # @masterblocks, %allocated, %free, %bigfree, %routed  (removed in /trunk)
+  # %IPDBacl
+  #initIPDBGlobals($ip_dbh);
+
+  my $res = Frontier::Responder->new(
+        methods => $methods
+        );
+
+  # "Can't do that" errors
+  if (!$ip_dbh) {
+    print "Content-type: text/xml\n\n".$res->{_decode}->encode_fault(5, $DBI::errstr);
+  } else {
+    print $res->answer;
+  }
+  last if $reqcnt++ > $IPDB::maxfcgi;
+} # while FCGI::accept
+
+exit 0;
+
+
+##
+## Private subs
+##
+
+# Check RPC ACL
+sub _aclcheck {
+  my $subsys = shift;
+  return 1 if grep /$ENV{REMOTE_ADDR}/, @{$IPDB::rpcacl{$subsys}};
+  warn "$subsys/$ENV{REMOTE_ADDR} not in ACL\n";        # a bit of logging
+  return 0;
+}
+
+sub _commoncheck {
+  my $argref = shift;
+  my $needslog = shift;
+
+  die "Missing remote system name\n" if !$argref->{rpcsystem};
+  die "Access denied\n" if !_aclcheck($argref->{rpcsystem});
+  if ($needslog) {
+    die "Missing remote username\n" if !$argref->{rpcuser};
+  }
+}
+
+# stripped-down copy from from main.cgi.  should probably be moved to IPDB.pm
+sub _validateInput {
+  my $argref = shift;
+
+  die "Block/IP is required\n" if !$argref->{block};
+
+  # Alloctype check.
+  chomp $argref->{alloctype};
+
+  die "Invalid allocation type\n" if (!grep /$argref->{alloctype}/, keys %disp_alloctypes);
+
+  # Arguably not quite correct, as the custID won't be checked for
+  # validity if there's a default on the type.
+  if ($def_custids{$argref->{alloctype}} eq '') {
+    # Types without a default custID must have one passed in
+    die "Customer ID is required\n" if !$argref->{custid};
+    # Crosscheck with billing.
+    my $status = CustIDCK->custid_exist($argref->{custid});
+    die "Error verifying customer ID: $CustIDCK::ErrMsg\n" if $CustIDCK::Error;
+    die "Customer ID not valid\n" if !$status;
+  } else {
+    # Types that have a default will use it unless one is specified.
+    if ((!$argref->{custid}) || ($argref->{custid} ne 'STAFF')) {
+      $argref->{custid} = $def_custids{$argref->{alloctype}};
+    }
+  }
+} # end validateInput()
+
+
+##
+## RPC method subs
+##
+
+# Prefixed with rpc_ to not conflict with subs in IPDB.pm
+
+sub rpc_getCityList {
+  my %args = @_;
+
+  _commoncheck(\%args, 'n');
+}
+
+# Get a list of available static IPs of the given type
+sub rpc_getAvailableStatics {
+  my %args = @_;
+
+  _commoncheck(\%args, 'n');
+
+  my ($base,undef) = split //, $args{type};
+  $base .= "_";
+  my @params = ($base);
+
+  # IPDB 2.7
+  my $sql = "SELECT 0 AS id,poolips.ip,0 AS parent_id,pool ".
+  "FROM poolips JOIN allocations ON poolips.pool=allocations.cidr WHERE poolips.type LIKE ?";
+
+  $sql .= " AND allocations.city=?" if $base ne 'd_';
+  push @params, $args{city} if $base ne 'd_';
+
+  $sql .= " AND poolips.available='y'";
+
+  my $ret = $ip_dbh->selectall_arrayref($sql, { Slice => {} }, (@params) );
+  # IPDB 3.0
+  #my $ret = $ipdb->getIPList(arg arg arg);
+  die $ip_dbh->errstr if !$ret;
+  return $ret;
+} # rpc_getAvailableStatics()
+
+sub rpc_allocateBlock {
+  my %args = @_;
+
+  _commoncheck(\%args, 'y');
+
+  _validateInput(\%args);
+
+  # Not required for update, delete
+  die "City is required\n" if !$args{city};
+  die "Block/pool to allocate from is required\n" if !$args{alloc_from};
+
+  # Desc, notes, circid, privdata, node, and vrf are all optional and handled in allocateBlock()
+  my ($code,$msg) = allocateBlock($ip_dbh, $args{block}, $args{alloc_from},
+        $args{custid}, $args{alloctype}, $args{city}, $args{desc}, $args{notes},
+        $args{circid}, $args{privdata}, $args{node}, $args{vrf});
+
+  if ($code eq 'OK' || $code eq 'WARN') {
+    if ($args{alloctype} =~ /^.i$/) {
+      $msg =~ s|/32||;
+      mailNotify($ip_dbh, "a$args{alloctype}", "ADDED: $disp_alloctypes{$args{alloctype}} allocation",
+        "$disp_alloctypes{$args{alloctype}} $msg allocated to customer $args{custid}\n".
+        "Description: $args{desc}\n\nAllocated by: $args{rpcsystem}/$args{rpcuser}\n");
+    } else {
+      my $netblock = new NetAddr::IP $args{block};
+      mailNotify($ip_dbh, "a$args{alloctype}", "ADDED: $disp_alloctypes{$args{alloctype}} allocation",
+        "$disp_alloctypes{$args{alloctype}} $args{block} allocated to customer $args{custid}\n".
+        "Description: $args{desc}\n\nAllocated by: $args{rpcsystem}/$args{rpcuser}\n");
+    }
+    syslog "notice", "$args{rpcsystem}/$args{rpcuser} allocated '$args{block}' to '$args{custid}' as ".
+        "'$args{alloctype}' ($msg)";
+  } else {
+    syslog "err", "Allocation of '$args{block}' to '$args{custid}' as ".
+        "'$args{alloctype}' by $args{rpcsystem}/$args{rpcuser} failed: '$msg'";
+    die "$msg\n";
+  }
+  return $msg;
+} # rpc_allocateBlock()
Index: tags/2.8.2/cgi-bin/ipdb.psql
===================================================================
--- tags/2.8.2/cgi-bin/ipdb.psql	(revision 617)
+++ tags/2.8.2/cgi-bin/ipdb.psql	(revision 617)
@@ -0,0 +1,179 @@
+-- Initial table setup for IP Database
+
+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")
+);
+
+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
+);
+
+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()
+);
+
+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'
+);
+
+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,
+	"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(),
+	"vrf" character varying(128) DEFAULT '' NOT NULL,
+	CHECK (((available = 'y'::bpchar) OR (available = 'n'::bpchar)))
+);
+
+CREATE TABLE "allocations" (
+	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"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'
+	"vrf" character varying(128) DEFAULT '' NOT NULL,
+);
+
+CREATE VIEW "searchme" as SELECT allocations.cidr, allocations.custid, allocations."type", allocations.city, allocations.description, allocations.notes, allocations.circuitid, allocations.vrf FROM allocations UNION SELECT poolips.ip, poolips.custid, poolips.type, poolips.city, poolips.description, poolips.notes, poolips.circuitid, poolips.vrf FROM poolips;
+
+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
+\.
+
+CREATE TABLE "cities" (
+	"id" serial NOT NULL PRIMARY KEY,
+	"city" character varying(30) DEFAULT '' NOT NULL,
+	"routing" character(1) DEFAULT 'n' NOT NULL
+);
+
+--
+-- 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(40) NOT NULL PRIMARY KEY,
+	"password" varchar(60) DEFAULT '',
+	"acl" varchar(30) DEFAULT 'b'
+);
+
+-- Default password is admin
+INSERT INTO users VALUES ('admin','luef5C4XumqIs','bacdsA');
+
+-- 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: tags/2.8.2/cgi-bin/main.cgi
===================================================================
--- tags/2.8.2/cgi-bin/main.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/main.cgi	(revision 617)
@@ -0,0 +1,905 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/main.cgi
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright (C) 2004-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
+use DBI;
+use POSIX qw(ceil);
+use NetAddr::IP;
+
+use Sys::Syslog;
+
+# don't remove!  required for GNU/FHS-ish install from tarball
+##uselib##
+
+use CustIDCK;
+use MyIPDB;
+
+openlog "IPDB","pid","$IPDB::syslog_facility";
+
+## Environment.  Collect some things, process some things, set some things...
+
+# 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'};
+}
+
+# anyone got a better name?  :P
+my $thingroot = $ENV{SCRIPT_FILENAME};
+$thingroot =~ s|cgi-bin/main.cgi||;
+
+syslog "debug", "$authuser active, $ENV{'REMOTE_ADDR'}";
+
+##fixme there *must* be a better order to do things in so this can go back where it was
+# CGI fiddling done here so we can declare %webvar so we can alter $webvar{action}
+# to show the right page on DB errors.
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+# 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 $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  $webvar{action} = "dberr";
+} else {
+  initIPDBGlobals($ip_dbh);
+}
+
+# Set up some globals
+$ENV{HTML_TEMPLATE_ROOT} = $thingroot."templates";
+
+my $header = HTML::Template->new(filename => "header.tmpl");
+my $footer = HTML::Template->new(filename => "footer.tmpl");
+
+$header->param(version => $IPDB::VERSION);
+$header->param(addperm => $IPDBacl{$authuser} =~ /a/);
+$header->param(webpath => $IPDB::webpath);
+print "Content-type: text/html\n\n", $header->output;
+
+
+#main()
+my $aclerr;
+
+if(!defined($webvar{action})) {
+  $webvar{action} = "index";	#shuts up the warnings.
+}
+
+my $page;
+if (-e "$ENV{HTML_TEMPLATE_ROOT}/$webvar{action}.tmpl") {
+  $page = HTML::Template->new(filename => "$webvar{action}.tmpl", loop_context_vars => 1, global_vars => 1);
+} else {
+  $page = HTML::Template->new(filename => "dunno.tmpl");
+}
+
+if($webvar{action} eq 'index') {
+  showSummary();
+} elsif ($webvar{action} eq 'addmaster') {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    $aclerr = 'addmaster';
+  }
+} elsif ($webvar{action} eq 'newmaster') {
+
+  if ($IPDBacl{$authuser} !~ /a/) {
+    $aclerr = 'addmaster';
+  } else {
+    my $cidr = new NetAddr::IP $webvar{cidr};
+    $page->param(cidr => "$cidr");
+
+    my ($code,$msg) = addMaster($ip_dbh, $webvar{cidr});
+
+    if ($code eq 'FAIL') {
+      syslog "err", "Could not add master block '$webvar{cidr}' to database: '$msg'";
+      $page->param(err => $msg);
+    } else {
+      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') {
+  showPool();
+}
+
+# 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') {
+  my $nodelist = getNodeList($ip_dbh);
+  $page->param(nodelist => $nodelist);
+}
+
+# DB failure.  Can't do much here, really.
+elsif ($webvar{action} eq 'dberr') {
+  $page->param(errmsg => $errstr);
+}
+
+# Default is an error.  It shouldn't be possible to get here unless you're
+# randomly feeding in values for webvar{action}.
+else {
+  my $rnd = rand 500;
+  my $boing = sprintf("%.2f", rand 500);
+  my @excuses = (
+	"Aether cloudy.  Ask again later about $webvar{action}.",
+	"The gods are unhappy with your sacrificial $webvar{action}.",
+	"Because one of $webvar{action}'s legs are both the same",
+	"<b>wibble</b><br>Can't $webvar{action}, the grue will get me!<br>Can't $webvar{action}, the grue will get me!",
+	"Hey, man, you've had your free $webvar{action}.  Next one's gonna...  <i>cost</i>....",
+	"I ain't done $webvar{action}",
+	"Oooo, look!  A flying $webvar{action}!",
+	"$webvar{action} too evil, avoiding.",
+	"Rocks fall, $webvar{action} dies.",
+	"Bit bucket must be emptied before I can $webvar{action}..."
+	);
+  $page->param(dunno => $excuses[$rnd/50.0]);
+}
+## Finally! Done with that NASTY "case" emulation!
+
+
+# Switch to a different template if we've tripped on an ACL error.
+# Note that this should only be exercised in development, when
+# deeplinked, or when being attacked;  normal ACL handling should
+# remove the links a user is not allowed to click on.
+if ($aclerr) {
+  $page = HTML::Template->new(filename => "aclerror.tmpl");
+  $page->param(ipdbfunc => $aclmsg{$aclerr});
+}
+
+# Clean up IPDB globals, DB handle, etc.
+finish($ip_dbh);
+
+## Do all our printing here so we can generate errors and stick them into the slots in the templates.
+
+# can't do this yet, too many blowups
+#print "Content-type: text/html\n\n", $header->output;
+$page->param(webpath => $IPDB::webpath);
+print $page->output;
+
+# include the admin tools link in the output?
+$footer->param(adminlink => ($IPDBacl{$authuser} =~ /A/));
+$footer->param(webpath => $IPDB::webpath);
+print $footer->output;
+
+# Just in case something waaaayyy down isn't in place
+# properly... we exit explicitly.
+exit 0;
+
+
+# Initial display:  Show master blocks with total allocated subnets, total free subnets
+sub showSummary {
+  my $masterlist = listSummary($ip_dbh);
+  $page->param(masterlist => $masterlist);
+
+  $page->param(addmaster => ($IPDBacl{$authuser} =~ /a/) );
+} # 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 {
+
+  $page->param(master => $webvar{block});
+  $page->param(delmaster => ($IPDBacl{$authuser} =~ /d/));
+
+  my $rlist = listMaster($ip_dbh, $webvar{block});
+  $page->param(routedlist => $rlist);
+
+  my $flist = listFree($ip_dbh, $webvar{block});
+  $page->param(unrouted => $flist);
+  $page->param(mayadd => ($IPDBacl{$authuser} =~ /a/));
+} # 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 {
+
+  $page->param(master => $webvar{block});
+  $page->param(delrouted => $IPDBacl{$authuser} =~ /d/);
+
+  my $rcity = getRoutedCity($ip_dbh, $webvar{block});
+  $page->param(rcity => $rcity);
+
+  my $blist = listRBlock($ip_dbh, $webvar{block});
+  $page->param(blocklist => $blist);
+
+  my $flist = listFree($ip_dbh, $webvar{block}, 'y');
+  $page->param(unassigned => $flist);
+} # showRBlock
+
+
+# List the IPs used in a pool
+sub showPool {
+
+  my $cidr = new NetAddr::IP $webvar{pool};
+
+  $page->param(block => $webvar{pool});
+  $page->param(netip => $cidr->addr);
+  $cidr++;
+  $page->param(gate => $cidr->addr);
+  $cidr--;  $cidr--;
+  $page->param(bcast => $cidr->addr);
+  $page->param(mask => $cidr->mask);
+
+  # Snag pool info for heading
+  my $poolinfo = getBlockData($ip_dbh, $webvar{pool});
+
+  $page->param(disptype => $disp_alloctypes{$poolinfo->{type}});
+  $page->param(city => $poolinfo->{city});
+
+  # Only display net/gw/bcast if it's a "real" netblock and not a PPP(oE) lunacy
+  $page->param(realblock => $poolinfo->{type} =~ /^.d$/);
+
+# probably have to add an "edit IP allocation" link here somewhere.
+
+  my $plist = listPool($ip_dbh, $webvar{pool});
+  # technically slightly more efficient to check the ACL in an if () once outside the foreach
+  foreach (@{$plist}) {
+    $$_{maydel} = $IPDBacl{$authuser} =~ /d/;
+  }
+  $page->param(poolips => $plist);
+} # end showPool
+
+
+# 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/) {
+    $aclerr = 'addblock';
+    return;
+  }
+
+  # hack pthbttt eww
+  $webvar{block} = '' if !$webvar{block};
+
+# hmm.  TMPL_IF block and TMPL_ELSE block on these instead?
+  $page->param(rowa => 'row'.($webvar{block} eq '' ? 1 : 0));
+  $page->param(rowb => 'row'.($webvar{block} eq '' ? 0 : 1));
+  $page->param(allocfrom => $webvar{block});    # fb-assign flag, if block is set, we're in fb-assign
+
+  if ($webvar{fbtype}) {
+
+    # Common case, according to reported usage.  Block to assign is specified.
+    my $block = new NetAddr::IP $webvar{block};
+
+    $webvar{fbtype} = '' if !$webvar{fbtype};
+    if ($webvar{fbtype} eq 'i') {
+      my $ipinfo = getBlockData($ip_dbh, $webvar{block}, 'i');
+##fixme:  a reversion!  kill it!
+my ($iptype,$pool) = $ip_dbh->selectrow_array("SELECT type,pool FROM poolips WHERE ip = ?", undef, ($block));
+      $page->param(
+        fbip => 1,
+        block => $ipinfo->{block},
+        fbdisptype => $list_alloctypes{$ipinfo->{type}},
+        type => $ipinfo->{type},
+        allocfrom => $pool,
+	vrf => $ipinfo->{vrf},
+        );
+    } elsif ($webvar{fbtype} eq 'n') {
+      $page->param(
+        fbrouted => 1,
+        fbdisptype => $list_alloctypes{'rm'},
+        type => 'rm',
+        block => $block,
+        );
+    } else {
+      # get "primary" alloctypes, since these are all that can correctly be assigned if we're in this branch
+      my $tlist = getTypeList($ip_dbh, 'n');
+      $tlist->[0]->{sel} = 1;
+      $page->param(typelist => $tlist, block => $block);
+    }
+
+  } else {
+
+    # Uncommon case, according to reported usage.  Block to assign needs to be found based on criteria.
+    my $mlist = getMasterList($ip_dbh, 'c');
+    $page->param(masterlist => $mlist);
+
+    my @pops;
+    foreach my $pop (@citylist) {
+      my %row = (pop => $pop);
+      push (@pops, \%row);
+    }
+    $page->param(pops => \@pops);
+
+    # get all standard alloctypes
+    my $tlist = getTypeList($ip_dbh, 'a');
+    $tlist->[0]->{sel} = 1;
+    $page->param(typelist => $tlist);
+  }
+  my @cities;
+  foreach my $city (@citylist) {
+    my %row = (city => $city);
+    push (@cities, \%row);
+  }
+  $page->param(citylist => \@cities);
+
+## node hack
+  my $nlist = getNodeList($ip_dbh);
+  $page->param(nodelist => $nlist);
+## end node hack
+
+  $page->param(privdata => $IPDBacl{$authuser} =~ /s/);
+
+} # assignBlock
+
+
+# Take info on requested IP assignment and see what we can provide.
+sub confirmAssign {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    $aclerr = 'addblock';
+    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$/ && $webvar{fbassign} ne 'y') {
+    my $plist = getPoolSelect($ip_dbh, $webvar{alloctype}, $webvar{pop});
+    $page->param(staticip => 1);
+    $page->param(poollist => $plist) if $plist;
+    $cidr = "Single static IP";
+##fixme:  need to handle "no available pools"
+
+  } else { # end show pool options
+
+    if ($webvar{fbassign} && $webvar{fbassign} eq 'y') {
+      $cidr = new NetAddr::IP $webvar{block};
+      $alloc_from = new NetAddr::IP $webvar{allocfrom};
+      $webvar{maskbits} = $cidr->masklen;
+    } else { # done with direct freeblocks assignment
+
+      if (!$webvar{maskbits}) {
+        $page->param(err => "Please specify a CIDR mask length.");
+	return;
+      }
+
+##fixme ick, ew, bleh.  gotta handle the failure message generation better.  push it into findAllocateFrom()?
+      my $failmsg = "No suitable free block found.<br>\n";
+      if ($webvar{alloctype} eq 'rm') {
+	$failmsg .= "We do not have a free routeable block of that size.<br>\n".
+		"You will have to either route a set of smaller netblocks or a single smaller netblock.";
+      } else {
+	if ($webvar{alloctype} =~ /^.[pc]$/) {
+	  $failmsg .= "You will have to route another superblock from one of the<br>\n".
+		"master blocks or chose a smaller block size for the pool.";
+	} else {
+	  if (!$webvar{pop}) {
+	    $page->param(err => 'Please select a POP to route the block from/through.');
+	    return;
+	  }
+	  $failmsg .= "You will have to route another superblock to $webvar{pop}<br>\n".
+		"from one of the master blocks or chose a smaller blocksize.";
+	}
+      }
+
+      $cidr = findAllocateFrom($ip_dbh, $webvar{maskbits}, $webvar{alloctype}, $webvar{city}, $webvar{pop},
+	(master => $webvar{allocfrom}, allowpriv => $webvar{allowpriv}) );
+      if (!$cidr) {
+	$page->param(err => $failmsg);
+	return;
+      }
+      $cidr = new NetAddr::IP $cidr;
+
+# this chunk now specific to "guided" allocation;  freeblock-select can now slice-n-dice on its own.
+      $alloc_from = "$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];
+      }
+    } # check for freeblocks assignment or IPDB-controlled assignment
+
+  } # if ($webvar{alloctype} =~ /^.i$/)
+
+## node hack
+  if ($webvar{node} && $webvar{node} ne '-') {
+    my $nodename = getNodeName($ip_dbh, $webvar{node});
+    $page->param(nodename => $nodename);
+    $page->param(nodeid => $webvar{node});
+  }
+## end node hack
+
+  # Stick in the allocation data
+  $page->param(alloc_type => $webvar{alloctype});
+  $page->param(typefull => $q->escapeHTML($disp_alloctypes{$webvar{alloctype}}));
+  $page->param(alloc_from => $alloc_from);
+  $page->param(cidr => $cidr);
+  $page->param(city => $q->escapeHTML($webvar{city}));
+  $page->param(vrf => $webvar{vrf});
+  $page->param(custid => $webvar{custid});
+  $page->param(circid => $q->escapeHTML($webvar{circid}));
+  $page->param(desc => $q->escapeHTML($webvar{desc}));
+
+##fixme: find a way to have the displayed copy have <br> substitutions
+# for newlines, and the <input> value have either encoded or bare newlines.
+# Also applies to privdata.
+  $page->param(notes => $q->escapeHTML($webvar{notes},'y'));
+
+  # Check to see if user is allowed to do anything with sensitive data
+  my $privdata = '';
+  $page->param(privdata => $q->escapeHTML($webvar{privdata},'y'))
+	if $IPDBacl{$authuser} =~ /s/;
+
+  # Yay!  This now has it's very own little home.
+  $page->param(billinguser => $webvar{userid})
+	if $webvar{userid};
+
+##fixme:  this is only needed iff confirm.tmpl and
+# confirmRemove.tmpl are merged (quite possible, just
+# a little tedious)
+  $page->param(action => "insert");
+
+} # end confirmAssign
+
+
+# Do the work of actually inserting a block in the database.
+sub insertAssign {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    $aclerr = 'addblock';
+    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}, $webvar{vrf});
+
+  if ($code eq 'OK' || $code eq 'WARN') {
+    if ($webvar{alloctype} =~ /^.i$/) {
+      $msg =~ s|/32||;
+      $page->param(staticip => $msg);
+      $page->param(custid => $webvar{custid});
+      $page->param(billinguser => $webvar{billinguser});
+      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};
+      $page->param(fullcidr => $webvar{fullcidr});
+      $page->param(alloctype => $disp_alloctypes{$webvar{alloctype}});
+      $page->param(custid => $webvar{custid});
+      if ($webvar{alloctype} eq 'pr' && $webvar{billinguser}) {
+	$page->param(billinguser => $webvar{billinguser});
+	$page->param(custid => $webvar{custid});
+	$page->param(netaddr => $netblock->addr);
+	$page->param(masklen => $netblock->masklen);
+      }
+      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'";
+    $page->param(err => "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 '-') {
+    $page->param(err => '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.
+    $page->param(err => '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}) {
+      $page->param(err => 'Please enter a customer ID.');
+      return;
+    }
+    # Crosscheck with billing.
+    my $status = CustIDCK->custid_exist($webvar{custid});
+    if ($CustIDCK::Error) {
+      $page->param(err => "Error verifying customer ID: ".$CustIDCK::ErrMsg);
+      return;
+    }
+    if (!$status) {
+      $page->param(err => "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} && $webvar{pop} =~ /^-$/) {
+      $flag = 'to route the block from/through';
+    }
+  }
+
+  # if the alloctype has a restricted city/POP list as determined above,
+  # and the reqested city/POP does not match that list, complain
+  if ($flag ne 'n') {
+    $page->param(err => "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 {
+
+  # snag block info from db
+  my $blockinfo = getBlockData($ip_dbh, $webvar{block});
+
+  # Clean up extra whitespace on alloc type.  Mainly a legacy-data cleanup.
+  $blockinfo->{type} =~ s/\s//;
+
+  $page->param(block => $webvar{block});
+
+  $page->param(custid	=> $blockinfo->{custid});
+  $page->param(city	=> $blockinfo->{city});
+  $page->param(circid	=> $blockinfo->{circuitid});
+  $page->param(desc	=> $blockinfo->{description});
+  $page->param(notes	=> $blockinfo->{notes});
+
+##fixme The check here should be built from the database
+# Need to expand to support pool types too
+  if ($blockinfo->{type} =~ /^.[ne]$/ && $IPDBacl{$authuser} =~ /c/) {
+    $page->param(changetype => 1);
+    $page->param(alloctype => [
+		{ selme => ($blockinfo->{type} eq 'me'), type => "me", disptype => "Dialup netblock" },
+		{ selme => ($blockinfo->{type} eq 'de'), type => "de", disptype => "Dynamic DSL netblock" },
+		{ selme => ($blockinfo->{type} eq 'ce'), type => "ce", disptype => "Dynamic cable netblock" },
+		{ selme => ($blockinfo->{type} eq 'we'), type => "we", disptype => "Dynamic wireless netblock" },
+		{ selme => ($blockinfo->{type} eq 'cn'), type => "cn", disptype => "Customer netblock" },
+		{ selme => ($blockinfo->{type} eq 'en'), type => "en", disptype => "End-use netblock" },
+		{ selme => ($blockinfo->{type} eq 'in'), type => "in", disptype => "Internal netblock" },
+		]
+	);
+  } else {
+    $page->param(disptype => $disp_alloctypes{$blockinfo->{type}});
+    $page->param(type => $blockinfo->{type});
+  }
+
+## node hack
+  my ($nodeid,$nodename) = getNodeInfo($ip_dbh, $webvar{block});
+  $page->param(havenodeid => $nodeid);
+
+  if ($blockinfo->{type} eq 'fr' || $blockinfo->{type} eq 'bi' || $blockinfo->{type} eq 'ai') {
+    $page->param(typesupportsnodes => 1);
+    $page->param(nodename => $nodename);
+
+##fixme:  this whole hack needs cleanup and generalization for all alloctypes
+##fixme:  arguably a bug that presence of a nodeid implies it can be changed..
+#  but except for manual database changes, only the two types fr and bi can
+#  (currently) have a nodeid set in the first place.
+    if ($IPDBacl{$authuser} =~ /c/) {
+      my $nlist = getNodeList($ip_dbh);
+      foreach (@{$nlist}) {
+	$$_{selme} = ($$_{node_id} == $nodeid);
+      }
+      $page->param(nodelist => $nlist);
+    }
+  }
+## end node hack
+
+  $page->param(vrf => $blockinfo->{vrf});
+
+  my ($lastmod,undef) = split /\s+/, $blockinfo->{lastmod};
+  $page->param(lastmod => $lastmod);
+
+  # not happy with the upside-down logic, but...
+  $page->param(swipable => $blockinfo->{type} !~ /.i/);
+  $page->param(swip => $blockinfo->{swip} ne 'n') if $blockinfo->{swip};
+
+  # Check to see if we can display sensitive data
+  $page->param(nocling => $IPDBacl{$authuser} =~ /s/);
+  $page->param(privdata => $blockinfo->{privdata});
+
+  # ACL trickery - these two template booleans control the presence of all form/input tags
+  $page->param(maychange => $IPDBacl{$authuser} =~ /c/);
+  $page->param(maydel => $IPDBacl{$authuser} =~ /d/);
+
+} # edit()
+
+
+# Stuff new info about a block into the db
+# action=update
+sub update {
+  if ($IPDBacl{$authuser} !~ /c/) {
+    $aclerr = 'updateblock';
+    return;
+  }
+
+  # Make sure incoming data is in correct format - custID among other things.
+  return if !validateInput;
+
+  $webvar{swip} = 'n' if !$webvar{swip};
+
+  my %updargs = (
+	custid		=> $webvar{custid},
+	city		=> $webvar{city},
+	description	=> $webvar{desc},
+	notes		=> $webvar{notes},
+	circuitid	=> $webvar{circid},
+	block		=> $webvar{block},
+	type		=> $webvar{alloctype},
+	swip		=> $webvar{swip},
+	vrf		=> $webvar{vrf},
+	);
+
+  # Semioptional values
+  $updargs{privdata} = $webvar{privdata} if $IPDBacl{$authuser} =~ /s/;
+  $updargs{node} = $webvar{node} if $webvar{node};
+
+  my ($code,$msg) = updateBlock($ip_dbh, %updargs);
+
+  if ($code eq 'FAIL') {
+    syslog "err", "$authuser could not update block/IP '$webvar{block}': '$msg'";
+    $page->param(err => "Could not update block/IP $webvar{block}: $msg");
+    return;
+  }
+
+  # If we get here, the operation succeeded.
+  syslog "notice", "$authuser updated $webvar{block}";
+##fixme:  log details of the change?  old way is in the .debug stream anyway.
+##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';
+
+## node hack
+  if ($webvar{node} && $webvar{node} ne '-') {
+    my $nodename = getNodeName($ip_dbh, $webvar{node});
+    $page->param(nodename => $nodename);
+  }
+## end node hack
+
+  # Link back to browse-routed or list-pool page on "Update complete" page.
+  my $cblock;
+  if (my $pooltype = ($webvar{alloctype} =~ /^(.)i$/) ) {
+    $page->param(backpool => 1);
+    $cblock = ipParent($ip_dbh, $webvar{block});
+  } else {
+    $cblock = blockParent($ip_dbh, $webvar{block});
+  }
+  $page->param(backblock => $cblock->{cidr});
+
+  # Do some HTML fiddling here instead of using ESCAPE=HTML in the template,
+  # because otherwise we can't convert \n to <br>.  *sigh*
+  $webvar{notes} = $q->escapeHTML($webvar{notes});	# escape first...
+  $webvar{notes} =~ s/\n/<br>\n/;			# ... then convert newlines
+  $webvar{privdata} = ($webvar{privdata} ? $q->escapeHTML($webvar{privdata}) : "&nbsp;");
+  $webvar{privdata} =~ s/\n/<br>\n/;
+
+  $page->param(cidr => $webvar{block});
+  $page->param(city => $webvar{city});
+  $page->param(disptype => $disp_alloctypes{$webvar{alloctype}});
+  $page->param(vrf => $webvar{vrf});
+  $page->param(custid => $webvar{custid});
+  $page->param(swip => $webvar{swip} eq 'on' ? 'Yes' : 'No');
+  $page->param(circid => $webvar{circid});
+  $page->param(desc => $webvar{desc});
+  $page->param(notes => $webvar{notes});
+  $page->param(privdata => $webvar{privdata})
+	if $IPDBacl{$authuser} =~ /s/;
+
+} # update()
+
+
+# Delete an allocation.
+sub remove {
+  if ($IPDBacl{$authuser} !~ /d/) {
+    $aclerr = 'delblock';
+    return;
+  }
+
+  # Serves'em right for getting here...
+  if (!defined($webvar{block})) {
+    $page->param(err => "Can't delete a block that doesn't exist");
+    return;
+  }
+
+  my $blockdata;
+
+  if ($webvar{alloctype} eq 'rm') {
+
+    $blockdata->{block} = $webvar{block};
+    $blockdata->{city} = getRoutedCity($ip_dbh, $webvar{block});
+    $blockdata->{custid} = "N/A";
+    $blockdata->{type} = $webvar{alloctype};
+    $blockdata->{circuitid} = "N/A";
+    $blockdata->{description} = "N/A";
+    $blockdata->{notes} = "N/A";
+    $blockdata->{privdata} = "N/A";
+
+  } elsif ($webvar{alloctype} eq 'mm') {
+
+    $blockdata->{block} = $webvar{block};
+    $blockdata->{city} = "N/A";
+    $blockdata->{custid} = "N/A";
+    $blockdata->{type} = $webvar{alloctype};
+    $blockdata->{circuitid} = "N/A";
+    $blockdata->{description} = "N/A";
+    $blockdata->{notes} = "N/A";
+    $blockdata->{privdata} = "N/A";
+
+  } else {
+
+    $blockdata = getBlockData($ip_dbh, $webvar{block})
+
+  } # end cases for different alloctypes
+
+  $page->param(block => $blockdata->{block});
+  $page->param(disptype => $disp_alloctypes{$blockdata->{type}});
+  $page->param(type => $blockdata->{type});
+  $page->param(city => $blockdata->{city});
+  $page->param(custid => $blockdata->{custid});
+  $page->param(circid => $blockdata->{circuitid});
+  $page->param(desc => $blockdata->{description});
+  $blockdata->{notes} = $q->escapeHTML($blockdata->{notes});
+  $blockdata->{notes} =~ s/\n/<br>\n/;
+  $page->param(notes => $blockdata->{notes});
+  $blockdata->{privdata} = $q->escapeHTML($blockdata->{privdata});
+  $blockdata->{privdata} = '&nbsp;' if !$blockdata->{privdata};
+  $blockdata->{privdata} =~ s/\n/<br>\n/;
+  $page->param(privdata => $blockdata->{privdata}) if $IPDBacl{$authuser} =~ /s/;
+  $page->param(delpool => $blockdata->{type} =~ /^.[pd]$/);
+
+} # end remove()
+
+
+# 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/) {
+    $aclerr = 'delblock';
+    return;
+  }
+
+  # need to retrieve block data before deleting so we can notify on that
+  my $blockinfo = getBlockData($ip_dbh, $webvar{block});
+# hack pthui.  This goes away with the new structure, since routed
+# and master blocks can be retrieved with getBlockData()
+if ($webvar{alloctype} eq 'rm') {
+  $blockinfo = { custid => 'N/A', city => $webvar{city}, description => 'N/A' };
+} elsif ($webvar{alloctype} eq 'mm') {
+  $blockinfo = { custid => 'N/A', city => 'N/A', description => 'N/A' };
+}
+
+  my ($code,$msg) = deleteBlock($ip_dbh, $webvar{block}, $webvar{alloctype});
+
+  $page->param(block => $webvar{block});
+  if ($code eq 'OK') {
+    syslog "notice", "$authuser deallocated '$webvar{alloctype}'-type netblock $webvar{block} ".
+	$blockinfo->{custid}.", ".$blockinfo->{city}.", desc='".$blockinfo->{description}."'";
+    mailNotify($ip_dbh, 'da', "REMOVED: $disp_alloctypes{$webvar{alloctype}} $webvar{block}",
+	"$disp_alloctypes{$webvar{alloctype}} $webvar{block} deallocated by $authuser\n".
+	"CustID: ".$blockinfo->{custid}."\nCity: ".$blockinfo->{city}.
+	"\nDescription: ".$blockinfo->{description}."\n");
+  } else {
+    $page->param(failmsg => $msg);
+    if ($webvar{alloctype} =~ /^.i$/) {
+      syslog "err", "$authuser could not deallocate static IP '$webvar{block}': '$msg'";
+    } else {
+      syslog "err", "$authuser could not deallocate netblock '$webvar{block}': '$msg'";
+      $page->param(netblock => 1);
+    }
+  }
+
+} # finalDelete
Index: tags/2.8.2/cgi-bin/newcity.cgi
===================================================================
--- tags/2.8.2/cgi-bin/newcity.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/newcity.cgi	(revision 617)
@@ -0,0 +1,81 @@
+#!/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-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;
+use warnings;
+#use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
+use DBI;
+#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'};
+}
+
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+my ($dbh,$errstr) = connectDB_My;
+my $sth;
+
+$ENV{HTML_TEMPLATE_ROOT} = '../templates';
+
+my $page = HTML::Template->new(filename => "newcity.tmpl");
+
+if ($webvar{city}) {
+  if ($webvar{pop} eq 'on') {
+    $sth = $dbh->prepare("insert into cities (city,routing) values (?,'y')");
+  } else {
+    $sth = $dbh->prepare("insert into cities (city,routing) values (?,'n')");
+  }
+##fixme:  don't allow duplicate cities
+  $sth->execute($webvar{city});
+  $page->param(city => $webvar{city});
+  if ($sth->err) {
+    $page->param(err => $sth->errstr);
+    my $msg = "$authuser could not add city '$webvar{city}' to database: ".$sth->errstr;
+    mailNotify($dbh, 'f:nci', "IPDB city add failure", $msg);
+    syslog "err", $msg;
+  } else {
+    syslog "notice", "$authuser added city/location '$webvar{pop}'".
+	(($webvar{pop} eq 'on') ? ' as POP location' : '');
+  }
+}
+
+print "Content-type: text/html\n\n";
+
+$page->param(webpath => $IPDB::webpath);
+
+print $page->output;
+
+finish($dbh);
+
Index: tags/2.8.2/cgi-bin/newnode.cgi
===================================================================
--- tags/2.8.2/cgi-bin/newnode.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/newnode.cgi	(revision 617)
@@ -0,0 +1,73 @@
+#!/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-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;
+use warnings;
+#use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
+use DBI;
+#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'};
+}
+
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+my ($dbh,$errstr) = connectDB_My;
+my $sth;
+
+$ENV{HTML_TEMPLATE_ROOT} = '../templates';
+
+my $page = HTML::Template->new(filename => "newnode.tmpl");
+
+if ($webvar{nodename}) {
+  $sth = $dbh->prepare("insert into nodes (node_type,node_name,node_ip) values (?,?,?)");
+  $sth->execute($webvar{type}, $webvar{nodename}, $webvar{nodeip});
+  $page->param(nodename => $webvar{nodename});
+  if ($sth->err) {
+    $page->param(err => $sth->errstr);
+    my $msg = "$authuser could not add node '$webvar{nodename}','$webvar{type}' to database: ".$sth->errstr;
+    mailNotify($dbh, 'f:nno', "IPDB node add failure", $msg);
+    syslog "err", $msg;
+  } else {
+    syslog "notice", "$authuser added node '$webvar{nodename}'";
+  }
+}
+
+print "Content-type: text/html\n\n";
+
+$page->param(webpath => $IPDB::webpath);
+
+print $page->output;
+
+finish($dbh);
Index: tags/2.8.2/cgi-bin/search.cgi
===================================================================
--- tags/2.8.2/cgi-bin/search.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/search.cgi	(revision 617)
@@ -0,0 +1,549 @@
+#!/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 (C) 2005-2013 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
+use DBI;
+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'};
+}
+
+# Global variables
+my $RESULTS_PER_PAGE = 25;
+
+# anyone got a better name?  :P
+my $thingroot = $ENV{SCRIPT_FILENAME};
+$thingroot =~ s|cgi-bin/search.cgi||;
+
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+if (defined($webvar{rpp})) {
+  ($RESULTS_PER_PAGE) = ($webvar{rpp} =~ /(\d+)/);
+}
+
+# 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) {
+  checkDBSanity($ip_dbh);
+  initIPDBGlobals($ip_dbh);
+}
+
+# Set up some globals
+$ENV{HTML_TEMPLATE_ROOT} = $thingroot."templates";
+
+my $page;
+if (!defined($webvar{stype})) {
+  $webvar{stype} = "<NULL>";   #shuts up the warnings.
+  $page = HTML::Template->new(filename => "search/compsearch.tmpl",
+	global_vars => 1);
+} else {
+  $page = HTML::Template->new(filename => "search/sresults.tmpl",
+	global_vars => 1);
+}
+$page->param(webpath => $IPDB::webpath);
+
+my $header = HTML::Template->new(filename => "header.tmpl");
+$header->param(version => $IPDB::VERSION);
+$header->param(addperm => $IPDBacl{$authuser} =~ /a/);
+$header->param(webpath => $IPDB::webpath);
+print "Content-type: text/html\n\n", $header->output;
+
+# Handle the DB error first
+if (!$ip_dbh) {
+  $page = HTML::Template->new(filename => "dberr.tmpl");
+  $page->param(errmsg => $errstr);
+} elsif ($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 {
+    # sum-buddy tryn'a game the system.  Match "all"
+    $sqlconcat = "INTERSECT";
+  }
+
+# 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,vrf";
+
+  # hack fix for undefined variables
+  $webvar{custid} = '' if !$webvar{custid};
+  $webvar{desc}   = '' if !$webvar{desc};
+  $webvar{notes}  = '' if !$webvar{notes};
+  $webvar{custexclude}  = '' if !$webvar{custexclude};
+  $webvar{descexclude}  = '' if !$webvar{descexclude};
+  $webvar{notesexclude} = '' if !$webvar{notesexclude};
+
+  # First chunk of SQL.  Filter on custid, description, and notes as necessary.
+  my $sql = qq(SELECT $cols FROM searchme\n);
+  $sql .= " WHERE $webvar{custexclude} (custid ~ '$webvar{custid}')\n";
+  $sql .= " $sqlconcat (select $cols from searchme where $webvar{descexclude} description ~ '$webvar{desc}')\n";
+  $sql .= " $sqlconcat (select $cols from searchme where $webvar{notesexclude} notes ~ '$webvar{notes}')";
+
+  # If we're not supposed to search for all types, search for the selected types.
+  $webvar{alltypes} = '' if !$webvar{alltypes};
+  $webvar{typeexclude} = '' if !$webvar{typeexclude};
+  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.
+  $webvar{allcities} = '' if !$webvar{allcities};
+  $webvar{cityexclude} = '' if !$webvar{cityexclude};
+  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 {
+    # do nothing.
+    ##fixme  we'll ignore this to clear out the references to legacy code.
+  } # 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) {
+    $page->param(errmsg => "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) {
+    $page->param(errmsg => "No customers currently listed as connected through this node.");
+##fixme:  still get the results table header
+  } 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.
+
+# Generate table of types
+  $sth = $ip_dbh->prepare("select type,dispname from alloctypes where listorder <500 ".
+	"order by listorder");
+  $sth->execute;
+  my $i=0;
+  my @typelist;
+  while (my ($type,$dispname) = $sth->fetchrow_array) {
+    my %row = (
+	newrow => ($i % 4 == 0),
+	type => $type,
+	dispname => $dispname,
+	endrow => ($i++ % 4 == 3)
+	);
+    push @typelist, \%row;
+  }
+  $page->param(typelist => \@typelist);
+
+# Generate table of cities
+  $sth = $ip_dbh->prepare("select id,city from cities order by city");
+  $sth->execute;
+  $i=0;
+  my @citylist;
+  while (my ($id, $city) = $sth->fetchrow_array) {
+    my %row = (
+	newrow => ($i % 4 == 0),
+	id => $id,
+	city => $city,
+	endrow => ($i++ % 4 == 3)
+	);	
+    push @citylist, \%row;
+  }
+  $page->param(citylist => \@citylist);
+
+}
+
+print $page->output;
+
+# Shut down and clean up.
+finish($ip_dbh);
+
+# We print the footer here, so we don't have to do it elsewhere.
+my $footer = HTML::Template->new(filename => "footer.tmpl");
+# include the admin tools link in the output?
+$footer->param(adminlink => ($IPDBacl{$authuser} =~ /A/));
+$footer->param(webpath => $IPDB::webpath);
+
+print $footer->output;
+
+# 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,vrf";
+
+  if ($category eq 'all') {
+
+    $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' || $category eq 'desc') {
+
+##fixme:  this and other quick-search areas;  fix up page heading title similar to first grouping above
+    print qq(<div class="heading">Searching for Customer IDs or Descriptions containing '$query'</div><br>\n);
+
+# head off the worst of SQL injection.  search really needs a big rewrite...
+$query =~ s/'/''/g;
+    # 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 description ilike '%$query%' or vrf 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...
+      $page->param(errmsg => "Invalid query.");
+    }
+  } else {
+    # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+    $page->param(errmsg => "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();
+
+  $page->param(searchtitle => "Showing all netblock and static-IP allocations");
+
+  my $count = 0;
+  my @sresults;
+  while (my ($block, $custid, $type, $city, $desc, $vrf) = $sth->fetchrow_array) {
+    my %row = (
+	rowclass => $count++ % 2,
+	issub => ($type =~ /^.r$/ ? 1 : 0),
+	block => $block,
+	ispool => ($type =~ /^.[pd]$/ ? 1 : 0),
+	custid => $custid,
+	disptype => $disp_alloctypes{$type},
+	city => $city,
+	desc => $desc,
+	vrf => $vrf,
+	);
+    push @sresults, \%row;
+  }
+  $page->param(sresults => \@sresults);
+
+  # 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;
+
+  $page->param(resfound => $rowCount);
+  $page->param(resstart => $offset+1);
+  $page->param(resstop => $upper);
+
+  # print the page thing..
+  if ($RESULTS_PER_PAGE > 0 && $rowCount > $RESULTS_PER_PAGE) {
+    $page->param(multipage => 1);
+    my $pages = ceil($rowCount/$RESULTS_PER_PAGE);
+    my @pagelist;
+    for (my $i = 1; $i <= $pages; $i++) {
+      my %row;
+      $row{pgnum} = $i;
+      if ($i == $pageNo) {
+	$row{thispage} = 1;
+      } else {
+	$row{stype} = $webvar{stype};
+	if ($webvar{stype} eq 'c') {
+	  $row{extraopts} = "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$/) {
+	      $row{extraopts} .= "$key=$webvar{$key}&";
+	    }
+	  }
+	} else {
+	  $row{extraopts} = "input=$webvar{input}&";
+	}
+      }
+      push @pagelist, \%row;
+    }
+    $page->param(pgnums => \@pagelist);
+  }
+
+} # 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: tags/2.8.2/cgi-bin/snCalc.cgi
===================================================================
--- tags/2.8.2/cgi-bin/snCalc.cgi	(revision 617)
+++ tags/2.8.2/cgi-bin/snCalc.cgi	(revision 617)
@@ -0,0 +1,115 @@
+#!/usr/bin/perl
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
+use NetAddr::IP;
+
+#file snCalc.cgi	little subnet calculator app
+use MyIPDB;
+
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+my $input;
+
+print "Content-Type: text/html\n\n";
+
+##fixme:  need better method to find templates.
+$ENV{HTML_TEMPLATE_ROOT} = $ENV{SCRIPT_FILENAME};
+$ENV{HTML_TEMPLATE_ROOT} =~ s|cgi-bin/snCalc.cgi||;
+
+my $page = HTML::Template->new(filename => "templates/subnet-calc.tmpl");
+
+# 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";
+
+$page->param(prenet => $ltinput);
+$page->param(net => $input);
+$page->param(postnet => $gtinput);
+$page->param(premask => $prenet->mask);
+$page->param(mask => $net->mask);
+$page->param(postmask => $postnet->mask);
+$page->param(prewildcard => scalar($prenet->wildcard));
+$page->param(wildcard => scalar($net->wildcard));
+$page->param(postwildcard => scalar($postnet->wildcard));
+
+my @prenets;
+foreach (getranges($ltinput)) {
+  my %row = (netrange => $_);
+  push (@prenets, \%row);
+}
+$page->param(prenets => \@prenets);
+my @nets;
+foreach (getranges($input)) {
+  my %row = (netrange => $_);
+  push (@nets, \%row);
+}
+$page->param(nets => \@nets);
+my @postnets;
+foreach (getranges($gtinput)) {
+  my %row = (netrange => $_);
+  push @postnets, \%row;
+}
+$page->param(postnets => \@postnets);
+
+print $page->output;
+	
+# 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)) {
+    push @ret, xrange($net,$masklen);
+  }
+  return @ret;
+} # getranges()
Index: tags/2.8.2/help.html
===================================================================
--- tags/2.8.2/help.html	(revision 617)
+++ tags/2.8.2/help.html	(revision 617)
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>IP Database Quick Help</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+	<link rel="stylesheet" type="text/css" href="ipdb.css">
+	<link rel="stylesheet" type="text/css" href="local.css">
+</head>
+<body>
+
+<table class="regular">
+
+<tr><td class="heading">Quick Searches:</td></tr>
+
+<tr class="row0">
+<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="row1">
+<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="row0">
+<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="row1">
+<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="row0">
+<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><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="row0"><td>Subnet Calculator</td>
+<td colspan="2">
+<form method="POST" action="cgi-bin/snCalc.cgi">
+<fieldset><legend></legend>
+ / <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.
+</fieldset>
+</form>
+</tr>
+
+<tr><td>
+
+
+</table>
+
+
+</body></html>
Index: tags/2.8.2/index.shtml
===================================================================
--- tags/2.8.2/index.shtml	(revision 617)
+++ tags/2.8.2/index.shtml	(revision 617)
@@ -0,0 +1,1 @@
+<!--#include virtual="cgi-bin/main.cgi?action=index" -->
Index: tags/2.8.2/ipdb.css
===================================================================
--- tags/2.8.2/ipdb.css	(revision 617)
+++ tags/2.8.2/ipdb.css	(revision 617)
@@ -0,0 +1,226 @@
+/* Default/global defs for specific tags */
+fieldset {
+        border: none;
+        padding: 0px;
+        margin: 0px;
+}
+
+#debug {
+        background-color: #990066;
+        padding: 2px;
+}
+
+/* Specific divs */
+#bodyheader {
+	margin-left: 4px;
+	position: relative;
+}
+#homelink {
+	font-size: 13px;
+	font-weight: bold;
+	position: absolute;
+	right: 10px;
+	bottom: 2px;
+	text-align: right;
+}
+#subheader {
+	background-color: #D0E0E0;
+	font-size: 90%;
+	padding: 3px;
+	border-top: thin solid #000000;
+	/* apparently this is required to keep "contained" position: absolute divs from escaping.  O_o */
+	position: relative;
+}
+#csearch {
+	float: right;
+	padding: 3px;
+}
+#newlink {
+	position: absolute;
+	right: 10px;
+	top: 6px;
+	text-align: right;
+}
+#main {
+        padding: 10px;
+        border-top: thin solid #000000;
+/*        text-align: center;*/
+        padding-top: 20px;
+        position: relative;
+}
+#utils {
+	font-size: 13px;
+	font-weight: bold;
+	position: absolute;
+	right: 10px;
+	top: 2px;
+	text-align: right;
+}
+#adminlink {
+	position: absolute;
+	right: 10px;
+	bottom: 5px;
+}
+#footer {
+	border-top: thin solid #000000;
+}
+#contact {
+	font-size: 10px;
+	position: absolute;
+	right: 10px;
+	text-align: right;
+}
+
+body {
+  background-color: #ffffff;
+  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 */
+
+table.center {
+	margin-left: auto;
+	margin-right: auto;
+	text-align: center;
+}
+
+/* Defs for bulk-data rows */
+tr.header {
+	background-color: #CCCCCC;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+/* for reasons of Please The Validation Gods, these may be applied to
+   things that are not technically table rows */
+.row0 {
+	background-color: #D0E0E0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 90%;
+}
+.row1 {
+	background-color: #A8C4D0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 90%;
+}
+
+.warnmsg {
+	border: solid 2px #FFFF00;
+	color: #333300;
+	background-color: #e0e0e0;
+	text-align: center;
+	padding: 5px;
+	width: 70%;
+}
+
+
+hr.w60 {
+	width: 60%;
+}
+hr.w30 {
+	width: 30%;
+}
+
+/* legacy defs */
+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;
+}
+
+td {
+	padding-right: 2px;
+	padding-left: 2px;
+}
+
+/* Generic classes */
+.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;
+}
+
+.tbltitle {
+	text-align: center;
+	font-size: 110%;
+	font-weight: bold;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+.tblsubtitle {
+        font-size: 105%;
+        font-weight: bold;
+        font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+.ljust {
+	text-align: left;
+}
+.topalign {
+	vertical-align: top;
+}
+
+.small {
+	font-size: 60%;
+}
+
+.center {
+	text-align: center;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 100%;
+}
+
+.err {
+	text-align: center;
+	font-size: 1em;
+}
+
+.red {
+	font-weight: bold;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size:100%;
+	color: red;
+}
+
+.mask {
+	background: #00ff00;
+}
+
+.wildcard {
+	background: #ffff00;
+}
+
+.altbg {
+	background: #d0e0e0;
+}
+
+.noshow {
+	visibility: none;
+	height: 0;
+	width: 0;
+}
Index: tags/2.8.2/ipdb.spec
===================================================================
--- tags/2.8.2/ipdb.spec	(revision 617)
+++ tags/2.8.2/ipdb.spec	(revision 617)
@@ -0,0 +1,90 @@
+# spec file for IPDB
+# $Id$
+
+# some defines for building on Debian with debbuild
+%if %{_vendor} == "debbuild"
+%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}
+%endif
+
+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: tags/2.8.2/templates/aclerror.tmpl
===================================================================
--- tags/2.8.2/templates/aclerror.tmpl	(revision 617)
+++ tags/2.8.2/templates/aclerror.tmpl	(revision 617)
@@ -0,0 +1,4 @@
+<br>
+<div class="center">You do not have permission to <TMPL_VAR NAME=ipdbfunc>.  Access denied.</div>
+<br>
+<TMPL_IF 0><TMPL_VAR NAME=webpath></TMPL_IF>
Index: tags/2.8.2/templates/addmaster.tmpl
===================================================================
--- tags/2.8.2/templates/addmaster.tmpl	(revision 617)
+++ tags/2.8.2/templates/addmaster.tmpl	(revision 617)
@@ -0,0 +1,17 @@
+<div class="indent">
+<div class="heading">Add new master IP block</div><br>
+<form method="POST" action="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<table class="regular" cellspacing="1" cellpadding="1">
+<tr class="row0">
+<td>Master block to add: (CIDR)</td>
+<td><input type="text" name="cidr"></td>
+</tr>
+<tr class="row1">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+</tr>
+</table>
+<input type="hidden" name="action" value="newmaster">
+</fieldset>
+</form>
+</div>
Index: tags/2.8.2/templates/admin/aclerr.tmpl
===================================================================
--- tags/2.8.2/templates/admin/aclerr.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/aclerr.tmpl	(revision 617)
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>Access denied</title>
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/local.css">
+</head>
+<body>
+<div>
+Access to this tool is restricted.  Contact
+<a href="mailto:<TMPL_VAR NAME=ipdbadmin_email>"><TMPL_VAR NAME=ipdbadmin_name></a> 
+for more information.
+</div>
+</body>
+</html>
+
Index: tags/2.8.2/templates/admin/addnotice.tmpl
===================================================================
--- tags/2.8.2/templates/admin/addnotice.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/addnotice.tmpl	(revision 617)
@@ -0,0 +1,12 @@
+<TMPL_IF cantry>
+Adding notice to <TMPL_VAR NAME=reciplist> for <TMPL_VAR NAME=dispnotice>:
+<TMPL_IF addfailed>
+Failed: DB error: <TMPL_VAR NAME=addfailed>
+<TMPL_ELSE>
+OK!<br>
+</TMPL_IF>
+<TMPL_ELSE>
+Need to specify at least one recipient, an action, and an allocation type.
+("Special" content is considered an allocation type).  Hit the Back button and try again.<br>
+</TMPL_IF>
+<a href="admin.cgi?action=emailnotice">Back to email notice list</a>
Index: tags/2.8.2/templates/admin/alloc.tmpl
===================================================================
--- tags/2.8.2/templates/admin/alloc.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/alloc.tmpl	(revision 617)
@@ -0,0 +1,54 @@
+<TMPL_IF errmsg>
+<TMPL_VAR NAME=errmsg>
+<TMPL_ELSE>
+<TMPL_IF warnmsg><div class="warnmsg"><TMPL_VAR NAME=warnmsg></div></TMPL_IF>
+<form method="POST" action="admin.cgi">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<table class="regular">
+<tr class="row0">
+<td>Allocating:</td>
+<td><TMPL_VAR NAME=cidr><input type="hidden" name="cidr" value="<TMPL_VAR NAME=cidr>"></td>
+</tr>
+<tr class="row1">
+<td>Type:</td>
+<td><TMPL_VAR NAME=disptype><input type="hidden" name="alloctype" value="<TMPL_VAR NAME=type>"></td>
+</tr>
+<tr class="row0">
+<td>Allocated from:</td>
+<td><TMPL_VAR NAME=alloc_from><input type="hidden" name="alloc_from" value="<TMPL_VAR NAME=alloc_from>"></td>
+</tr>
+<tr class="row1">
+<td>Customer ID:</td>
+<td><TMPL_VAR NAME=custid><input type="hidden" name="custid" value="<TMPL_VAR NAME=custid>"></td>
+</tr>
+<tr class="row0">
+<td>Customer location:</td>
+<td>
+<select name="city"><option selected>-</option>
+<TMPL_LOOP NAME=citylist><option><TMPL_VAR NAME=city></option>
+</TMPL_LOOP></select>
+&nbsp;<a href="javascript:popNotes('<TMPL_VAR NAME=webpath>/cgi-bin/newcity.cgi')">Add new location</a>
+</td>
+</tr>
+<tr class="row1">
+<td>Circuit ID:</td><td><input name="circid" size="40"></td>
+</tr>
+<tr class="row0">
+<td>VRF identifier:</td><td><input name=vrf size=40></td>
+</tr>
+<tr class="row1">
+<td>Description/Name:</td><td><input name="desc" size="40"></td>
+</tr>
+<tr class="row0">
+<td>Notes:</td><td><textarea name="notes" rows="3" cols="40"></textarea></td>
+</tr>
+<tr class="warning">
+<td class="center" colspan="2">WARNING:  This will IMMEDIATELY assign this block!!</td>
+</tr>
+<tr class="row1">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+</tr>
+</table>
+<input type="hidden" name="action" value="confirm">
+</fieldset></form>
+</TMPL_IF>
Index: tags/2.8.2/templates/admin/alloctweak.tmpl
===================================================================
--- tags/2.8.2/templates/admin/alloctweak.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/alloctweak.tmpl	(revision 617)
@@ -0,0 +1,1 @@
+<TMPL_INCLUDE showallocs.tmpl>
Index: tags/2.8.2/templates/admin/confirm.tmpl
===================================================================
--- tags/2.8.2/templates/admin/confirm.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/confirm.tmpl	(revision 617)
@@ -0,0 +1,11 @@
+<TMPL_IF locerr>
+<TMPL_VAR NAME=locerr>
+<TMPL_ELSE>
+Assigning <TMPL_VAR NAME=cidr> to <TMPL_VAR NAME=custid> ("<TMPL_VAR NAME=desc>") as
+<TMPL_VAR NAME=disptype>...<br>
+<TMPL_IF errmsg>
+Allocation failed: <TMPL_VAR NAME=errmsg>
+<TMPL_ELSE>
+Allocation OK!
+</TMPL_IF>
+</TMPL_IF>
Index: tags/2.8.2/templates/admin/dberr.tmpl
===================================================================
--- tags/2.8.2/templates/admin/dberr.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/dberr.tmpl	(revision 617)
@@ -0,0 +1,5 @@
+<TMPL_INCLUDE header.tmpl>
+<div class="center regular">Database error:<br>
+<TMPL_VAR NAME=errmsg><br><br>
+</div>
+<TMPL_INCLUDE footer.tmpl>
Index: tags/2.8.2/templates/admin/delnotice.tmpl
===================================================================
--- tags/2.8.2/templates/admin/delnotice.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/delnotice.tmpl	(revision 617)
@@ -0,0 +1,7 @@
+Deleting notices on <TMPL_VAR NAME=dispnotice>:
+<TMPL_IF delfailed>
+Failed: DB error: <TMPL_VAR NAME=delfailed>
+<TMPL_ELSE>
+OK!<br>
+</TMPL_IF>
+<a href="admin.cgi?action=emailnotice">Back to email notice list</a>
Index: tags/2.8.2/templates/admin/deluser.tmpl
===================================================================
--- tags/2.8.2/templates/admin/deluser.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/deluser.tmpl	(revision 617)
@@ -0,0 +1,3 @@
+Deleting user <TMPL_VAR NAME=username>...
+<TMPL_IF errmsg><br>Error deleting user: <TMPL_VAR NAME=errmsg><TMPL_ELSE>OK</TMPL_IF>
+<hr><a href="admin.cgi?action=showusers">Back</a> to user listing
Index: tags/2.8.2/templates/admin/dunno.tmpl
===================================================================
--- tags/2.8.2/templates/admin/dunno.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/dunno.tmpl	(revision 617)
@@ -0,0 +1,1 @@
+webvar{action} check failed: Don't know how to <TMPL_VAR NAME=dunno>
Index: tags/2.8.2/templates/admin/edcust.tmpl
===================================================================
--- tags/2.8.2/templates/admin/edcust.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/edcust.tmpl	(revision 617)
@@ -0,0 +1,43 @@
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="updcust">
+<table border="1">
+<tr>
+<td>CustID:</td><td><TMPL_VAR NAME=custid><input type="hidden" name="custid" value="<TMPL_VAR NAME=custid>"></td>
+<td>Name:</td><td><input name="name" value="<TMPL_VAR NAME=name>"></td></tr>
+<tr><td>Street:</td><td><input name="street" value="<TMPL_VAR NAME=street>"></td>
+<!-- <td>Street2:</td><td><input name=street2 value="<TMPL_VAR NAME=street2>"></td> -->
+<td>City:</td><td><input name="city" value="<TMPL_VAR NAME=city>"></td></tr>
+<tr><td>Province/State: (2/3-letter code)</td><td><input name="province" value="<TMPL_VAR NAME=prov>" maxlength="3" size="3"></td>
+<td>Country: (2-letter code)</td><td><input name="country" value="<TMPL_VAR NAME=country>" maxlength="2" size="2"></td></tr>
+<tr><td>Postal/ZIP Code:</td><td><input name="pocode" value="<TMPL_VAR NAME=pocode>"></td>
+<td>Phone:</td><td><input name="phone" value="<TMPL_VAR NAME=phone>"></td></tr>
+<!-- <td>Default rDNS:</td><td><input name="def_rdns" value="<TMPL_VAR NAME=def_rdns>"></td></tr>
+<td>Description:</td><td><input name="description" value="<TMPL_VAR NAME=desc>"></td> -->
+<tr><td>Contacts/ARIN Handles:</td><td>
+ Tech: <input name="tech_handle" value="<TMPL_VAR NAME=tech>"><br>
+ Abuse: <input name="abuse_handle" value="<TMPL_VAR NAME=abuse>"><br>
+ Admin: <input name="admin_handle" value="<TMPL_VAR NAME=admin>"><br>
+Note:  Only tech is required at the moment.
+</td>
+<td>"Special":</td><td><textarea name="special" rows="4" cols="50"><TMPL_VAR NAME=special></textarea></td>
+</tr>
+<tr><td colspan="4" align="center"><input type="submit" value="Update"></td></tr>
+</table>
+</fieldset></form>
+
+<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>
Index: tags/2.8.2/templates/admin/ednotice.tmpl
===================================================================
--- tags/2.8.2/templates/admin/ednotice.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/ednotice.tmpl	(revision 617)
@@ -0,0 +1,15 @@
+<h4>Editing recipient list for '<TMPL_VAR NAME=dispnotice>':</h4>
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="code" value="<TMPL_VAR NAME=code>">
+<input type="hidden" name="action" value="updnotice">
+<table border="1">
+<tr>
+<td><textarea cols="40" rows="5" name=reciplist><TMPL_VAR NAME=reciplist></textarea></td>
+<td><input type="submit" value="Update"></td>
+</tr>
+</table>
+</fieldset>
+</form>
+<hr>
+<a href="admin.cgi?action=emailnotice">Back to email notice list</a>
Index: tags/2.8.2/templates/admin/emailnotice.tmpl
===================================================================
--- tags/2.8.2/templates/admin/emailnotice.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/emailnotice.tmpl	(revision 617)
@@ -0,0 +1,57 @@
+<h4>Email notice management:</h4>
+Click the email addresses to edit that list.
+<table border=1>
+<TMPL_LOOP name=spamlist>
+<tr>
+<td><TMPL_VAR NAME=action></td>
+<td><a href="admin.cgi?action=ednotice&amp;code=<TMPL_VAR NAME=code>"><TMPL_VAR NAME=recips></a></td>
+<td><a href="admin.cgi?action=delnotice&amp;code=<TMPL_VAR NAME=code>">Delete</a></td>
+</tr>
+</TMPL_LOOP>
+<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>
+
+<h4>Add new notification:</h4>
+Note:  Failure notices on most conditions are not yet supported.
+
+<form action=admin.cgi method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="addnotice">
+<table border="1">
+<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>
+<TMPL_LOOP name=typelist>		<td><input type="radio" name="alloctype" value="<TMPL_VAR NAME=type>"><TMPL_VAR NAME=disptype></td>
+<TMPL_UNLESS newrow>	</tr>
+	<tr>
+</TMPL_UNLESS></TMPL_LOOP>
+	</tr>
+	</table>
+</tr>
+<tr><td colspan="4" align="center"><input type="submit" value="Add notice"></td></tr>
+</table>
+</fieldset>
+</form>
Index: tags/2.8.2/templates/admin/header.tmpl
===================================================================
--- tags/2.8.2/templates/admin/header.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/header.tmpl	(revision 617)
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>[IPDB admin tools v<TMPL_VAR NAME=version>]</title>
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/local.css">
+	<script src="<TMPL_VAR NAME=webpath>/templates/widgets.js" type="text/javascript"></script>
+</head>
+<body>
+<h2>IPDB - Administrative Tools</h2>
+
+<div>
+
+<TMPL_UNLESS mainpage><a href="<TMPL_VAR NAME=webpath>/cgi-bin/admin.cgi">Back</a> to Admin Tools<hr></TMPL_UNLESS>
+
Index: tags/2.8.2/templates/admin/listcust.tmpl
===================================================================
--- tags/2.8.2/templates/admin/listcust.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/listcust.tmpl	(revision 617)
@@ -0,0 +1,24 @@
+Add new entry:
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="edcust">
+<input type="hidden" name="newcust" value="1">
+<table border="1">
+<tr>
+<td>CustID:</td><td><input name="custid"></td>
+<td align="center"><input type="submit" value="Go to edit page for this custid"></td>
+</tr>
+</table>
+</fieldset></form>
+
+<p>Click CustID to edit existing customer contact data:
+<table border="1">
+<tr><td>CustID</td><td>Name</td><td>Tech handle</td></tr>
+<TMPL_LOOP NAME=custlist>
+<tr>
+<td><a href="admin.cgi?action=edcust&amp;custid=<TMPL_VAR NAME=custid>"><TMPL_VAR NAME=custid></a></td>
+<td><TMPL_VAR NAME=custname></td>
+<td><TMPL_VAR NAME=tech></td>
+</tr>
+</TMPL_LOOP>
+</table>
Index: tags/2.8.2/templates/admin/main.tmpl
===================================================================
--- tags/2.8.2/templates/admin/main.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/main.tmpl	(revision 617)
@@ -0,0 +1,61 @@
+<hr>
+<div>WARNING:  There are FAR fewer controls on what you can do here.  Use the
+main interface if at all possible.</div>
+<hr>
+<form action="admin.cgi" method="POST"><fieldset><legend class="noshow">&nbsp;</legend>
+
+<input type="hidden" name="action" value="alloc">
+Allocate block/IP: <input name="cidr"> as
+<select name="alloctype">
+<TMPL_LOOP NAME=typelist><option value="<TMPL_VAR NAME=type>"<TMPL_IF sel> selected</TMPL_IF>><TMPL_VAR NAME=listname></option>
+</TMPL_LOOP>
+</select>
+to <input name="custid">
+<input type="submit" value=" GIMME!! ">
+</fieldset></form>
+
+<hr>
+<form action="admin.cgi" method="POST"><fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="alloctweak">
+Manually update allocation data in this /24: <input name="allocfrom">
+<input type="submit" value="Show allocations">
+</fieldset></form>
+
+<hr>
+<div>
+rWHOIS tools:
+<form action="admin.cgi" method="POST"><fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="touch">
+Bump "last updated" timestamp on this master:
+<select name="whichmaster">
+<TMPL_LOOP NAME=masterlist><option value="<TMPL_VAR NAME=master>"><TMPL_VAR NAME=master> (<TMPL_VAR NAME=mtime>)</option>
+</TMPL_LOOP>
+</select>
+<input type="submit" value="Update timestamp"> (Sets timestamp to "now")
+</fieldset></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.
+</div>
+
+<hr>
+<div>
+<a href="admin.cgi?action=showpools">List IP Pools</a> for manual tweaking and updates
+</div>
+
+<hr>
+<div>
+<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)
+</div>
+
+<hr>
+<div>
+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)
+</div>
Index: tags/2.8.2/templates/admin/newuser.tmpl
===================================================================
--- tags/2.8.2/templates/admin/newuser.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/newuser.tmpl	(revision 617)
@@ -0,0 +1,3 @@
+Adding user <TMPL_VAR NAME=username>...
+<TMPL_IF errmsg><br>Error adding user: <TMPL_VAR NAME=errmsg><TMPL_ELSE>OK</TMPL_IF>
+<hr><a href="admin.cgi?action=showusers">Back</a> to user listing
Index: tags/2.8.2/templates/admin/showallocs.tmpl
===================================================================
--- tags/2.8.2/templates/admin/showallocs.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/showallocs.tmpl	(revision 617)
@@ -0,0 +1,27 @@
+Edit custID, allocation type, city for allocations in <TMPL_VAR NAME=within>:
+<table border="1">
+<TMPL_LOOP name=blocklist>
+<tr>
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="update">
+<td><TMPL_VAR NAME=cidr><input type="hidden" value="<TMPL_VAR NAME=cidr>" name=block></td>
+<td><input name="custid" value="<TMPL_VAR NAME=custid>"></td>
+<td>
+	<select name="alloctype">
+<TMPL_LOOP name=typelist>	<option value="<TMPL_VAR NAME=type>"<TMPL_IF selected> selected</TMPL_IF>><TMPL_VAR NAME=dispname></option>
+</TMPL_LOOP>	</select>
+</td>
+<td><input name="city" value="<TMPL_VAR NAME=city>"></td>
+<td><TMPL_VAR NAME=desc></td>
+<td><input type="submit" value="Update"></td>
+</fieldset></form>
+</tr>
+</TMPL_LOOP>
+</table>
+<hr>
+<b>Notes:</b>
+<ul>
+	<li>Use the main interface to update description and notes fields.
+	<li>Changing the allocation type here will NOT affect IP pool data.
+</ul>
Index: tags/2.8.2/templates/admin/showpools.tmpl
===================================================================
--- tags/2.8.2/templates/admin/showpools.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/showpools.tmpl	(revision 617)
@@ -0,0 +1,7 @@
+IP Pools currently allocated:
+<table border=1>
+<tr><td>Pool</td><td># of free IPs</td></tr>
+<TMPL_LOOP NAME=poollist>
+<tr><td><a href="admin.cgi?action=tweakpool&amp;pool=<TMPL_VAR NAME=pool>"><TMPL_VAR NAME=pool></a></td><td><TMPL_VAR NAME=free></td></tr>
+</TMPL_LOOP>
+</table>
Index: tags/2.8.2/templates/admin/showusers.tmpl
===================================================================
--- tags/2.8.2/templates/admin/showusers.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/showusers.tmpl	(revision 617)
@@ -0,0 +1,43 @@
+Notes:
+<ul>
+<li>Admin users automatically get all other priviledges.
+<li>Everyone has basic read access.
+</ul>
+<hr>Add new user:<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+Username: <input name=username><br>
+Password: <input name=password> <input type=checkbox name=preenc>Password is pre-encrypted (MUST be crypt() encrypted)<br>
+<input type=submit value='Add user'><input type=hidden name=action value=newuser>
+</fieldset></form>
+
+<hr>Users with access:
+
+<table border="1">
+<tr><td></td><td align="center" colspan="3">General access</td></tr>
+<tr><td>Username</td><td>Add new</td><td>Change</td><td>Delete</td><td>Systems/Networking</td><td>Admin user</td></tr>
+
+<TMPL_LOOP name=userlist>
+<tr>
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="updacl">
+<td><TMPL_VAR NAME=username><input type="hidden" name="username" value="<TMPL_VAR NAME=username>"></td>
+<td><input type=checkbox name="add"<TMPL_IF can_add> checked="checked"</TMPL_IF>></td>
+<td><input type=checkbox name="change"<TMPL_IF can_change> checked="checked"</TMPL_IF>></td>
+<td><input type=checkbox name="del"<TMPL_IF can_del> checked="checked"</TMPL_IF>></td>
+<td><input type=checkbox name="sysnet"<TMPL_IF sysnet> checked="checked"</TMPL_IF>></td>
+<td><input type=checkbox name="admin"<TMPL_IF is_admin> checked="checked"</TMPL_IF>></td>
+<td><input type=submit value="Update"></td>
+</fieldset></form> 
+<td>
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type=hidden name=action value=deluser>
+<input type=hidden name=username value="<TMPL_VAR NAME=username>">
+<input type=submit value="Delete user">
+</fieldset></form> 
+</td>
+<td><TMPL_VAR NAME=acl></td>
+</tr>
+</TMPL_LOOP>
+</table>
Index: tags/2.8.2/templates/admin/touch.tmpl
===================================================================
--- tags/2.8.2/templates/admin/touch.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/touch.tmpl	(revision 617)
@@ -0,0 +1,5 @@
+Touching master <TMPL_VAR NAME=master>...<br>
+<TMPL_IF errmsg><p>Error updating modified timestamp on master <TMPL_VAR NAME=master>: <TMPL_VAR NAME=errmsg></p>
+<TMPL_ELSE>OK!
+</TMPL_IF>
+
Index: tags/2.8.2/templates/admin/tweakpool.tmpl
===================================================================
--- tags/2.8.2/templates/admin/tweakpool.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/tweakpool.tmpl	(revision 617)
@@ -0,0 +1,25 @@
+Listing pool <TMPL_VAR NAME=pool>:
+<form action="admin.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="updatepool">
+<table border="1">
+<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>
+<TMPL_LOOP name=typelist><option value="<TMPL_VAR NAME=type>"><TMPL_VAR NAME=dispname></option>
+</TMPL_LOOP>
+</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>
+<TMPL_LOOP NAME=iplist>
+<tr><td><input type="radio" name="ip" value="<TMPL_VAR NAME=ip>"><TMPL_VAR NAME=ip></td>
+<td><TMPL_VAR NAME=custid></td><td><TMPL_VAR NAME=city></td><td><TMPL_VAR NAME=type></td>
+<td><TMPL_VAR NAME=avail></td><td><TMPL_VAR NAME=desc></td><td><TMPL_VAR NAME=notes></td></tr>
+</TMPL_LOOP>
+</table>
+</fieldset></form>
Index: tags/2.8.2/templates/admin/updacl.tmpl
===================================================================
--- tags/2.8.2/templates/admin/updacl.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/updacl.tmpl	(revision 617)
@@ -0,0 +1,4 @@
+Updating ACL for <TMPL_VAR NAME=username>:<br>
+New ACL: <TMPL_VAR NAME=acl><br>
+<TMPL_UNLESS errmsg>OK!<TMPL_ELSE><TMPL_VAR NAME=errmsg></TMPL_UNLESS>
+<hr><a href="admin.cgi?action=showusers">Back</a> to user listing
Index: tags/2.8.2/templates/admin/update.tmpl
===================================================================
--- tags/2.8.2/templates/admin/update.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/update.tmpl	(revision 617)
@@ -0,0 +1,7 @@
+<TMPL_IF updfailed>
+Update of <TMPL_VAR NAME=block> failed: <TMPL_VAR NAME=updfailed>
+<TMPL_ELSE>
+Allocation <TMPL_VAR NAME=block> updated
+</TMPL_IF>
+<hr>
+<TMPL_INCLUDE showallocs.tmpl>
Index: tags/2.8.2/templates/admin/updatepool.tmpl
===================================================================
--- tags/2.8.2/templates/admin/updatepool.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/updatepool.tmpl	(revision 617)
@@ -0,0 +1,9 @@
+<TMPL_IF errmsg>
+Error updating pool IP <TMPL_VAR NAME=ip>: <TMPL_VAR NAME=errmsg>
+<TMPL_ELSE>
+<TMPL_VAR NAME=ip> in <TMPL_VAR NAME=pool> updated.
+</TMPL_IF>
+<hr>
+<a href="admin.cgi?action=tweakpool&amp;pool=<TMPL_VAR NAME=pool>">Back</a> to list of IPs in <TMPL_VAR NAME=pool>
+<hr>
+<a href="admin.cgi?action=showpools">Back</a> to list of pools
Index: tags/2.8.2/templates/admin/updcust.tmpl
===================================================================
--- tags/2.8.2/templates/admin/updcust.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/updcust.tmpl	(revision 617)
@@ -0,0 +1,19 @@
+Updated <TMPL_VAR NAME=custid><br>
+<table border=1>
+<tr><td>CustID:</td><td><TMPL_VAR NAME=custid></td></tr>
+<tr><td>Name:</td><td><TMPL_VAR NAME=name></td></tr>
+<tr><td>Street:</td><td><TMPL_VAR NAME=street></td></tr>
+<tr><td>City:</td><td><TMPL_VAR NAME=city></td></tr>
+<tr><td>Province/State:</td><td><TMPL_VAR NAME=prov></td></tr>
+<tr><td>Country:</td><td><TMPL_VAR NAME=country></td></tr>
+<tr><td>Postal/ZIP Code:</td><td><TMPL_VAR NAME=pocode></td></tr>
+<tr><td>Phone:</td><td><TMPL_VAR NAME=phone></td></tr>
+<!-- <td>Default rDNS:</td><td><TMPL_VAR NAME=def_rdns></td></tr> -->
+<tr><td>Contacts/ARIN Handles:</td><td>
+ Tech: <TMPL_VAR NAME=tech><br>
+ Abuse: <TMPL_VAR NAME=abuse><br>
+ Admin: <TMPL_VAR NAME=admin><br>
+</td></tr>
+<tr><td>"Special":</td><td><pre><TMPL_VAR NAME=special></pre></td></tr>
+</table>
+<a href="admin.cgi?action=listcust">Back</a> to rWHOIS customer list<br>
Index: tags/2.8.2/templates/admin/updnotice.tmpl
===================================================================
--- tags/2.8.2/templates/admin/updnotice.tmpl	(revision 617)
+++ tags/2.8.2/templates/admin/updnotice.tmpl	(revision 617)
@@ -0,0 +1,7 @@
+<h4>Updating recipient list for '<TMPL_VAR NAME=dispnotice>':</h4>
+<TMPL_IF updfailed>
+Failed: DB error: <TMPL_VAR NAME=updfailed>
+<TMPL_ELSE>
+OK!<br>
+</TMPL_IF>
+<a href="admin.cgi?action=emailnotice">Back to email notice list</a>
Index: tags/2.8.2/templates/assign.tmpl
===================================================================
--- tags/2.8.2/templates/assign.tmpl	(revision 617)
+++ tags/2.8.2/templates/assign.tmpl	(revision 617)
@@ -0,0 +1,152 @@
+<div class="indent">
+<div class="tblsubtitle">Assign IP<TMPL_UNLESS fbip>s</TMPL_UNLESS></div>
+<br>
+
+<form method="post" action="main.cgi" class="regular">
+<fieldset><legend class="noshow">&nbsp;</legend>
+
+<table class="regular" cellspacing="1" cellpadding="1">
+
+<TMPL_IF allocfrom>
+<tr class="row0">
+<TMPL_IF fbip>
+<td>IP pool:</td>
+<TMPL_ELSE>
+<td>Free block selected:</td>
+</TMPL_IF>
+<td><TMPL_VAR NAME=allocfrom><input type="hidden" name="allocfrom" value="<TMPL_VAR NAME=allocfrom>">
+<input type="hidden" name="parent" value="<TMPL_VAR NAME=parent>">
+<input type="hidden" name="fbid" value="<TMPL_VAR NAME=fbid>">
+</td>
+</tr>
+<tr class="row1">
+<TMPL_IF fbip>
+<td>IP:</td>
+<td>
+<TMPL_VAR NAME=block><input type="hidden" name="block" value="<TMPL_VAR NAME=block>">
+<input type="hidden" name="fbassign" value="y">
+</td>
+<TMPL_ELSE>
+<td>Allocation:</td>
+<td>
+<input name="block" size="25" value="<TMPL_VAR NAME=block>">
+<input type="hidden" name="fbassign" value="y">
+</td>
+</TMPL_IF>
+</tr>
+</TMPL_IF>
+
+<tr class="row0">
+<td>Customer location:</td>
+<td>
+<select name="city">
+	<option selected="selected">-</option>
+<TMPL_LOOP NAME=citylist>
+	<option><TMPL_VAR NAME=city></option></TMPL_LOOP>
+</select>
+&nbsp;<a href="javascript:popNotes('<TMPL_VAR NAME=webpath>/cgi-bin/newcity.cgi')">Add new location</a>
+</td>
+</tr>
+
+<tr class="row1">
+<td>Allocation type:</td>
+<td>
+<TMPL_IF fbip>
+<TMPL_VAR NAME=fbdisptype><input type="hidden" name="alloctype" value="<TMPL_VAR NAME=type>">
+<TMPL_ELSE>
+<TMPL_IF fbrouted>
+<TMPL_VAR NAME=fbdisptype><input type="hidden" name="alloctype" value="<TMPL_VAR NAME=type>">
+<TMPL_ELSE>
+<select name="alloctype">
+<TMPL_LOOP name=typelist>
+	<option value="<TMPL_VAR NAME=type>"<TMPL_IF sel> selected</TMPL_IF>><TMPL_VAR NAME=listname></option></TMPL_LOOP>
+</select>
+<input type="button" value=" ? " onclick="helpAllocTypes()" class="regular">
+</TMPL_IF>
+</TMPL_IF>
+</td>
+</tr>
+
+<TMPL_UNLESS allocfrom>
+<tr class="row0">
+<td>Subnet CIDR mask length:</td>
+<td valign="top">&nbsp;/&nbsp;<input type="text" name="maskbits" size="3" maxlength="3"></td>
+</tr>
+</TMPL_UNLESS>
+
+<tr class="<TMPL_VAR NAME=rowa>">
+<td>Customer ID:</td>
+<td><input type="text" name="custid" size="15" maxlength="15"> (Only required for Customer allocations)</td>
+</tr>
+
+<tr class="<TMPL_VAR NAME=rowb>">
+<td>Layer-2/demarc node</td>
+<td>
+<select name="node">
+	<option selected>-</option>
+<TMPL_LOOP NAME=nodelist>
+	<option value="<TMPL_VAR NAME=node_id>"><TMPL_VAR NAME=node_name></option></TMPL_LOOP>
+</select>
+&nbsp;<a href="javascript:popNotes('<TMPL_VAR NAME=webpath>/cgi-bin/newnode.cgi')">Add new demarc node</a>
+</td>
+</tr>
+
+<tr class="<TMPL_VAR NAME=rowa>"><td>VRF instance</td><td><input name="vrf" size="40" value="<TMPL_VAR NAME=vrf>"></td></tr>
+
+<TMPL_UNLESS allocfrom>
+<tr class="<TMPL_VAR NAME=rowb>">
+<td>Limit to blocks in this location:</td>
+<td>
+<select name="pop">
+	<option selected value="">-</option>
+<TMPL_LOOP NAME=pops>
+	<option><TMPL_VAR NAME=pop></option></TMPL_LOOP>
+</select>
+</td>
+</tr>
+
+<tr class="<TMPL_VAR NAME=rowa>">
+<td>Limit to blocks in this master:&nbsp;</td>
+<td>
+<select name="allocfrom">
+	<option selected="selected">-</option>
+<TMPL_LOOP NAME=masterlist>
+	<option value=<TMPL_VAR NAME=id>><TMPL_VAR NAME=master> (<TMPL_VAR NAME=vrf>)</option></TMPL_LOOP>
+</select>
+Allow automatic allocation from private IP ranges:<input type="checkbox" name="allowpriv"></td>
+</tr>
+</TMPL_UNLESS>
+
+<tr class="<TMPL_VAR NAME=rowb>">
+<td>Circuit ID:</td>
+<td><input name="circid" size="40"></td>
+</tr>
+
+<tr class="<TMPL_VAR NAME=rowa>">
+<td>Description/Name:</td>
+<td><input name="desc" size="40"></td>
+</tr>
+
+<tr class="<TMPL_VAR NAME=rowb>">
+<td>Notes:&nbsp;</td>
+<td><textarea name="notes" rows="3" cols="40"></textarea></td>
+</tr>
+
+<TMPL_IF privdata>
+<tr class="<TMPL_VAR NAME=rowa>">
+<td>Restricted data:</td>
+<td><textarea rows="3" cols="64" name="privdata" class="regular"></textarea></td>
+</tr>
+</TMPL_IF>
+
+<tr class="<TMPL_IF privdata><TMPL_VAR NAME=rowb><TMPL_ELSE><TMPL_VAR NAME=rowb></TMPL_IF>">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+</tr>
+
+</table>
+
+<input type="hidden" name="action" value="confirm">
+</fieldset>
+</form>
+
+</div>
Index: tags/2.8.2/templates/confirm.tmpl
===================================================================
--- tags/2.8.2/templates/confirm.tmpl	(revision 617)
+++ tags/2.8.2/templates/confirm.tmpl	(revision 617)
@@ -0,0 +1,105 @@
+<TMPL_IF webpath></TMPL_IF>
+<TMPL_IF err>
+<div class="regular err">
+<p><TMPL_VAR NAME=err></p>
+<input type="button" value="Back" onclick="history.go(-1)">
+</div>
+<TMPL_ELSE>
+<div class="heading">Please confirm</div>
+<div class="indent">
+
+<form method="POST" action="main.cgi" class="regular">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<table class="regular" cellspacing="1" cellpadding="1">
+
+<tr class="row0">
+<td>CIDR block to be allocated:</td>
+<td><TMPL_VAR NAME=cidr></td>
+</tr>
+
+<tr class="row1">
+<td>Block allocation is taken from:</td>
+<TMPL_IF staticip>
+<td>
+<select name="alloc_from">
+<TMPL_LOOP name=poollist>	<option value="<TMPL_VAR NAME=poolblock>"><TMPL_VAR NAME=poolblock> [<TMPL_VAR NAME=poolfree>] free IP(s) in <TMPL_VAR NAME=poolcit></option>
+</TMPL_LOOP></select>
+</td>
+<TMPL_ELSE>
+<td><TMPL_VAR NAME=alloc_from></td>
+</TMPL_IF>
+</tr>
+
+<tr class="row0">
+<td>City:</td>
+<td><TMPL_VAR NAME=city></td>
+</tr>
+
+<tr class="row1">
+<td>Demarc switch/wifi tower:</td>
+<td><TMPL_VAR NAME=nodename></td>
+</tr>
+
+<tr class="row0">
+<td>VRF instance:&nbsp;</td>
+<td><TMPL_VAR NAME=vrf><input type="hidden" name="vrf" value="<TMPL_VAR NAME=vrf>"></td>
+</tr>
+
+<tr class="row1">
+<td>Allocation type:</td>
+<td><TMPL_VAR NAME=typefull></td>
+</tr>
+
+<tr class="row0">
+<td>Customer ID:</td>
+<td><TMPL_VAR NAME=custid></td>
+</tr>
+
+<tr class="row1">
+<td valign="top">Circuit ID:</td>
+<td><TMPL_VAR NAME=circid></td>
+</tr>
+
+<tr class="row0">
+<td valign="top">Description/name:</td>
+<td><TMPL_VAR NAME=desc></td>
+</tr>
+
+<tr class="row1">
+<td valign="top">Notes:</td>
+<td><TMPL_VAR NAME=notes></td>
+</tr>
+
+<TMPL_IF privdata>
+<tr class="row0">
+<td>Restricted data:</td>
+<td><TMPL_VAR NAME=privdata></td>
+</tr>
+</TMPL_IF>
+
+<!-- warn -->
+<tr class="<TMPL_IF privdata>row1<TMPL_ELSE>row0</TMPL_IF>">
+<td class="center" colspan="2">
+<TMPL_UNLESS poollist><input type="hidden" name="alloc_from" value="<TMPL_VAR NAME=alloc_from>"></TMPL_UNLESS>
+<input type="hidden" name="fullcidr" value="<TMPL_VAR NAME=cidr>">
+<input type="hidden" name="city" value="<TMPL_VAR NAME=city>">
+<input type="hidden" name="node" value="<TMPL_VAR NAME=nodeid>">
+<input type="hidden" name="alloctype" value="<TMPL_VAR NAME=alloc_type>">
+<input type="hidden" name="custid" value="<TMPL_VAR NAME=custid>">
+<input type="hidden" name="circid" value="<TMPL_VAR NAME=circid>">
+<input type="hidden" name="desc" value="<TMPL_VAR NAME=desc>">
+<input type="hidden" name="notes" value="<TMPL_VAR NAME=notes>">
+<TMPL_IF privdata>
+<input type="hidden" name="privdata" value="<TMPL_VAR NAME=privdata>">
+</TMPL_IF>
+<TMPL_IF billinguser>
+<input type="hidden" name="billinguser" value="<TMPL_VAR NAME=billinguser>">
+</TMPL_IF>
+<input type="hidden" name="action" value="<TMPL_VAR NAME=action>">
+<input type="button" value="Back" onclick="history.go(-1)"><input type="submit" value="Confirm">
+</td></tr>
+</table>
+</fieldset>
+</form>
+</div>
+</TMPL_IF>
Index: tags/2.8.2/templates/dberr.tmpl
===================================================================
--- tags/2.8.2/templates/dberr.tmpl	(revision 617)
+++ tags/2.8.2/templates/dberr.tmpl	(revision 617)
@@ -0,0 +1,5 @@
+<br><div class="center regular">Database error:<br>
+<TMPL_VAR NAME=errmsg><br><br>
+<input type="button" value="Back" onclick="history.go(-1)">
+</div>
+<TMPL_IF webpath></TMPL_IF>
Index: tags/2.8.2/templates/delete.tmpl
===================================================================
--- tags/2.8.2/templates/delete.tmpl	(revision 617)
+++ tags/2.8.2/templates/delete.tmpl	(revision 617)
@@ -0,0 +1,70 @@
+<TMPL_IF webpath></TMPL_IF>
+<div class="heading">Please confirm removal of:</div>
+<br>
+
+<table class="indent" cellspacing="1" cellpadding="1">
+
+<tr class="row0">
+<td>IP block:</td>
+<td><TMPL_VAR NAME=block></td>
+</tr>
+
+<tr class="row1">
+<td>City:</td>
+<td><TMPL_VAR NAME=city></td>
+</tr>
+
+<tr class="row0">
+<td>Type:</td>
+<td><TMPL_VAR NAME=disptype></td>
+</tr>
+
+<tr class="row1">
+<td>Customer ID:</td>
+<td><TMPL_VAR NAME=custid></td>
+</tr>
+
+<tr class="row0">
+<td>Circuit ID:</td>
+<td><TMPL_VAR ESCAPE=HTML NAME=circid></td>
+</tr>
+
+<tr class="row1">
+<td valign="top">Description/Name:</td>
+<td><TMPL_VAR ESCAPE=HTML NAME=desc></td>
+</tr>
+
+<tr class="row0">
+<td valign="top">Notes:</td>
+<td><TMPL_VAR NAME=notes></td>
+</tr>
+
+<TMPL_IF privdata>
+<tr class="row1">
+<td valign="top">Restricted data:</td>
+<td><TMPL_VAR NAME=privdata></td>
+</tr>
+</TMPL_IF>
+
+<tr class="warning">
+<td colspan="2">
+Warning: clicking confirm will remove this record entirely.
+<TMPL_IF delpool><br>Any IPs allocated from this pool will also be removed!</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row<TMPL_IF privdata>0<TMPL_ELSE>1</TMPL_IF>">
+<td class="center" colspan="2">
+<form action="main.cgi" method="POST" class="regular">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="button" value="Back" onclick="history.go(-1)"><input type="submit" value="Confirm">
+<input type="hidden" name="block" value="<TMPL_VAR NAME=block>">
+<input type="hidden" name="alloctype" value="<TMPL_VAR NAME=type>">
+<input type="hidden" name="city" value="<TMPL_VAR NAME=city>">
+<input type="hidden" name="action" value="finaldelete">
+</fieldset>
+</form>
+</td>
+</tr>
+
+</table>
Index: tags/2.8.2/templates/dunno.tmpl
===================================================================
--- tags/2.8.2/templates/dunno.tmpl	(revision 617)
+++ tags/2.8.2/templates/dunno.tmpl	(revision 617)
@@ -0,0 +1,5 @@
+<br>
+<div class="center">
+<TMPL_VAR NAME=dunno>
+</div>
+<br>
Index: tags/2.8.2/templates/edit.tmpl
===================================================================
--- tags/2.8.2/templates/edit.tmpl	(revision 617)
+++ tags/2.8.2/templates/edit.tmpl	(revision 617)
@@ -0,0 +1,187 @@
+<TMPL_IF webpath></TMPL_IF>
+<div class="indent">
+<div class="heading">Edit the information below</div>
+
+<table cellpadding="0" cellspacing="0">
+<tr>
+<td>
+
+<TMPL_IF maychange>
+<form method="POST" action="main.cgi">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="action" value="update">
+<input type="hidden" name="block" value="<TMPL_VAR NAME=block>">
+</TMPL_IF>
+
+<table cellspacing="1">
+
+<tr class="row0">
+<td class=heading>IP block:</td>
+<td class="regular"><TMPL_VAR NAME=block></td>
+</tr>
+
+<tr class="row1">
+<td class=heading>City:</td>
+<td class="regular">
+<TMPL_IF maychange>
+<input type=text name=city value="<TMPL_VAR NAME=city>">
+<TMPL_ELSE>
+<TMPL_VAR NAME=city>
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row0">
+<td class=heading>Type:</td>
+<td class=regular>
+<TMPL_IF changetype>
+<select name=alloctype>
+<TMPL_LOOP name=alloctype>	<option <TMPL_IF selme>selected="selected"</TMPL_IF> value="<TMPL_VAR NAME=type>"><TMPL_VAR NAME=disptype></option>
+</TMPL_LOOP>
+</select>
+<TMPL_ELSE>
+<TMPL_VAR NAME=disptype>
+<input type="hidden" name="alloctype" value="<TMPL_VAR NAME=type>">
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row1">
+<td class=heading>Demarc/tower:</td>
+<td class=regular>
+<TMPL_IF typesupportsnodes>
+<TMPL_IF maychange>
+<select name="node"><TMPL_UNLESS havenodeid>
+	<option selected="selected">--</option></TMPL_UNLESS>
+<TMPL_LOOP NAME=nodelist>
+	<option value="<TMPL_VAR NAME=node_id>"<TMPL_IF selme> selected</TMPL_IF>><TMPL_VAR NAME=node_name></option></TMPL_LOOP>
+</select>
+<TMPL_ELSE>
+<TMPL_IF nodename><TMPL_VAR NAME=nodename><TMPL_ELSE>N/A</TMPL_IF>
+</TMPL_IF>
+<TMPL_ELSE>
+N/A
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row0">
+<td class=heading>VRF instance:</td>
+<td class="regular">
+<TMPL_IF maychange>
+<input type="text" name="vrf" value="<TMPL_VAR NAME=vrf>">
+<TMPL_ELSE>
+<TMPL_VAR NAME=vrf>
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row1">
+<td class=heading>CustID:</td>
+<td class="regular">
+<TMPL_IF maychange>
+<input type=text name=custid value="<TMPL_VAR NAME=custid>" maxlength=15 class="regular">
+<TMPL_ELSE>
+<TMPL_VAR NAME=custid>
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row0">
+<td class=heading>SWIPed?:</td>
+<td class=regular>
+<TMPL_IF swipable>
+<TMPL_IF maychange>
+<input type="checkbox" name="swip"<TMPL_IF swip> checked="checked"</TMPL_IF>>
+<TMPL_ELSE>
+<TMPL_IF swip>Yes<TMPL_ELSE>No</TMPL_IF>
+</TMPL_IF>
+<TMPL_ELSE>
+N/A
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row1">
+<td class=heading>Last modified:</td>
+<td class=regular><TMPL_VAR NAME=lastmod></td>
+</tr>
+
+<tr class="row0">
+<td class="heading">Circuit ID:</td>
+<td class="regular">
+<TMPL_IF maychange>
+<input type="text" name="circid" value="<TMPL_VAR ESCAPE=HTML NAME=circid>" maxlength=64 size=64 class="regular">
+<TMPL_ELSE>
+<TMPL_VAR ESCAPE=HTML NAME=circid>
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row1">
+<td class="heading">Description/Name:</td>
+<td class="regular">
+<TMPL_IF maychange>
+<input type="text" name="desc" value="<TMPL_VAR ESCAPE=HTML NAME=desc>" maxlength=64 size=64 class="regular">
+<TMPL_ELSE>
+<TMPL_VAR ESCAPE=HTML NAME=desc>
+</TMPL_IF>
+</td>
+</tr>
+
+<tr class="row0">
+<td class="heading" valign="top">Notes:</td>
+<td class="regular">
+<TMPL_IF maychange><textarea rows="8" cols="64" name="notes" class="regular"><TMPL_VAR ESCAPE=HTML NAME=notes></textarea>
+<TMPL_ELSE>
+<TMPL_VAR ESCAPE=HTML NAME=notes>
+</TMPL_IF>
+</td>
+</tr>
+
+<TMPL_IF nocling>
+<tr class="row1">
+<td class="heading" valign="top">Restricted data:</td>
+<td class="regular">
+<TMPL_IF maychange>
+<textarea rows="3" cols="64" name="privdata" class="regular"><TMPL_VAR ESCAPE=HTML NAME=privdata></textarea>
+<TMPL_ELSE>
+<TMPL_VAR ESCAPE=HTML NAME=privdata>
+</TMPL_IF>
+</td>
+</tr>
+</TMPL_IF>
+
+<TMPL_IF maychange>
+<tr class="row<TMPL_IF nocling>0<TMPL_ELSE>1</TMPL_IF>">
+<td colspan="2" class="center">
+<input type="submit" value=" Update this block " class="regular">
+</td>
+</tr>
+</TMPL_IF>
+</table>
+<TMPL_IF maychange>
+</fieldset>
+</form>
+</TMPL_IF>
+
+<TMPL_IF maydel>
+</td></tr>
+<tr><td class="center">
+<form method="POST" action="main.cgi">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<div class="row<TMPL_IF nocling><TMPL_IF maychange>1<TMPL_ELSE>0</TMPL_IF><TMPL_ELSE><TMPL_IF maychange>0<TMPL_ELSE>1</TMPL_IF></TMPL_IF>">
+<input type="hidden" name="action" value="delete">
+<input type="hidden" name="block" value="<TMPL_VAR NAME=block>">
+<input type="hidden" name="alloctype" value="<TMPL_VAR NAME=type>">
+<input type=submit value=" Delete this block ">
+</div>
+</fieldset>
+</form>
+</TMPL_IF>
+
+</td>
+</tr>
+</table>
+
+</div>
Index: tags/2.8.2/templates/finaldelete.tmpl
===================================================================
--- tags/2.8.2/templates/finaldelete.tmpl	(revision 617)
+++ tags/2.8.2/templates/finaldelete.tmpl	(revision 617)
@@ -0,0 +1,10 @@
+<TMPL_IF webpath></TMPL_IF>
+<div class="center">
+<TMPL_IF failmsg>
+<p>Could not deallocate <TMPL_IF netblock>netblock<TMPL_ELSE>static IP</TMPL_IF> <TMPL_VAR NAME=block>: <TMPL_VAR NAME=failmsg></p>
+<input type="button" value="Back" onclick="history.go(-1)">
+<TMPL_ELSE>
+<div class="heading">Success!  <TMPL_VAR NAME=block> deallocated.</div>
+</TMPL_IF>
+</div>
+
Index: tags/2.8.2/templates/footer.tmpl
===================================================================
--- tags/2.8.2/templates/footer.tmpl	(revision 617)
+++ tags/2.8.2/templates/footer.tmpl	(revision 617)
@@ -0,0 +1,14 @@
+<br>
+<TMPL_IF adminlink><div id="adminlink"><a href="<TMPL_VAR NAME=webpath>/cgi-bin/admin.cgi">Admin tools</a></div></TMPL_IF>
+<!-- Done with the main div opened in the header template -->
+</div>
+
+<div id="footer">
+<div id="contact">DeepNet <a href="http://projects.deepnet.cx/trac/ipdb">IP Database</a> |
+ Copyright 2004-2013 <a href="mailto:kdeugau@deepnet.cx">Kris Deugau</a><br>
+Written for standards-based browsers (eg <a href="http://www.mozilla.org">Mozilla</a>)
+</div>
+</div>
+
+</body>
+</html>
Index: tags/2.8.2/templates/header.tmpl
===================================================================
--- tags/2.8.2/templates/header.tmpl	(revision 617)
+++ tags/2.8.2/templates/header.tmpl	(revision 617)
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<!-- DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" -->
+
+    <head>
+        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+	<title>[IP Database v<TMPL_VAR NAME=version>]</title>
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/local.css">
+	<script src="<TMPL_VAR NAME=webpath>/templates/widgets.js" type="text/javascript"></script>
+    </head>
+
+<body>
+
+<div id="bodyheader">
+<div id="hlogo">
+<a href="<TMPL_VAR NAME=webpath>/index.shtml"><img src="<TMPL_VAR NAME=webpath>/images/logo.png" style="border:0;" alt="[ Logo ]"></a>
+</div>
+<div id="homelink">
+<a href="<TMPL_VAR NAME=webpath>/index.shtml">[IP Database v<TMPL_VAR NAME=version> Home]</a>
+</div>
+</div>
+
+<div id="subheader">
+<div id="qsearch">
+<form method="post" action="<TMPL_VAR NAME=webpath>/cgi-bin/search.cgi">
+<fieldset><legend class="noshow">&nbsp;</legend>
+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">
+&nbsp; &nbsp;<a href="<TMPL_VAR NAME=webpath>/cgi-bin/search.cgi">Complex Search</a>
+</fieldset>
+</form>
+</div>
+<TMPL_IF addperm>
+<div id="newlink"><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=assign">Add new assignment</a></div>
+</TMPL_IF>
+</div>
+
+<div id="main">
+<div id="utils">
+<a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=nodesearch">Find by connection point</a>
+</div>
Index: tags/2.8.2/templates/index.tmpl
===================================================================
--- tags/2.8.2/templates/index.tmpl	(revision 617)
+++ tags/2.8.2/templates/index.tmpl	(revision 617)
@@ -0,0 +1,27 @@
+<br>
+<div class="center">
+<table class="center" width="98%" cellspacing="0">
+<tr class="heading">
+<td>Master netblock</td>
+<td>Routed netblocks</td>
+<td>Allocated netblocks</td>
+<td>Free netblocks</td>
+<td>Largest free block</td>
+</tr>
+<TMPL_LOOP NAME=masterlist>
+<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
+<td><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=showmaster&amp;block=<TMPL_VAR NAME=master>"><TMPL_VAR NAME=master></a></td>
+<td><TMPL_VAR NAME=routed></td>
+<td><TMPL_VAR NAME=allocated></td>
+<td><TMPL_VAR NAME=free></td>
+<td><TMPL_VAR ESCAPE=HTML NAME=bigfree></td>
+</tr>
+</TMPL_LOOP>
+</table>
+<TMPL_IF addmaster>
+<a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=addmaster">Add new master block</a>
+</TMPL_IF>
+<br><br>
+Note:  Free blocks noted here include both routed and unrouted blocks.
+</div>
+
Index: tags/2.8.2/templates/insert.tmpl
===================================================================
--- tags/2.8.2/templates/insert.tmpl	(revision 617)
+++ tags/2.8.2/templates/insert.tmpl	(revision 617)
@@ -0,0 +1,23 @@
+<TMPL_IF webpath></TMPL_IF>
+<TMPL_IF err>
+<div class="err">
+<p><TMPL_VAR NAME=err></p>
+<input type="button" value="Back" onclick="history.go(-1)">
+</div>
+<TMPL_ELSE>
+<TMPL_IF staticip>
+<div class="center">
+<div class="heading">The IP <TMPL_VAR NAME=staticip> has been allocated to customer <TMPL_VAR NAME=custid></div>
+<TMPL_IF billinguser>
+<div><a href="https://billing.example.com/radius.pl?action=new_radius_user&amp;custid=<TMPL_VAR NAME=custid>&amp;userid=<TMPL_VAR NAME=billinguser>&amp;ipdb=1&amp;ip=<TMPL_VAR NAME=staticip>">Add this IP to RADIUS user table</a></div>
+</TMPL_IF>
+</div>
+<TMPL_ELSE>
+<div class="center">
+<div class="heading">The block <TMPL_VAR NAME=fullcidr> was sucessfully added as: <TMPL_VAR NAME=alloctype></div>
+<TMPL_IF billinguser>
+<div><a href="https://billing.example.com/radius.pl?action=new_radius_user&amp;custid=<TMPL_VAR NAME=custid>&amp;userid=<TMPL_VAR NAME=billinguser>&amp;route_subnet=<TMPL_VAR NAME=netaddr>&amp;subnet_slash=<TMPL_VAR NAME=masklen>&amp;include_routed_subnet=1&amp;ipdb=1">Add this netblock to RADIUS user table</a></div>
+</TMPL_IF>
+</div>
+</TMPL_IF>
+</TMPL_IF>
Index: tags/2.8.2/templates/listpool.tmpl
===================================================================
--- tags/2.8.2/templates/listpool.tmpl	(revision 617)
+++ tags/2.8.2/templates/listpool.tmpl	(revision 617)
@@ -0,0 +1,49 @@
+<TMPL_IF webpath></TMPL_IF>
+<br>
+<div class="tbltitle">Listing pool IPs for <TMPL_VAR NAME=block><br>
+(<TMPL_VAR NAME=disptype> in <TMPL_VAR NAME=city>)</div>
+<br>
+
+<TMPL_IF realblock>
+<div class="indent"><b>Reserved IPs:</b><br>
+<div class="indent">
+<table>
+<tr class="row0"><td>Network IP:</td><td><TMPL_VAR NAME=netip></td></tr>
+<tr class="row1"><td>Gateway:</td><td><TMPL_VAR NAME=gate></td></tr>
+<tr class="row0"><td>Broadcast:</td><td><TMPL_VAR NAME=bcast></td></tr>
+<tr class="row1"><td>Netmask:</td><td><TMPL_VAR NAME=mask></td></tr>
+</table>
+</div>
+</div>
+</TMPL_IF>
+
+<table width="98%" cellspacing="0" class="center">
+<tr>
+<td class="heading">IP</td>
+<td class="heading">Customer ID</td>
+<td class="heading">Available?</td>
+<td class="heading">Description</td>
+<TMPL_IF maydel><td class="heading"></td></TMPL_IF>
+</tr>
+
+<TMPL_LOOP name=poolips>
+<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
+<td>
+<TMPL_UNLESS delme>
+<a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=assign&amp;block=<TMPL_VAR NAME=ip>&amp;fbtype=i"><TMPL_VAR NAME=ip></a>
+<TMPL_ELSE>
+<a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=edit&amp;block=<TMPL_VAR NAME=ip>"><TMPL_VAR NAME=ip></a>
+</TMPL_UNLESS>
+</td>
+<td><TMPL_VAR NAME=custid></td>
+<td><TMPL_VAR NAME=available></td>
+<td><TMPL_VAR NAME=desc></td>
+<TMPL_IF maydel><td><TMPL_IF delme>
+<a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=delete&amp;block=<TMPL_VAR NAME=ip>&amp;alloctype=<TMPL_VAR NAME=type>">Unassign this IP</a>
+</TMPL_IF></td></TMPL_IF>
+</tr>
+</TMPL_LOOP>
+
+</table>
+
+<br>
Index: tags/2.8.2/templates/newcity.tmpl
===================================================================
--- tags/2.8.2/templates/newcity.tmpl	(revision 617)
+++ tags/2.8.2/templates/newcity.tmpl	(revision 617)
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>IP Database - Add new city</title>
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/local.css">
+	<script src="<TMPL_VAR NAME=webpath>/templates/widgets.js" type="text/javascript"></script>
+</head>
+
+<body onUnload="redoParent()">
+<TMPL_IF city>
+<div style="padding=3px;">
+<TMPL_IF err>
+Error adding city <TMPL_VAR NAME=city> to database: <TMPL_VAR NAME=err>
+<TMPL_ELSE>
+City <TMPL_VAR NAME=city> added.  Closing this window should refresh the page.
+</TMPL_IF>
+</div>
+<TMPL_ELSE>
+<form action="newcity.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<table>
+<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>
+</fieldset>
+</form>
+</TMPL_IF>
+</body>
+</html>
Index: tags/2.8.2/templates/newmaster.tmpl
===================================================================
--- tags/2.8.2/templates/newmaster.tmpl	(revision 617)
+++ tags/2.8.2/templates/newmaster.tmpl	(revision 617)
@@ -0,0 +1,9 @@
+<TMPL_IF webpath></TMPL_IF>
+<div class="center">
+<div class="heading">Adding <TMPL_VAR NAME=cidr> as master block....</div>
+<TMPL_IF err>
+Could not add master block <TMPL_VAR NAME=cidr> to database: <TMPL_VAR NAME=err>
+<TMPL_ELSE>
+<div class="heading">Success!</div>
+</TMPL_IF>
+</div>
Index: tags/2.8.2/templates/newnode.tmpl
===================================================================
--- tags/2.8.2/templates/newnode.tmpl	(revision 617)
+++ tags/2.8.2/templates/newnode.tmpl	(revision 617)
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>IP Database - Add new tower/demarc switch</title>
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/local.css">
+	<script src="<TMPL_VAR NAME=webpath>/templates/widgets.js" type="text/javascript"></script>
+</head>
+
+<body onUnload="redoParent()">
+<TMPL_IF nodename>
+<div style="padding: 3px;">
+<TMPL_IF err>
+Error adding node <TMPL_VAR NAME=nodename> to database: <TMPL_VAR NAME=err>
+<TMPL_ELSE>
+Node <TMPL_VAR NAME=nodename> added.  Closing this window should refresh the page.
+</TMPL_IF>
+</div>
+<TMPL_ELSE>
+<form action="newnode.cgi" method="POST">
+<fieldset><legend class="noshow">&nbsp;</legend>
+<table>
+<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>
+</fieldset>
+</form>
+</TMPL_IF>
+</body>
+</html>
Index: tags/2.8.2/templates/nodesearch.tmpl
===================================================================
--- tags/2.8.2/templates/nodesearch.tmpl	(revision 617)
+++ tags/2.8.2/templates/nodesearch.tmpl	(revision 617)
@@ -0,0 +1,28 @@
+<div class="indent">
+<TMPL_IF errmsg>
+Failed to retrieve node list: <TMPL_VAR NAME=errmsg>
+<TMPL_ELSE>
+<div class="heading">Switch/tower search</div>
+<form action="<TMPL_VAR NAME=webpath>/cgi-bin/search.cgi" method=POST>
+<fieldset><legend class="noshow">&nbsp;</legend>
+<table cellspacing="1" cellpadding="2">
+<tr class="row0">
+<td>Find customers connected through:</td>
+<td><select name="node">
+	<option>--</option>
+<TMPL_LOOP NAME=nodelist>	<option value="<TMPL_VAR NAME=node_id>"><TMPL_VAR NAME=node_name></option>
+</TMPL_LOOP>
+</select></td>
+</tr>
+
+<tr class="row1">
+<td colspan="2" align="center"><input type="submit" value="Find customers"></td>
+</tr>
+</table>
+<input type="hidden" name="stype" value="n">
+<input type="hidden" name="page" value="1">
+<input type="hidden" name="rpp" value="50">
+</fieldset>
+</form>
+</TMPL_IF>
+</div>
Index: tags/2.8.2/templates/search/compsearch.tmpl
===================================================================
--- tags/2.8.2/templates/search/compsearch.tmpl	(revision 617)
+++ tags/2.8.2/templates/search/compsearch.tmpl	(revision 617)
@@ -0,0 +1,76 @@
+<div class="indent">
+<div class="heading">Complex Search</div>
+
+<form action="<TMPL_VAR NAME=webpath>/cgi-bin/search.cgi" method=POST>
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type="hidden" name="stype" value="c">
+<input type="hidden" name="page" value="1">
+<table cellspacing="1" cellpadding="2">
+
+<tr class="row0">
+<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>
+<td align=right><input type=submit value="Search Now"></td>
+</tr>
+
+<tr class="row1">
+<td>Exclude?<input type=checkbox name=cidrexclude value='not'></td>
+<td>IP/netblock:</td><td colspan=2><input name=cidr></td>
+</tr>
+
+<tr class="row0">
+<td>Exclude?<input type=checkbox name=custexclude value='not'></td>
+<td>CustID:</td><td colspan=2><input name=custid></td>
+</tr>
+
+<tr class="row1">
+<td>Exclude?<input type=checkbox name=descexclude value='not'></td>
+<td>Description:</td><td colspan=2><input name=desc></td>
+</tr>
+
+<tr class="row0">
+<td>Exclude?<input type=checkbox name=notesexclude value='not'></td>
+<td>Notes:</td><td colspan=2><input name=notes></td>
+</tr>
+
+<tr class="row1">
+<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
+<table class="regular" cellspacing="0">
+<TMPL_LOOP NAME=typelist>
+<TMPL_IF newrow><tr>
+</TMPL_IF><td><input type="checkbox" name="type[<TMPL_VAR NAME=type>]"><TMPL_VAR NAME=dispname></td><TMPL_IF endrow>
+</tr></TMPL_IF>
+</TMPL_LOOP>
+</table>
+</td>
+</tr>
+
+<tr class="row0">
+<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
+<table class="regular" cellspacing="0">
+<TMPL_LOOP NAME=citylist>
+<TMPL_IF newrow><tr>
+</TMPL_IF><td><input type="checkbox" name="city[<TMPL_VAR NAME=id>]"><TMPL_VAR NAME=city></td><TMPL_IF endrow>
+</tr></TMPL_IF>
+</TMPL_LOOP>
+</table>
+</td>
+</tr>
+
+</table>
+</fieldset>
+</form>
+
+</div>
Index: tags/2.8.2/templates/search/sresults.tmpl
===================================================================
--- tags/2.8.2/templates/search/sresults.tmpl	(revision 617)
+++ tags/2.8.2/templates/search/sresults.tmpl	(revision 617)
@@ -0,0 +1,39 @@
+<TMPL_IF errmsg>
+<br><div class="center"><TMPL_VAR NAME=errmsg><br><br>
+<input type="button" value="Back" onclick="history.go(-1)">
+</div><br>
+<TMPL_ELSE>
+<div class="heading"><TMPL_VAR NAME=searchtitle></div><br>
+<div class="center">
+<table width="98%" cellspacing="0" class="center">
+
+<tr>
+<td class="heading">Allocation</td>
+<td class="heading">CustID</td>
+<td class="heading">Type</td>
+<td class="heading">City</td>
+<td class="heading">Description/Name</td>
+</tr>
+<TMPL_LOOP NAME=sresults>
+<tr class="row<TMPL_VAR NAME=rowclass>">
+<td><TMPL_IF issub>Sub </TMPL_IF><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=edit&amp;block=<TMPL_VAR NAME=block>"><TMPL_VAR NAME=block></a><TMPL_IF ispool>&nbsp; <a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=listpool&amp;pool=<TMPL_VAR NAME=block>">List IPs</a></TMPL_IF></td>
+<td><TMPL_VAR NAME=custid></td>
+<td><TMPL_VAR NAME=disptype></td>
+<td><TMPL_VAR NAME=city></td>
+<td><TMPL_VAR NAME=desc><TMPL_IF vrf> - vrf:<TMPL_VAR NAME=vrf></TMPL_IF></td>
+</tr>
+</TMPL_LOOP>
+
+<tr><td colspan="5">Total records found: <TMPL_VAR NAME=resfound><br><i>Displaying: <TMPL_VAR NAME=resstart> - <TMPL_VAR 
+NAME=resstop></i></td></tr>
+</table>
+
+<TMPL_IF multipage>
+<div class="center">Page:
+<TMPL_LOOP NAME=pgnums><TMPL_IF thispage><b><TMPL_VAR NAME=pgnum>&nbsp;</b><TMPL_ELSE><a href="<TMPL_VAR NAME=webpath>/cgi-bin/search.cgi?page=<TMPL_VAR NAME=pgnum>&amp;stype=<TMPL_VAR NAME=stype>&amp;<TMPL_VAR NAME=extraopts>"><TMPL_VAR NAME=pgnum></a>&nbsp;</TMPL_IF>
+</TMPL_LOOP>
+</div>
+</TMPL_IF>
+
+</div>
+</TMPL_IF>
Index: tags/2.8.2/templates/showmaster.tmpl
===================================================================
--- tags/2.8.2/templates/showmaster.tmpl	(revision 617)
+++ tags/2.8.2/templates/showmaster.tmpl	(revision 617)
@@ -0,0 +1,65 @@
+<div class="tbltitle">Summarizing routed blocks for <TMPL_VAR NAME=master>:</div>
+<br>
+
+<TMPL_IF routedlist>
+<table class="center" width="98%" cellspacing="0">
+<tr>
+<td class="heading">Routed block</td>
+<td class="heading">Routed to</td>
+<td class="heading">Allocated blocks</td>
+<td class="heading">Free blocks</td>
+<td class="heading">Largest free block</td>
+</tr>
+
+<TMPL_LOOP NAME=routedlist>
+<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
+<td><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=showrouted&amp;block=<TMPL_VAR NAME=block>"><TMPL_VAR NAME=block></a></td>
+<td><TMPL_VAR NAME=city></td>
+<td><TMPL_VAR NAME=nsubs></td>
+<td><TMPL_VAR NAME=nfree></td>
+<td><TMPL_VAR ESCAPE=HTML NAME=lfree></td>
+</tr>
+</TMPL_LOOP>
+</table>
+<TMPL_ELSE>
+<hr class="w30">
+<div class="tbltitle">No allocations in <TMPL_VAR NAME=master>.
+<TMPL_IF delmaster>
+<br>
+<form action="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi" method=POST>
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type=hidden name=action value="delete">
+<input type=hidden name=block value="<TMPL_VAR NAME=master>">
+<input type=hidden name=alloctype value="mm">
+<input type=submit value=" Remove this master ">
+</fieldset>
+</form>
+</TMPL_IF>
+</div>
+</TMPL_IF>
+
+<br>
+<hr class="w60">
+
+<div class="tbltitle">Unrouted blocks in <TMPL_VAR NAME=master>:</div>
+<br>
+
+<TMPL_IF NAME=unrouted>
+<table class="center" width="98%" cellspacing="0">
+<tr>
+<td class="heading">Netblock</td>
+<td class="heading">Range</td>
+</tr>
+
+<TMPL_LOOP name=unrouted>
+<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
+<td><TMPL_IF mayadd><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=assign&block=<TMPL_VAR NAME=fblock>&fbtype=n"><TMPL_VAR NAME=fblock><TMPL_ELSE><TMPL_VAR NAME=fblock></TMPL_IF></td>
+<td><TMPL_VAR NAME=frange></td>
+</tr>
+</TMPL_LOOP>
+
+</table>
+<TMPL_ELSE>
+<hr class="w30">
+<div class="tbltitle">No unrouted blocks in <TMPL_VAR NAME=master>.</div>
+</TMPL_IF>
Index: tags/2.8.2/templates/showrouted.tmpl
===================================================================
--- tags/2.8.2/templates/showrouted.tmpl	(revision 617)
+++ tags/2.8.2/templates/showrouted.tmpl	(revision 617)
@@ -0,0 +1,70 @@
+<div class="tbltitle">Summarizing allocated blocks for <TMPL_VAR NAME=master> (<TMPL_VAR NAME=rcity>):</div>
+<br>
+
+<TMPL_IF blocklist>
+<table width="98%" cellspacing="0" class="center">
+
+<tr>
+<td class="heading">CIDR allocation</td>
+<td class="heading">Customer Location</td>
+<td class="heading">Type</td>
+<td class="heading">CustID</td>
+<td class="heading">SWIPed?</td>
+<td class="heading">Description/Name</td>
+</tr>
+
+<TMPL_LOOP NAME=blocklist>
+<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
+<td>
+<TMPL_IF subblock>Sub </TMPL_IF><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=edit&amp;block=<TMPL_VAR NAME=block>"><TMPL_VAR NAME=block></a>
+<TMPL_IF listpool> &nbsp; <a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=listpool&amp;pool=<TMPL_VAR NAME=block>">List IPs</a></TMPL_IF>
+</td>
+<td><TMPL_VAR NAME=city></td>
+<td><TMPL_VAR NAME=type></td>
+<td><TMPL_VAR NAME=custid></td>
+<td><TMPL_VAR NAME=swip><TMPL_IF partswip><small>*</small></TMPL_IF></td>
+<td><TMPL_VAR NAME=desc></td>
+</tr>
+</TMPL_LOOP>
+</table>
+<TMPL_ELSE>
+<hr class="w30">
+<div class="tbltitle">No allocations in <TMPL_VAR NAME=master>.
+<TMPL_IF delrouted>
+<br>
+<form action="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi" method=POST>
+<fieldset><legend class="noshow">&nbsp;</legend>
+<input type=hidden name=action value="delete">
+<input type=hidden name=block value="<TMPL_VAR NAME=master>">
+<input type=hidden name=alloctype value="rm">
+<input type=submit value=" Remove this block ">
+</fieldset>
+</form>
+</TMPL_IF>
+</div>
+</TMPL_IF>
+
+<br>
+<hr class="w60">
+
+<div class="tbltitle">Free blocks within routed block <TMPL_VAR NAME=master></div>
+
+<TMPL_IF unassigned>
+<table width="98%" cellspacing="0" class="center">
+<tr>
+<td class="heading">CIDR block</td>
+<td class="heading">Range</td>
+</tr>
+
+<TMPL_LOOP name=unassigned>
+<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
+<td><TMPL_IF subblock>Sub </TMPL_IF><a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=assign&amp;block=<TMPL_VAR NAME=fblock>&amp;fbtype=<TMPL_VAR NAME=fbtype>"><TMPL_VAR NAME=fblock></a></td>
+<td><TMPL_VAR NAME=frange></td>
+</tr>
+</TMPL_LOOP>
+</table>
+<TMPL_ELSE>
+<br>
+<hr class="w30">
+<div class="tbltitle">No unassigned blocks in routed block <TMPL_VAR NAME=master></div>
+</TMPL_IF>
Index: tags/2.8.2/templates/subnet-calc.tmpl
===================================================================
--- tags/2.8.2/templates/subnet-calc.tmpl	(revision 617)
+++ tags/2.8.2/templates/subnet-calc.tmpl	(revision 617)
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<title>IP Database - Subnet Calculator</title>
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/ipdb.css">
+	<link rel="stylesheet" type="text/css" href="<TMPL_VAR NAME=webpath>/local.css">
+
+</head>
+<body>
+
+<div class="center">
+<table class="center" cellspacing="3" cellpadding="3">
+<tr>
+	<td class="heading">Results for /<TMPL_VAR NAME=prenet></td>
+	<td class="heading">Results for /<TMPL_VAR NAME=net></td>
+	<td class="heading">Results for /<TMPL_VAR NAME=postnet></td>
+</tr>
+<tr><td class="ljust" valign="top">
+	<div class="mask"><TMPL_VAR NAME=premask></div>
+	<div class="wildcard"><TMPL_VAR NAME=prewildcard></div>
+<TMPL_LOOP NAME=prenets>	<TMPL_VAR NAME=netrange><br>
+</TMPL_LOOP></td>
+<td class="ljust topalign altbg">
+	<div class="mask"><TMPL_VAR NAME=mask></div>
+	<div class="wildcard"><TMPL_VAR NAME=wildcard></div>
+<TMPL_LOOP NAME=nets>	<TMPL_VAR NAME=netrange><br>
+</TMPL_LOOP></td>
+<td class="ljust" valign="top">
+	<div class="mask"><TMPL_VAR NAME=postmask></div>
+	<div class="wildcard"><TMPL_VAR NAME=postwildcard></div>
+<TMPL_LOOP NAME=postnets>	<TMPL_VAR NAME=netrange><br>
+</TMPL_LOOP></td>
+</tr>
+</table>
+<input type="button" value="Back" onclick="history.go(-1)" class="heading">
+</div>
+
+</body>
+</html>
Index: tags/2.8.2/templates/update.tmpl
===================================================================
--- tags/2.8.2/templates/update.tmpl	(revision 617)
+++ tags/2.8.2/templates/update.tmpl	(revision 617)
@@ -0,0 +1,74 @@
+<TMPL_IF err>
+<div class="regular err">
+<p><TMPL_VAR NAME=err></p>
+<input type="button" value="Back" onclick="history.go(-1)">
+</div>
+<TMPL_ELSE>
+<div class="heading">Allocation info updated:</div>
+
+<table class="indent" cellspacing="1" cellpadding="1">
+
+<tr class="row0">
+<td>IP block:</td>
+<td><TMPL_VAR NAME=cidr></td>
+</tr>
+
+<tr class="row1">
+<td>City:</td>
+<td><TMPL_VAR NAME=city></td>
+</tr>
+
+<tr class="row0">
+<td>Type:</td>
+<td><TMPL_VAR NAME=disptype></td>
+</tr>
+
+<tr class="row1">
+<td>Demarc/tower:</td>
+<td><TMPL_IF nodename><TMPL_VAR NAME=nodename><TMPL_ELSE>N/A</TMPL_IF></td>
+</tr>
+
+<tr class="row0">
+<td>VRF Instance:</td>
+<td><TMPL_VAR NAME=vrf></td>
+</tr>
+
+<tr class="row1">
+<td>Customer ID:</td>
+<td><TMPL_VAR NAME=custid></td>
+</tr>
+
+<tr class="row0">
+<td>SWIPed?:</td>
+<td><TMPL_VAR NAME=swip></td>
+</tr>
+
+<tr class="row1">
+<td>Circuit ID:</td>
+<td><TMPL_VAR ESCAPE=HTML NAME=circid></td>
+</tr>
+
+<tr class="row0">
+<td valign="top">Description/Name:</td>
+<td><TMPL_VAR ESCAPE=HTML NAME=desc></td>
+</tr>
+
+<tr class="row1">
+<td valign="top">Notes:</td>
+<td><TMPL_VAR NAME=notes></td>
+</tr>
+
+<TMPL_IF privdata>
+<tr class="row0">
+<td valign="top">Restricted data:</td>
+<td><TMPL_VAR NAME=privdata></td>
+</tr>
+</TMPL_IF>
+
+</table>
+
+<br>
+<div class="backlink">
+<a href="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi?action=<TMPL_IF backpool>listpool&amp;pool=<TMPL_ELSE>showrouted&amp;block=</TMPL_IF><TMPL_VAR NAME=backblock>">Back to <TMPL_VAR NAME=backblock></a>
+</div>
+</TMPL_IF>
Index: tags/2.8.2/templates/widgets.js
===================================================================
--- tags/2.8.2/templates/widgets.js	(revision 617)
+++ tags/2.8.2/templates/widgets.js	(revision 617)
@@ -0,0 +1,27 @@
+function webPath() {
+	var mypath = "";
+	var pathbits = window.location.pathname.split( '/' );
+	for ( i = 0; i <= pathbits.length-2; i++ ) {
+		if (pathbits[i] != 'cgi-bin') {
+			mypath += pathbits[i];
+			mypath += "/";
+		}
+	}
+	return mypath;
+}
+
+function openHelp() {
+	window.open(webPath() + "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(webPath() + "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(webPath() + "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");
+}
+function redoParent() {
+	opener.location.reload(true);
+}
