# # # rename "" # to "www/viewmtn" # # add_dir "" # # add_dir "graphs" # # add_dir "projects" # # add_dir "skel" # # add_dir "skel/keys" # # add_dir "www" # # add_dir "www/projects" # # add_file "COPYING" # content [3e9d284b3c48a939e82407d5c3fa6c9e0bf5efcb] # # add_file "Makefile" # content [9a2ffce8aaa8a1c927d5f18eedc448b0cbaf6c6b] # # add_file "README" # content [c66843122901d904dc11f75fd54f9e64b3bc7950] # # add_file "TODO" # content [35bfbacfdeb69eba0f9c08fde8201265ab532555] # # add_file "hostconfig.dist" # content [63f15af4248fea5e953624f10e5f04391e847d6d] # # add_file "reset" # content [5086bf043c1fc5fbd7e55828a1a993ed31626f66] # # add_file "schema.sql" # content [1ffd2ee143269f78ae061d03e8b36e4da1a3cc26] # # add_file "skel/keys/address@hidden" # content [9bc20d5e58966d35f2981f121786116c85d4e9ed] # # add_file "skel/monotonerc" # content [f6760ff68af902d1e6ecbde57097b7085fa1de91] # # add_file "usher.cc" # content [4ed30076af3edf03fd8c284c14f81780fca81e7a] # # add_file "www/JSON.php" # content [e74c9792a17996ea4e2ab7c313e24238e8022650] # # add_file "www/admin-description.js" # content [d8fab719409fc04467c21a9670d51ebd8b1ebbc1] # # add_file "www/admin-description.php" # content [f4d40babaff2b48eb133853bd2eb938bf01ce084] # # add_file "www/admin-description_backend.php" # content [35aaa14b1ed2fd7b061c029208d095f3ed520a58] # # add_file "www/admin-files.js" # content [1d84e9f90e267d99e4bddffe5f2e3185e31a0952] # # add_file "www/admin-files.php" # content [cf293356f2e2925a5212262f24965413fd10d1d2] # # add_file "www/admin-files_backend.php" # content [ef16f5dbaffe7f400e6b75126f7707abb5d5f9af] # # add_file "www/admin-permissions.js" # content [1968aed7faf177b1941a3bb92dd05c21f4886c26] # # add_file "www/admin-permissions.php" # content [1b1e6d4dc769ca39aa9dd01a5a8f7b3c4438c2bc] # # add_file "www/admin-permissions_backend.php" # content [9dfe7767585351057995a7131f324748ec3136d1] # # add_file "www/admin-resources.js" # content [dc9e1b0a799f0c4a8930df2588b4a551f032d248] # # add_file "www/admin-resources.php" # content [09655eae399ed384eccdd0a3ef9b27c3590a5863] # # add_file "www/admin-resources_backend.php" # content [40f88c5129333920a6e8ac69824d8c483c1313c3] # # add_file "www/admin-source_control.js" # content [a082da5d4a5e50cb274bb9328c9f9b1c2118043b] # # add_file "www/admin-source_control.php" # content [c0b97c31128d5033bfb60b4808e9778720a7c9c2] # # add_file "www/admin-source_control_backend.php" # content [ada854e455f7bca6723429842e412ea3559554c0] # # add_file "www/admin-web.js" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/admin-web.php" # content [011cc85d5bd32650ca04e0b1b40f2dfd40e1b19e] # # add_file "www/admin-web_backend.php" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/admin.php" # content [582d78fb3a3fb92315e0c750fe4d91e3ff27f521] # # add_file "www/common-ctrl.php" # content [5cdf8502620216366fe9a4fbf60f228ef2e4414b] # # add_file "www/common.js" # content [775a226f88575520d8f512e38400ca78a4f85168] # # add_file "www/common.php" # content [bbc7521adf962914983c9ac5dd801693075f9461] # # add_file "www/index.php" # content [c13794fe13c9178b6da873f0a6114d4507703a1e] # # add_file "www/login.php" # content [7dffbbb7499011ce9ae28bff03d91ad185d2027f] # # add_file "www/logo.png" # content [e6c7794bf02d16122ecfe0906eaa74cac0b3e17e] # # add_file "www/main.css" # content [8ca234500f1b958097f8df1b2ea5f66580055dfb] # # add_file "www/proj-ctrl.php" # content [3edf710d4011a6e74f3adc0484e91bdc214d1581] # # add_file "www/project.php" # content [44be90a1f008f16900f68f135c62b7f37cba651f] # # add_file "www/sidebar.js" # content [591674ba9a1f37b50fe779982c88caaca5aa0145] # # add_file "www/sidebar.php" # content [14624d84c31ca34d0b93fbf79f62350ee7c20e9a] # # add_file "www/top.php" # content [f0b9cc047ef6aa963f3fcd04e5fda912b4d0ba3a] # # add_file "www/user.php" # content [8a88e97360ed641441ac944fbbb831fca160ec65] # # set "reset" # attr "mtn:execute" # value "true" # # set "www/logo.png" # attr "mtn:manual_merge" # value "true" # ============================================================ --- COPYING 3e9d284b3c48a939e82407d5c3fa6c9e0bf5efcb +++ COPYING 3e9d284b3c48a939e82407d5c3fa6c9e0bf5efcb @@ -0,0 +1,724 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + +GNU Free Documentation License +****************************** + + Version 1.1, March 2000 + Copyright (C) 2000 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + 0. PREAMBLE + + The purpose of this License is to make a manual, textbook, or other + written document "free" in the sense of freedom: to assure everyone + the effective freedom to copy and redistribute it, with or without + modifying it, either commercially or noncommercially. Secondarily, + this License preserves for the author and publisher a way to get + credit for their work, while not being considered responsible for + modifications made by others. + + This License is a kind of "copyleft", which means that derivative + works of the document must themselves be free in the same sense. + It complements the GNU General Public License, which is a copyleft + license designed for free software. + + We have designed this License in order to use it for manuals for + free software, because free software needs free documentation: a + free program should come with manuals providing the same freedoms + that the software does. But this License is not limited to + software manuals; it can be used for any textual work, regardless + of subject matter or whether it is published as a printed book. + We recommend this License principally for works whose purpose is + instruction or reference. + + 1. APPLICABILITY AND DEFINITIONS + + This License applies to any manual or other work that contains a + notice placed by the copyright holder saying it can be distributed + under the terms of this License. The "Document", below, refers to + any such manual or work. Any member of the public is a licensee, + and is addressed as "you". + + A "Modified Version" of the Document means any work containing the + Document or a portion of it, either copied verbatim, or with + modifications and/or translated into another language. + + A "Secondary Section" is a named appendix or a front-matter + section of the Document that deals exclusively with the + relationship of the publishers or authors of the Document to the + Document's overall subject (or to related matters) and contains + nothing that could fall directly within that overall subject. + (For example, if the Document is in part a textbook of + mathematics, a Secondary Section may not explain any mathematics.) + The relationship could be a matter of historical connection with + the subject or with related matters, or of legal, commercial, + philosophical, ethical or political position regarding them. + + The "Invariant Sections" are certain Secondary Sections whose + titles are designated, as being those of Invariant Sections, in + the notice that says that the Document is released under this + License. + + The "Cover Texts" are certain short passages of text that are + listed, as Front-Cover Texts or Back-Cover Texts, in the notice + that says that the Document is released under this License. + + A "Transparent" copy of the Document means a machine-readable copy, + represented in a format whose specification is available to the + general public, whose contents can be viewed and edited directly + and straightforwardly with generic text editors or (for images + composed of pixels) generic paint programs or (for drawings) some + widely available drawing editor, and that is suitable for input to + text formatters or for automatic translation to a variety of + formats suitable for input to text formatters. A copy made in an + otherwise Transparent file format whose markup has been designed + to thwart or discourage subsequent modification by readers is not + Transparent. A copy that is not "Transparent" is called "Opaque". + + Examples of suitable formats for Transparent copies include plain + ASCII without markup, Texinfo input format, LaTeX input format, + SGML or XML using a publicly available DTD, and + standard-conforming simple HTML designed for human modification. + Opaque formats include PostScript, PDF, proprietary formats that + can be read and edited only by proprietary word processors, SGML + or XML for which the DTD and/or processing tools are not generally + available, and the machine-generated HTML produced by some word + processors for output purposes only. + + The "Title Page" means, for a printed book, the title page itself, + plus such following pages as are needed to hold, legibly, the + material this License requires to appear in the title page. For + works in formats which do not have any title page as such, "Title + Page" means the text near the most prominent appearance of the + work's title, preceding the beginning of the body of the text. + + 2. VERBATIM COPYING + + You may copy and distribute the Document in any medium, either + commercially or noncommercially, provided that this License, the + copyright notices, and the license notice saying this License + applies to the Document are reproduced in all copies, and that you + add no other conditions whatsoever to those of this License. You + may not use technical measures to obstruct or control the reading + or further copying of the copies you make or distribute. However, + you may accept compensation in exchange for copies. If you + distribute a large enough number of copies you must also follow + the conditions in section 3. + + You may also lend copies, under the same conditions stated above, + and you may publicly display copies. + + 3. COPYING IN QUANTITY + + If you publish printed copies of the Document numbering more than + 100, and the Document's license notice requires Cover Texts, you + must enclose the copies in covers that carry, clearly and legibly, + all these Cover Texts: Front-Cover Texts on the front cover, and + Back-Cover Texts on the back cover. Both covers must also clearly + and legibly identify you as the publisher of these copies. The + front cover must present the full title with all words of the + title equally prominent and visible. You may add other material + on the covers in addition. Copying with changes limited to the + covers, as long as they preserve the title of the Document and + satisfy these conditions, can be treated as verbatim copying in + other respects. + + If the required texts for either cover are too voluminous to fit + legibly, you should put the first ones listed (as many as fit + reasonably) on the actual cover, and continue the rest onto + adjacent pages. + + If you publish or distribute Opaque copies of the Document + numbering more than 100, you must either include a + machine-readable Transparent copy along with each Opaque copy, or + state in or with each Opaque copy a publicly-accessible + computer-network location containing a complete Transparent copy + of the Document, free of added material, which the general + network-using public has access to download anonymously at no + charge using public-standard network protocols. If you use the + latter option, you must take reasonably prudent steps, when you + begin distribution of Opaque copies in quantity, to ensure that + this Transparent copy will remain thus accessible at the stated + location until at least one year after the last time you + distribute an Opaque copy (directly or through your agents or + retailers) of that edition to the public. + + It is requested, but not required, that you contact the authors of + the Document well before redistributing any large number of + copies, to give them a chance to provide you with an updated + version of the Document. + + 4. MODIFICATIONS + + You may copy and distribute a Modified Version of the Document + under the conditions of sections 2 and 3 above, provided that you + release the Modified Version under precisely this License, with + the Modified Version filling the role of the Document, thus + licensing distribution and modification of the Modified Version to + whoever possesses a copy of it. In addition, you must do these + things in the Modified Version: + + A. Use in the Title Page (and on the covers, if any) a title + distinct from that of the Document, and from those of + previous versions (which should, if there were any, be listed + in the History section of the Document). You may use the + same title as a previous version if the original publisher of + that version gives permission. + + B. List on the Title Page, as authors, one or more persons or + entities responsible for authorship of the modifications in + the Modified Version, together with at least five of the + principal authors of the Document (all of its principal + authors, if it has less than five). + + C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. + + D. Preserve all the copyright notices of the Document. + + E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. + + F. Include, immediately after the copyright notices, a license + notice giving the public permission to use the Modified + Version under the terms of this License, in the form shown in + the Addendum below. + + G. Preserve in that license notice the full lists of Invariant + Sections and required Cover Texts given in the Document's + license notice. + + H. Include an unaltered copy of this License. + + I. Preserve the section entitled "History", and its title, and + add to it an item stating at least the title, year, new + authors, and publisher of the Modified Version as given on + the Title Page. If there is no section entitled "History" in + the Document, create one stating the title, year, authors, + and publisher of the Document as given on its Title Page, + then add an item describing the Modified Version as stated in + the previous sentence. + + J. Preserve the network location, if any, given in the Document + for public access to a Transparent copy of the Document, and + likewise the network locations given in the Document for + previous versions it was based on. These may be placed in + the "History" section. You may omit a network location for a + work that was published at least four years before the + Document itself, or if the original publisher of the version + it refers to gives permission. + + K. In any section entitled "Acknowledgments" or "Dedications", + preserve the section's title, and preserve in the section all + the substance and tone of each of the contributor + acknowledgments and/or dedications given therein. + + L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section + titles. + + M. Delete any section entitled "Endorsements". Such a section + may not be included in the Modified Version. + + N. Do not retitle any existing section as "Endorsements" or to + conflict in title with any Invariant Section. + + If the Modified Version includes new front-matter sections or + appendices that qualify as Secondary Sections and contain no + material copied from the Document, you may at your option + designate some or all of these sections as invariant. To do this, + add their titles to the list of Invariant Sections in the Modified + Version's license notice. These titles must be distinct from any + other section titles. + + You may add a section entitled "Endorsements", provided it contains + nothing but endorsements of your Modified Version by various + parties--for example, statements of peer review or that the text + has been approved by an organization as the authoritative + definition of a standard. + + You may add a passage of up to five words as a Front-Cover Text, + and a passage of up to 25 words as a Back-Cover Text, to the end + of the list of Cover Texts in the Modified Version. Only one + passage of Front-Cover Text and one of Back-Cover Text may be + added by (or through arrangements made by) any one entity. If the + Document already includes a cover text for the same cover, + previously added by you or by arrangement made by the same entity + you are acting on behalf of, you may not add another; but you may + replace the old one, on explicit permission from the previous + publisher that added the old one. + + The author(s) and publisher(s) of the Document do not by this + License give permission to use their names for publicity for or to + assert or imply endorsement of any Modified Version. + + 5. COMBINING DOCUMENTS + + You may combine the Document with other documents released under + this License, under the terms defined in section 4 above for + modified versions, provided that you include in the combination + all of the Invariant Sections of all of the original documents, + unmodified, and list them all as Invariant Sections of your + combined work in its license notice. + + The combined work need only contain one copy of this License, and + multiple identical Invariant Sections may be replaced with a single + copy. If there are multiple Invariant Sections with the same name + but different contents, make the title of each such section unique + by adding at the end of it, in parentheses, the name of the + original author or publisher of that section if known, or else a + unique number. Make the same adjustment to the section titles in + the list of Invariant Sections in the license notice of the + combined work. + + In the combination, you must combine any sections entitled + "History" in the various original documents, forming one section + entitled "History"; likewise combine any sections entitled + "Acknowledgments", and any sections entitled "Dedications". You + must delete all sections entitled "Endorsements." + + 6. COLLECTIONS OF DOCUMENTS + + You may make a collection consisting of the Document and other + documents released under this License, and replace the individual + copies of this License in the various documents with a single copy + that is included in the collection, provided that you follow the + rules of this License for verbatim copying of each of the + documents in all other respects. + + You may extract a single document from such a collection, and + distribute it individually under this License, provided you insert + a copy of this License into the extracted document, and follow + this License in all other respects regarding verbatim copying of + that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + + A compilation of the Document or its derivatives with other + separate and independent documents or works, in or on a volume of + a storage or distribution medium, does not as a whole count as a + Modified Version of the Document, provided no compilation + copyright is claimed for the compilation. Such a compilation is + called an "aggregate", and this License does not apply to the + other self-contained works thus compiled with the Document, on + account of their being thus compiled, if they are not themselves + derivative works of the Document. + + If the Cover Text requirement of section 3 is applicable to these + copies of the Document, then if the Document is less than one + quarter of the entire aggregate, the Document's Cover Texts may be + placed on covers that surround only the Document within the + aggregate. Otherwise they must appear on covers around the whole + aggregate. + + 8. TRANSLATION + + Translation is considered a kind of modification, so you may + distribute translations of the Document under the terms of section + 4. Replacing Invariant Sections with translations requires special + permission from their copyright holders, but you may include + translations of some or all Invariant Sections in addition to the + original versions of these Invariant Sections. You may include a + translation of this License provided that you also include the + original English version of this License. In case of a + disagreement between the translation and the original English + version of this License, the original English version will prevail. + + 9. TERMINATION + + You may not copy, modify, sublicense, or distribute the Document + except as expressly provided for under this License. Any other + attempt to copy, modify, sublicense or distribute the Document is + void, and will automatically terminate your rights under this + License. However, parties who have received copies, or rights, + from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + + The Free Software Foundation may publish new, revised versions of + the GNU Free Documentation 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. See + `http://www.gnu.org/copyleft/'. + + Each version of the License is given a distinguishing version + number. If the Document specifies that a particular numbered + version of this License "or any later version" applies to it, you + have the option of following the terms and conditions either of + that specified version or of any later version that has been + published (not as a draft) by the Free Software Foundation. If + the Document does not specify a version number of this License, + you may choose any version ever published (not as a draft) by the + Free Software Foundation. + +ADDENDUM: How to use this License for your documents +---------------------------------------------------- + + To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and license +notices just after the title page: + + Copyright (C) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.1 + or any later version published by the Free Software Foundation; + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + A copy of the license is included in the section entitled ``GNU + Free Documentation License''. + + If you have no Invariant Sections, write "with no Invariant Sections" +instead of saying which ones are invariant. If you have no Front-Cover +Texts, write "no Front-Cover Texts" instead of "Front-Cover Texts being +LIST"; likewise for Back-Cover Texts. + + If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, to +permit their use in free software. + ============================================================ --- Makefile 9a2ffce8aaa8a1c927d5f18eedc448b0cbaf6c6b +++ Makefile 9a2ffce8aaa8a1c927d5f18eedc448b0cbaf6c6b @@ -0,0 +1,8 @@ +all: + +symlinks: + ln -s viewmtn/MochiKit www/MochiKit + ln -s ../../graphs www/viewmtn/graph + +usher: usher.cc + g++ usher.cc -o usher -g -Wall `pkg-config libpqxx --cflags --libs` ============================================================ --- README c66843122901d904dc11f75fd54f9e64b3bc7950 +++ README c66843122901d904dc11f75fd54f9e64b3bc7950 @@ -0,0 +1,37 @@ +make symlinks + Set up a couple of symlinks +make usher + Modified usher that uses the project database for a server list, + and uses the same config file as the web interface. +createdb +psql -f schema.sql + setup database, if not done as the webserver user, that user will need + permission to read/insert/update. + If you want the reset script to work, you need permission to delete. +cp hostconfig.dist hostconfig + and then update to match your local setup + +skel/ Some useful files + +www/ Make this accessible to your webserver +www/viewmtn/ viewmtn install, modified slightly + +projects/*/ Project-specific (internal) +www/projects/*/ Project-specific (web-available) +graphs/ viewmtn graph cache + These three need to be +rw for the webserver. + Also, the webserver needs to be able to symlink from + www/projects/ to www/{project.php,admin.php,viewmtn/}. + This probably means that those have to be owned by the + webserver, since apache likes to only follow symlinks + that are owned by the same user as the target file. + +hostconfig config file +common.php $conffile must point to the hostconfig file + +Make sure all dns addresses *. map to an address that the +usher is listening on. + +usher hostconfig + There's a modified usher that uses the same config file and + takes the server list from a database ============================================================ --- TODO 35bfbacfdeb69eba0f9c08fde8201265ab532555 +++ TODO 35bfbacfdeb69eba0f9c08fde8201265ab532555 @@ -0,0 +1,24 @@ +script to keep viewmtn database in sync with server database + projects/*/database server database + projects/*/database.viewmtn viewmtn database + projects/*/database.transfer extra database + + pull -d database.transfer + ln database.viewmtn database.temp + mv database.transfer database.viewmtn + mv database.temp database.transfer + + from note_netsync_revision_received, with at(1) and a flag so multiple + times don't get queued at once? + + ...or just be very sure that attempted concurrent access from viewmtn + and the server won't crash the server. + +script to remove the oldest files from graphs/ whenever it gets too big + +www/admin-web* + maybe have webspace? + +www/admin-resources* + have links to various things (ml, irc logs, newsgroups, etc) + have these links go in the sidebar? ============================================================ --- hostconfig.dist 63f15af4248fea5e953624f10e5f04391e847d6d +++ hostconfig.dist 63f15af4248fea5e953624f10e5f04391e847d6d @@ -0,0 +1,10 @@ +userpass "username" "password" +hostname "hostname" +serverdir "/home/timothy/experiment" +dbstring "dbname=testdb" +admin "127.0.0.5:12345" + +hostkey "address@hidden" +hostkeypass "address@hidden" +monotone "/home/timothy/bin/monotone" +comment "parsing is somewhat broken, keep this line at the end" ============================================================ --- reset 5086bf043c1fc5fbd7e55828a1a993ed31626f66 +++ reset 5086bf043c1fc5fbd7e55828a1a993ed31626f66 @@ -0,0 +1,7 @@ +#!/bin/sh + +psql -c 'delete from users;' testdb +psql -c 'delete from projects;' testdb +psql -c 'delete from permissions;' testdb +rm -rf projects/* +rm -rf www/projects/* ============================================================ --- schema.sql 1ffd2ee143269f78ae061d03e8b36e4da1a3cc26 +++ schema.sql 1ffd2ee143269f78ae061d03e8b36e4da1a3cc26 @@ -0,0 +1,18 @@ + +CREATE TABLE users ( username varchar(80), password varchar(80) ); + +CREATE TABLE projects ( name varchar(80) ); + +CREATE TABLE permissions ( username varchar(80), + project varchar(80), + give smallint, + upload smallint, + homepage smallint, + access smallint, + server smallint, + description smallint ); + +CREATE TABLE resources ( project varchar(80), + name varchar(80), + url varchar(80) + ); ============================================================ --- skel/keys/address@hidden 9bc20d5e58966d35f2981f121786116c85d4e9ed +++ skel/keys/address@hidden 9bc20d5e58966d35f2981f121786116c85d4e9ed @@ -0,0 +1,19 @@ +[keypair address@hidden +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMYG6FSLV6gI1k6WXYOd+SWZllh2EZi0a9 +aB+tqyUneitVpG8YN9JJfLPOunBG9eNH1ydcPYxQSTIxwBKQkI/zyNoiy/5J5UYKndJ3Hudb +aQnHGCwkQJ4VpJ33YkhJdV2pP89rHsdxmemdlPHB8G09+Fld4eoI0W5j+utdGxhKYQIDAQAB# +MIICyTBDBgkqhkiG9w0BBQ0wNjAeBgkqhkiG9w0BBQwwEQQIeCiY2t9IweYCAggAAgEYMBQG +CCqGSIb3DQMHBAjyozzRlfQzzwSCAoCaoSHdkWWQNVO0+g6bBKHaAr9rTAQwWkoyMhhmHzlI +DppORiCGtr6Eu3ipgnGQG12itWzvTNXflbga9mTgtFGR2LlJ6SFuts+t6itzd/BAhfpDwLV4 +lvFsdKpEUZMDTCG/yE5gH0TMrqraIk9rUmKme1bTbMuBdK17RrzlPh3Ud9CfwJ1TlElItGWK +4HgUMo4DuxxWw9L4yCK/uB8FZvtYlfKTKYASj4kXuHjJkhju0vS1OtZF9B42aimjEq+qEQqy +fl9Ol2ECdwQem1wbVT+nHUDoyfLDypNR8JriT3IhulH58/twAzasmCb0fgwo2OMbZAERgL6D +iTVpm0gmscBxw7eqaWFt9wrjs+B83Fho8H/UC2dsTCkHhCbIKknE/AG0uX5GXZ5QPfFd53c4 +fo1KcQqMJxEDk7QWiT/WspE/3OWr0SAVVBIm8qhR/JpCYvQ+Pn2dkek0iK8Ke6hixfDY89BR +5UnsCixSsxZqAhbTh47T2BaZFGHRDyQXqj4l5qMgJ2SfAv+qWIoeJ+9dPfQYITeIKsRzCJ2R +F89qDWgk2ed9TwSLF6roo5dSYl6uUXIuemnmpkpuKMdNTIdLFusYfxIQ4KXPqeQmr6vUDnEK +uEnMlGZQa/bNP+XuYl+4g9YZRKTC68oMxN7SsNnTMjwVsYV1hIBDfGIBaS+pemSJ5Kckknu+ +bq9t2yDMts8TasivwrlN8zFXzEVlM0bqsQUdn//lKqiocUciq18L1PSv/VwiyXQcA4tGg/YP +zj94iK9k6eKcFMXGZVAxR+GGn2JKcL4lO60fp59n8EXE2MBzD5MkT1tAEx3jn2VbmAh6m50C +hSq3zxT9RAa7uHQ3VbBv +[end] ============================================================ --- skel/monotonerc f6760ff68af902d1e6ecbde57097b7085fa1de91 +++ skel/monotonerc f6760ff68af902d1e6ecbde57097b7085fa1de91 @@ -0,0 +1,47 @@ +function read_config() + local permfile = io.open(get_confdir() .. "/../../hostconfig", "r") + if (permfile == nil) then return false end + local dat = permfile:read("*a") + io.close(permfile) + local res = parse_basic_io(dat) + if res == nil then + io.stderr:write("config file cannot be parsed\n") + return false + end + for i, item in pairs(res) + do + if item.name == "hostkey" then + for j, val in pairs(item.values) do + hostkey = val + end + elseif item.name == "hostkeypass" then + for j, val in pairs(item.values) do + hostkeypass = val + end + end + end + return false +end + +read_config(); + +function get_passphrase(keyid) + if keyid == hostkey then + return hostkeypass + end + return nil +end + +function persist_phrase_ok() + return true +end + +old_get_wperm = get_netsync_write_permitted +function get_netsync_write_permitted(ident) + if ident == hostkey then return true end + return old_get_wperm(ident) +end + +function get_netsync_read_permitted(foo, bar) + return true +end ============================================================ --- usher.cc 4ed30076af3edf03fd8c284c14f81780fca81e7a +++ usher.cc 4ed30076af3edf03fd8c284c14f81780fca81e7a @@ -0,0 +1,1507 @@ +// Timothy Brownawell +// GPL v2 +// +// **************** SPECIALIZED FOR A SPECIFIC HOSTING SETUP ****************** +// **************************************************************************** +// +// USERPASS username password +// Required before any other command, so random people can't do bad things. +// If incorrect, the connection will be closed immediately. +// +// STATUS [servername] +// Get the status of a server, as named by the "server" lines in the +// config file. If a server is specified, the result will be one of: +// REMOTE - this is a remote server without active connections +// ACTIVE n - this server currently has n active connections +// WAITING - this (local) server is running, but has no connections +// SLEEPING - this (local) server is not running, but is available +// STOPPING n - this (local) server has been asked to stop, but still has +// n active connections. It will not accept further connections. +// STOPPED - this (local) server has been stopped, and will not accept +// connections. The server process is not running. +// SHUTTINGDOWN - the usher has been shut down, no servers are accepting +// connections. +// SHUTDOWN - the usher has been shut down, all connections have been closed, +// and all local server processes have been stopped. +// If no server is specified, the repsonse will be SHUTTINGDOWN, SHUTDOWN, +// WAITING, or ACTIVE (with n being the total number of open connections, +// across all servers). +// +// STOP servername +// Prevent the given local server from receiving further connections, and stop +// it once all connections are closed. The result will be the new status of +// that server: ACTIVE local servers become STOPPING, and WAITING and SLEEPING +// servers become STOPPED. Servers in other states are not affected. +// +// START servername +// Allow a stopped or stopping server to receive connections again. The result +// will be the new status of that server. (A server in the "STOPPING" state +// becomes ACTIVE, and a STOPPED server becomes SLEEPING. A server in some +// other state is not affected.) +// +// LIST [state] +// Returns a space-separated list of all servers. If a state is given, only +// list the servers that are in that state. +// +// SHUTDOWN +// Do not accept new connections for any servers, local or remote. Returns "ok". +// +// STARTUP +// Begin accepting connections again after a SHUTDOWN. Returns "ok". +// +// CONNECTIONS +// Returns the number of connections currently open. +// +// RELOAD +// Reload the config file (same as sending SIGHUP). The reply will be "ok", +// and will not be given until the config file has been reloaded. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using std::vector; +using std::max; +using std::string; +using std::list; +using std::set; +using std::map; +using boost::lexical_cast; +using boost::shared_ptr; +using std::cerr; +using std::pair; +using std::make_pair; + +namespace { + +string hostname; +string serverdir; +string dbstring; + +// defaults, overridden by command line +int listenport = 4691; +string listenaddr = "0.0.0.0"; +string monotone = "monotone"; + +// keep local servers around for this many seconds after the last +// client disconnects from them (only accurate to ~10 seconds) +int const server_idle_timeout = 60; + +// ranges that dynamic (local) servers can be put on +int const minport = 15000; +int const maxport = 65000; +int const minaddr[] = {127, 0, 1, 1}; +int const maxaddr[] = {127, 254, 254, 254}; +int currport = 0; +int curraddr[] = {0, 0, 0, 0}; + +char const netsync_version = 6; + +string const greeting = " Hello! This is the monotone usher at localhost. What would you like?"; + +string const notfound = "!Sorry, I don't know where to find that."; + +string const disabled = "!Sorry, this usher is not currently accepting connections."; + +string const srvdisabled = "!Sorry, that server is currently disabled."; + +struct errstr +{ + std::string name; + int err; + errstr(std::string const & s): name(s), err(0) {} + errstr(std::string const & s, int e): name(s), err(e) {} +}; + +int tosserr(int ret, std::string const & name) +{ + if (ret == -1) + throw errstr(name, errno); + if (ret < 0) + throw errstr(name, ret); + return ret; +} + +// packet format is: +// byte version +// byte cmd {100 if we send, 101 if we receive} +// uleb128 {size of everything after this} +// uleb128 {size of string} +// string +// { +// uleb128 {size of string} +// string +// } // only present if we're receiving + +// uleb128 is +// byte 0x80 | +// byte 0x80 | +// ... +// byte 0xff & +// +// the high bit says that this byte is not the last + +void make_packet(std::string const & msg, char * & pkt, int & size) +{ + size = msg.size(); + char const * txt = msg.c_str(); + char header[6]; + header[0] = netsync_version; + header[1] = 100; + int headersize; + if (size >= 128) { + header[2] = 0x80 | (0x7f & (char)(size+2)); + header[3] = (char)((size+2)>>7); + header[4] = 0x80 | (0x7f & (char)(size)); + header[5] = (char)(size>>7); + headersize = 6; + } else if (size >= 127) { + header[2] = 0x80 | (0x7f & (char)(size+1)); + header[3] = (char)((size+1)>>7); + header[4] = (char)(size); + headersize = 5; + } else { + header[2] = (char)(size+1); + header[3] = (char)(size); + headersize = 4; + } + pkt = new char[headersize + size]; + memcpy(pkt, header, headersize); + memcpy(pkt + headersize, txt, size); + size += headersize; +} + +struct buffer +{ + static int const buf_size = 16*1024; + static int const buf_reset_size = 8*1024; + char * ptr; + int readpos; + int writepos; + buffer(): readpos(0), writepos(0) + { + ptr = new char[buf_size]; + } + ~buffer(){delete[] ptr;} + buffer(buffer const & b) + { + ptr = new char[buf_size]; + memcpy(ptr, b.ptr, buf_size); + readpos = b.readpos; + writepos = b.writepos; + } + bool canread(){return writepos > readpos;} + bool canwrite(){return writepos < buf_size;} + void getread(char *& p, int & n) + { + p = ptr + readpos; + n = writepos - readpos; + } + void getwrite(char *& p, int & n) + { + p = ptr + writepos; + n = buf_size - writepos; + } + void fixread(int n) + { + if (n < 0) throw errstr("negative read\n", 0); + readpos += n; + if (readpos == writepos) { + readpos = writepos = 0; + } else if (readpos > buf_reset_size) { + memcpy(ptr, ptr+readpos, writepos-readpos); + writepos -= readpos; + readpos = 0; + } + } + void fixwrite(int n) + { + if (n < 0) + throw errstr("negative write\n", 0); + writepos += n; + } +}; + +struct sock +{ + int *s; + static set all_socks; + operator int() + { + if (!s) + return -1; + else + return s[0]; + } + sock(int ss) + { + s = new int[2]; + s[0] = ss; + s[1] = 1; + all_socks.insert(s); + } + sock(sock const & ss) + { + s = ss.s; + if (s) + s[1]++; + } + void deref() + { + if (s && !(--s[1])) { + try { + close(); + } catch(errstr & e) { + // if you want it to throw errors, call close manually + } + delete[] s; + all_socks.erase(all_socks.find(s)); + } + s = 0; + } + ~sock() + { + deref(); + } + sock const & operator=(int ss) + { + deref(); + s = new int[2]; + all_socks.insert(s); + s[0]=ss; + return *this; + } + sock const & operator=(sock const & ss) + { + deref(); + s = ss.s; + if (s) + ++s[1]; + return *this; + } + void close() + { + if (!s || s[0] == -1) + return; + shutdown(s[0], SHUT_RDWR); + while (::close(s[0]) < 0) { + if (errno == EIO) + throw errstr("close failed", errno); + if (errno != EINTR) + break; + } + s[0]=-1; + } + static void close_all_socks() + { + for (set::iterator i = all_socks.begin(); i != all_socks.end(); ++i) { + while (::close((*i)[0]) < 0) + if (errno != EINTR) + break; + } + } + bool read_to(buffer & buf) + { + if (!s) + return false; + char *p; + int n; + buf.getwrite(p, n); + n = read(s[0], p, n); + if (n < 1) { + close(); + return false; + } else + buf.fixwrite(n); + return true; + } + bool write_from(buffer & buf) + { + if (!s) + return false; + char *p; + int n; + buf.getread(p, n); + n = write(s[0], p, n); + if (n < 1) { + close(); + return false; + } else + buf.fixread(n); + return true; + } +}; +set sock::all_socks; + +bool check_address_empty(string const & addr, int port) +{ + sock s = tosserr(socket(AF_INET, SOCK_STREAM, 0), "socket()"); + int yes = 1; + tosserr(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof(yes)), "setsockopt"); + sockaddr_in a; + memset (&a, 0, sizeof (a)); + if (!inet_aton(addr.c_str(), (in_addr *) &a.sin_addr.s_addr)) + throw errstr("bad ip address format", 0); + a.sin_port = htons(port); + a.sin_family = AF_INET; + int r = bind(s, (sockaddr *) &a, sizeof(a)); + s.close(); + return r == 0; +} + +void find_addr(string & addr, int & port) +{ + if (currport == 0) { + currport = minport-1; + for(int i = 0; i < 4; ++i) + curraddr[i] = minaddr[i]; + } + do { + // get the next address in our list + if (++currport > maxport) { + currport = minport; + for (int i = 0; i < 4; ++i) { + if (++curraddr[i] <= maxaddr[i]) + break; + curraddr[i] = minaddr[i]; + } + } + port = currport; + addr = lexical_cast(curraddr[0]) + "." + + lexical_cast(curraddr[1]) + "." + + lexical_cast(curraddr[2]) + "." + + lexical_cast(curraddr[3]); + } while (!check_address_empty(addr, port)); +} + +sock start(string addr, int port) +{ + sock s = tosserr(socket(AF_INET, SOCK_STREAM, 0), "socket()"); + int yes = 1; + tosserr(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof(yes)), "setsockopt"); + sockaddr_in a; + memset (&a, 0, sizeof (a)); + if (!inet_aton(addr.c_str(), (in_addr *) &a.sin_addr.s_addr)) + throw errstr("bad ip address format", 0); + a.sin_port = htons(port); + a.sin_family = AF_INET; + tosserr(bind(s, (sockaddr *) &a, sizeof(a)), "bind"); + cerr<<"bound to "<(addrport.substr(c+1)); + } catch (...) { + throw errstr("Could not parse address"); + } + return start(addr, port); +} + +sock make_outgoing(int port, string const & address) +{ + sock s = tosserr(socket(AF_INET, SOCK_STREAM, 0), "socket()"); + + struct sockaddr_in a; + memset(&a, 0, sizeof(a)); + a.sin_family = AF_INET; + a.sin_port = htons(port); + + if (!inet_aton(address.c_str(), (in_addr *) &a.sin_addr.s_addr)) + throw errstr("bad ip address format", 0); + + tosserr(connect(s, (sockaddr *) &a, sizeof (a)), "connect()"); + return s; +} + +int fork_server(vector const & args) +{ + int err[2]; + if (pipe(err) < 0) + return false; + int pid = fork(); + if (pid == -1) { + close(err[0]); + close(err[1]); + cerr<<"Failed to fork server.\n"; + return false; + } else if (pid == 0) { + close(err[0]); + close(0); + close(1); + close(2); + sock::close_all_socks(); + if (dup2(err[1], 2) < 0) { + exit(1); + } + + char ** a = new char*[args.size()+1]; + for (unsigned int i = 0; i < args.size(); ++i) { + a[i] = new char[args[i].size()+1]; + memcpy(a[i], args[i].c_str(), args[i].size()+1); + } + a[args.size()] = 0; + + execvp(a[0], a); + perror("execvp failed\n"); + exit(1); + } else { + close(err[1]); + char head[256]; + int got = 0; + int r = 0; + bool line = false; + // the first line output on the server's stderr will be either + // "monotone: beginning service on : " or + // "monotone: network error: bind(2) error: Address already in use" + do { + r = read(err[0], head + got, 256 - got); + if (r) + cerr<<"Read '"< 0) { + for (int i = 0; i < r && !line; ++i) + if (head[got+i] == '\n') + line = true; + got += r; + } + } while(r > 0 && !line && got < 256); + head[got] = 0; + if (string(head).find("beginning service") != string::npos) + return pid; + kill(pid, SIGKILL); + do {r = waitpid(pid, 0, 0);} while (r==-1 && errno == EINTR); + return -1; + } +} + +bool connections_allowed = true; +int total_connections = 0; + +struct serverstate +{ + enum ss {remote, active, waiting, sleeping, stopping, + stopped, shuttingdown, shutdown, unknown}; + ss state; + int num; + serverstate(): state(unknown), num(0) {} + serverstate const & operator=(string const & s) + { + if (s == "REMOTE") + state = remote; + else if (s == "ACTIVE") + state = active; + else if (s == "WAITING") + state = waiting; + else if (s == "SLEEPING") + state = sleeping; + else if (s == "STOPPING") + state = stopping; + else if (s == "STOPPED") + state = stopped; + else if (s == "SHUTTINGDOWN") + state = shuttingdown; + else if (s == "SHUTDOWN") + state = shutdown; + return *this; + } + bool operator==(string const & s) + { + serverstate foo; + foo = s; + return foo.state == state; + } +}; +std::ostream & operator<<(std::ostream & os, serverstate const & ss) +{ + switch (ss.state) { + case serverstate::remote: + os<<"REMOTE"; + break; + case serverstate::active: + os<<"ACTIVE "< >::iterator> by_host, by_pat; + map >::iterator by_name; + static map > servers_by_host; + static map > servers_by_pattern; + static map > servers_by_name; + static set > live_servers; + bool local; + int pid; + string arguments; + string addr; + int port; + int connection_count; + int last_conn_time; + server() : enabled(true), local(false), pid(-1), port(0), + connection_count(0), last_conn_time(0) + { + } + ~server() + { + yeskill(); + } + serverstate get_state() + { + serverstate ss; + ss.num = connection_count; + if (!connections_allowed) { + if (!total_connections) + ss.state = serverstate::shutdown; + else + ss.state = serverstate::shuttingdown; + } else if (connection_count) { + if (enabled) + ss.state = serverstate::active; + else + ss.state = serverstate::stopping; + } else if (!local) + ss.state = serverstate::remote; + else if (!enabled) + ss.state = serverstate::stopped; + else if (pid == -1) + ss.state = serverstate::sleeping; + else + ss.state = serverstate::waiting; + return ss; + } + void delist() + { + vector foo; + set_hosts(foo); + set_patterns(foo); + servers_by_name.erase(by_name); + by_name = 0; + } + void rename(string const & n) + { + shared_ptr me = by_name->second; + servers_by_name.erase(by_name); + by_name = servers_by_name.insert(make_pair(n, me)).first; + } + void set_hosts(vector const & h) + { + shared_ptr me = by_name->second; + map >::iterator c; + for (list >::iterator>::iterator + i = by_host.begin(); i != by_host.end(); ++i) + servers_by_host.erase(*i); + by_host.clear(); + for (vector::const_iterator i = h.begin(); i != h.end(); ++i) { + c = servers_by_host.find(*i); + if (c != servers_by_host.end()) { + list >::iterator>::iterator j; + for (j = c->second->by_host.begin(); j != c->second->by_host.end(); ++j) + if ((*j)->first == *i) { + servers_by_host.erase(*j); + c->second->by_host.erase(j); + } + } + c = servers_by_host.insert(make_pair(*i, me)).first; + by_host.push_back(c); + } + } + void set_patterns(vector const & p) + { + shared_ptr me = by_name->second; + map >::iterator c; + for (list >::iterator>::iterator + i = by_pat.begin(); i != by_pat.end(); ++i) + servers_by_pattern.erase(*i); + by_pat.clear(); + for (vector::const_iterator i = p.begin(); i != p.end(); ++i) { + c = servers_by_pattern.find(*i); + if (c != servers_by_pattern.end()) { + list >::iterator>::iterator j; + for (j = c->second->by_pat.begin(); j != c->second->by_pat.end(); ++j) + if ((*j)->first == *i) { + servers_by_pattern.erase(*j); + c->second->by_pat.erase(j); + } + } + c = servers_by_pattern.insert(make_pair(*i, me)).first; + by_pat.push_back(c); + } + } + sock connect() + { + if (!connections_allowed) + throw errstr("all servers disabled"); + if (!enabled) + throw errstr("server disabled"); + if (local && pid == -1) { + // server needs to be started + // we'll try 3 times, since there's a delay between our checking that + // a port's available and the server taking it + for (int i = 0; i < 3 && pid == -1; ++i) { + if (i > 0 || port == 0) + find_addr(addr, port); + vector args; + args.push_back(monotone); + args.push_back("serve"); + args.push_back("--bind=" + addr + ":" + lexical_cast(port)); + unsigned int n = 0, m = 0; + n = arguments.find_first_not_of(" \t"); + while (n != string::npos && m != string::npos) { + m = arguments.find_first_of(" ", n); + args.push_back(arguments.substr(n, m-n)); + n = arguments.find_first_not_of(" ", m); + } + pid = fork_server(args); + } + } + sock s = make_outgoing(port, addr); + if (local && !connection_count) { + live_servers.insert(by_name->second); + } + ++connection_count; + ++total_connections; + return s; + } + void disconnect() + { + --total_connections; + if (--connection_count || !local) + return; + last_conn_time = time(0); + maybekill(); + } + void maybekill() + { + if (!local) + return; + if (pid == -1) + return; + int difftime = time(0) - last_conn_time; + if (!connection_count + && (difftime > server_idle_timeout || !connections_allowed)) + yeskill(); + else if (waitpid(pid, 0, WNOHANG) > 0) { + pid = -1; + port = 0; + } + } + void yeskill() + { + if (local && pid != -1) { + kill(pid, SIGTERM); + int r; + do {r = waitpid(pid, 0, 0);} while (r==-1 && errno == EINTR); + pid = -1; + port = 0; + live_servers.erase(live_servers.find(by_name->second)); + } + } + string name() + { + if (local && port == 0) + return "dynamic local server"; + else + return addr + ":" + lexical_cast(port); + } +}; + +map > server::servers_by_host; +map > server::servers_by_pattern; +map > server::servers_by_name; +set > server::live_servers; + +string getline(std::istream & in) +{ + string out; + char buf[256]; + do { + in.getline(buf, 256); + int got = in.gcount()-1; + if (got > 0) + out.append(buf, got); + } while (in.fail() && !in.eof()); + return out; +} + +void make_server(string const & name) +{ + vector hosts, patterns; + string projdir = serverdir + "/projects/" + name; + string desc = "-d " + projdir + "/database "; + desc += "--confdir " + projdir + " *"; + hosts.push_back(name + "." + hostname); + + // If it exists, find it. Otherwise, make a new one. + shared_ptr srv; + map >::iterator + i = server::servers_by_name.find(name); + if (i != server::servers_by_name.end()) { + srv = i->second; + i->second->delist(); + } else + srv = shared_ptr(new server); + + srv->by_name = server::servers_by_name.insert(make_pair(name, srv)).first; + srv->set_hosts(hosts); + srv->local = true; + srv->arguments = desc; +} + +shared_ptr get_server(string const & srv, string const & pat) +{ + map >::iterator i; + for (i = server::servers_by_host.begin(); + i != server::servers_by_host.end(); ++i) { + if (srv.find(i->first) == 0) { + return i->second; + } + } + for (i = server::servers_by_pattern.begin(); + i != server::servers_by_pattern.end(); ++i) { + if (pat.find(i->first) == 0) { + return i->second; + } + } + std::cerr<<"no server found for '"<(); +} + +shared_ptr get_server(string const & name) +{ + map >::iterator i; + for (i = server::servers_by_name.begin(); + i != server::servers_by_name.end(); ++i) { + if (name == i->first) { + return i->second; + } + } + return shared_ptr(); +} + +void kill_old_servers() +{ + set >::iterator i; + for (i = server::live_servers.begin(); i != server::live_servers.end(); ++i) { + (*i)->maybekill(); + } +} + +int extract_uleb128(char *p, int maxsize, int & out) +{ + out = 0; + int b = 0; + unsigned char got; + do { + if (b == maxsize) + return -1; + got = p[b]; + out += ((int)(p[b] & 0x7f))<<(b*7); + ++b; + } while ((unsigned int)(b*7) < sizeof(int)*8-1 && (got & 0x80)); + return b; +} + +int extract_vstr(char *p, int maxsize, string & out) +{ + int size; + out.clear(); + int chars = extract_uleb128(p, maxsize, size); + if (chars == -1 || chars + size > maxsize) { + return -1; + } + out.append(p+chars, size); + return chars+size; +} + +bool extract_reply(buffer & buf, string & host, string & pat) +{ + char *p; + int n, s; + buf.getread(p, n); + if (n < 4) return false; + p += 2; // first 2 bytes are header + n -= 2; + // extract size, and make sure we have the entire packet + int pos = extract_uleb128(p, n, s); + if (pos == -1 || n < s+pos) { + return false; + } + // extract host vstr + int num = extract_vstr(p+pos, n-pos, host); + if (num == -1) { + return false; + } + pos += num; + // extract pattern vstr + num = extract_vstr(p+pos, n-pos, pat); + if (num == -1) { + cerr<<"old-style reply.\n"; + pat = host; + host.clear(); + return true; + } + pos += num; + buf.fixread(pos+2); + return true; +} + +struct channel +{ + static int counter; + int num; + sock cli; + sock srv; + bool have_routed; + bool no_server; + buffer cbuf; + buffer sbuf; + shared_ptr who; + channel(sock & c): num(++counter), + cli(c), srv(-1), + have_routed(false), no_server(false) + { + char * dat; + int size; + make_packet(greeting, dat, size); + char *p; + int n; + sbuf.getwrite(p, n); + if (n < size) size = n; + memcpy(p, dat, size); + sbuf.fixwrite(size); + delete[] dat; + + cli.write_from(sbuf); + } + ~channel() + { + if (who && !no_server) + who->disconnect(); + } + bool is_finished() + { + return (cli == -1) && (srv == -1); + } + void add_to_select(int & maxfd, fd_set & rd, fd_set & wr, fd_set & er) + { + int c = cli; + int s = srv; + + if (c > 0) { + FD_SET(c, &er); + if (cbuf.canwrite()) + FD_SET(c, &rd); + if (sbuf.canread()) + FD_SET(c, &wr); + maxfd = max(maxfd, c); + } + if (s > 0) { + FD_SET(s, &er); + if (sbuf.canwrite()) + FD_SET(s, &rd); + if (cbuf.canread()) + FD_SET(s, &wr); + maxfd = max(maxfd, s); + } + } + bool process_selected(fd_set & rd, fd_set & wr, fd_set & er) + { + int c = cli; + int s = srv; +/* NB: read oob data before normal reads */ + if (c > 0 && FD_ISSET(c, &er)) { + char d; + errno = 0; + if (recv(c, &d, 1, MSG_OOB) < 1) + cli.close(), c = -1; + else + send(s, &d, 1, MSG_OOB); + } + if (s > 0 && FD_ISSET(s, &er)) { + char d; + errno = 0; + if (recv(s, &d, 1, MSG_OOB) < 1) + srv.close(), s = -1; + else + send(c, &d, 1, MSG_OOB); + } + + char *p=0; + int n; + + if (c > 0 && FD_ISSET(c, &rd)) { + if (!cli.read_to(cbuf)) c = -1; + if (!have_routed) { + string reply_srv, reply_pat; + if (extract_reply(cbuf, reply_srv, reply_pat)) { + who = get_server(reply_srv, reply_pat); + if (who && who->enabled) { + try { + srv = who->connect(); + have_routed = true; + s = srv; + } catch (errstr & e) { + cerr<<"cannot contact server "<name()<<"\n"; + no_server = true; + } + } else { + char * dat; + int size; + sbuf.getwrite(p, n); + if (who) + make_packet(srvdisabled, dat, size); + else + make_packet(notfound, dat, size); + if (n < size) size = n; + memcpy(p, dat, size); + sbuf.fixwrite(size); + delete[] dat; + no_server = true; + } + } + } + } + if (s > 0 && FD_ISSET(s, &rd)) { + if (!srv.read_to(sbuf)) s = -1; + } + + if (c > 0 && FD_ISSET(c, &wr)) { + if (!cli.write_from(sbuf)) c = -1; + } + if (s > 0 && FD_ISSET(s, &wr)) { + if (!srv.write_from(cbuf)) s = -1; + } + + // close sockets we have nothing more to send to + if (c < 0 && !cbuf.canread()) { + srv.close(), s = -1; + } + if ((no_server || have_routed && s < 0) && !sbuf.canread()) { + cli.close(), c = -1; + } + return true; + } +}; +int channel::counter = 0; + +bool reload_pending; +map admins; +string conffile; + +struct token +{ + enum type {key, str, hex, notok}; + type t; + string s; +}; + +token readtok(std::istream & is) +{ + char c; + do + is.get(c); + while(is && isspace(c)); + token tok; + if(!is) { + tok.t = token::notok; + return tok; + } + switch(c) { + case '[': + is.get(c); + tok.t = token::hex; + do { + tok.s += c; + is.get(c); + } while(is && c != ']'); + break; + case '"': + is.get(c); + tok.t = token::str; + do { + tok.s += c; + is.get(c); + if(c == '\\') { + is.get(c); + tok.s += c; + is.get(c); + } + } while(is && c != '"'); + break; + default: + tok.t = token::key; + do { + tok.s += c; + is.get(c); + } while(is && !isspace(c)); + } + return tok; +} + + +void sched_reload(int sig) +{ + reload_pending = true; +} + +void reload_conffile(string const & file); + +struct administrator +{ + sock port; + struct cstate + { + bool auth; + bool rdone; + string buf; + cstate(): auth(false), rdone(false) {} + }; + list > conns; + administrator(): port(-1) + {} + bool process(cstate & cs) + { + unsigned int n = cs.buf.find("\n"); + if (n == cs.buf.npos) + return true; + string l = cs.buf.substr(0, n); + cs.buf.erase(0, n+1); + std::istringstream iss(l); + string cmd; + iss>>cmd; + if (cmd == "USERPASS") { + string user, pass; + iss>>user>>pass; + map::iterator i = admins.find(user); + if (i == admins.end() || i->second != pass) { + cerr<<"Failed admin login.\n"; + return false; + } else { + if (cs.auth == true) + return false; + cs.auth = true; + return process(cs); + } + } else if (cmd == "STATUS") { + string srv; + iss>>srv; + std::ostringstream oss; + if (srv.empty()) { + serverstate ss; + ss.num = total_connections; + if (connections_allowed) { + if (total_connections) + ss.state = serverstate::active; + else + ss.state = serverstate::waiting; + } else { + if (total_connections) + ss.state = serverstate::shuttingdown; + else + ss.state = serverstate::shutdown; + } + oss< >::iterator i; + i = server::servers_by_name.find(srv); + if (i != server::servers_by_name.end()) + oss<second->get_state()<<"\n"; + else + oss<<"No such server.\n"; + } + cs.buf = oss.str(); + } else if (cmd == "ADD") { + string srv; + iss>>srv; + make_server(srv); + cs.buf = "ok"; + } else if (cmd == "START") { + string srv; + iss>>srv; + std::ostringstream oss; + map >::iterator i; + i = server::servers_by_name.find(srv); + if (i != server::servers_by_name.end()) { + i->second->enabled = true; + oss<second->get_state()<<"\n"; + } else + oss<<"No such server.\n"; + cs.buf = oss.str(); + } else if (cmd == "STOP") { + string srv; + iss>>srv; + std::ostringstream oss; + map >::iterator i; + i = server::servers_by_name.find(srv); + if (i != server::servers_by_name.end()) { + i->second->enabled = false; + i->second->maybekill(); + oss<second->get_state()<<"\n"; + } else + oss<<"No such server.\n"; + cs.buf = oss.str(); + } else if (cmd == "LIST") { + string state; + iss>>state; + map >::iterator i; + for (i = server::servers_by_name.begin(); + i != server::servers_by_name.end(); ++i) { + if (state.empty() || i->second->get_state() == state) + cs.buf += (cs.buf.empty()?"":" ") + i->first; + } + cs.buf += "\n"; + } else if (cmd == "SHUTDOWN") { + connections_allowed = false; + kill_old_servers(); + cs.buf = "ok\n"; + } else if (cmd == "CONNECTIONS") { + cs.buf = lexical_cast(total_connections) + "\n"; + } else if (cmd == "RELOAD") { + reload_conffile(conffile); + cs.buf = "ok\n"; + } else if (cmd == "STARTUP") { + connections_allowed = true; + cs.buf = "ok\n"; + } else { + return true; + } + cs.rdone = true; + return true; + } + void initialize(string const & ap) + { + try { + port = start(ap); + } catch (errstr & s) { + cerr<<"Could not initialize admin port: "< >::iterator i = conns.begin(); + i != conns.end(); ++i) { + int c = i->second; + if (!i->first.rdone) + FD_SET(c, &rd); + else + FD_SET(c, &wr); + maxfd = max(maxfd, int(c)); + } + } + void process_selected(fd_set & rd, fd_set & wr, fd_set & er) + { + if (int(port) == -1) + return; + if (FD_ISSET(port, &rd)) { + try { + struct sockaddr_in addr; + unsigned int l = sizeof(addr); + memset(&addr, 0, l); + sock nc = tosserr(accept(port, (struct sockaddr *) + &addr, &l), "accept()"); + conns.push_back(make_pair(cstate(), nc)); + } catch(errstr & s) { + cerr<<"During new admin connection: "< >::iterator> del; + for (list >::iterator i = conns.begin(); + i != conns.end(); ++i) { + int c = i->second; + if (c <= 0) { +// cerr<<"Bad socket.\n"; + del.push_back(i); + } else if (FD_ISSET(c, &rd)) { + char buf[120]; + int n; + n = read(c, buf, 120); + if (n < 1) { + // cerr<<"Read failed.\n"; + del.push_back(i); + } + i->first.buf.append(buf, n); + if (!process(i->first)) { +// cerr<<"Closing connection...\n"; +// i->second.close(); + del.push_back(i); + } + } + else if (FD_ISSET(c, &wr)) { + int n = write(c, i->first.buf.c_str(), i->first.buf.size()); + if (n < 1) { +// cerr<<"Write failed.\n"; + del.push_back(i); + } else { + i->first.buf.erase(0, n); + if (i->first.buf.empty() && i->first.rdone) { +// cerr<<"Done.\n"; + del.push_back(i); + } + } + } + } + for (list >::iterator>::iterator i = del.begin(); + i != del.end(); ++i) { + conns.erase(*i); + } + } +}; + +struct pidfile +{ + string filename; + void initialize(string const & file) + { + filename = file; + std::ofstream ofs(filename.c_str()); + ofs< names; + pqxx::connection conn(dbstring); + pqxx::work w(conn, "trx"); + pqxx::result r = w.exec("SELECT name FROM projects"); + for(unsigned int i = 0; i < r.size(); ++i) { + string n = r[i][0].c_str(); + if(n.empty()) + continue; + make_server(n); + names.insert(n); + cerr<<"Server: "< >::iterator + i = server::servers_by_name.begin(); + i != server::servers_by_name.end(); ++i) { + if (names.find(i->first) == names.end()) + i->second->delist(); + } + cerr<<"Reload complete.\n"; +} + +};//namespace anonymous + +int main (int argc, char **argv) +{ + pidfile pf; +// administrator admin; + { + int i; + for (i = 1; i < argc; ++i) { + if (string(argv[i]) == "-p") + pf.initialize(argv[++i]); + else + conffile = argv[i]; + } + if (conffile.empty() || i != argc) { + cerr<<"Usage:\n"; + cerr<<"\tusher [-p pidfile] \n"; + exit (1); + } + } + reload_conffile(conffile); + + + struct sigaction sa, sa_old; + sa.sa_handler = &sched_reload; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + while(sigaction(SIGHUP, &sa, &sa_old) == -1 && errno == EINTR); + sa.sa_handler = SIG_IGN; + while(sigaction(SIGPIPE, &sa, &sa_old) == -1 && errno == EINTR); + sa.sa_handler = sig_end; + while(sigaction(SIGTERM, &sa, &sa_old) == -1 && errno == EINTR); + while(sigaction(SIGINT, &sa, &sa_old) == -1 && errno == EINTR); + + try { + h = start(listenaddr, listenport); + } catch (errstr & s) { + std::cerr<<"Error while opening socket: "< channels; + + done = false; + while (!done) { + fd_set rd, wr, er; + FD_ZERO (&rd); + FD_ZERO (&wr); + FD_ZERO (&er); + FD_SET (h, &rd); + int nfds = h; + channel *newchan = 0; + + for (std::list::iterator i = channels.begin(); + i != channels.end(); ++i) + i->add_to_select(nfds, rd, wr, er); + + admin.add_to_select(nfds, rd, wr, er); + + timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + int r = select(nfds+1, &rd, &wr, &er, &timeout); + + if (r < 0 && errno != EINTR) { + perror ("select()"); + exit (1); + } + if (done) + return 0; + if (FD_ISSET(h, &rd)) { + try { + struct sockaddr_in client_address; + unsigned int l = sizeof(client_address); + memset(&client_address, 0, l); + sock cli = tosserr(accept(h, (struct sockaddr *) + &client_address, &l), "accept()"); + if (connections_allowed) + newchan = new channel(cli); + else { + char * dat; + int size; + make_packet(disabled, dat, size); + write(cli, dat, size); + delete[] dat; + } + } catch(errstr & s) { + cerr<<"During new connection: "<::iterator> finished; + for (std::list::iterator i = channels.begin(); + i != channels.end(); ++i) { + try { + i->process_selected(rd, wr, er); + if (i->is_finished()) + finished.push_back(i); + } catch (errstr & e) { + finished.push_back(i); + cerr<<"Error proccessing connection "<num<<": "<::iterator>::iterator i = finished.begin(); + i != finished.end(); ++i) + channels.erase(*i); + if (newchan) { + channels.push_back(*newchan); + delete newchan; + newchan = 0; + } + kill_old_servers(); + if (reload_pending) + reload_conffile(conffile); + + admin.process_selected(rd, wr, er); + } + return 0; +} ============================================================ --- www/JSON.php e74c9792a17996ea4e2ab7c313e24238e8022650 +++ www/JSON.php e74c9792a17996ea4e2ab7c313e24238e8022650 @@ -0,0 +1,791 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $ut8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) + if(Services_JSON::isError($property)) + return $property; + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) + if(Services_JSON::isError($element)) + return $element; + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) + if(Services_JSON::isError($property)) + return $property; + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) + return $encoded_value; + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($chrs{$c - 1} != '\\') || + ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('pear_error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> ============================================================ --- www/admin-description.js d8fab719409fc04467c21a9670d51ebd8b1ebbc1 +++ www/admin-description.js d8fab719409fc04467c21a9670d51ebd8b1ebbc1 @@ -0,0 +1,25 @@ +ctrl = '../../admin-description_backend.php'; + +chdesc = function () { + status("Changing project description..."); + var args = {'project':project}; + args.description = getElement("description").value; + args.longdescription = getElement("longdescription").value; + args.action = "chdesc"; + call_server(ctrl, args, "chdesc", function(data){clearstatus();}); +} + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getdesc"; + call_server(ctrl, x, "getdesc", function (data) { + if (data.description) { + getElement("description").value = data.description; + } + if (data.longdescription) { + getElement("longdescription").value = data.longdescription; + } + clearstatus(); + }); +}); ============================================================ --- www/admin-description.php f4d40babaff2b48eb133853bd2eb938bf01ce084 +++ www/admin-description.php f4d40babaff2b48eb133853bd2eb938bf01ce084 @@ -0,0 +1,7 @@ +
+Short description:
+
+Long description:
+
+ +
============================================================ --- www/admin-description_backend.php 35aaa14b1ed2fd7b061c029208d095f3ed520a58 +++ www/admin-description_backend.php 35aaa14b1ed2fd7b061c029208d095f3ed520a58 @@ -0,0 +1,20 @@ +encode(array( + "description" => file_get_contents($projwww . "/description"), + "longdescription" => file_get_contents($projwww . "/longdescription") + )); +} else if ($action === "chdesc") { + if(allowed('description')) { + file_put_contents($projwww . "/description", $args->description, LOCK_EX); + file_put_contents($projwww . "/longdescription", $args->longdescription, LOCK_EX); + print $json->encode(array("description" => file_get_contents($projwww . "/description"), + "longdescription" => file_get_contents($projwww . "/longdescription"))); + } +} +pg_close($db); +?> ============================================================ --- www/admin-files.js 1d84e9f90e267d99e4bddffe5f2e3185e31a0952 +++ www/admin-files.js 1d84e9f90e267d99e4bddffe5f2e3185e31a0952 @@ -0,0 +1,71 @@ +ctrl = '../../admin-files_backend.php'; + +do_rmfile = function (name) { + status("removing " + name + "..."); + var x = {'project':project,"action":"rmfile","file":name}; + call_server(ctrl, x, "rmfile", function (data) { + hideElement("trow-" + name); + clearstatus(); + }); +} + +do_chfilecomment = function (name) { + var c = getElement("filecomment-" + name).value; + status("changing description of " + name + "..."); + var x = {'project':project,"action":"chfiledesc","filedesc":c,"file":name}; + call_server(ctrl, x, "chfiledesc", function (data) { + clearstatus(); + }); +} + +add_file_to_present = function (name, desc) { + appendChildNodes("currentfiles-body", TR({}, + TD(null, A({'href': "files/" + name}, name)), + TD(null, INPUT({'type':'text','size':40,'value':desc,'id':'filecomment-' + name})), + TD(null, BUTTON({'type':'button','onclick':'do_rmfile("' + name + '");'}, "Remove")), + TD(null, BUTTON({'type':'button','onclick':'do_chfilecomment("' + name + '");'}, "Change description")) + )); +} + +empty_row = function () { + return TR({}, + TD(null, INPUT({"type": "file", "size": 40, "name": "upfiles[]"})), + TD(null, INPUT({"type": "text", "size": 40, "name": "upfile_comments[]"})) + ); +} +morefiles = function() { + appendChildNodes("filetablebody", empty_row()); +} +row_display = function (row) { + return TR(null, map(partial(TD, null), row)); +} +setup_upload = function() { + replaceChildNodes("filelist", TABLE({"id":"filetable"}, + THEAD(null, row_display(["File to upload", "File description"])), + TFOOT(null, row_display(["",""])), + TBODY({"id":"filetablebody"})) + ); + morefiles(); +} + + +do_upload = function() { + status("Uploading page..."); + getElement("upload_form").target = "upload_frame"; + getElement("upload_form").submit(); +} +uploaddone = function() { + getElement("upload_form").reset(); + clearstatus(); +} + +addLoadEvent(function() { +// status("Loading..."); +// var x = {'project':project}; +// x.action = "getmaint"; +// call_server(ctrl, x, "getmaint", function (data) { +// set_maint_display(data.maintainers); + setup_upload(); +// clearstatus(); +// }); +}); ============================================================ --- www/admin-files.php cf293356f2e2925a5212262f24965413fd10d1d2 +++ www/admin-files.php cf293356f2e2925a5212262f24965413fd10d1d2 @@ -0,0 +1,34 @@ +
+ +
+This project has already released the following files:
+ + + + + + + + + +
File nameFile description
\n"; +?> +
+
+ + +

+ + +
+ +
============================================================ --- www/admin-files_backend.php ef16f5dbaffe7f400e6b75126f7707abb5d5f9af +++ www/admin-files_backend.php ef16f5dbaffe7f400e6b75126f7707abb5d5f9af @@ -0,0 +1,51 @@ +File uploadFile uploaded"; +} elseif ($action == "rmfile") { + header('Content-type: text/x-json'); + unlink($projwww . "/files/" . basename($args->file)); + unlink($projwww . "/files-about/" . basename($args->file)); + print $json->encode(array("ok" => "ok")); +} elseif ($action == "chfiledesc") { + header('Content-type: text/x-json'); + unlink($projwww . "/files-about/" . basename($args->file)); + file_put_contents($projwww . "/files-about/" . basename($args->file), $args->filedesc); + print $json->encode(array("ok" => "ok")); +} +pg_close($db); +?> ============================================================ --- www/admin-permissions.js 1968aed7faf177b1941a3bb92dd05c21f4886c26 +++ www/admin-permissions.js 1968aed7faf177b1941a3bb92dd05c21f4886c26 @@ -0,0 +1,93 @@ +ctrl = '../../admin-permissions_backend.php'; + +row_display = function (row) { + return TR(null, map(partial(TD, null), row)); +} +make_cbox_properties = function (row, who) { + var out = {}; + out['type'] = "checkbox"; + out['name'] = "maint_" + who + "[]"; + if(row[who]) { + out['checked'] = true; + } + return out; +} +maint_row = function (row) { + return TR({"class":"maintainerrow"}, + TD(null, INPUT({"type": "text", "name": "maint_username[]", "value": row.username})), + TD(null, INPUT(make_cbox_properties(row, "give"))), + TD(null, INPUT(make_cbox_properties(row, "server"))), + TD(null, INPUT(make_cbox_properties(row, "upload"))), + TD(null, INPUT(make_cbox_properties(row, "description")))/*, + TD(null, INPUT(make_cbox_properties(row, "homepage"))), + TD(null, INPUT(make_cbox_properties(row, "resources")))*/ + ); +} +empty_row = function () { + return TR({"class":"maintainerrow"}, + TD(null, INPUT({"type": "text", "name": "maint_username[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_give[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_server[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_upload[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_description[]"}))/*, + TD(null, INPUT({"type": "checkbox", "name": "maint_homepage[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_resources[]"}))*/ + ); +} +maint_addrow = function() { + appendChildNodes("maintainertablebody", empty_row()); +} +set_maint_display = function(data) { + var labels = []; + replaceChildNodes("maintainerlist", TABLE({"id":"maintainertable","class":"hilighttable"}, + THEAD(null, row_display(["Username", "Edit maintainers", "Control server", "Upload files", "Edit description"/*, "Edit homepage", "Change resources"*/])), + TFOOT(null, row_display(["","","","",""/*,"",""*/])), + TBODY({"id":"maintainertablebody"}, map(maint_row, data))) + ); + maint_addrow(); +} +read_maint_line = function(row) { + var out = {}; + for(var i = 0; i < row.childNodes.length; ++i) { + var cur = row.childNodes[i].firstChild; + var nm = getNodeAttribute(cur,"name"); + nm = nm.replace(/.*_(.*)\[\]/, "$1"); + if (nm == "username") { + out[nm] = cur.value; + } else { + out[nm] = cur.checked; + } + } + return out; +} + + +chmaint = function () { + status("Changing maintainers..."); + var args = {'project':project}; + args.action = "chmaint"; + var lst = getElement("maintainertablebody").childNodes; + var maints = []; + for(var i = 0; i < lst.length; ++i) { + var line = read_maint_line(lst[i]); + if (line.username != "") { + maints.push(line); + } + } + args.newmaint = maints; + call_server(ctrl, args, "chmaint", function (data) { + set_maint_display(data.maintainers); + clearstatus(); + }); +} + + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getmaint"; + call_server(ctrl, x, "getmaint", function (data) { + set_maint_display(data.maintainers); + clearstatus(); + }); +}); ============================================================ --- www/admin-permissions.php 1b1e6d4dc769ca39aa9dd01a5a8f7b3c4438c2bc +++ www/admin-permissions.php 1b1e6d4dc769ca39aa9dd01a5a8f7b3c4438c2bc @@ -0,0 +1,6 @@ +
+Project maintainers:
+

+ + +
============================================================ --- www/admin-permissions_backend.php 9dfe7767585351057995a7131f324748ec3136d1 +++ www/admin-permissions_backend.php 9dfe7767585351057995a7131f324748ec3136d1 @@ -0,0 +1,82 @@ +encode(array("maintainers" => maintlist())); +} else if ($action == "chmaint") { + if(allowed('give')) { + $res = array(); + $ok = true; + $begun = false; + foreach ($args->newmaint as $i) { + if ($i->username === $username && !$i->give) { + $ok = false; + break; + } + } + do { + if(!$ok) { + $res["error"] = "You're not allowed to revoke your own permissions to edit maintainers."; + break; + } + pg_exec($db, "BEGIN"); + $begun = true; + pg_exec($db, "LOCK TABLE permissions"); + $query = sprintf("DELETE FROM permissions WHERE project = '%s'", $safeproj); + $result = pg_exec($db, $query); + if (!$result) { + $res['error'] = 'Internal server error.'; + $ok = false; + break; + } + $fields = "username, project, give, upload, homepage, access, server, description"; + $query = sprintf("INSERT INTO permissions (%s) VALUES (%%s)", $fields); + foreach ($args->newmaint as $i) { + $values = sprintf("'%s', '%s', %s, %s, %s, %s, %s, %s", + pg_escape_string($i->username), $safeproj, + $i->give?1:0, $i->upload?1:0, $i->homepage?1:0, + $i->access?1:0, $i->server?1:0, $i->description?1:0); + $result = pg_exec($db, sprintf($query, $values)); + if (!$result) { + $res['error'] = 'Internal server error.'; + $ok = false; + break; + } + } + if($ok) + $res["maintainers"] = maintlist(); + } while(false); + if (!$ok && $begun) + pg_exec($db, "ROLLBACK"); + print $json->encode($res); + pg_exec($db, "END"); + } +} +pg_close($db); +?> ============================================================ --- www/admin-resources.js dc9e1b0a799f0c4a8930df2588b4a551f032d248 +++ www/admin-resources.js dc9e1b0a799f0c4a8930df2588b4a551f032d248 @@ -0,0 +1,67 @@ +ctrl = '../../admin-resources_backend.php'; + +row_display = function (row) { + return TR(null, map(partial(TD, null), row)); +} +make_row = function (row) { + return TR({"class":"resourcerow"}, + TD(null, INPUT({"type": "text", "size": 30, "name": "rscname[]", "value": row.name})), + TD(null, INPUT({"type": "text", "size": 40, "name": "rscurl[]", "value": row.url})) + ); +} +addrow = function() { + appendChildNodes("rtblbody", TR({"class":"resourcerow"}, + TD(null, INPUT({"type": "text", "size": 30, "name": "rscname[]"})), + TD(null, INPUT({"type": "text", "size": 40, "name": "rscurl[]"})) + )); +} +set_display = function(data) { + var labels = []; + replaceChildNodes("resourcediv", TABLE({"id":"rtbl","class":"hilighttable"}, + THEAD(null, row_display(["Name", "URL"])), + TFOOT(null, row_display(["",""])), + TBODY({"id":"rtblbody"}, map(make_row, data))) + ); + addrow(); +} +read_line = function(row) { + var out = {}; + for(var i = 0; i < row.childNodes.length; ++i) { + var cur = row.childNodes[i].firstChild; + var nm = getNodeAttribute(cur,"name"); + nm = nm.replace(/rsc(.*)\[\]/, "$1"); + out[nm] = cur.value; + } + return out; +} + + +update = function () { + status("Updating..."); + var args = {'project':project}; + args.action = "chresources"; + var lst = getElement("rtblbody").childNodes; + var maints = []; + for(var i = 0; i < lst.length; ++i) { + var line = read_line(lst[i]); + if (line.username != "") { + maints.push(line); + } + } + args.newmaint = maints; + call_server(ctrl, args, "chresources", function (data) { + set_display(data.resources); + clearstatus(); + }); +} + + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getresources"; + call_server(ctrl, x, "getresources", function (data) { + set_display(data.resources); + clearstatus(); + }); +}); ============================================================ --- www/admin-resources.php 09655eae399ed384eccdd0a3ef9b27c3590a5863 +++ www/admin-resources.php 09655eae399ed384eccdd0a3ef9b27c3590a5863 @@ -0,0 +1,6 @@ +
+Project resources:
+

+ + +
============================================================ --- www/admin-resources_backend.php 40f88c5129333920a6e8ac69824d8c483c1313c3 +++ www/admin-resources_backend.php 40f88c5129333920a6e8ac69824d8c483c1313c3 @@ -0,0 +1,62 @@ +encode(array("resources" => resourcelist())); +} else if ($action == "chresources") { + $res = array(); + $ok = true; + do { + pg_exec($db, "BEGIN"); + pg_exec($db, "LOCK TABLE resources"); + $query = sprintf("DELETE FROM resources WHERE project = '%s'", $safeproj); + $result = pg_exec($db, $query); + if (!$result) { + $res['error'] = 'Internal server error. (1)'; + $ok = false; + break; + } + $fields = "project, name, url"; + $query = sprintf("INSERT INTO resources (%s) VALUES (%%s)", $fields); + foreach ($args->newmaint as $i) { + $values = sprintf("'%s', '%s', '%s'", + $safeproj, + pg_escape_string($i->name), + pg_escape_string($i->url)); + $result = pg_exec($db, sprintf($query, $values)); + if (!$result) { + $res['error'] = 'Internal server error. (2)'; + $res['verboseError'] = sprintf($query, $values); + $ok = false; + break; + } + } + if($ok) + $res["resources"] = resourcelist(); + } while(false); + if (!$ok) + pg_exec($db, "ROLLBACK"); + print $json->encode($res); + pg_exec($db, "END"); +} +pg_close($db); +?> ============================================================ --- www/admin-source_control.js a082da5d4a5e50cb274bb9328c9f9b1c2118043b +++ www/admin-source_control.js a082da5d4a5e50cb274bb9328c9f9b1c2118043b @@ -0,0 +1,82 @@ +ctrl = '../../admin-source_control_backend.php'; + +chwperm = function () { + status("Changing permissions..."); + var args = {'project':project}; + args.newperm = getElement("newperm").value; + args.action = "chwriters"; + call_server(ctrl, args, "chwperm", function(data){clearstatus();}); +} + +sendkey = function() { + status("Uploading key..."); + var args = {'project':project}; + args.action = "new_key"; + args.keydata = getElement("keydata").value; + call_server(ctrl, args, "sendkey", function(data){ + getElement("keydata").value = ""; + status("Key uploaded."); + }); +} + +resetdb = function() { + status("Resetting database..."); + var args = {'project':project}; + args.action = "resetdb"; + args.oath = getElement("oath").value; + call_server(ctrl, args, "resetdb", function(data){ + getElement("oath").value = ""; + status(data.ok); + }); +} + +chstate = function() { + status("Changing server state..."); + var args = {'project':project}; + if (getElement("chstate").value == "Disable server") { + args.action = "disable"; + } else { + args.action = "enable"; + } + call_server(ctrl, args, "chstate", function(data){ + set_state_display(data.status); + clearstatus(); + }); +} + +set_state_display = function(state){ + if (state == "") { + replaceChildNodes("serverstate", "(server not found)"); + } else { + replaceChildNodes("serverstate", state); + } + // set 'chstate' button to enable/disable appropriately + if (state.match("WAITING") || state == "RUNNING" || state == "SLEEPING") { + getElement("chstate").value = "Disable server"; + } else { + getElement("chstate").value = "Enable server"; + } +} + +restate = function() { + status("Refreshing server state..."); + var args = {'project':project}; + args.action = "refresh_state"; + call_server(ctrl, args, "restate", function(data){ + set_state_display(data.status); + clearstatus(); + }); +} + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getsrv"; + call_server(ctrl, x, "getsrv", function (data) { + if (data.wperm) { + getElement("newperm").value = data.wperm; + } + set_state_display(data.status); + clearstatus(); + }); +}); ============================================================ --- www/admin-source_control.php c0b97c31128d5033bfb60b4808e9778720a7c9c2 +++ www/admin-source_control.php c0b97c31128d5033bfb60b4808e9778720a7c9c2 @@ -0,0 +1,29 @@ +
+ + + + +
+People allowed to write to the database:
+(key name)
+
+ +
+Upload a new pubkey.
+This is equivalent to someone with push access doing +
mtn read file-with-key-packet
+mtn --key-to-push=<keyname> push  ''
+Pubkey packet:
+
+ +
+Current server status is "server state"
+ + +
+
+This is to reset your database, for example if you had to do a rebuild. For this to work, you must type "I solemnly swear that I have a backup." in the box below.
+Note that Bad Things may happen if your server is running while you do this. ("Current server status" above should be SLEEPING or STOPPED or SHUTDOWN)
+ + +
============================================================ --- www/admin-source_control_backend.php ada854e455f7bca6723429842e412ea3559554c0 +++ www/admin-source_control_backend.php ada854e455f7bca6723429842e412ea3559554c0 @@ -0,0 +1,82 @@ +encode(array( + "status" => usherctrl("STATUS " . $project), + "wperm" => file_get_contents($projdir . "/write-permissions"), + )); +} else if ($action === "refresh_state") { + print $json->encode(array("status" => usherctrl("STATUS " . $project))); +} else if ($action === "disable" || $action === "enable") { + if(allowed('server')) { + if ($action === "disable") + $what = "STOP"; + else + $what = "START"; + print $json->encode(array("status" => usherctrl("$what $project"))); + } +} else if ($action === "resetdb") { + if(allowed('server')) { + if ($args->oath === "I solemnly swear that I have a backup.") { + $out = array(); + exec("rm $projdir/database 2>&1", $out, $res1); + exec("$monotone -d $projdir/database db init 2>&1", $out, $res2); + exec("chmod g+rw $projdir/database 2>&1", $out, $res3); + if ($res2 > 0 || $res3 > 0) { + $err = ""; + if ($res1) $err = $err . "Remove old database failed. "; + if ($res2) $err = $err . "Init new database failed. "; + if ($res3) $err = $err . "Set database permissions failed."; + print $json->encode(array("error" => $err, "verboseError" => $out)); + } else + print $json->encode(array("ok" => "Database reset... You *did* disable the server first, right?")); + } else { + print $json->encode(array("error" => "I'm sorry Dave, I can't let you do that.")); + } + } +} else if ($action === "new_key") { + if(allowed('access')) { + $args->keydata; + preg_match('/^\[[^ \]]* ([^\]]*)\]/', $args->keydata, $matches); + $keyname = $matches[1]; + if($keyname == null) + print $json->encode(array("error" => "Cannot extract key name.")); + else { + $out = array(); + file_put_contents($projdir . "/keyfile", $args->keydata, LOCK_EX); + exec("$monotone -d '$projdir/kdb' db init 2>&1", $out, $res1); + exec("$monotone -d '$projdir/kdb' read '$projdir/keyfile'", $out, $res2); + exec("$monotone --key-to-push '$keyname' -d '$projdir/kdb' push '$project.$hostname' '' 2>&1", $out, $res3); + exec("rm '$projdir/kdb' '$projdir/keyfile'"); + if($res1 || $res2 || $res3) { + $outstr = ""; + foreach ($out as $i) + $outstr = $outstr . $i . "\n"; + print $json->encode(array("error" => "Key upload failed.", "verboseError" => $outstr)); + } else + print $json->encode(array("result" => "ok")); + } + } +} +pg_close($db); +?> ============================================================ ============================================================ --- www/admin-web.php 011cc85d5bd32650ca04e0b1b40f2dfd40e1b19e +++ www/admin-web.php 011cc85d5bd32650ca04e0b1b40f2dfd40e1b19e @@ -0,0 +1,2 @@ +
+
============================================================ ============================================================ --- www/admin.php 582d78fb3a3fb92315e0c750fe4d91e3ff27f521 +++ www/admin.php 582d78fb3a3fb92315e0c750fe4d91e3ff27f521 @@ -0,0 +1,53 @@ + + + + + + +Project admin page for <?=$project?> + + + + + + + + + + + + + +
+ +
    + "Permissions", + 'source_control' => "Source Control", + 'files' => "Files", + 'description' => "Description", + /*'web' => "Web",*/ + 'resources' => "Resources"); + foreach ($pages as $k => $i) { + if ($k == $page) + print "
  • $i
  • "; + else + print "
  • $i
  • "; + } +?> +
+ +
+ + ============================================================ --- www/common-ctrl.php 5cdf8502620216366fe9a4fbf60f228ef2e4414b +++ www/common-ctrl.php 5cdf8502620216366fe9a4fbf60f228ef2e4414b @@ -0,0 +1,73 @@ +decode($GLOBALS['HTTP_RAW_POST_DATA']); + +$action = $args->action; if($_REQUEST['action']) $action = $_REQUEST['action']; +$project = $args->project; if($_REQUEST['project']) $project = $_REQUEST['project']; + +$safeproj = pg_escape_string($project); +$projdir = "$serverdir/projects/" . basename($project); +$projwww = "$serverdir/www/projects/" . basename($project); +$monotone = "$monotone --confdir '$projdir'"; + + +function getarg($name) { + global $args; + if ($_REQUEST[$name]) + return $_REQUEST[$name]; + else + return $args->$name; +} +function safearg($name) { + return pg_escape_string(getarg($name)); +} +function dirsafe($name) { + return ($name == basename($name)) && ($name != '..'); +} + +if (!$project) { + $project = basename(dirname($_SERVER['PHP_SELF'])); + $safeproj = pg_escape_string($project); +} +function allowed($what) { + global $json, $permissions, $validuser, $username; + if(!$validuser) + print $json->encode(array("error" => "username or password is incorrect.")); + else if(!$permissions[$what]) + print $json->encode(array("error" => "You're not allowed to do that.")); + else + return true; + return false; +} + +$permissions = array( + "give" => false, // grant/revoke permissions for other users + "upload" => false, // upload files + "homepage" => false, // upload new home page + "access" => false, // upload key, write-permissions + "server" => false, // start/stop server, reset db + "description" => false + ); + +if ($validuser) { + $fields = "give, upload, homepage, access, server, description"; + $query = sprintf("SELECT %s FROM permissions WHERE ", $fields); + $query = $query . "username = '%s' AND project = '%s'"; + $result = pg_exec($db, sprintf($query, $safeuser, $safeproj)); + if ($result) { + $rows = pg_numrows($result); + $permissions['rows'] = $rows; + $permissions['safeuser'] = $safeuser; + $permissions['safeproj'] = $safeproj; + if ($rows == 1) { + $row = pg_fetch_row ($result, 0); + $permissions['give'] = ($row[0] == 1); + $permissions['upload'] = ($row[1] == 1); + $permissions['homepage'] = ($row[2] == 1); + $permissions['access'] = ($row[3] == 1); + $permissions['server'] = ($row[4] == 1); + $permissions['description'] = ($row[5] == 1); + } + } +} +?> ============================================================ --- www/common.js 775a226f88575520d8f512e38400ca78a4f85168 +++ www/common.js 775a226f88575520d8f512e38400ca78a4f85168 @@ -0,0 +1,65 @@ +var hostname = "localhost"; +var userctrl = "user-ctrl.php"; + +var todo = {}; +var verberr = ""; +var respTxt = ""; + +// POST an object in JSON format, and get a return object also in JSON format +loadJSONPost = function (url, obj) { + respTxt = ""; + var data = serializeJSON(obj); + + var req = getXMLHttpRequest(); + req.open("POST", url, true); + req.setRequestHeader('Content-Type', 'text/x-json'); + var d = sendXMLHttpRequest(req, data); + d = d.addCallback(function(data){respTxt = data.responseText; return data;}); + d = d.addCallback(evalJSONRequest); + return d; +} + +call_server = function (url, args, nam, callback) { + var d = loadJSONPost(url, args); + todo[nam] = d; + d.addCallback(function (data) { + if (!isNull(data.error)) { + if(isNull(data.verboseError)) { + status("Error: " + data.error); + } else { + verberr = data.verboseError; + status("Error: " + data.error, BR(), SPAN({"class": "jslink", "onclick": "alert(verberr);"}, "(Click here for details)")); + } + } else { + callback(data); + } + }); + d.addErrback(function(err){ + status("Error: " + err, BR(), SPAN({"class": "jslink", "onclick": "alert(respTxt);"}, "(Click here for details)")); + }); +} + +userpass = function () { + var x = {}; + x.username = getElement("username").value; + x.password = getElement("password").value; + return x; +} + +userpassproj = function () { + var x = {}; + x.username = getElement("username").value; + x.password = getElement("password").value; + x.project = getElement("project").value; + return x; +} + + +clearstatus = function() { + replaceChildNodes('status'); +} + +status = function() { + replaceChildNodes("status", SPAN({"class": "jslink", "onclick": "clearstatus();", "style": "float: right;"}, "[X]"), arguments); +} + ============================================================ --- www/common.php bbc7521adf962914983c9ac5dd801693075f9461 +++ www/common.php bbc7521adf962914983c9ac5dd801693075f9461 @@ -0,0 +1,105 @@ +decode(stripslashes($_COOKIE['AUTH'])); + do { + if ($auth->token != mktok($auth->username, $auth->password, + $auth->expiration)) { + break; + } + if ($auth->expiration < time()) { + break; + } + $username = $auth->username; + $shapass = $auth->password; + } while (false); +} else { + $username = ''; + $shapass = ''; +} + +$safeuser = pg_escape_string($username); + +$validuser = false; +$db = pg_connect($dbstring); +$query = sprintf("SELECT password FROM users WHERE username = '%s'", $safeuser); +$result = pg_exec($db, $query); +if ($result) { + $rows = pg_numrows($result); + if ($rows == 1) { + $row = pg_fetch_row ($result, 0); + if ($row[0] == $shapass) { + $validuser = true; + } + } +} +?> ============================================================ --- www/index.php c13794fe13c9178b6da873f0a6114d4507703a1e +++ www/index.php c13794fe13c9178b6da873f0a6114d4507703a1e @@ -0,0 +1,42 @@ + + + + + +Project List + +File uploaded"; + pg_close(); + exit; +} else if ($action === "upload_files") { + header('Content-type: text/html'); + print "File uploadFile uploaded"; + pg_close(); + exit; +} + +function usherctrl($what) { + global $adminaddr, $adminport, $adminuser, $adminpass; + $addr = sprintf("tcp://%s:%s", $adminaddr, $adminport); + $fp = stream_socket_client($addr, $errno, $errstr, 30); + if (!$fp) { + return "Error: $errstr ($errno)"; + } else { + $out = ""; + fprintf($fp, "USERPASS %s %s\n%s\n", $adminuser, $adminpass, $what); + while (!feof($fp)) { + $out = $out . fgets($fp, 1024); + } + fclose($fp); + return $out; + } +} + +function maintlist() { + global $db, $safeproj; + $fields = "username, give, upload, homepage, access, server, description"; + $query = sprintf("SELECT %s FROM permissions WHERE ", $fields); + $query = $query . "project = '%s'"; + $result = pg_exec($db, sprintf($query, $safeproj)); + $out = array(); + if ($result) { + $rows = pg_numrows($result); + for($i = 0; $i < $rows; ++$i) { + $row = pg_fetch_row ($result,$i); + $perm['username'] = $row[0]; + $perm['give'] = ($row[1] == 1); + $perm['upload'] = ($row[2] == 1); + $perm['homepage'] = ($row[3] == 1); + $perm['access'] = ($row[4] == 1); + $perm['server'] = ($row[5] == 1); + $perm['description'] = ($row[6] == 1); + $out[] = $perm; + } + } + return $out; +} + +header('Content-type: text/x-json'); + +if ($action == "new_project") { + if ($validuser) { + pg_exec($db, "BEGIN"); + pg_exec($db, "LOCK TABLE projects, permissions"); + $err = false; + + $query = "SELECT * FROM projects WHERE name = '%s'"; + $result = pg_exec($db, sprintf($query, $safeproj)); + do { + if(!preg_match('/^[a-zA-Z0-9-]*$/D', $project)) { + print $json->encode(array("error" => "Only letters, numbers, and dash are allowed in a project name.")); + $err = true; + break; + } + if(!$result) { + $err = true; + print $json->encode(array("error" => "Internal server error.")); + break; + } + if(pg_numrows($result)) { + print $json->encode(array("error" => "That project name is already taken.")); + $err = true; + break; + } + $projdir = $serverdir . '/projects/'. $project; + $projwww = $serverdir . '/www/projects/'. $project; + $query = "INSERT INTO projects (name, directory) VALUES ('%s', '%s')"; + $result = pg_exec($db, sprintf($query, $safeproj, '/foobar')); + if(!$result) { + $err = true; + print $json->encode(array("error" => "Internal server error.")); + break; + } + $fields = "username, project, give, upload, homepage, access, server, description"; + $query = sprintf("INSERT INTO permissions (%s) VALUES (%%s)", $fields); + $values = sprintf("'%s', '%s', 1, 1, 1, 1, 1, 1", $username, $safeproj); + $result = pg_exec($db, sprintf($query, $values)); + if(!$result) { + $err = true; + print $json->encode(array("error" => "Internal server error.")); + break; + } + $out = array(); + exec("cp -r '$serverdir/skel' '$projdir'", $out, $res1); + mkdir($projwww); + mkdir("$projwww/files"); + mkdir("$projwww/files-about"); + symlink('../../admin.php', "$projwww/admin.php"); + symlink('../../project.php', "$projwww/index.php"); + symlink('../../viewmtn', "$projwww/viewmtn"); + exec("$monotone -d $projdir/database db init 2>&1", $out, $res2); + exec("cp $projdir/database $projdir/database.viewmtn"); + exec("cp $projdir/database $projdir/database.transfer"); + exec("chmod ug+rwX '$projdir' '$projdir'/* 2>&1", $out, $res3); + if ($res1 || $res2 || $res3) { + print $json->encode(array("error" => "Internal server error", "verboseError" => $out)); + $err = true; + } + usherctrl("ADD $project"); + } while (false); + if ($err) + pg_exec($db, "ROLLBACK"); + else + print $json->encode(array("name" => $project)); + + pg_exec($db, "END"); + } else + print $json->encode(array("error" => "username or password incorrect.")); +} else + print $json->encode(array("error" => sprintf("'%s' not implemented.", $action))); +pg_close($db); +?> ============================================================ --- www/project.php 44be90a1f008f16900f68f135c62b7f37cba651f +++ www/project.php 44be90a1f008f16900f68f135c62b7f37cba651f @@ -0,0 +1,39 @@ +\n" + . "This project has released the following files:
\n" + . "
    \n"; + foreach($files as $i) { + if ($i == "." || $i == "..") + continue; + $inf = file_get_contents("files-about/$i"); + $filediv = $filediv + . "
  • $i: $inf
  • \n"; + } + $filediv = $filediv . "
\n"; + return $filediv; +} + +?> + + +Project page for <?=$project?> + + + + + + + +
+

+ +
+ ============================================================ --- www/sidebar.js 591674ba9a1f37b50fe779982c88caaca5aa0145 +++ www/sidebar.js 591674ba9a1f37b50fe779982c88caaca5aa0145 @@ -0,0 +1,31 @@ +//needs common.js + +do_newuser = function () { + var x = userpass(); + x.action = "newuser"; + status("Adding user '" + x.username + "'..."); + call_server(userctrl, x, "newuser", function (data) { + status("Added user '" + data.username + "'."); + }); +} + +do_chpass = function () { + var x = userpass(); + x.action = "chpass"; + x.newpass = getElement("newpassword").value; + status("Changing password..."); + call_server(userctrl, x, "chpass", function (data) { + getElement("password").value = getElement("newpassword").value; + getElement("newpassword").value = ""; + clearstatus(); + }); +} + +do_newproj = function() { + status("Creating project..."); + var args = {'project':getElement('newproject').value}; + args.action = "new_project"; + call_server('proj-ctrl.php', args, "newproj", function(data) { + window.location = "projects/" + data.name + "/admin.php"; + }); +} ============================================================ --- www/sidebar.php 14624d84c31ca34d0b93fbf79f62350ee7c20e9a +++ www/sidebar.php 14624d84c31ca34d0b93fbf79f62350ee7c20e9a @@ -0,0 +1,58 @@ + +
+
+ + You are .
+
+ + +
+ +
+ Username:
+
+ Password:
+
+ + +
+ +
+ + + +
+Project index
+
+ + Project info
+ Maintainer section
+ Project resources:\n
    "; + print "
  • Source browser
  • "; + if ($result) { + $rows = pg_numrows($result); + for($i = 0; $i < $rows; ++$i) { + $row = pg_fetch_row ($result,$i); + $r['name'] = $row[0]; + $r['url'] = $row[1]; + print "
  • " . + $row[0] . "
  • \n"; + } + } + print "
\n"; +} +?> +
============================================================ --- www/top.php f0b9cc047ef6aa963f3fcd04e5fda912b4d0ba3a +++ www/top.php f0b9cc047ef6aa963f3fcd04e5fda912b4d0ba3a @@ -0,0 +1,4 @@ + ============================================================ --- www/user.php 8a88e97360ed641441ac944fbbb831fca160ec65 +++ www/user.php 8a88e97360ed641441ac944fbbb831fca160ec65 @@ -0,0 +1,31 @@ + + + + + + +Project admin page for <?=$project?> + + + + + + + + + + + +
+ +
+Change password:
+
+ +
+ +
+ +