paivana

HTTP paywall reverse proxy
Log | Files | Refs | README | LICENSE

commit 021cc96a8f6d3173b789abbedc83fb58b2c14cbb
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 26 Nov 2025 14:12:58 +0100

initial import, based on twister

Diffstat:
AAUTHORS | 1+
ACOPYING | 661+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AChangeLog | 0
AMakefile.am | 8++++++++
Aconfigure.ac | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Makefile.am | 4++++
Asrc/backend/Makefile.am | 21+++++++++++++++++++++
Asrc/backend/paivana-httpd.c | 1753+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 2787 insertions(+), 0 deletions(-)

diff --git a/AUTHORS b/AUTHORS @@ -0,0 +1 @@ +Christian Grothoff diff --git a/COPYING b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/ChangeLog b/ChangeLog diff --git a/Makefile.am b/Makefile.am @@ -0,0 +1,8 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +SUBDIRS = . src doc + +ACLOCAL_AMFLAGS = -I m4 +EXTRA_DIST = \ + AUTHORS diff --git a/configure.ac b/configure.ac @@ -0,0 +1,339 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. +# +# This file is part of TALER +# Copyright (C) 2025 Taler Systems SA +# +# TALER is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# TALER 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 +# TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/license> +# +# +AC_PREREQ([2.71]) +AC_INIT([paivana],[0.0.0],[taler-bug@gnunet.org]) +AC_CONFIG_SRCDIR([src/backend/paivana-httpd.c]) +AC_CONFIG_HEADERS([paivana_config.h]) +# support for non-recursive builds +AM_INIT_AUTOMAKE([subdir-objects 1.9 tar-pax]) + +# pretty build rules +AM_SILENT_RULES([yes]) + +AC_CONFIG_MACRO_DIR([m4]) + +LT_INIT + +# Checks for programs. + +AC_PROG_CC + +CFLAGS="-Wall $CFLAGS" + +# Checks for header files. +AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h]) + + +# Require minimum libgcrypt version +need_libgcrypt_version=1.6.1 +AC_DEFINE_UNQUOTED([NEED_LIBGCRYPT_VERSION], ["$need_libgcrypt_version"], + [minimum version of libgcrypt required]) +AM_PATH_LIBGCRYPT([$need_libgcrypt_version]) + + +# Check for GNUnet's libgnunetutil. +libgnunetutil=0 +AC_MSG_CHECKING([for libgnunetutil]) +AC_ARG_WITH(gnunet, + [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])], + [AC_MSG_RESULT([given as $with_gnunet])], + [AC_MSG_RESULT(not given) + with_gnunet=yes]) +AS_CASE([$with_gnunet], + [yes], [], + [no], [AC_MSG_ERROR([--with-gnunet is required])], + [LDFLAGS="-L$with_gnunet/lib $LDFLAGS" + CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"]) +AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h], + [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)]) +AS_IF([test $libgnunetutil != 1], + [AC_MSG_ERROR([[ +*** +*** You need libgnunetutil to build this program. +*** This library is part of GNUnet, available at +*** https://gnunet.org +*** ]])]) + +# Check for GNUnet's libgnunetjson. +libgnunetjson=0 +AC_MSG_CHECKING([for libgnunetjson]) +AC_ARG_WITH(gnunet, + [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])], + [AC_MSG_RESULT([given as $with_gnunet])], + [AC_MSG_RESULT(not given) + with_gnunet=yes]) +AS_CASE([$with_gnunet], + [yes], [], + [no], [AC_MSG_ERROR([--with-gnunet is required])], + [LDFLAGS="-L$with_gnunet/lib $LDFLAGS" + CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"]) +AC_CHECK_HEADERS([gnunet/gnunet_json_lib.h], + [AC_CHECK_LIB([gnunetjson], [GNUNET_JSON_parse], libgnunetjson=1)]) +AS_IF([test $libgnunetjson != 1], + [AC_MSG_ERROR([[ +*** +*** You need libgnunetjson to build this program. +*** Make sure you have libjansson installed while +*** building GNUnet. +*** ]])]) + + +# Save before checking libcurl +CFLAGS_SAVE=$CFLAGS +LDFLAGS_SAVE=$LDFLAGS +LIBS_SAVE=$LIBS + +# check for libcurl +LIBCURL_CHECK_CONFIG(,7.34.0,[curl=1],[curl=0]) + +# cURL must support CURLINFO_TLS_SESSION, version >= 7.34 +AS_IF([test "x$curl" = x1],[ + AC_CHECK_HEADER([curl/curl.h], + [AC_CHECK_DECLS(CURLINFO_TLS_SESSION,[curl=1],[curl=0],[[#include <curl/curl.h>]])], + [curl=0]) +]) + +# libcurl should be mutually exclusive +AS_IF([test "$curl" = 1], + AM_CONDITIONAL(HAVE_LIBCURL, true) + AC_DEFINE([HAVE_LIBCURL],[1],[Have libcurl]) + [LIBCURL_LIBS="-lcurl"], + [AC_MSG_ERROR([FATAL: No libcurl])]) + +AC_SUBST([LIBCURL_LIBS]) + + +# Check for GNUnet's libgnunetcurl. +libgnunetcurl=0 +AC_MSG_CHECKING([for libgnunetcurl]) +AC_ARG_WITH(gnunet, + [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])], + [AC_MSG_RESULT([given as $with_gnunet])], + [AC_MSG_RESULT(not given) + with_gnunet=yes]) +AS_CASE([$with_gnunet], + [yes], [], + [no], [AC_MSG_ERROR([--with-gnunet is required])], + [LDFLAGS="-L$with_gnunet/lib $LDFLAGS" + CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"]) +AC_CHECK_HEADERS([gnunet/gnunet_curl_lib.h], + [AC_CHECK_LIB([gnunetcurl], [GNUNET_CURL_get_select_info], libgnunetcurl=1)]) +AS_IF([test $libgnunetcurl != 1], + [AC_MSG_ERROR([[ +*** +*** You need libgnunetcurl to build this program. +*** Make sure you have libcurl installed while +*** building GNUnet. +*** ]])]) + + +# check for libmicrohttpd +microhttpd=0 +AC_MSG_CHECKING([for microhttpd]) +AC_ARG_WITH([microhttpd], + [AS_HELP_STRING([--with-microhttpd=PFX], [base of microhttpd installation])], + [AC_MSG_RESULT([given as $with_microhttpd])], + [AC_MSG_RESULT([not given]) + with_microhttpd=yes]) +AS_CASE([$with_microhttpd], + [yes], [], + [no], [AC_MSG_ERROR([--with-microhttpd is required])], + [LDFLAGS="-L$with_microhttpd/lib $LDFLAGS" + CPPFLAGS="-I$with_microhttpd/include $CPPFLAGS"]) +AC_CHECK_LIB(microhttpd,MHD_start_daemon, + [AC_CHECK_HEADER([microhttpd.h],[microhttpd=1])]) +AC_CHECK_DECL([MHD_DAEMON_INFO_CURRENT_CONNECTIONS],,[microhttpd=0],[[#include <microhttpd.h>]]) +AS_IF([test $microhttpd = 0], + [AC_MSG_ERROR([[ +*** +*** You need libmicrohttpd >= 0.9.39 to build this program. +*** ]])]) + + +# check for libjansson (Jansson JSON library) +jansson=0 +AC_MSG_CHECKING([for jansson]) +AC_ARG_WITH([jansson], + [AS_HELP_STRING([--with-jansson=PFX], [base of jansson installation])], + [AC_MSG_RESULT([given as $with_jansson])], + [AC_MSG_RESULT([not given]) + with_jansson=yes]) +AS_CASE([$with_jansson], + [yes], [], + [no], [AC_MSG_ERROR([--with-jansson is required])], + [LDFLAGS="-L$with_jansson/lib $LDFLAGS" + CPPFLAGS="-I$with_jansson/include $CPPFLAGS"]) +AC_CHECK_LIB(jansson,json_pack, + [AC_CHECK_HEADER([jansson.h],[jansson=1])]) +AS_IF([test $jansson = 0], + [AC_MSG_ERROR([[ +*** +*** You need libjansson to build this program. +*** ]])]) + + +TALER_LIB_LDFLAGS="-export-dynamic -no-undefined" +TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined" + +AC_SUBST(TALER_LIB_LDFLAGS) +AC_SUBST(TALER_PLUGIN_LDFLAGS) + +CFLAGS_SAVE=$CFLAGS +LDFLAGS_SAVE=$LDFLAGS +LIBS_SAVE="$LIBS" + + +# test for zlib +SAVE_LDFLAGS=$LDFLAGS +SAVE_CPPFLAGS=$CPPFLAGS +AC_ARG_WITH(zlib, + [ --with-zlib[[=DIR]] use libz in DIR], + [AS_IF([test "$withval" = "no"], + [AC_MSG_ERROR([Twister requires zlib])], + [test "$withval" != "yes"], + [ + Z_DIR=$withval + CPPFLAGS="${CPPFLAGS} -I$withval/include" + LDFLAGS="${LDFLAGS} -L$withval/lib" + ]) + ]) +AC_CHECK_HEADER(zlib.h, + [], + [AC_MSG_ERROR([GNUnet requires zlib])]) +AC_CHECK_LIB(z, compress2, + [ + AC_DEFINE([HAVE_ZLIB], [], [Have compression library]) + if test "x${Z_DIR}" != "x"; then + Z_CFLAGS="-I${Z_DIR}/include" + Z_LIBS="-L${Z_DIR}/lib -lz" + else + Z_LIBS="-lz" + fi], + [AC_MSG_ERROR([Twister requires zlib])]) +AC_SUBST(Z_CFLAGS) +AC_SUBST(Z_LIBS) + + +# should developer logic be compiled (not-for-production code)? +AC_MSG_CHECKING(whether to compile developer logic) +AC_ARG_ENABLE([developer-mode], + [AS_HELP_STRING([--enable-developer-mode], [enable compiling developer code])], + [enable_developer=${enableval}], + [enable_developer=yes]) +AC_MSG_RESULT($enable_developer) +AM_CONDITIONAL([HAVE_DEVELOPER], [test "x$enable_developer" = "xyes"]) +enable_dev=1 +AS_IF([test "x$enableval" = "xno"], [enable_dev=0]) +# developer-logic requires a more recent MHD than usual. +AC_CHECK_DECL([MHD_OPTION_NOTIFY_CONNECTION],,[enable_dev=0],[[#include <microhttpd.h>]]) +AC_DEFINE_UNQUOTED([HAVE_DEVELOPER],[$enable_dev],[1 if developer logic is enabled, 0 otherwise]) + + + +# Adam shostack suggests the following for Windows: +# -D_FORTIFY_SOURCE=2 -fstack-protector-all +AC_ARG_ENABLE(gcc-hardening, + AS_HELP_STRING(--enable-gcc-hardening, enable compiler security checks), +[if test x$enableval = xyes; then + CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fstack-protector-all" + CFLAGS="$CFLAGS -fwrapv -fPIE -Wstack-protector" + CFLAGS="$CFLAGS --param ssp-buffer-size=1" + LDFLAGS="$LDFLAGS -pie" +fi]) + + +# Linker hardening options +# Currently these options are ELF specific - you can't use this with MacOSX +AC_ARG_ENABLE(linker-hardening, + AS_HELP_STRING(--enable-linker-hardening, enable linker security fixups), +[if test x$enableval = xyes; then + LDFLAGS="$LDFLAGS -z relro -z now" +fi]) + + +# logging +extra_logging=0 +AC_ARG_ENABLE([logging], + AS_HELP_STRING([--enable-logging@<:@=value@:>@],[Enable logging calls. Possible values: yes,no,verbose ('yes' is the default)]), + [AS_IF([test "x$enableval" = "xyes"], [], + [test "x$enableval" = "xno"], [AC_DEFINE([GNUNET_CULL_LOGGING],[],[Define to cull all logging calls])], + [test "x$enableval" = "xverbose"], [extra_logging=1] + [test "x$enableval" = "xveryverbose"], [extra_logging=2]) + ], []) +AC_DEFINE_UNQUOTED([GNUNET_EXTRA_LOGGING],[$extra_logging],[1 if extra logging is enabled, 2 for very verbose extra logging, 0 otherwise]) + +# gcov compilation +AC_MSG_CHECKING(whether to compile with support for code coverage analysis) +AC_ARG_ENABLE([coverage], + AS_HELP_STRING([--enable-coverage], + [compile the library with code coverage support]), + [use_gcov=${enableval}], + [use_gcov=no]) +AC_MSG_RESULT($use_gcov) +AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"]) + +# version info +AC_PATH_PROG(gitcommand, git) +AC_MSG_CHECKING(for source being under a VCS) +git_version= +AS_IF([test ! "X$gitcommand" = "X"], +[ + git_version=$(cd $srcdir ; git rev-list --full-history --all --abbrev-commit | head -n 1 2>/dev/null) +]) +AS_IF([test "X$git_version" = "X"], + [ + vcs_name="no" + vcs_version="\"release\"" + ], + [ + vcs_name="yes, git-svn" + vcs_version="\"git-$git_version\"" + ]) +AC_MSG_RESULT($vcs_name) + +AC_MSG_CHECKING(VCS version) +AC_MSG_RESULT($vcs_version) +AC_DEFINE_UNQUOTED(VCS_VERSION, [$vcs_version], [VCS revision/hash or tarball version]) + + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_INTMAX_T +AC_TYPE_UINTMAX_T + +# Checks for library functions. +AC_CHECK_FUNCS([strdup]) + + +AC_ARG_ENABLE([[doc]], + [AS_HELP_STRING([[--disable-doc]], [do not build any documentation])], , + [enable_doc=yes]) +test "x$enable_doc" = "xno" || enable_doc=yes +AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"]) + + +AC_CONFIG_FILES([Makefile + src/Makefile + src/backend/Makefile + ]) +AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am @@ -0,0 +1,4 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +SUBDIRS = backend diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -0,0 +1,21 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +bin_PROGRAMS = \ + paivana-httpd + +paivana_httpd_SOURCES = \ + paivana-httpd.c +paivana_httpd_LDADD = \ + $(LIBGCRYPT_LIBS) \ + -lmicrohttpd \ + -lcurl \ + -ljansson \ + -lgnunetutil \ + -lgnunetjson \ + -lz diff --git a/src/backend/paivana-httpd.c b/src/backend/paivana-httpd.c @@ -0,0 +1,1753 @@ +/* + This file is part of GNU Taler + Copyright (C) 2012-2014 GNUnet e.V. + Copyright (C) 2018, 2025 Taler Systems SA + + GNU Taler is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version + 3, or (at your option) any later version. + + GNU Taler 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 GNU Taler; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 51 Franklin + Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** + * @author Martin Schanzenbach + * @author Christian Grothoff + * @author Marcello Stanisci + * @file src/backend/paivana-httpd.c + * @brief HTTP proxy that acts as a GNU Taler paywall + */ +#include "platform.h" +#include <microhttpd2.h> +#include <curl/curl.h> +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> + + +#define PAIVANA_LOG_INFO(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__) +#define PAIVANA_LOG_DEBUG(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) +#define PAIVANA_LOG_WARNING(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__) +#define PAIVANA_LOG_ERROR(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__) + +#define REQUEST_BUFFER_MAX (1024 * 1024) +#define UNIX_BACKLOG 500 + +/** + * Log curl error. + * + * @param level log level + * @param fun name of curl_easy-function that gave the error + * @param rc return code from curl + */ +#define LOG_CURL_EASY(level,fun,rc) \ + GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \ + __LINE__, \ + curl_easy_strerror (rc)) + +/* ******** Datastructures for HTTP handling ********** */ + + +/** + * State machine for HTTP requests (per request). + */ +enum RequestState +{ + /** + * Starting state. + */ + REQUEST_STATE_WITH_MHD = 0, + + /** + * We've started receiving upload data from MHD. + */ + REQUEST_STATE_CLIENT_UPLOAD_STARTED, + + /** + * Wa have started uploading data to the proxied service. + */ + REQUEST_STATE_PROXY_UPLOAD_STARTED, + + /** + * We're done with the upload from MHD. + */ + REQUEST_STATE_CLIENT_UPLOAD_DONE, + + /** + * We're done uploading data to the proxied service. + */ + REQUEST_STATE_PROXY_UPLOAD_DONE, + + /** + * We've finished uploading data via CURL and can now download. + */ + REQUEST_STATE_PROXY_DOWNLOAD_STARTED, + + /** + * We've finished receiving download data from cURL. + */ + REQUEST_STATE_PROXY_DOWNLOAD_DONE +}; + + +/** + * A header list + */ +struct HttpResponseHeader +{ + /** + * DLL + */ + struct HttpResponseHeader *next; + + /** + * DLL + */ + struct HttpResponseHeader *prev; + + /** + * Header type + */ + char *type; + + /** + * Header value + */ + char *value; +}; + + +/** + * A structure for socks requests + */ +struct HttpRequest +{ + + /** + * Kept in DLL. + */ + struct HttpRequest *prev; + + /** + * Kept in DLL. + */ + struct HttpRequest *next; + + /** + * MHD request that triggered us. + */ + struct MHD_Connection *con; + + /** + * Client socket read task + */ + struct GNUNET_SCHEDULER_Task *rtask; + + /** + * Client socket write task + */ + struct GNUNET_SCHEDULER_Task *wtask; + + /** + * Hold the response obtained by modifying the original one. + */ + struct MHD_Response *mod_response; + + /** + * MHD response object for this request. + */ + struct MHD_Response *response; + + /** + * The URL to fetch + */ + char *url; + + /** + * JSON we use to parse payloads (in both directions). + */ + json_t *json; + + /** + * Handle to cURL + */ + CURL *curl; + + /** + * HTTP request headers for the curl request. + */ + struct curl_slist *headers; + + /** + * Headers from response + */ + struct HttpResponseHeader *header_head; + + /** + * Headers from response + */ + struct HttpResponseHeader *header_tail; + + /** + * Buffer we use for moving data between MHD and + * curl (in both directions). + */ + char *io_buf; + + /** + * Number of bytes already in the IO buffer. + */ + size_t io_len; + + /** + * Number of bytes allocated for the IO buffer. + */ + unsigned int io_size; + + /** + * HTTP response code to give to MHD for the response. + */ + unsigned int response_code; + + /** + * Request processing state machine. + */ + enum RequestState state; + + /** + * Did we suspend MHD processing? + */ + enum GNUNET_GenericReturnValue suspended; + + /** + * Did we pause CURL processing? + */ + int curl_paused; +}; + + +/* *********************** Globals **************************** */ + +/** + * The cURL download task (curl multi API). + */ +static struct GNUNET_SCHEDULER_Task *curl_download_task; + +/** + * DLL of active HTTP requests. + */ +static struct HttpRequest *hr_head; + +/** + * DLL of active HTTP requests. + */ +static struct HttpRequest *hr_tail; + +/** + * The cURL multi handle + */ +static CURLM *curl_multi; + +/** + * The daemon handle + */ +static struct MHD_Daemon *mhd_daemon; + +/** + * The task ID + */ +static struct GNUNET_SCHEDULER_Task *httpd_task; + +/** + * Response we return on cURL failures. + */ +static struct MHD_Response *curl_failure_response; + +/** + * Our configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Destination to which HTTP server we forward requests to. + * Of the format "http://servername:PORT" + */ +static char *target_server_base_url; + + +/* ********************* Global helpers ****************** */ + +/** + * Run MHD now, we have extra data ready for the callback. + */ +static void +run_mhd_now (void); + + +/* *************** HTTP handling with cURL ***************** */ + + +/** + * Transform _one_ CURL header (gotten from the request) into + * MHD format and put it into the response headers list; mostly + * copies the headers, but makes special adjustments based on + * control requests. + * + * @param buffer curl buffer with a single + * line of header data; not 0-terminated! + * @param size curl blocksize + * @param nmemb curl blocknumber + * @param cls our `struct HttpRequest *` + * @return size of processed bytes + */ +static size_t +curl_check_hdr (void *buffer, + size_t size, + size_t nmemb, + void *cls) +{ + struct HttpRequest *hr = cls; + struct HttpResponseHeader *header; + size_t bytes = size * nmemb; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *tok; + + /* Raw line is not guaranteed to be null-terminated. */ + ndup = GNUNET_malloc (bytes + 1); + memcpy (ndup, + buffer, + bytes); + ndup[bytes] = '\0'; + + hdr_type = strtok (ndup, ":"); + + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return bytes; + } + hdr_val = strtok (NULL, ""); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return bytes; + } + if (' ' == *hdr_val) + hdr_val++; + + /* MHD does not allow certain characters in values, + * remove those, plus those could alter strings matching. */ + if (NULL != (tok = strchr (hdr_val, '\n'))) + *tok = '\0'; + if (NULL != (tok = strchr (hdr_val, '\r'))) + *tok = '\0'; + if (NULL != (tok = strchr (hdr_val, '\t'))) + *tok = '\0'; + + PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n", + hdr_type, + hdr_val); + + /* Skip "Content-length:" header as it will be wrong, given + that we are man-in-the-middling the connection */ + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONTENT_LENGTH)) + { + GNUNET_free (ndup); + return bytes; + } + /* Skip "Connection: Keep-Alive" header, it will be + done by MHD if possible */ + if ( (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONNECTION)) && + (0 == strcasecmp (hdr_val, + "Keep-Alive")) ) + { + GNUNET_free (ndup); + return bytes; + } + if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ + { + header = GNUNET_new (struct HttpResponseHeader); + header->type = GNUNET_strdup (hdr_type); + header->value = GNUNET_strdup (hdr_val); + GNUNET_CONTAINER_DLL_insert (hr->header_head, + hr->header_tail, + header); + } + GNUNET_free (ndup); + return bytes; +} + + +/** + * Create the MHD response with CURL's as starting base; + * mainly set the response code and parses the response into + * JSON, if it is such. + * + * @param hr pointer to where to store the new data. Despite + * its name, the struct contains response data as well. + * @return #GNUNET_OK if it succeeds. + */ +static enum GNUNET_GenericReturnValue +create_mhd_response_from_hr (struct HttpRequest *hr) +{ + long resp_code; + json_error_t error; + + if (NULL != hr->response) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Response already set!\n"); + return GNUNET_SYSERR; + } + GNUNET_break (CURLE_OK == + curl_easy_getinfo (hr->curl, + CURLINFO_RESPONSE_CODE, + &resp_code)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating MHD response with code %u\n", + (unsigned int) resp_code); + hr->response_code = resp_code; + /* Note, will be NULL if io_buf does not represent + * a JSON value. */ + hr->json = json_loadb (hr->io_buf, + hr->io_len, + JSON_DECODE_ANY, + &error); + if (GNUNET_YES == hr->suspended) + { + MHD_resume_connection (hr->con); + hr->suspended = GNUNET_NO; + } + run_mhd_now (); + return GNUNET_OK; +} + + +/** + * Handle response payload data from cURL. + * Copies it into our `io_buf` to make it available to MHD. + * + * @param ptr pointer to the data + * @param size number of blocks of data + * @param nmemb blocksize + * @param ctx our `struct HttpRequest *` + * @return number of bytes handled + */ +static size_t +curl_download_cb (void *ptr, + size_t size, + size_t nmemb, + void *ctx) +{ + struct HttpRequest *hr = ctx; + size_t total = size * nmemb; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Curl download proceeding\n"); + + if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) + { + /* Web server started with response before we finished + the upload. In this case, current libcurl decides + to NOT complete the upload, so we should jump in the + state machine to process the download, dropping the + rest of the upload. This should only really happen + with uploads without "Expect: 100 Continue" and + Web servers responding with an error (i.e. upload + not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; + GNUNET_log + (GNUNET_ERROR_TYPE_INFO, + "Stopping %u byte upload: we are already downloading...\n", + (unsigned int) hr->io_len); + hr->io_len = 0; + } + + if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Download callback goes to sleep\n"); + hr->curl_paused = GNUNET_YES; + return CURL_WRITEFUNC_PAUSE; + } + GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == + hr->state); + if (hr->io_size - hr->io_len < total) + { + GNUNET_assert (total + hr->io_size >= total); + GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); + GNUNET_array_grow (hr->io_buf, + hr->io_size, + GNUNET_MAX (total + hr->io_len, + hr->io_size * 2 + 1024)); + } + GNUNET_memcpy (&hr->io_buf[hr->io_len], + ptr, + total); + hr->io_len += total; + return total; +} + + +/** + * Ask cURL for the select() sets and schedule cURL operations. + */ +static void +curl_download_prepare (void); + + +/** + * cURL callback for uploaded (PUT/POST) data. + * Copies from our `io_buf` to make it available to cURL. + * + * @param buf where to write the data + * @param size number of bytes per member + * @param nmemb number of members available in @a buf + * @param cls our `struct HttpRequest` that generated the data + * @return number of bytes copied to @a buf + */ +static size_t +curl_upload_cb (void *buf, + size_t size, + size_t nmemb, + void *cls) +{ + struct HttpRequest *hr = cls; + size_t len = size * nmemb; + size_t to_copy; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Upload cb is working...\n"); + + if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) || + (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) ) + { + GNUNET_log + (GNUNET_ERROR_TYPE_INFO, + "Upload cb aborts: we are already downloading...\n"); + return CURL_READFUNC_ABORT; + } + + if ( (0 == hr->io_len) && + (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing CURL UPLOAD, need more data\n"); + return CURL_READFUNC_PAUSE; + } + + /** + * We got rescheduled because the download callback was asleep. + * FIXME: can this block be eliminated and the unpausing being + * moved in the last block where we return zero as well? + */ + if ( (0 == hr->io_len) && + (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ) + { + if (GNUNET_YES == hr->curl_paused) + { + hr->curl_paused = GNUNET_NO; + curl_easy_pause (hr->curl, + CURLPAUSE_CONT); + } + curl_download_prepare (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Completed CURL UPLOAD\n"); + return 0; /* upload finished, can now download */ + } + to_copy = GNUNET_MIN (hr->io_len, + len); + GNUNET_memcpy (buf, + hr->io_buf, + to_copy); + /* shift remaining data back to the beginning of the buffer. */ + memmove (hr->io_buf, + &hr->io_buf[to_copy], + hr->io_len - to_copy); + hr->io_len -= to_copy; + if (0 == hr->io_len) + { + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Completed CURL UPLOAD\n"); + } + return to_copy; +} + + +/* ************** helper functions ************* */ + +/** + * Extract the hostname from a complete URL. + * + * @param url full fledged URL + * + * @return pointer to the 0-terminated hostname, to be freed + * by the caller. + */ +static char * +build_host_header (const char *url) +{ + #define MARKER "://" + + char *header; + char *end; + char *hostname; + char *dup = GNUNET_strdup (url); + + hostname = strstr (dup, MARKER); + hostname += 3; + + end = strchrnul (hostname, '/'); + *end = '\0'; + + GNUNET_asprintf (&header, + "Host: %s", + hostname); + + GNUNET_free (dup); + return header; +} + + +/* ************** main loop of cURL interaction ************* */ + + +/** + * Task that is run when we are ready to receive more data + * from curl + * + * @param cls closure + */ +static void +curl_task_download (void *cls); + + +/** + * Ask cURL for the select() sets and schedule cURL operations. + */ +static void +curl_download_prepare () +{ + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + int max; + struct GNUNET_NETWORK_FDSet *grs; + struct GNUNET_NETWORK_FDSet *gws; + long to; + struct GNUNET_TIME_Relative rtime; + + if (NULL != curl_download_task) + { + GNUNET_SCHEDULER_cancel (curl_download_task); + curl_download_task = NULL; + } + max = -1; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + if (CURLM_OK != (mret = curl_multi_fdset (curl_multi, + &rs, + &ws, + &es, + &max))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s failed at %s:%d: `%s'\n", + "curl_multi_fdset", + __FILE__, + __LINE__, + curl_multi_strerror (mret)); + return; + } + to = -1; + GNUNET_break (CURLM_OK == + curl_multi_timeout (curl_multi, + &to)); + if (-1 == to) + rtime = GNUNET_TIME_UNIT_FOREVER_REL; + else + rtime = GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_MILLISECONDS, to); + if (-1 != max) + { + grs = GNUNET_NETWORK_fdset_create (); + gws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (grs, + &rs, + max + 1); + GNUNET_NETWORK_fdset_copy_native (gws, + &ws, + max + 1); + curl_download_task + = GNUNET_SCHEDULER_add_select ( + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + rtime, + grs, gws, + &curl_task_download, + curl_multi); + GNUNET_NETWORK_fdset_destroy (gws); + GNUNET_NETWORK_fdset_destroy (grs); + } + else + { + curl_download_task = GNUNET_SCHEDULER_add_delayed + (rtime, + &curl_task_download, + curl_multi); + } +} + + +/** + * Task that is run when we are ready to receive + * more data from curl. + * + * @param cls closure, usually NULL. + */ +static void +curl_task_download (void *cls) +{ + int running; + int msgnum; + struct CURLMsg *msg; + CURLMcode mret; + struct HttpRequest *hr; + + (void) cls; + curl_download_task = NULL; + do + { + running = 0; + mret = curl_multi_perform (curl_multi, + &running); + while (NULL != (msg = curl_multi_info_read (curl_multi, + &msgnum))) + { + GNUNET_break + (CURLE_OK == curl_easy_getinfo + (msg->easy_handle, + CURLINFO_PRIVATE, + (char **) &hr)); + + if (NULL == hr) + { + GNUNET_break (0); + continue; + } + switch (msg->msg) + { + case CURLMSG_NONE: + /* documentation says this is not used */ + GNUNET_break (0); + break; + case CURLMSG_DONE: + switch (msg->data.result) + { + case CURLE_OK: + case CURLE_GOT_NOTHING: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "CURL download completed.\n"); + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; + if (NULL == hr->response) + GNUNET_assert (GNUNET_OK == + create_mhd_response_from_hr (hr)); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Download curl failed: %s\n", + curl_easy_strerror (msg->data.result)); + /* FIXME: indicate error somehow? + * close MHD connection badly as well? */ + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; + if (GNUNET_YES == hr->suspended) + { + MHD_resume_connection (hr->con); + hr->suspended = GNUNET_NO; + } + run_mhd_now (); + break; + } + if (NULL == hr->response) + hr->response = curl_failure_response; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Curl request for `%s' finished (got the response)\n", + hr->url); + run_mhd_now (); + break; + case CURLMSG_LAST: + /* documentation says this is not used */ + GNUNET_break (0); + break; + default: + /* unexpected status code */ + GNUNET_break (0); + break; + } + } + ; + } while (mret == CURLM_CALL_MULTI_PERFORM); + if (CURLM_OK != mret) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s failed at %s:%d: `%s'\n", + "curl_multi_perform", + __FILE__, + __LINE__, + curl_multi_strerror (mret)); + if (0 == running) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Suspending cURL multi loop," + " no more events pending\n"); + return; /* nothing more in progress */ + } + curl_download_prepare (); +} + + +/* *************** MHD response generation ***************** */ + + +/** + * Main MHD callback for handling requests. + * + * @param cls unused + * @param con MHD connection handle + * @param url the url in the request + * @param meth the HTTP method used ("GET", "PUT", etc.) + * @param ver the HTTP version string (i.e. "HTTP/1.1") + * @param upload_data the data being uploaded (excluding HEADERS, + * for a POST that fits into memory and that is encoded + * with a supported encoding, the POST data will NOT be + * given in upload_data and is instead available as + * part of MHD_get_connection_values; very large POST + * data *will* be made available incrementally in + * upload_data) + * @param upload_data_size set initially to the size of the + * @a upload_data provided; the method must update this + * value to the number of bytes NOT processed; + * @param con_cls pointer to location where we store the + * 'struct Request' + * @return #MHD_YES if the connection was handled successfully, + * #MHD_NO if the socket must be closed due to a serious + * error while handling the request + */ +static MHD_RESULT +create_response (void *cls, + struct MHD_Connection *con, + const char *url, + const char *meth, + const char *ver, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct HttpRequest *hr = *con_cls; + + (void) cls; + (void) url; + + if (NULL == hr) + { + GNUNET_break (0); + return MHD_NO; + } + + if (REQUEST_STATE_WITH_MHD == hr->state) + { + hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED; + /* TODO: hacks for 100 continue suppression would go here! */ + return MHD_YES; + } + + /* continuing to process request */ + if (0 != *upload_data_size) + { + GNUNET_assert + (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Processing %u bytes UPLOAD\n", + (unsigned int) *upload_data_size); + + /* Grow the buffer if remaining space isn't enough. */ + if (hr->io_size - hr->io_len < *upload_data_size) + { + /* How can this assertion be false? */ + GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); + /* This asserts that upload_data_size > 0, ? */ + GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len); + + GNUNET_array_grow (hr->io_buf, + hr->io_size, + GNUNET_MAX + (hr->io_size * 2 + 1024, + *upload_data_size + hr->io_len)); + } + + /* Finally copy upload data. */ + GNUNET_memcpy (&hr->io_buf[hr->io_len], + upload_data, + *upload_data_size); + + hr->io_len += *upload_data_size; + *upload_data_size = 0; + + return MHD_YES; + } + + /* Upload (*from the client*) finished or just a without-body + * request. */ + if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state) + { + hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finished processing UPLOAD\n"); + } + + /* generate curl request to the proxied service. */ + if (NULL == hr->curl) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Generating curl request\n"); + hr->curl = curl_easy_init (); + if (NULL == hr->curl) + { + PAIVANA_LOG_ERROR ("Could not init the curl handle\n"); + return MHD_queue_response (con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + curl_failure_response); + } + + /* No need to check whether we're POSTing or PUTting. + * If not needed, one of the following values will be + * ignored.*/ + curl_easy_setopt (hr->curl, + CURLOPT_POSTFIELDSIZE, + hr->io_len); + curl_easy_setopt (hr->curl, + CURLOPT_INFILESIZE, + hr->io_len); + curl_easy_setopt (hr->curl, + CURLOPT_HEADERFUNCTION, + &curl_check_hdr); + curl_easy_setopt (hr->curl, + CURLOPT_HEADERDATA, + hr); + curl_easy_setopt (hr->curl, + CURLOPT_FOLLOWLOCATION, + 0); + curl_easy_setopt (hr->curl, + CURLOPT_CONNECTTIMEOUT, + 600L); + curl_easy_setopt (hr->curl, + CURLOPT_TIMEOUT, + 600L); + curl_easy_setopt (hr->curl, + CURLOPT_NOSIGNAL, + 1L); + curl_easy_setopt (hr->curl, + CURLOPT_PRIVATE, + hr); + curl_easy_setopt (hr->curl, + CURLOPT_VERBOSE, + 0); + + curl_easy_setopt (hr->curl, + CURLOPT_READFUNCTION, + &curl_upload_cb); + + curl_easy_setopt (hr->curl, + CURLOPT_READDATA, + hr); + + curl_easy_setopt (hr->curl, + CURLOPT_WRITEFUNCTION, + &curl_download_cb); + + curl_easy_setopt (hr->curl, + CURLOPT_WRITEDATA, + hr); + { + char *curlurl; + char *host_hdr; + + GNUNET_asprintf (&curlurl, + "%s%s", + target_server_base_url, + hr->url); + curl_easy_setopt (hr->curl, + CURLOPT_URL, + curlurl); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Forwarding request to: %s\n", + curlurl); + GNUNET_free (curlurl); + + host_hdr = build_host_header (target_server_base_url); + PAIVANA_LOG_DEBUG ("Faking the host header, %s\n", + host_hdr); + hr->headers = curl_slist_append (hr->headers, + host_hdr); + GNUNET_free (host_hdr); + } + + if (0 == strcasecmp (meth, + MHD_HTTP_METHOD_PUT)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Crafting a CURL PUT request\n"); + + curl_easy_setopt (hr->curl, + CURLOPT_UPLOAD, + 1L); + hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; + + } + else if (0 == strcasecmp (meth, + MHD_HTTP_METHOD_POST)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Crafting a CURL POST request\n"); + curl_easy_setopt (hr->curl, + CURLOPT_POST, + 1L); + curl_easy_setopt (hr->curl, + CURLOPT_VERBOSE, + 1L); + hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; + + } + else if (0 == strcasecmp (meth, + MHD_HTTP_METHOD_HEAD)) + { + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; + curl_easy_setopt (hr->curl, + CURLOPT_NOBODY, + 1L); + } + else if (0 == strcasecmp (meth, + MHD_HTTP_METHOD_OPTIONS)) + { + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; + curl_easy_setopt (hr->curl, + CURLOPT_CUSTOMREQUEST, + "OPTIONS"); + } + else if (0 == strcasecmp (meth, + MHD_HTTP_METHOD_GET)) + { + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; + curl_easy_setopt (hr->curl, + CURLOPT_HTTPGET, + 1L); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unsupported HTTP method `%s'\n", + meth); + curl_easy_cleanup (hr->curl); + hr->curl = NULL; + return MHD_NO; + } + + if (0 == strcasecmp (ver, + MHD_HTTP_VERSION_1_0)) + { + curl_easy_setopt (hr->curl, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_0); + } + else if (0 == strcasecmp (ver, + MHD_HTTP_VERSION_1_1)) + { + curl_easy_setopt (hr->curl, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_1); + } + else + { + curl_easy_setopt (hr->curl, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_NONE); + } + + if (CURLM_OK != curl_multi_add_handle (curl_multi, + hr->curl)) + { + GNUNET_break (0); + curl_easy_cleanup (hr->curl); + hr->curl = NULL; + return MHD_NO; + } + + MHD_get_connection_values (con, + MHD_HEADER_KIND, + &con_val_iter, + hr); + + curl_easy_setopt (hr->curl, + CURLOPT_HTTPHEADER, + hr->headers); + curl_download_prepare (); + + return MHD_YES; + } + + if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state) + { + GNUNET_assert (GNUNET_NO == hr->suspended); + MHD_suspend_connection (con); + hr->suspended = GNUNET_YES; + return MHD_YES; /* wait for curl */ + } + + GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state); + if (0 != hack_response_code) + { + hr->response_code = hack_response_code; + hack_response_code = 0; /* reset for next request */ + } + + for (struct HttpResponseHeader *header = hr->header_head; + NULL != header; + header = header->next) + { + const char *value = header->value; + + if ((NULL != modify_header_dl) && + (0 == strcmp (header->type, + modify_header_dl))) + { + value = modify_value; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding MHD response header %s->%s\n", + header->type, + value); + GNUNET_break (MHD_YES == + MHD_add_response_header (hr->response, + header->type, + value)); + } + run_mhd_now (); + + return MHD_queue_response (con, + hr->response_code, + hr->response); +} + + +/* ************ MHD HTTP setup and event loop *************** */ + + +/** + * Function called when MHD decides that we + * are done with a request. + * + * @param cls NULL + * @param connection connection handle + * @param con_cls value as set by the last call to + * the MHD_AccessHandlerCallback, should be + * our `struct HttpRequest *` (set by `create_response()`) + * @param toe reason for request termination (ignored) + */ +static void +mhd_completed_cb (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct HttpRequest *hr = *con_cls; + struct HttpResponseHeader *header; + + (void) cls; + (void) connection; + if (NULL == hr) + return; + if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "MHD encountered error handling request: %d\n", + toe); + if (NULL != hr->curl) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resetting cURL handle\n"); + curl_multi_remove_handle (curl_multi, + hr->curl); + curl_easy_cleanup (hr->curl); + hr->curl = NULL; + hr->io_len = 0; + } + if (NULL != hr->headers) + { + curl_slist_free_all (hr->headers); + hr->headers = NULL; + } + if ( (NULL != hr->response) && + (curl_failure_response != hr->response) ) + /* Destroy non-error responses... (?) */ + MHD_destroy_response (hr->response); + + for (header = hr->header_head; + header != NULL; + header = hr->header_head) + { + GNUNET_CONTAINER_DLL_remove (hr->header_head, + hr->header_tail, + header); + GNUNET_free (header->type); + GNUNET_free (header->value); + GNUNET_free (header); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Proxying of '%s' completely done\n", + hr->url); + + GNUNET_free (hr->url); + GNUNET_free (hr->io_buf); + GNUNET_CONTAINER_DLL_remove (hr_head, + hr_tail, + hr); + GNUNET_free (hr); + *con_cls = NULL; +} + + +/** + * Function called when MHD first processes an incoming connection. + * Gives us the respective URI information. + * + * We use this to associate the `struct MHD_Connection` with our + * internal `struct HttpRequest` data structure (by checking + * for matching sockets). + * + * @param cls the HTTP server handle (a `struct MhdHttpList`) + * @param url the URL that is being requested + * @param connection MHD connection object for the request + * @return the `struct HttpRequest` that this @a connection is for + */ +static void * +mhd_log_callback (void *cls, + const char *url, + struct MHD_Connection *connection) +{ + struct HttpRequest *hr; + const union MHD_ConnectionInfo *ci; + + (void) cls; + ci = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_SOCKET_CONTEXT); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Processing %s\n", + url); + if (NULL == ci) + { + GNUNET_break (0); + return NULL; + } + + hr = GNUNET_new (struct HttpRequest); + hr->con = connection; + hr->url = GNUNET_strdup (url); + GNUNET_CONTAINER_DLL_insert (hr_head, + hr_tail, + hr); + return hr; +} + + +/** + * Kill the MHD daemon. + */ +static void +kill_httpd (void) +{ + MHD_stop_daemon (mhd_daemon); + mhd_daemon = NULL; + if (NULL != httpd_task) + { + GNUNET_SCHEDULER_cancel (httpd_task); + httpd_task = NULL; + } +} + + +/** + * Task run whenever HTTP server operations are pending. + * + * @param cls the `struct MhdHttpList *` + * of the daemon that is being run + */ +static void +do_httpd (void *cls); + + +/** + * Schedule MHD. This function should be called initially when an + * MHD is first getting its client socket, and will then + * automatically always be called later whenever there is work to + * be done. + */ +static void +schedule_httpd (void) +{ + fd_set rs; + fd_set ws; + fd_set es; + struct GNUNET_NETWORK_FDSet *wrs; + struct GNUNET_NETWORK_FDSet *wws; + int max; + int haveto; + MHD_UNSIGNED_LONG_LONG timeout; + struct GNUNET_TIME_Relative tv; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + max = -1; + if (MHD_YES != + MHD_get_fdset (mhd_daemon, + &rs, + &ws, + &es, + &max)) + { + kill_httpd (); + return; + } + haveto = MHD_get_timeout (mhd_daemon, + &timeout); + if (MHD_YES == haveto) + tv.rel_value_us = (uint64_t) timeout * 1000LL; + else + tv = GNUNET_TIME_UNIT_FOREVER_REL; + if (-1 != max) + { + wrs = GNUNET_NETWORK_fdset_create (); + wws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (wrs, + &rs, + max + 1); + GNUNET_NETWORK_fdset_copy_native (wws, + &ws, + max + 1); + } + else + { + wrs = NULL; + wws = NULL; + } + if (NULL != httpd_task) + { + GNUNET_SCHEDULER_cancel (httpd_task); + httpd_task = NULL; + } + httpd_task = + GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + tv, + wrs, + wws, + &do_httpd, + NULL); + if (NULL != wrs) + GNUNET_NETWORK_fdset_destroy (wrs); + if (NULL != wws) + GNUNET_NETWORK_fdset_destroy (wws); +} + + +/** + * Task run whenever HTTP server operations are pending. + * + * @param cls NULL + */ +static void +do_httpd (void *cls) +{ + (void) cls; + httpd_task = NULL; + MHD_run (mhd_daemon); + schedule_httpd (); +} + + +/** + * Run MHD now, we have extra data ready for the callback. + */ +static void +run_mhd_now (void) +{ + if (NULL != httpd_task) + GNUNET_SCHEDULER_cancel (httpd_task); + httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, + NULL); +} + + +/* *************** General / main code *************** */ + + +/** + * Task run on shutdown + * + * @param cls closure + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Shutting down...\n"); + /* MHD requires resuming before destroying the daemons */ + for (struct HttpRequest *hr = hr_head; + NULL != hr; + hr = hr->next) + { + if (GNUNET_YES == hr->suspended) + { + hr->suspended = GNUNET_NO; + MHD_resume_connection (hr->con); + } + } + kill_httpd (); + if (NULL != curl_multi) + { + curl_multi_cleanup (curl_multi); + curl_multi = NULL; + } + if (NULL != curl_download_task) + { + GNUNET_SCHEDULER_cancel (curl_download_task); + curl_download_task = NULL; + } + GNUNET_free (target_server_base_url); +} + + +/** + * Connect to a unix domain socket. + * + * @param path the IPC path + * @param mode the IPC path mode + * @return the file descriptor of the connection. + */ +static int +open_unix_path (const char *path, + mode_t mode) +{ + + struct GNUNET_NETWORK_Handle *nh; + struct sockaddr_un *un; + int fd; + + if (sizeof (un->sun_path) <= strlen (path)) + { + fprintf (stderr, + "path `%s' too long\n", + path); + return -1; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Creating listen socket '%s' with mode %o\n", + path, + mode); + + if (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (path)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + path); + } + + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + + strncpy (un->sun_path, + path, + sizeof (un->sun_path) - 1); + GNUNET_NETWORK_unix_precheck (un); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, + SOCK_STREAM, + 0))) + { + fprintf (stderr, + "create failed for AF_UNIX\n"); + GNUNET_free (un); + return -1; + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + (void *) un, + sizeof (struct sockaddr_un))) + { + fprintf (stderr, + "bind failed for AF_UNIX\n"); + GNUNET_free (un); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + GNUNET_free (un); + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + fprintf (stderr, + "listen failed for AF_UNIX\n"); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + + if (0 != chmod (path, + mode)) + { + fprintf (stderr, + "chmod failed: %s\n", + strerror (errno)); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "set socket '%s' to mode %o\n", + path, + mode); + fd = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + return fd; +} + + +/** + * Crawl the configuration file and extracts the serving + * method, TCP vs IPC, and the respective details (port/path/mode) + * + * @param ccfg configuration handle. + * @param port[out] port number to use + * @param path[out] unix path for IPC. + * @param mode[out] mode string for @a path. + * @return #GNUNET_SYSERR if the parsing didn't succeed. + */ +static int +parse_serving_mean (const struct GNUNET_CONFIGURATION_Handle *ccfg, + uint16_t *port, + char **path, + mode_t *mode) +{ + + const char *serve; + const char *choices[] = {"tcp", "unix", NULL}; + char *modestring; + unsigned long long port_ull; + + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_choice (ccfg, + "paivana", + "SERVE", + choices, + &serve)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "SERVE"); + return GNUNET_SYSERR; + } + + if (0 == strcmp ("tcp", serve)) + { + *path = NULL; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (ccfg, + "paivana", + "HTTP_PORT", + &port_ull)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "HTTP_PORT"); + return GNUNET_SYSERR; + } + *port = (uint16_t) port_ull; + return GNUNET_OK; + } + + /* serving via unix */ + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (ccfg, + "paivana", + "SERVE_UNIXPATH", + path)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "SERVE_UNIXPATH"); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ccfg, + "paivana", + "SERVE_UNIXMODE", + &modestring)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "SERVE_UNIXMODE"); + return GNUNET_SYSERR; + } + + errno = 0; + *mode = (mode_t) strtoul (modestring, NULL, 8); + + if (0 != errno) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "SERVE_UNIXMODE", + "must be octal number"); + GNUNET_free (modestring); + return GNUNET_SYSERR; + } + GNUNET_free (modestring); + return GNUNET_OK; +} + + +/** + * Main function that will be run. Main tasks are (1) init. the + * curl infrastructure (curl_global_init() / curl_multi_init()), + * then fetch the HTTP port where its Web service should listen at, + * and finally start MHD on that port. + * + * @param cls closure + * @param c configuration + * @param service the initialized service +*/ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + uint16_t port = 0; + int fh = -1; + char *serve_unixpath = NULL; + mode_t serve_unixmode = 0; + + (void) cls; + (void) service; + cfg = c; + + if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "cURL global init failed!\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (NULL == (curl_multi = curl_multi_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create cURL multi handle!\n"); + return; + } + + /* No need to check return value. If given, we take, + * otherwise it stays zero. */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string + (c, + "paivana", + "DESTINATION_BASE_URL", + &target_server_base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "DESTINATION_BASE_URL"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_SYSERR == + parse_serving_mean (c, + &port, + &serve_unixpath, + &serve_unixmode)) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (NULL != serve_unixpath) + { + /* Connect the 'fh' socket. */ + fh = open_unix_path (serve_unixpath, + serve_unixmode); + + GNUNET_assert (-1 != fh); + } + + /* start MHD daemon for HTTP */ + mhd_daemon = MHD_start_daemon + (MHD_USE_DEBUG | MHD_ALLOW_SUSPEND_RESUME | MHD_USE_DUAL_STACK, + (-1 == fh) ? (uint16_t) port : 0, + NULL, NULL, + &create_response, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16, + MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL, + MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL, + MHD_OPTION_LISTEN_SOCKET, fh, + MHD_OPTION_END); + + if (NULL == mhd_daemon) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + run_mhd_now (); +} + + +/** + * Main function. + */ +int +main (int argc, + char *const *argv) +{ + return GNUNET_PROGRAM_run_ (PAIVANA_project_data (), + argc, + argv, + "paivana-httpd", + &run, + NULL); +} + + +/* end of paivana-httpd.c */