diff options
973 files changed, 149590 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..721a4816 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +*~ +.tar.gz +.deps +.libs +*.o +*.lo +*.la +Makefile +Makefile.in +autom4te.cache +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +libtool +ltmain.sh +aclocal.m4 +depcomp +stamp-h1 +INSTALL +install-sh +missing +compile +rsyslogd +rsyslog.service +ylwrap +*.orig +rg.conf* +*.swp +*.cache +# some common names I use during development +utils +tmp* +log +logfile +debug +core.* +shave +shave-libtool +core* +vgcore* +*.tar.gz +*.new +*.orig +*.old +*.diff +*.patch +checklog +*.bad +*.rej +*.log +logfile* +*.conf +log diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..8a3b5560 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,14 @@ +Thankfully, we have had so many contributions that maintaining the +AUTHORS file would be a big task in itself. On the other hand, we +now use git and I make sure that each author receives proper credit +for patches I receive. + +So rather than trying to reproduce the git author log here (and +often making mistakes in that), I invite you to check the git logs. +You can also do this online at + +http://git.adiscon.com/?p=rsyslog.git;a=summary + +Rainer Gerhards +<rgerhards@adiscon.com> +lead rsyslog developer diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..804eb5cc --- /dev/null +++ b/COPYING @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/COPYING.ASL20 b/COPYING.ASL20 new file mode 100644 index 00000000..9d78c8b9 --- /dev/null +++ b/COPYING.ASL20 @@ -0,0 +1,50 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and + 2. You must cause any modified files to carry prominent notices stating that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 00000000..34b8ea79 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..f9ddf939 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,7911 @@ +--------------------------------------------------------------------------- +Version 7.5.1 [devel] 2013-06-?? +- omrelp: + * new parameter "compression.prioritystring" to control encryption + parameters used by GnuTLS +- imrelp: + * new parameter "compression.dhbits" to control the number of + bits being used for Diffie-Hellman key generation + * new parameter "compression.prioritystring" to control encryption + parameters used by GnuTLS + * support for impstats added + * support for setting permitted peers (client authentication) added +- imjournal: imported patches from 7.4.1 +- added experimental TCP stream compression (imptcp only, currently) +- added BSD-specific syslog facilities + * "console" + * "bsd_security" - this is called "security" under BSD, but that name + was unfortunately already taken by some standard facility. So I + did the (hopefully) second-best thing and renamed it a little. +--------------------------------------------------------------------------- +Version 7.5.0 [devel] 2013-06-11 +- imrelp: implement "ruleset" module parameter +- imrelp/omrelp: add TLS & compression (zip) support +- omrelp: add "rebindInterval" parameter +- add -S command line option to specify IP address to use for RELP client + connections + Thanks to Axel Rau for the patch. +--------------------------------------------------------------------------- +Version 7.4.1 [v7.4-stable] 2013-06-17 +- imjournal: add ratelimiting capability + The original imjournal code did not support ratelimiting at all. We + now have our own ratelimiter. This can mitigate against journal + database corruption, when the journal re-sends old data. This is a + current bug in systemd journal, but we won't outrule this to happen + in the future again. So it is better to have a safeguard in place. + By default, we permit 20,000 messages witin 10 minutes. This may + be a bit restrictive, but given the risk potential it seems reasonable. + Users requiring larger traffic flows can always adjust the value. +- bugfix: potential loop in rate limiting + if the message that tells about rate-limiting gets rate-limited itself, + it will potentially create and endless loop +- bugfix: potential segfault in imjournal if journal DB is corrupted +- bugfix: prevent a segfault in imjournal if state file is not defined +- bugfix imzmq3: potential segfault on startup + if no problem happend at startup, everything went fine + Thanks to Hongfei Cheng and Brian Knox for the patch +--------------------------------------------------------------------------- +Version 7.4.0 [v7.4-stable] 2013-06-06 +This starts a new stable branch based on 7.3.15 plus the following changes: +- add --enable-cached-man-pages ./configure option + permits to build rsyslog on a system where rst2man is not installed. In + that case, cached versions of the man pages are used (they were built + during "make dist", so they should be current for the version in + question. +- doc bugfix: ReadMode wrong in imfile doc, two values were swapped + Thanks to jokajak@gmail.com for mentioning this + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=450 +- imjournal: no longer do periodic wakeup +- bugfix: potential hang *in debug mode* on rsyslogd termination + This ONLY affected rsyslogd if it were running with debug output + enabled. +- bugfix: $template statement with multiple spaces lead to invalid tpl name + If multiple spaces were used in front of the template name, all but one + of them became actually part of the template name. So + $template a,"..." would be name " a", and as such "a" was not + available, e.g. in + *.* /var/log/file;a + This is a legacy config problem. As it was unreported for many years, + no backport of the fix to old versions will happen. + This is a long-standing bug that was only recently reported by forum + user mc-sim. + Reference: http://kb.monitorware.com/post23448.html +- 0mq fixes; credits to Hongfei Cheng and Brian Knox +--------------------------------------------------------------------------- +Version 7.3.15 [beta] 2013-05-15 +- bugfix: problem in build system (especially when cross-compiling) + Thanks to Tomas Heinrich and winfried_mb2@xmsnet.nl for the patch. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=445 +- bugfix: imjournal had problem with systemd journal API change +- imjournal: now obtain and include PID +- bugfix: .logsig files had tlv16 indicator bit at wrong offset +- bugfix: omrelp legacy config parameters set a timeout of zero + which lead the legacy config to be unusable. +- bugfix: segfault on startup if a disk queue was configure without file + name + Now this triggers an error message and the queue is changed to + linkedList type. +- bugfix: invalid addressing in string class (recent regression) +--------------------------------------------------------------------------- +Version 7.3.14 [beta] 2013-05-06 +- bugfix: some man pages were not properly installed + either rscryutil or rsgtutil man was installed, but not both + Thanks to Marius Tomaschewski for the patch. +- bugfix: potential segfault on startup when builtin module was specified + in module() statement. + Thanks to Marius Tomaschewski for reporting the bug. +- bugfix: segfault due to invalid dynafile cache handling + Accidently, the old-style cache size parameter was used when the + dynafile cache was created in a RainerScript action. If the old-style + size was lower than the one actually set, this lead to misadressing + when the size was overrun, and that could lead to all kinds of + "interesting things", often in segfaults. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=440 +--------------------------------------------------------------------------- +Version 7.3.13 [beta] 2013-04-29 +- added omrabbitmq module (contributed, untested) + Note: this is unsupported and as such was moved immediately into the + beta version. + Thanks to Vaclav Tomec for providing this module. +- bugfix: build problem when --enable-encryption was not selected + Thanks to Michael Biebl for fixing this. +- doc bugfix: omfile parameter "VeryRobustZip" was documentas as + "VeryReliableZip" + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=437 + Thanks to Thomas Doll for reporting this. +--------------------------------------------------------------------------- +Version 7.3.12 [devel] 2013-04-25 +- added doc for omelasticsearch + Thanks to Radu Gheorghe for the doc contribution. +- omelasticsearch: _id field support for bulk operations + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=392 + Thanks to Jérôme Renard for the idea and patches. +- max number of templates for plugin use has been increased to five +- platform compatibility enhancement: solve compile issue with libgcrypt + do not use GCRY_CIPHER_MODE_AESWRAP where not available +- fix compile on Solaris + Thanks to Martin Carpenter for the patch. +- bugfix: off-by-one error in handling local FQDN name (regression) + A remporary buffer was allocated one byte too small. Did only + affect startup, not actual operations. Came up during routine tests, + and can have no effect once the engine runs. Bug was introduced in + 7.3.11. +- bugfix: build problems on Solaris + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=436 +- bugfix: block size limit was not properly honored +- bugfix: potential segfault in guardtime signature provider + it could segfault if an error was reported by the GuardTime API, because + an invalid free could happen then +--------------------------------------------------------------------------- +Version 7.3.11 [devel] 2013-04-23 +- added support for encrypting log files +- omhiredis: added support for redis pipeline support + Thanks to Brian Knox for the patch. +- bugfix: $PreserveFQDN is not properly working + Thanks to Louis Bouchard for the patch + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=426 +- bugfix: imuxsock aborted due to problem in ratelimiting code + Thanks to Tomas Heinrich for the patch. +- bugfix: imuxsock aborted under some conditions + regression from ratelimiting enhancements - this was a different one + to the one Tomas Heinrich patched. +- bugfix: timestamp problems in imkmsg +--------------------------------------------------------------------------- +Version 7.3.10 [devel] 2013-04-10 +- added RainerScript re_extract() function +- omrelp: added support for RainerScript-based configuration +- omrelp: added ability to specify session timeout +- templates now permit substring extraction relative to end-of-string +- bugfix: failover/action suspend did not work correctly + This was experienced if the retry action took more than one second + to complete. For suspending, a cached timestamp was used, and if the + retry took longer, that timestamp was already in the past. As a + result, the action never was kept in suspended state, and as such + no failover happened. The suspend functionalit now does no longer use + the cached timestamp (should not have any performance implication, as + action suspend occurs very infrequently). +- bugfix: gnutls RFC5425 driver had some undersized buffers + Thanks to Tomas Heinrich for the patch. +- bugfix: nested if/prifilt conditions did not work properly + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=415 +- bugfix: imuxsock aborted under some conditions + regression from ratelimiting enhancements +- bugfix: build problems on Solaris + Thanks to Martin Carpenter for the patches. +--------------------------------------------------------------------------- +Version 7.3.9 [devel] 2013-03-27 +- support for signing logs added +- imudp: now supports user-selectable inputname +- omlibdbi: now supports transaction interface + if recent enough lbdbi is present +- imuxsock: add ability to NOT create/delete sockets during startup and + shutdown + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=259 +- imfile: errors persisting state file are now reported + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=292 +- imfile: now detects file change when rsyslog was inactive + Previosly, this case could not be detected, so if a file was overwritten + or rotated away while rsyslog was stopped, some data was missing. This + is now detected and the new file being forwarded right from the + beginning. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=228 +- updated systemd files to match current systemd source +- bugfix: imudp scheduling parameters did affect main thread, not imudp + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=409 +- bugfix: build problem on platforms without GLOB_NOMAGIC +- bugfix: build problems on non-Linux platforms +- bugfix: stdout/stderr were not closed on forking + but were closed when running in the forground - this was just reversed + of what it should be. This is a regression of a recent change. +--------------------------------------------------------------------------- +Version 7.3.8 [devel] 2013-03-18 +- imrelp: now supports listening to IPv4/v6 only instead of always both + build now requires librelp 1.0.2 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=378 +- bugfix: mmanon did not build on some platforms (e.g. Ubuntu) +- bugfix: segfault in expression optimizer + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=423 +- bugfix: imuxsock was missing SysSock.ParseTrusted module parameter + To use that functionality, legacy rsyslog.conf syntax had to be used. + Also, the doc was missing information on the "ParseTrusted" set of + config directives. +- bugfix: include files got included in the wrong order + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=411 + This happens if an $IncludeConfig directive was done on multiple + files (e.g. the distro default of $IncludeConfig /etc/rsyslog.d/*.conf). + In that case, the order of include file processing is reversed, which + could lead to all sorts of problems. + Thanks to Nathan Stratton Treadway for his great analysis of the problem, + which made bug fixing really easy. +--------------------------------------------------------------------------- +Version 7.3.7 [devel] 2013-03-12 +- add support for anonymizing IPv4 addresses +- add support for writing to the Linux Journal (omjournal) +- imuxsock: add capability to ignore messages from ourselfes + This helps prevent message routing loops, and is vital to have + if omjournal is used together with traditional syslog. +- field() function now supports a string as field delimiter +- added ability to configure debug system via rsyslog.conf +- bugfix: imuxsock segfault when system log socket was used +- bugfix: mmjsonparse segfault if new-style config was used +- bugfix: script == comparison did not work properly on JSON objects +- bugfix: field() function did never return "***FIELD NOT FOUND***" + instead it returned "***ERROR in field() FUNCTION***" in that case +--------------------------------------------------------------------------- +Version 7.3.6 [devel] 2013-01-28 +- greatly improved speed of large-array [N]EQ RainerScript comparisons + Thanks to David Lang for a related discussion that inspired the idea + to do this with a much simpler (yet sufficient) approach than orignally + planned for. +- greatly improved speed of DNS cache for large cache sizes +- general performance improvements +- omfile: added stats counters for dynafile caches +- omfile: improved async writing, finally enabled full async write + also fixed a couple of smaller issues along that way +- impstats: added ability to write stats records to local file + and avoid going through the syslog log stream. syslog logging can now + also be turned off (see doc for details). +- bugfix: imklog issued wrong facility in error messages + ...what could lead to problems in other parts of the code +- fix compile problem in imklog +- added capability to output thread-id-to-function debug info + This is a useful debug aid, but nothing of concern for regular users. +--------------------------------------------------------------------------- +Version 7.3.5 [devel] 2012-12-19 +- ommysql: addded batching/transaction support +- enhanced script optimizer to optimize common PRI-based comparisons + These constructs are especially used in SUSE default config files, + but also by many users (as they are more readable than the equivalent + PRI-based filter). +- omudpspoof: add support for new config system +- omudpspoof: add support for packets larger than 1472 bytes + On Ethernet, they need to be transmitted in multiple fragments. While + it is known that fragmentation can cause issues, it is the best choice + to be made in that case. Also improved debug output. +- bugfix: omudpspoof failed depending on the execution environment + The v7 engine closes fds, and closed some of libnet's fds as well, what + lead to problems (unfortunately, at least some libnet versions do not + report a proper error state but still "success"...). The order of libnet + calls has been adjusted to by in sync with what the core engine does. +- bugfix: segfault on imuxsock startup if system log socket is used + and no ratelimiting supported. Happens only during initial config + read phase, once this is over, everything works stable. +- bugfix: mmnormalize build problems +- bugfix: mmnormalize could abort rsyslog if config parameter was in error +- bugfix: no error message for invalid string template parameters + rather a malformed template was generated, and error information emitted + at runtime. However, this could be quite confusing. Note that with this + "bugfix" user experience changes: formerly, rsyslog and the affected + actions properly started up, but the actions did not produce proper + data. Now, there are startup error messages and the actions are NOT + executed (due to missing template due to template error). +- bugfix[minor]: invalid error code when mmnormalize could not access + rulebase +- bugfix(kind of): script optimizer did not work for complex boolean + expressions +- doc bugfix: corrections and improvements in mmnormalize html doc page +- bugfix: some message properties could be garbled due to race condition + This happened only on very high volume systems, if the same message was + being processed by two different actions. This was a regression caused + by the new config processor, which did no longer properly enable msg + locking in multithreaded cases. The bugfix is actually a refactoring of + the msg locking code - we no longer do unlocked operations, as the use + case for it has mostly gone away. It is potentially possible only at + very low-end systems, and there the small additional overhead of doing + the locking does not really hurt. Instead, the removal of that + capability can actually slightly improve performance in common cases, + as the code path is smaller and requires slightly less memory writes. + That probably outperforms the extra locking overhead (which in the + low-end case always happens in user space, without need for kernel + support as we can always directly aquire the lock - there is no + contention at all). +- build system cleanup (thanks to Michael Biebl for this!) +- bugfix: omelasticsearch did not properly compile on some platforms + due to missing libmath. Thanks to Michael Biebl for the fix +--------------------------------------------------------------------------- +Version 7.3.4 [devel] 2012-11-23 +- further (and rather drastically) improved disk queue performance + we now save one third of the IO calls +- imklog: added ParseKernelTimestamp parameter (import from 5.10.2) + Thanks to Marius Tomaschewski for the patch. +- imklog: added KeepKernelTimestamp parameter (import from 5.10.2) + Thanks to Marius Tomaschewski for the patch. +- bugfix: improper handling of backslash in string-type template()s +- bugfix: leading quote (") in string-type template() lead to thight loop + on startup +- bugfix: no error msg on invalid field option in legacy/string template +- bugfix: imklog mistakenly took kernel timestamp subseconds as nanoseconds + ... actually, they are microseconds. So the fractional part of the + timestamp was not properly formatted. (import from 5.10.2) + Thanks to Marius Tomaschewski for the bug report and the patch idea. +--------------------------------------------------------------------------- +Version 7.3.3 [devel] 2012-11-07 +- improved disk queue performance +- bugfix: dynafile zip files could be corrupted + This could happen if a dynafile was destructed before the first write. + In practice, this could happen if few lines were written to a file and + it then became evicted from the dynafile cache. This would probably + look very random, because it depended on the timing in regard to + message volume and dynafile cache size. +--------------------------------------------------------------------------- +Version 7.3.2 [devel] 2012-10-30 +- mmnormalize: support for v6+ config interface added +- mmjsonparse: support for v6+ config interface added +--------------------------------------------------------------------------- +Version 7.3.2 [devel] 2012-10-30 +- totally reworked ratelimiting and "last message repeated n times" + all over rsyslog code. Each of the supported inputs now supports + linux-like ratelimiting (formerly only imuxsock did). Also, the + "last message repeated n times" is now processed at the input side + and no longer at the output side of rsyslog processing. This + provides the basis for new future additions as well as usually more + performance and a much simpler output part (which can be even further + refactored). +- imtcp: support for Linux-Type ratelimiting added +- imptcp: support for Linux-Type ratelimiting added +- imudp enhancements: + * support for input batching added (performance improvement) + * support for Linux-Type ratelimiting added +- permited action-like statements (stop, call, ...) in action lists +- bugfix: segfault on startup when modules using MSG_PASSING mode are used +- omelasticsearch: support for writing data errors to local file added +- omelasticsearch: fix check for bulk processing status response +--------------------------------------------------------------------------- +Version 7.3.1 [devel] 2012-10-19 +- optimized template processing performance, especially for $NOW family + of properties +- change lumberjack cookie to "@cee:" from "@cee: " + CEE originally specified the cookie with SP, whereas other lumberjack + tools used it without space. In order to keep interop with lumberjack, + we now use the cookie without space as well. I hope this can be changed + in CEE as well when it is released at a later time. + Thanks to Miloslav TrmaÄ for pointing this out and a similiar v7 patch. +- bugfix: imuxsock and imklog truncated head of received message + This happened only under some circumstances. Thanks to Marius + Tomaschewski, Florian Piekert and Milan Bartos for their help in + solving this issue. +- bugfix: imuxsock did not properly honor $LocalHostIPIF +--------------------------------------------------------------------------- +Version 7.3.0 [devel] 2012-10-09 +- omlibdbi improvements, added + * support for config load phases & module() parameters + * support for default templates + * driverdirectory is now cleanly a global parameter, but can no longer + be specified as an action paramter. Note that in previous versions + this parameter was ignored in all but the first action definition +- improved omfile zip writer to increase compression + This was achieved by somewhat reducing the robustness of the zip archive. + This is controlled by the new action parameter "VeryReliableZip". +---------------------------------------------------------------------------- +Version 7.2.8 [v7-stable] 2013-0?-?? +- bugfix: potential segfault on startup when builtin module was specified + in module() statement. + Thanks to Marius Tomaschewski for reporting the bug. +- bugfix: segfault due to invalid dynafile cache handling + Accidently, the old-style cache size parameter was used when the + dynafile cache was created in a RainerScript action. If the old-style + size was lower than the one actually set, this lead to misadressing + when the size was overrun, and that could lead to all kinds of + "interesting things", often in segfaults. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=440 +---------------------------------------------------------------------------- +Version 7.2.7 [v7-stable] 2013-04-17 +- rsyslogd startup information is now properly conveyed back to init + when privileges are beging dropped + Actually, we have moved termination of the parent in front of the + priv drop. So it shall work now in all cases. See code comments in + commit for more details. +- If forking, the parent now waits for a maximum of 60 seconds for + termination by the child +- improved debugging support in forked (auto-backgrounding) mode + The rsyslog debug log file is now continued to be written across the + fork. +- updated systemd files to match current systemd source +- bugfix: failover/action suspend did not work correctly + This was experienced if the retry action took more than one second + to complete. For suspending, a cached timestamp was used, and if the + retry took longer, that timestamp was already in the past. As a + result, the action never was kept in suspended state, and as such + no failover happened. The suspend functionalit now does no longer use + the cached timestamp (should not have any performance implication, as + action suspend occurs very infrequently). +- bugfix: nested if/prifilt conditions did not work properly + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=415 +- bugfix: script == comparison did not work properly on JSON objects + [backport from 7.3 branch] +- bugfix: imudp scheduling parameters did affect main thread, not imudp + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=409 +- bugfix: imuxsock rate-limiting could not be configured via legacy conf + Rate-limiting for the system socket could not be configured via legacy + configuration directives. However, the new-style RainerScript config + options worked. + Thanks to Milan Bartos for the patch. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=390 +- bugfix: using group resolution could lead to endless loop + Thanks to Tomas Heinrich for the patch. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=310 +- bugfix: $mmnormalizeuseramsg paramter was specified with wrong type + Thank to Renzhong Zhang for alerting us of the problem. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=420 +- bugfix: RainerScript getenv() function caused segfault when var was + not found. + Thanks to Philippe Muller for the patch. +- bugfix: several issues in imkmsg + see bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=421#c8 +- bugfix: imuxsock was missing SysSock.ParseTrusted module parameter + To use that functionality, legacy rsyslog.conf syntax had to be used. + Also, the doc was missing information on the "ParseTrusted" set of + config directives. +- bugfix: parameter action.execOnlyWhenPreviousIsSuspended was accidently + of integer-type. For obvious reasons, it needs to be boolean. Note + that this change can break existing configurations if they circumvented + the problem by using 0/1 values. +- doc bugfix: rsyslog.conf man page had invalid file format info + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=418 +---------------------------------------------------------------------------- +Version 7.2.6 [v7-stable] 2013-03-05 +- slightly improved config parser error messages when invalid escapes happen +- bugfix: include files got included in the wrong order + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=411 + This happens if an $IncludeConfig directive was done on multiple + files (e.g. the distro default of $IncludeConfig /etc/rsyslog.d/*.conf). + In that case, the order of include file processing is reversed, which + could lead to all sorts of problems. + Thanks to Nathan Stratton Treadway for his great analysis of the problem, + which made bug fixing really easy. +- bugfix: omelasticsearch failed when authentication data was provided + ... at least in most cases it emitted an error message: + "snprintf failed when trying to build auth string" + Thanks to Joerg Heinemann for alerting us. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=404 +- bugfix: some property-based filter were incorrectly parsed + This usually lead to a syntax error on startup and rsyslogd not actually + starting up. The problem was the regex, which did not care for double + quote characters to follow in the action part - unfortunately something + that can frequently happen with v6+ format. An example: + :programname, isequal, "as" {action(type="omfile" ...) } + Here, the part + :programname, isequal, "as" {action(type="omfile" + was treated as the property filter, and the rest as action part. + Obviously, this did not work out. Unfortunately, such situations usually + resulted in very hard to understand error messages. +---------------------------------------------------------------------------- +Version 7.2.5 [v7-stable] 2013-01-08 +- build system cleanup (thanks to Michael Biebl for this!) +- bugfix: omelasticsearch did not properly compile on some platforms + due to missing libmath. Thanks to Michael Biebl for the fix +- bugfix: invalid DST handling under Solaris + Thanks to Scott Severtson for the patch. +- bugfix: on termination, actions were incorrectly called + The problem was that incomplete fiter evaluation was done *during the + shutdown phase*. This affected only the LAST batches being processed. No + problem existed during the regular run. Could usually only happen on + very busy systems, which were still busy during shutdown. +- bugfix: very large memory consumption (and probably out of memory) when + FromPos was specified in template, but ToPos not. + Thanks to Radu Gheorghe for alerting us of this bug. +- bugfix: timeval2syslogTime cause problems on some platforms + due to invalid assumption on structure data types. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=394 + Thanks to David Hill for the patch [under ASL2.0 as per email conversation + 2013-01-03]. +- bugfix: compile errors in im3195 + Thanks to Martin Körper for the patch +- bugfix: doGetFileCreateMode() had invalid validity check ;) + Thanks to Chandler Latour for the patch. +- bugfix: mmjsonparse errornously returned action error when no CEE cookie + was present. +---------------------------------------------------------------------------- +Version 7.2.4 [v7-stable] 2012-12-07 +- enhance: permit RFC3339 timestamp in local log socket messages + Thanks to Sebastien Ponce for the patch. +- imklog: added ParseKernelTimestamp parameter (import from 5.10.2) + Thanks to Marius Tomaschewski for the patch. +- fix missing functionality: ruleset(){} could not specify ruleset queue + The "queue.xxx" parameter set was not supported, and legacy ruleset + config statements did not work (by intention). The fix introduces the + "queue.xxx" parameter set. It has some regression potential, but only + for the new functionality. Note that using that interface it is possible + to specify duplicate queue file names, which will cause trouble. This + will be solved in v7.3, because there is a too-large regression + potential for the v7.2 stable branch. +- imklog: added KeepKernelTimestamp parameter (import from 5.10.2) + Thanks to Marius Tomaschewski for the patch. +- bugfix: imklog mistakenly took kernel timestamp subseconds as nanoseconds + ... actually, they are microseconds. So the fractional part of the + timestamp was not properly formatted. (import from 5.10.2) + Thanks to Marius Tomaschewski for the bug report and the patch idea. +- bugfix: supportoctetcountedframing parameter did not work in imptcp +- bugfix: modules not (yet) supporting new conf format were not properly + registered. This lead to a "module not found" error message instead of + the to-be-expected "module does not support new style" error message. + That invalid error message could be quite misleading and actually stop + people from addressing the real problem (aka "go nuts" ;)) +- bugfix: template "type" parameter is mandatory (but was not) +- bugfix: some message properties could be garbled due to race condition + This happened only on very high volume systems, if the same message was + being processed by two different actions. This was a regression caused + by the new config processor, which did no longer properly enable msg + locking in multithreaded cases. The bugfix is actually a refactoring of + the msg locking code - we no longer do unlocked operations, as the use + case for it has mostly gone away. It is potentially possible only at + very low-end systems, and there the small additional overhead of doing + the locking does not really hurt. Instead, the removal of that + capability can actually slightly improve performance in common cases, + as the code path is smaller and requires slightly less memory writes. + That probably outperforms the extra locking overhead (which in the + low-end case always happens in user space, without need for kernel + support as we can always directly aquire the lock - there is no + contention at all). +---------------------------------------------------------------------------- +Version 7.2.3 [v7-stable] 2012-10-21 +- regression fix: rsyslogd terminated when wild-card $IncludeConfig did not + find actual include files. For example, if this directive is present: + $IncludeConfig /etc/rsyslog.d/*.conf + and there are no *.conf files in /etc/rsyslog.d (but rsyslog.d exists), + rsyslogd will emit an error message and terminate. Previous (and expected) + behaviour is that an empty file set is no problem. HOWEVER, if the + directory itself does not exist, this is flagged as an error and will + load to termination (no startup). + Unfortunately, this is often the case by default in many distros, so this + actually prevents rsyslog startup. +---------------------------------------------------------------------------- +Version 7.2.2 [v7-stable] 2012-10-16 +- doc improvements +- enabled to build without libuuid, at loss of uuid functionality + this enables smoother builds on older systems that do not support + libuuid. Loss of functionality should usually not matter too much as + uuid support has only recently been added and is very seldom used. +- bugfix: omfwd did not properly support "template" parameter +- bugfix: potential segfault when re_match() function was used + Thanks to oxpa for the patch. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=371 +- bugfix: potential abort of imtcp on rsyslogd shutdown +- bugfix: imzmq3 segfault with PULL subscription + Thanks to Martin Nilsson for the patch. +- bugfix: improper handling of backslash in string-type template()s +- bugfix: leading quote (") in string-type template() lead to thight loop + on startup +- bugfix: no error msg on invalid field option in legacy/string template +- bugfix: potential segfault due to invalid param handling in comparisons + This could happen in RainerScript comparisons (like contains); in some + cases an unitialized variable was accessed, which could lead to an + invalid free and in turn to a segfault. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=372 + Thanks to Georgi Georgiev for reporting this bug and his great help + in solving it. +- bugfix: no error msg on unreadable $IncludeConfig path +- bugfix: $IncludeConfig did not correctly process directories + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=376 + The testbench was also enhanced to check for these cases. + Thanks to Georgi Georgiev for the bug report. +- bugfix: make rsyslog compile on kfreebsd again + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=380 + Thanks to Guillem Jover for the patch. +- bugfix: garbled message if field name was used with jsonf property option + The length for the field name was invalidly computed, resulting in either + truncated field names or including extra random data. If the random data + contained NULs, the rest of the message became unreadable. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=374 +- bugfix: potential segfault at startup with property-based filter + If the property name was followed by a space before the comma, rsyslogd + aborted on startup. Note that no segfault could happen if the initial + startup went well (this was a problem with the config parser). + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=381 +- bugfix: imfile discarded some file parts + File lines that were incomplete (LF missing) *at the time imfile polled + the file* were partially discarded. That part of the line that was read + without the LF was discarded, and the rest of the line was submitted in + the next polling cycle. This is now changed so that the partial content + is saved until the complete line is read. Note that the patch affects + only read mode 0. + Thanks to Milan Bartos for providing the base idea for the solution. +---------------------------------------------------------------------------- +Version 7.2.1 [v7-stable] 2012-10-29 +- bugfix: ruleset()-object did only support a single statement +- added -D rsyslogd option to enable config parser debug mode +- improved syntax error messages by outputting the error token +- the rsyslog core now suspeneds actions after 10 failures in a row + This was former the case after 1,000 failures and could cause rsyslog + to be spammed/ressources misused. See the v6 compatibility doc for more + details. +- ommongodb rate-limits error messages to prevent spamming the syslog + closes (for v7.2): http://bugzilla.adiscon.com/show_bug.cgi?id=366 +---------------------------------------------------------------------------- +Version 7.2.0 [v7-stable] 2012-10-22 +This starts a new stable branch based on 7.1.12 plus the following changes: +- bugfix: imuxsock did not properly honor $LocalHostIPIF +- omruleset/omdiscard do no longer issue "deprecated" warings, as 7.1 + grammar does not permit to use the replacements under all circumstances +---------------------------------------------------------------------------- +Version 7.1.12 [beta] 2012-10-18 +- minor updates to better support newer systemd developments + Thanks to Michael Biebl for the patches. +- build system cleanup + Thanks to Michael Biebl for the patch series. +- cleanup: removed remains of -c option (compatibility mode) + both from code & doc and emitted warning message if still used + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=361 + Thanks to Michael Biebl for reporting & suggestions +- bugfix: imklog truncated head of received message + This happened only under some circumstances. Thanks to Marius + Tomaschewski and Florian Piekert for their help in solving this issue. +---------------------------------------------------------------------------- +Version 7.1.11 [beta] 2012-10-16 +- bugfix: imuxsock truncated head of received message + This happened only under some circumstances. Thanks to Marius + Tomaschewski, Florian Piekert and Milan Bartos for their help in + solving this issue. +- bugfix: do not crash if set statement is used with date field + Thanks to Miloslav TrmaÄ for the patch. +- change lumberjack cookie to "@cee:" from "@cee: " + CEE originally specified the cookie with SP, whereas other lumberjack + tools used it without space. In order to keep interop with lumberjack, + we now use the cookie without space as well. I hope this can be changed + in CEE as well when it is released at a later time. + Thanks to Miloslav TrmaÄ for pointing this out and a similiar v7 patch. +- added deprecated note to omruleset (plus clue to use "call") +- added deprecated note to discard action (plus clue to use "stop") +--------------------------------------------------------------------------- +Version 7.1.10 [beta] 2012-10-11 + - bugfix: m4 directory was not present in release tarball + - bugfix: small memory leak with string-type templates + - bugfix: small memory leak when template was specified in omfile + - bugfix: some config processing warning messages were treated as errors + - bugfix: small memory leak when processing action() statements + - bugfix: unknown action() parameters were not reported +--------------------------------------------------------------------------- +Version 7.1.9 [beta] 2012-10-09 +- bugfix: comments inside objects (e.g. action()) were not properly handled +- bugfix: in (non)equal comparisons the position of arrays influenced result + This behaviour is OK for "contains"-type of comparisons (which have quite + different semantics), but not for == and <>, which shall be commutative. + This has been fixed now, so there is no difference any longer if the + constant string array is the left or right hand operand. We solved this + via the optimizer, as it keeps the actual script execution code small. +--------------------------------------------------------------------------- +Version 7.1.8 [beta] 2012-10-02 +- bugfix: ruleset(){} directive errornously changed default ruleset + much like the $ruleset legacy conf statement. This potentially lead + to statements being assigned to the wrong ruleset. +- improved module doc +- added "parser" parameter to ruleset(), so that parser chain can be + configured +- implemented "continue" RainerScript statement +--------------------------------------------------------------------------- +Version 7.1.7 [devel] 2012-10-01 +- implemented RainerScript "call" statement +- implemented RainerScript array-based string comparison operations +- implemented imtcp "permittedPeers" module-global parameter +- imudp: support for specifying multiple ports via array added +--------------------------------------------------------------------------- +Version 7.1.6 [devel] 2012-09-28 +- implemented RainerScript input() statement, including support for it + in major input plugins +- implemented RainerScript ruleset() statement +--------------------------------------------------------------------------- +Version 7.1.5 [devel] 2012-09-25 +- implemented RainerScript prifield() function +- implemented RainerScript field() function +- added new module imkmsg to process structured kernel log + Thanks to Milan Bartos for contributing this module +- implemented basic RainerScript optimizer, which will speed up script + operations +- bugfix: invalid free if function re_match() was incorrectly used + if the config file parser detected that param 2 was not constant, some + data fields were not initialized. The destructor did not care about that. + This bug happened only if rsyslog startup was unclean. +--------------------------------------------------------------------------- +Version 7.1.4 [devel] 2012-09-19 +- implemented ability for CEE-based properties to be stored in disk queues +- implemented string concatenation in expressions via &-operator +- implemented json subtree copy in variable assignment +- implemented full JSON support for variable manipulation +- introduced "subtree"-type templates +- bugfix: omfile action did not respect "template" parameter + ... and used default template in all cases +- bugfix: MsgDup() did not copy CEE structure + This function was called at various places, most importantly during + "last messages repeated n times" processing and omruleset. If CEE(JSON) + data was present, it was lost as part of the copy process. +- bugfix: debug output indicated improper queue type +--------------------------------------------------------------------------- +Version 7.1.3 [devel] 2012-09-17 +- introduced "set" and "unset" config statements +- bugfix: missing support for escape sequences in RainerScript + only \' was supported. Now the usual set is supported. Note that v5 + used \x as escape where x was any character (e.g. "\n" meant "n" and NOT + LF). This also means there is some incompatibility to v5 for well-know + sequences. Better break it now than later. +- bugfix: invalid property name in property-filter could cause abort + if action chaining (& operator) was used + http://bugzilla.adiscon.com/show_bug.cgi?id=355 + Thanks to pilou@gmx.com for the bug report +--------------------------------------------------------------------------- +Version 7.1.2 [devel] 2012-09-12 +- bugfix: messages were duplicated, sometimes massively + regression from new code in 7.1.1 and reason for early release +- bugfix: remove invalid socket option call from imuxsock + Thanks to Cristian Ionescu-Idbohrn and Jonny Törnbom +- bugfix: abort when invalid property name was configured + in property-based filter +- bugfix: multiple rulesets did no longer work correctly (7.1.1 regression) +--------------------------------------------------------------------------- +Version 7.1.1 [devel] 2012-09-11 +- MAJOR NEW FEATURE: rulengine now fully supports nesting + including if ... then ... else ... constructs. This is a big change + and it obviously has a lot of bug potential. +- BSD-style (filter) blocks are no longer supported + see http://www.rsyslog.com/g/BSD for details and solution +- imuxsock now stores trusted properties by default in the CEE root + This was done in order to keep compatible with other implementations of + the lumberjack schema + Thanks to Miloslav TrmaÄ for pointing to this. +- bugfix: string-generating templates caused abort if CEE field could not + be found +--------------------------------------------------------------------------- +Version 7.1.0 [devel] 2012-09-06 +- added support for hierarchical properties (CEE/lumberjack) +- added pure JSON output plugin parameter passing mode +- ommongodb now supports templates +- bugfix: imtcp could abort on exit due to invalid free() +- imported bugfixes from 6.4.1 +--------------------------------------------------------------------------- +Version 6.6.1 [v6-stable] 2012-10-?? +- bugfix: build problems on some platforms +- bugfix: misaddressing of $mmnormalizeuserawmsg parameter + On many platforms, this has no effect at all. At some, it may cause + a segfault. The problem occurs only during config phase, no segfault + happens when rsyslog has fully started. +- fix API "glitch" in some plugins + This did not affect users, but could have caused trouble in the future + for developers. +- bugfix: no error msg on invalid field option in legacy/string template +- bugfix: no error msg on unreadable $IncludeConfig path +- bugfix: $IncludeConfig did not correctly process directories + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=376 + The testbench was also enhanced to check for these cases. + Thanks to Georgi Georgiev for the bug report. +- bugfix: spurios error messages from imuxsock about (non-error) EAGAIN + Thanks to Marius Tomaschewski for the patch. +- imklog: added $klogParseKernelTimestamp option + When enabled, kernel message [timestamp] is converted for message time. + Default is to use receive time as in 5.8.x and before, because the clock + used to create the timestamp is not supposed to be as accurate as the + monotonic clock (depends on hardware and kernel) resulting in differences + between kernel and system messages which occurred at same time. + Thanks to Marius Tomaschewski for the patch. +- imklog: added $klogKeepKernelTimestamp option + When enabled, the kernel [timestamp] remains at begin of + each message, even it is used for the message time too. + Thanks to Marius Tomaschewski for the patch. +- bugfix: imklog mistakenly took kernel timestamp subseconds as nanoseconds + ... actually, they are microseconds. So the fractional part of the + timestamp was not properly formatted. + Thanks to Marius Tomaschewski for the bug report and the patch idea. +- bugfix: hostname set in rsyslog.conf was not picked up until HUP + which could also mean "never" or "not for a very long time". + Thanks to oxpa for providing analysis and a patch +- bugfix: some message properties could be garbled due to race condition + This happened only on very high volume systems, if the same message was + being processed by two different actions. This was a regression caused + by the new config processor, which did no longer properly enable msg + locking in multithreaded cases. The bugfix is actually a refactoring of + the msg locking code - we no longer do unlocked operations, as the use + case for it has mostly gone away. It is potentially possible only at + very low-end systems, and there the small additional overhead of doing + the locking does not really hurt. Instead, the removal of that + capability can actually slightly improve performance in common cases, + as the code path is smaller and requires slightly less memory writes. + That probably outperforms the extra locking overhead (which in the + low-end case always happens in user space, without need for kernel + support as we can always directly aquire the lock - there is no + contention at all). +- bugfix: invalid DST handling under Solaris + Thanks to Scott Severtson for the patch. +--------------------------------------------------------------------------- +Version 6.6.0 [v6-stable] 2012-10-22 +This starts a new stable branch, based on the 6.5.x series, plus: +- bugfix: imuxsock did not properly honor $LocalHostIPIF +--------------------------------------------------------------------------- +Version 6.5.1 [beta] 2012-10-11 +- added tool "logctl" to handle lumberjack logs in MongoDB +- imfile ported to new v6 config interface +- imfile now supports config parameter for maximum number of submits + which is a fine-tuning parameter in regard to input baching +- added pure JSON output plugin parameter passing mode +- ommongodb now supports templates +- bugfix: imtcp could abort on exit due to invalid free() +- bugfix: remove invalid socket option call from imuxsock + Thanks to Cristian Ionescu-Idbohrn and Jonny Törnbom +- added pure JSON output plugin parameter passing mode +- ommongodb now supports templates +- bugfix: imtcp could abort on exit due to invalid free() +- bugfix: missing support for escape sequences in RainerScript + only \' was supported. Now the usual set is supported. Note that v5 + used \x as escape where x was any character (e.g. "\n" meant "n" and NOT + LF). This also means there is some incompatibility to v5 for well-know + sequences. Better break it now than later. +- bugfix: small memory leaks in template() statements + these were one-time memory leaks during startup, so they did NOT grow + during runtime +- bugfix: config validation run did not always return correct return state +- bugfix: config errors did not always cause statement to fail + This could lead to startup with invalid parameters. +--------------------------------------------------------------------------- +Version 6.5.0 [devel] 2012-08-28 +- imrelp now supports non-cancel thread termination + (but now requires at least librelp 1.0.1) +- implemented freeCnf() module interface + This was actually not present in older versions, even though some modules + already used it. The implementation was now done, and not in 6.3/6.4 + because the resulting memory leak was ultra-slim and the new interface + handling has some potential to seriously break things. Not the kind of + thing you want to add in late beta state, if avoidable. +- added --enable-debugless configure option for very high demanding envs + This actually at compile time disables a lot of debug code, resulting + in some speedup (but serious loss of debugging capabilities) +- added new 0mq plugins (via czmq lib) + Thanks to David Kelly for contributing these modules +- bugfix: omhdfs did no longer compile +- bugfix: SystemLogSocketAnnotate did not work correctly + Thanks to Miloslav TrmaÄ for the patch +- $SystemLogParseTrusted config file option + Thanks to Milan Bartos for the patch +- added template config directive +- added new uuid message property + Thanks to Jérôme Renard for the idea and patches. + Note: patches were released under ASL 2.0, see + http://bugzilla.adiscon.com/show_bug.cgi?id=353 +--------------------------------------------------------------------------- +Version 6.4.3 [V6-STABLE/NEVER RELEASED] 2012-??-?? +This version was never released as 6.6.0 came quickly enough. Note that +all these patches here are present in 6.6.0. +- cleanup: removed remains of -c option (compatibility mode) + both from code & doc and emitted warning message if still used + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=361 + Thanks to Michael Biebl for reporting & suggestions +- bugfix: imuxsock and imklog truncated head of received message + This happened only under some circumstances. Thanks to Marius + Tomaschewski, Florian Piekert and Milan Bartos for their help in + solving this issue. +- change lumberjack cookie to "@cee:" from "@cee: " + CEE originally specified the cookie with SP, whereas other lumberjack + tools used it without space. In order to keep interop with lumberjack, + we now use the cookie without space as well. I hope this can be changed + in CEE as well when it is released at a later time. + Thanks to Miloslav TrmaÄ for pointing this out and a similiar v7 patch. +- bugfix: comments inside objects (e.g. action()) were not properly handled +- bugfix: sysklogd-emulating standard template was no longer present in v6 + This was obviously lost during the transition to the new config format. + Thanks to Milan Bartos for alerting us and a patch! +- bugfix: some valid legacy PRI filters were flagged as errornous + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=358 + This happend to filters of the style "local0,local1.*", where the + multiple facilities were comma-separated. +- bugfix: imuxsock did not properly honor $LocalHostIPIF +--------------------------------------------------------------------------- +Version 6.4.2 [V6-STABLE] 2012-09-20 +- bugfix: potential abort, if action queue could not be properly started + This most importantly could happen due to configuration errors. +- bugfix: remove invalid socket option call from imuxsock + Thanks to Cristian Ionescu-Idbohrn and Jonny Törnbom +- bugfix: missing support for escape sequences in RainerScript + only \' was supported. Now the usual set is supported. Note that v5 + used \x as escape where x was any character (e.g. "\n" meant "n" and NOT + LF). This also means there is some incompatibility to v5 for well-know + sequences. Better break it now than later. +- bugfix: config validation run did not always return correct return state +--------------------------------------------------------------------------- +Version 6.4.1 [V6-STABLE] 2012-09-06 +- bugfix: multiple main queues with same queue file name were not detected + This lead to queue file corruption. While the root cause is a config + error, it is a bug that this important and hard to find config error + was not detected by rsyslog. +- bugfix: "jsonf" property replacer option did generate invalid JSON + in JSON, we have "fieldname":"value", but the option emitted + "fieldname"="value". Interestingly, this was accepted by a couple + of sinks, most importantly elasticsearch. Now the correct format is + emitted, which causes a remote chance that some things that relied on + the wrong format will break. + Thanks to Miloslav TrmaÄ for the patch +- change $!all-json did emit an empty (thus non-JSON) string if no libee + data was present. It now emits {} and thus valid JSON. There is a + small risk that this may break some things that relied on the previous + inconsistency. + Thanks to Miloslav TrmaÄ for the patch +- bugfix: omusrsmsg incorrect return state & config warning handling + During config file processing, Omusrmsg often incorrectly returned a + warning status, even when no warning was present (caused by + uninitialized variable). Also, the core handled warning messages + incorrectly, and treated them as errors. As a result, omusrmsg + (most often) could not properly be loaded. Note that this only + occurs with legacy config action syntax. This was a regression + caused by an incorrect merge in to the 6.3.x codebase. + Thanks to Stefano Mason for alerting us of this bug. +- bugfix: Fixed TCP CheckConnection handling in omfwd.c. Interface needed + to be changed in lower stream classes. Syslog TCP Sending is now resumed + properly. Unfixed, that lead to non-detection of downstate of remote + hosts. +--------------------------------------------------------------------------- +Version 6.4.0 [V6-STABLE] 2012-08-20 +- THIS IS THE FIRST VERSION OF THE 6.4.x STABLE BRANCH + It includes all enhancements made in 6.3.x plus what is written in the + ChangeLog below. +- omelasticsearch: support for parameters parent & dynparent added +- bugfix: imtcp aborted when more than 2 connections were used. + Incremented pthread stack size to 4MB for imtcp, imptcp and imttcp + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=342 +- bugfix: imptcp aborted when $InputPTCPServerBindRuleset was used +- bugfix: problem with cutting first 16 characters from message with + bAnnotate + Thanks to Milan Bartos for the patch. +--------------------------------------------------------------------------- +Version 6.3.12 [BETA] 2012-07-02 +- support for elasticsearch via omelasticsearch added + Note that this module has been tested quite well by a number of folks, + and this is why we merge in new functionality in a late beta stage. + Even if problems would exist, only users of omelasticsearch would + experience them, making it a pretty safe addition. +- bugfix: $ActionName was not properly honored + Thanks to Abby Edwards for alerting us +--------------------------------------------------------------------------- +Version 6.3.11 [BETA] 2012-06-18 +- bugfix: expression-based filters with AND/OR could segfault + due to a problem with boolean shortcut operations. From the user's + perspective, the segfault is almost non-deterministic (it occurs when + a shortcut is used). + Thanks to Lars Peterson for providing the initial bug report and his + support in solving it. +- bugfix: "last message repeated n times" message was missing hostname + Thanks to Zdenek Salvet for finding this bug and to Bodik for reporting +--------------------------------------------------------------------------- +Version 6.3.10 [BETA] 2012-06-04 +- bugfix: delayble source could block action queue, even if there was + a disk queue associated with it. The root cause of this problem was + that it makes no sense to delay messages once they arrive in the + action queue - the "input" that is being held in that case is the main + queue worker, what makes no sense. + Thanks to Marcin for alerting us on this problem and providing + instructions to reproduce it. +- bugfix: invalid free in imptcp could lead to abort during startup +- bugfix: if debug message could end up in log file when forking + if rsyslog was set to auto-background (thus fork, the default) and debug + mode to stdout was enabled, debug messages ended up in the first log file + opened. Currently, stdout logging is completely disabled in forking mode + (but writing to the debug log file is still possible). This is a change + in behaviour, which is under review. If it causes problems to you, + please let us know. + Thanks to Tomas Heinrich for the patch. +- bugfix: --enable-smcustbindcdr configure directive did not work + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=330 + Thanks to Ultrabug for the patch. +- bugfix: made rsyslog compile when libestr ist not installed in /usr + Thanks to Miloslav TrmaÄ for providing patches and suggestions +--------------------------------------------------------------------------- +Version 6.3.9 [BETA] 2012-05-22 +- bugfix: imtcp could cause hang during reception + this also applied to other users of core file tcpsrv.c, but imtcp was + by far the most prominent and widely-used, the rest rather exotic + (like imdiag) +- added capability to specify substrings for field extraction mode +- added the "jsonf" property replacer option (and fieldname) +- bugfix: omudpspoof did not work correctly if no spoof hostname was + configured +- bugfix: property replacer option "json" could lead to content loss + message was truncated if escaping was necessary +- bugfix: assigned ruleset was lost when using disk queues + This looked quite hard to diagnose for disk-assisted queues, as the + pure memory part worked well, but ruleset info was lost for messages + stored inside the disk queue. +- bugfix/imuxsock: solving abort if hostname was not set; configured + hostname was not used (both merge regressions) + -bugfix/omfile: template action parameter was not accepted + (and template name set to "??" if the parameter was used) + Thanks to Brian Knox for alerting us on this bug. +- bugfix: ommysql did not properly init/exit the mysql runtime library + this could lead to segfaults. Triggering condition: multiple action + instances using ommysql. Thanks to Tomas Heinrich for reporting this + problem and providing an initial patch (which my solution is based on, + I need to add more code to clean the mess up). +- bugfix: rsyslog did not terminate when delayable inputs were blocked + due to unvailable sources. Fixes: + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + Thanks to Marcin M for bringing up this problem and Andre Lorbach + for helping to reproduce and fix it. +- added capability to specify substrings for field extraction mode +- bugfix: disk queue was not persisted on shutdown, regression of fix to + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + The new code also handles the case of shutdown of blocking light and + full delayable sources somewhat smarter and permits, assuming sufficient + timouts, to persist message up to the max queue capacity. Also some nits + in debug instrumentation have been fixed. +--------------------------------------------------------------------------- +Version 6.3.8 [DEVEL] 2012-04-16 +- added $PStatJSON directive to permit stats records in JSON format +- added "date-unixtimestamp" property replacer option to format as a + unix timestamp (seconds since epoch) +- added "json" property replacer option to support JSON encoding on a + per-property basis +- added omhiredis (contributed module) +- added mmjsonparse to support recognizing and parsing JSON enhanced syslog + messages +- upgraded more plugins to support the new v6 config format: + - ommysql + - omlibdbi + - omsnmp +- added configuration directives to customize queue light delay marks + $MainMsgQueueLightDelayMark, $ActionQueueLightDelayMark; both + specify number of messages starting at which a delay happens. +- added message property parsesuccess to indicate if the last run + higher-level parser could successfully parse the message or not + (see property replacer html doc for details) +- bugfix: abort during startup when rsyslog.conf v6+ format was used in + a certain way +- bugfix: property $!all-json made rsyslog abort if no normalized data + was available +- bugfix: memory leak in array passing output module mode +- added configuration directives to customize queue light delay marks +- permit size modifiers (k,m,g,...) in integer config parameters + Thanks to Jo Rhett for the suggestion. +- bugfix: hostname was not requeried on HUP + Thanks to Per Jessen for reporting this bug and Marius Tomaschewski for + his help in testing the fix. +- bugfix: imklog invalidly computed facility and severity + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=313 +- added configuration directive to disable octet-counted framing + for imtcp, directive is $InputTCPServerSupportOctetCountedFraming + for imptcp, directive is $InputPTCPServerSupportOctetCountedFraming +- added capability to use a local interface IP address as fromhost-ip for + locally originating messages. New directive $LocalHostIPIF +--------------------------------------------------------------------------- +Version 6.3.7 [DEVEL] 2012-02-02 +- imported refactored v5.9.6 imklog linux driver, now combined with BSD + driver +- removed imtemplate/omtemplate template modules, as this was waste of time + The actual input/output modules are better copy templates. Instead, the + now-removed modules cost time for maintenance AND often caused confusion + on what their role was. +- added a couple of new stats objects +- improved support for new v6 config system. The build-in output modules + now all support the new config language +- bugfix: facility local<x> was not correctly interpreted in legacy filters + Was only accepted if it was the first PRI in a multi-filter PRI. + Thanks to forum user Mark for bringing this to our attention. +- bugfix: potential abort after reading invalid X.509 certificate + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=290 + Thanks to Tomas Heinrich for the patch +- bufgix: legacy parsing of some filters did not work correctly +- bugfix: rsyslog aborted during startup if there is an error in loading + an action and legacy configuration mode is used +- bugfix: bsd klog driver did no longer compile +- relicensed larger parts of the code under Apache (ASL) 2.0 +--------------------------------------------------------------------------- +Version 6.3.6 [DEVEL] 2011-09-19 +- added $InputRELPServerBindRuleset directive to specify rulesets for RELP +- bugfix: config parser did not support properties with dashes in them + inside property-based filters. Thanks to Gerrit Seré for reporting this. +--------------------------------------------------------------------------- +Version 6.3.5 [DEVEL] (rgerhards/al), 2011-09-01 +- bugfix/security: off-by-two bug in legacy syslog parser, CVE-2011-3200 +- bugfix: mark message processing did not work correctly +- imudp&imtcp now report error if no listener at all was defined + Thanks to Marcin for suggesting this error message. +- bugfix: potential misadressing in property replacer +--------------------------------------------------------------------------- +Version 6.3.4 [DEVEL] (rgerhards), 2011-08-02 +- added support for action() config object + * in rsyslog core engine + * in omfile + * in omusrmsg +- bugfix: omusrmsg format usr1,usr2 was no longer supported +- bugfix: misaddressing in config handler + In theory, can cause segfault, in practice this is extremely unlikely + Thanks to Marcin for alertig me. +--------------------------------------------------------------------------- +Version 6.3.3 [DEVEL] (rgerhards), 2011-07-13 +- rsyslog.conf format: now parsed by RainerScript parser + this provides the necessary base for future enhancements as well as some + minor immediate ones. For details see: + http://blog.gerhards.net/2011/07/rsyslog-633-config-format-improvements.html +- performance of script-based filters notably increased +- removed compatibility mode as we expect people have adjusted their + confs by now +- added support for the ":omfile:" syntax for actions +--------------------------------------------------------------------------- +Version 6.3.2 [DEVEL] (rgerhards), 2011-07-06 +- added support for the ":omusrmsg:" syntax in configuring user messages +- systemd support: set stdout/stderr to null - thx to Lennart for the patch +- added support for obtaining timestamp for kernel message from message + If the kernel time-stamps messages, time is now take from that + timestamp instead of the system time when the message was read. This + provides much better accuracy. Thanks to Lennart Poettering for + suggesting this feature and his help during implementation. +- added support for obtaining timestamp from system for imuxsock + This permits to read the time a message was submitted to the system + log socket. Most importantly, this is provided in microsecond resolution. + So we are able to obtain high precision timestampis even for messages + that were - as is usual - not formatted with them. This also simplifies + things in regard to local time calculation in chroot environments. + Many thanks to Lennart Poettering for suggesting this feature, + providing some guidance on implementing it and coordinating getting the + necessary support into the Linux kernel. +- bugfix: timestamp was incorrectly calculated for timezones with minute + offset + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=271 +- bugfix: memory leak in imtcp & subsystems under some circumstances + This leak is tied to error conditions which lead to incorrect cleanup + of some data structures. +--------------------------------------------------------------------------- +Version 6.3.1 [DEVEL] (rgerhards), 2011-06-07 +- added a first implementation of a DNS name cache + this still has a couple of weaknesses, like no expiration of entries, + suboptimal algorithms -- but it should perform much better than + what we had previously. Implementation will be improved based on + feedback during the next couple of releases +--------------------------------------------------------------------------- +Version 6.3.0 [DEVEL] (rgerhards), 2011-06-01 +- introduced new config system + http://blog.gerhards.net/2011/06/new-rsyslog-config-system-materializes.html +--------------------------------------------------------------------------- +Version 6.2.2 [v6-stable], 2012-06-13 +- build system improvements and spec file templates + Thanks to Abby Edwards for providing these enhancements +- bugfix: disk queue was not persisted on shutdown, regression of fix to + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + The new code also handles the case of shutdown of blocking light and + full delayable sources somewhat smarter and permits, assuming sufficient + timouts, to persist message up to the max queue capacity. Also some nits + in debug instrumentation have been fixed. +- bugfix: --enable-smcustbindcdr configure directive did not work + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=330 + Thanks to Ultrabug for the patch. +- add small delay (50ms) after sending shutdown message + There seem to be cases where the shutdown message is otherwise not + processed, not even on an idle system. Thanks to Marcin for + bringing this problem up. +- support for resolving huge groups + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=310 + Thanks to Alec Warner for the patch +- bugfix: potential hang due to mutex deadlock + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=316 + Thanks to Andreas Piesk for reporting&analyzing this bug as well as + providing patches and other help in resolving it. +- bugfix: property PROCID empty instead of proper nilvalue if not present + If it is not present, it must have the nilvalue "-" as of RFC5424 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=332 + Thanks to John N for reporting this issue. +- bugfix: did not compile under solaris due to $uptime property code + For the time being, $uptime is not supported on Solaris +- bugfix: "last message repeated n times" message was missing hostname + Thanks to Zdenek Salvet for finding this bug and to Bodik for reporting +--------------------------------------------------------------------------- +Version 6.2.1 [v6-stable], 2012-05-10 +- change plugin config interface to be compatible with pre-v6.2 system + The functionality was already removed (because it is superseeded by the + v6.3+ config language), but code was still present. I have now removed + those parts that affect interface. Full removal will happen in v6.3, in + order to limit potential regressions. However, it was considered useful + enough to do the interface change in v6-stable; this also eases merging + branches! +- re-licensed larger parts of the codebase under the Apache license 2.0 +- bugfix: omprog made rsyslog abort on startup if not binary to + execute was configured +- bugfix: imklog invalidly computed facility and severity + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=313 +- bugfix: stopped DA queue was never processed after a restart due to a + regression from statistics module +- bugfix: memory leak in array passing output module mode +- bugfix: ommysql did not properly init/exit the mysql runtime library + this could lead to segfaults. Triggering condition: multiple action + instances using ommysql. Thanks to Tomas Heinrich for reporting this + problem and providing an initial patch (which my solution is based on, + I need to add more code to clean the mess up). +- bugfix: rsyslog did not terminate when delayable inputs were blocked + due to unvailable sources. Fixes: + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + Thanks to Marcin M for bringing up this problem and Andre Lorbach + for helping to reproduce and fix it. +- bugfix/tcpflood: sending small test files did not work correctly +--------------------------------------------------------------------------- +Version 6.2.0 [v6-stable], 2012-01-09 +- bugfix (kind of): removed numerical part from pri-text + see v6 compatibility document for reasons +- bugfix: race condition when extracting program name, APPNAME, structured + data and PROCID (RFC5424 fields) could lead to invalid characters e.g. + in dynamic file names or during forwarding (general malfunction of these + fields in templates, mostly under heavy load) +- bugfix: imuxsock did no longer ignore message-provided timestamp, if + so configured (the *default*). Lead to no longer sub-second timestamps. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=281 +- bugfix: omfile returns fatal error code for things that go really wrong + previously, RS_RET_RESUME was returned, which lead to a loop inside the + rule engine as omfile could not really recover. +- bugfix: rsyslogd -v always said 64 atomics were not present + thanks to mono_matsuko for the patch +- bugfix: potential abort after reading invalid X.509 certificate + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=290 + Thanks to Tomas Heinrich for the patch +- enhanced module loader to not rely on PATH_MAX +- imuxsock: added capability to "annotate" messages with "trusted + information", which contains some properties obtained from the system + and as such sure to not be faked. This is inspired by the similiar idea + introduced in systemd. +--------------------------------------------------------------------------- +Version 6.1.12 [BETA], 2011-09-01 +- bugfix/security: off-by-two bug in legacy syslog parser, CVE-2011-3200 +- bugfix: mark message processing did not work correctly +- bugfix: potential misadressing in property replacer +- bugfix: memcpy overflow can occur in allowed sender checkig + if a name is resolved to IPv4-mapped-on-IPv6 address + Found by Ismail Dönmez at suse +- bugfix: The NUL-Byte for the syslogtag was not copied in MsgDup (msg.c) +- bugfix: fixed incorrect state handling for Discard Action (transactions) + Note: This caused all messages in a batch to be set to COMMITTED, + even if they were discarded. +--------------------------------------------------------------------------- +Version 6.1.11 [BETA] (rgerhards), 2011-07-11 +- systemd support: set stdout/stderr to null - thx to Lennart for the patch +- added support for the ":omusrmsg:" syntax in configuring user messages +- added support for the ":omfile:" syntax in configuring user messages +--------------------------------------------------------------------------- +Version 6.1.10 [BETA] (rgerhards), 2011-06-22 +- bugfix: problems in failover action handling + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=270 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=254 +- bugfix: mutex was invalidly left unlocked during action processing + At least one case where this can occur is during thread shutdown, which + may be initiated by lower activity. In most cases, this is quite + unlikely to happen. However, if it does, data structures may be + corrupted which could lead to fatal failure and segfault. I detected + this via a testbench test, not a user report. But I assume that some + users may have had unreproducable aborts that were cause by this bug. +--------------------------------------------------------------------------- +Version 6.1.9 [BETA] (rgerhards), 2011-06-14 +- bugfix: problems in failover action handling + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=270 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=254 +- bugfix: mutex was invalidly left unlocked during action processing + At least one case where this can occur is during thread shutdown, which + may be initiated by lower activity. In most cases, this is quite + unlikely to happen. However, if it does, data structures may be + corrupted which could lead to fatal failure and segfault. I detected + this via a testbench test, not a user report. But I assume that some + users may have had unreproducable aborts that were cause by this bug. +- bugfix/improvement:$WorkDirectory now gracefully handles trailing slashes +- bugfix: memory leak in imtcp & subsystems under some circumstances + This leak is tied to error conditions which lead to incorrect cleanup + of some data structures. [backport from v6.3] +- bugfix: $ActionFileDefaultTemplate did not work + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=262 +--------------------------------------------------------------------------- +Version 6.1.8 [BETA] (rgerhards), 2011-05-20 +- official new beta version (note that in a sense 6.1.7 was already beta, + so we may release the first stable v6 earlier than usual) +- new module mmsnmptrapd, a sample message modification module +- import of minor bug fixes from v4 & v5 +--------------------------------------------------------------------------- +Version 6.1.7 [DEVEL] (rgerhards), 2011-04-15 +- added log classification capabilities (via mmnormalize & tags) +- speeded up tcp forwarding by reducing number of API calls + this especially speeds up TLS processing +- somewhat improved documentation index +- bugfix: enhanced imudp config processing code disabled due to wrong + merge (affected UDP realtime capabilities) +- bugfix (kind of): memory leak with tcp reception epoll handler + This was an extremely unlikely leak and, if it happend, quite small. + Still it is better to handle this border case. +- bugfix: IPv6-address could not be specified in omrelp + this was due to improper parsing of ":" + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=250 +- bugfix: do not open files with full privileges, if privs will be dropped + This make the privilege drop code more bulletproof, but breaks Ubuntu's + work-around for log files created by external programs with the wrong + user and/or group. Note that it was long said that this "functionality" + would break once we go for serious privilege drop code, so hopefully + nobody still depends on it (and, if so, they lost...). +- bugfix: pipes not opened in full priv mode when privs are to be dropped +--------------------------------------------------------------------------- +Version 6.1.6 [DEVEL] (rgerhards), 2011-03-14 +- enhanced omhdfs to support batching mode. This permits to increase + performance, as we now call the HDFS API with much larger message + sizes and far more infrequently +- improved testbench + among others, life tests for ommysql (against a test database) have + been added, valgrind-based testing enhanced, ... +- bugfix: minor memory leak in omlibdbi (< 1k per instance and run) +- bugfix: (regression) omhdfs did no longer compile +- bugfix: omlibdbi did not use password from rsyslog.con + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=203 +- systemd support somewhat improved (can now take over existing log sockt) +- bugfix: discard action did not work under some circumstances + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=217 +- bugfix: file descriptor leak in gnutls netstream driver + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=222 +- fixed compile problem in imtemplate + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=235 +--------------------------------------------------------------------------- +Version 6.1.5 [DEVEL] (rgerhards), 2011-03-04 +- improved testbench +- enhanced imtcp to use a pool of worker threads to process incoming + messages. This enables higher processing rates, especially in the TLS + case (where more CPU is needed for the crypto functions) +- added support for TLS (in anon mode) to tcpflood +- improved TLS error reporting +- improved TLS startup (Diffie-Hellman bits do not need to be generated, + as we do not support full anon key exchange -- we always need certs) +- bugfix: fixed a memory leak and potential abort condition + this could happen if multiple rulesets were used and some output batches + contained messages belonging to more than one ruleset. + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=226 + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=218 +- bugfix: memory leak when $RepeatedMsgReduction on was used + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=225 +- bugfix: potential abort condition when $RepeatedMsgReduction set to on + as well as potentially in a number of other places where MsgDup() was + used. This only happened when the imudp input module was used and it + depended on name resolution not yet had taken place. In other words, + this was a strange problem that could lead to hard to diagnose + instability. So if you experience instability, chances are good that + this fix will help. +--------------------------------------------------------------------------- +Version 6.1.4 [DEVEL] (rgerhards), 2011-02-18 +- bugfix/omhdfs: directive $OMHDFSFileName rendered unusable + due to a search and replace-induced bug ;) +- bugfix: minor race condition in action.c - considered cosmetic + This is considered cosmetic as multiple threads tried to write exactly + the same value into the same memory location without sync. The method + has been changed so this can no longer happen. +- added pmsnare parser module (written by David Lang) +- enhanced imfile to support non-cancel input termination +- improved systemd socket activation thanks to Marius Tomaschewski +- improved error reporting for $WorkDirectory + non-existance and other detectable problems are now reported, + and the work directory is NOT set in this case +- bugfix: pmsnare causded abort under some conditions +- bugfix: abort if imfile reads file line of more than 64KiB + Thanks to Peter Eisentraut for reporting and analysing this problem. + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=221 +- bugfix: queue engine did not properly slow down inputs in FULL_DELAY mode + when in disk-assisted mode. This especially affected imfile, which + created unnecessarily queue files if a large set of input file data was + to process. +- bugfix: very long running actions could prevent shutdown under some + circumstances. This has now been solved, at least for common + situations. +- bugfix: fixed compile problem due to empty structs + this occured only on some platforms/compilers. thanks to Dražen KaÄar + for the fix +--------------------------------------------------------------------------- +Version 6.1.3 [DEVEL] (rgerhards), 2011-02-01 +- experimental support for monogodb added +- added $IMUDPSchedulingPolicy and $IMUDPSchedulingPriority config settings +- added $LocalHostName config directive +- improved tcpsrv performance by enabling multiple-entry epoll + so far, we always pulled a single event from the epoll interface. + Now 128, what should result in performance improvement (less API + calls) on busy systems. Most importantly affects imtcp. +- imptcp now supports non-cancel termination mode, a plus in stability +- imptcp speedup: multiple worker threads can now be used to read data +- new directive $InputIMPTcpHelperThreads added +- bugfix: fixed build problems on some platforms + namely those that have 32bit atomic operations but not 64 bit ones +- bugfix: local hostname was pulled too-early, so that some config + directives (namely FQDN settings) did not have any effect +- enhanced tcpflood to support multiple sender threads + this is required for some high-throughput scenarios (and necessary to + run some performance tests, because otherwise the sender is too slow). +- added some new custom parsers (snare, aix, some Cisco "specialities") + thanks to David Lang +--------------------------------------------------------------------------- +Version 6.1.2 [DEVEL] (rgerhards), 2010-12-16 +- added experimental support for log normalizaton (via liblognorm) + support for normalizing log messages has been added in the form of + mmnormalize. The core engine (property replacer, filter engine) has + been enhanced to support properties from normalized events. + Note: this is EXPERIMENTAL code. It is currently know that + there are issues if the functionality is used with + - disk-based queues + - asynchronous action queues + You can not use the new functionality together with these features. + This limitation will be removed in later releases. However, we + preferred to release early, so that one can experiment with the new + feature set and accepted the price that this means the full set of + functionality is not yet available. If not used together with + these features, log normalizing should be pretty stable. +- enhanced testing tool tcpflood + now supports sending via UDP and the capability to run multiple + iterations and generate statistics data records +- bugfix: potential abort when output modules with different parameter + passing modes were used in configured output modules +--------------------------------------------------------------------------- +Version 6.1.1 [DEVEL] (rgerhards), 2010-11-30 +- bugfix(important): problem in TLS handling could cause rsyslog to loop + in a tight loop, effectively disabling functionality and bearing the + risk of unresponsiveness of the whole system. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=194 +- support for omhdfs officially added (import from 5.7.1) +- merged imuxsock improvements from 5.7.1 (see there) +- support for systemd officially added (import from 5.7.0) +- bugfix: a couple of problems that imfile had on some platforms, namely + Ubuntu (not their fault, but occured there) +- bugfix: imfile utilizes 32 bit to track offset. Most importantly, + this problem can not experienced on Fedora 64 bit OS (which has + 64 bit long's!) +- a number of other bugfixes from older versions imported +--------------------------------------------------------------------------- +Version 6.1.0 [DEVEL] (rgerhards), 2010-08-12 + +*********************************** NOTE ********************************** +The v6 versions of rsyslog feature a greatly redesigned config system +which, among others, supports scoping. However, the initial version does +not contain the whole new system. Rather it will evolve. So it is +expected that interfaces, even new ones, break during the initial +6.x.y releases. +*********************************** NOTE ********************************** + +- added $Begin, $End and $ScriptScoping config scope statments + (at this time for actions only). +- added imptcp, a simplified, Linux-specific and potentielly fast + syslog plain tcp input plugin (NOT supporting TLS!) + [ported from v4] +--------------------------------------------------------------------------- +Version 5.10.2 [V5-STABLE], 201?-??-?? +- updated systemd files to match current systemd source +- bugfix: spurios error messages from imuxsock about (non-error) EAGAIN + Thanks to Marius Tomaschewski for the patch. +- imklog: added $klogParseKernelTimestamp option + When enabled, kernel message [timestamp] is converted for message time. + Default is to use receive time as in 5.8.x and before, because the clock + used to create the timestamp is not supposed to be as accurate as the + monotonic clock (depends on hardware and kernel) resulting in differences + between kernel and system messages which occurred at same time. + Thanks to Marius Tomaschewski for the patch. +- imklog: added $klogKeepKernelTimestamp option + When enabled, the kernel [timestamp] remains at begin of + each message, even it is used for the message time too. + Thanks to Marius Tomaschewski for the patch. +- bugfix: imklog mistakenly took kernel timestamp subseconds as nanoseconds + ... actually, they are microseconds. So the fractional part of the + timestamp was not properly formatted. + Thanks to Marius Tomaschewski for the bug report and the patch idea. +- imklog: added $klogKeepKernelTimestamp option + When enabled, the kernel [timestamp] remains at begin of + each message, even it is used for the message time too. + Thanks to Marius Tomaschewski for the patch. +- bugfix: imklog mistakenly took kernel timestamp subseconds as nanoseconds + ... actually, they are microseconds. So the fractional part of the + timestamp was not properly formatted. + Thanks to Marius Tomaschewski for the bug report and the patch idea. +- bugfix: invalid DST handling under Solaris + Thanks to Scott Severtson for the patch. +- bugfix: invalid decrement in pm5424 could lead to log truncation + Thanks to Tomas Heinrich for the patch. +- bugfix[kind of]: omudpspoof discarded messages >1472 bytes (MTU size) + it now truncates these message, but ensures they are sent. Note that + 7.3.5+ will switch to fragmented UDP messages instead (up to 64K) +--------------------------------------------------------------------------- +Version 5.10.1 [V5-STABLE], 2012-10-17 +- bugfix: imuxsock and imklog truncated head of received message + This happened only under some circumstances. Thanks to Marius + Tomaschewski, Florian Piekert and Milan Bartos for their help in + solving this issue. +- enable DNS resolution in imrelp + Thanks to Apollon Oikonomopoulos for the patch +- bugfix: invalid property name in property-filter could cause abort + if action chaining (& operator) was used + http://bugzilla.adiscon.com/show_bug.cgi?id=355 + Thanks to pilou@gmx.com for the bug report +- bugfix: remove invalid socket option call from imuxsock + Thanks to Cristian Ionescu-Idbohrn and Jonny Törnbom +- bugfix: fixed wrong bufferlength for snprintf in tcpflood.c when using + the -f (dynafiles) option. +- fixed issues in build system (namely related to cust1 dummy plugin) +--------------------------------------------------------------------------- +Version 5.10.0 [V5-STABLE], 2012-08-23 + +NOTE: this is the new rsyslog v5-stable, incorporating all changes from the + 5.9.x series. In addition to that, it contains the fixes and + enhancements listed below in this entry. + +- bugfix: delayble source could block action queue, even if there was + a disk queue associated with it. The root cause of this problem was + that it makes no sense to delay messages once they arrive in the + action queue - the "input" that is being held in that case is the main + queue worker, what makes no sense. + Thanks to Marcin for alerting us on this problem and providing + instructions to reproduce it. +- bugfix: disk queue was not persisted on shutdown, regression of fix to + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + The new code also handles the case of shutdown of blocking light and + full delayable sources somewhat smarter and permits, assuming sufficient + timouts, to persist message up to the max queue capacity. Also some nits + in debug instrumentation have been fixed. +- add small delay (50ms) after sending shutdown message + There seem to be cases where the shutdown message is otherwise not + processed, not even on an idle system. Thanks to Marcin for + bringing this problem up. +- support for resolving huge groups + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=310 + Thanks to Alec Warner for the patch +- bugfix: potential hang due to mutex deadlock + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=316 + Thanks to Andreas Piesk for reporting&analyzing this bug as well as + providing patches and other help in resolving it. +- bugfix: property PROCID empty instead of proper nilvalue if not present + If it is not present, it must have the nilvalue "-" as of RFC5424 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=332 + Thanks to John N for reporting this issue. +- bugfix: "last message repeated n times" message was missing hostname + Thanks to Zdenek Salvet for finding this bug and to Bodik for reporting +- bugfix: multiple main queues with same queue file name was not detected + This lead to queue file corruption. While the root cause is a config + error, it is a bug that this important and hard to find config error + was not detected by rsyslog. +--------------------------------------------------------------------------- +Version 5.9.7 [V5-BETA], 2012-05-10 +- added capability to specify substrings for field extraction mode +- bugfix: ommysql did not properly init/exit the mysql runtime library + this could lead to segfaults. Triggering condition: multiple action + instances using ommysql. Thanks to Tomas Heinrich for reporting this + problem and providing an initial patch (which my solution is based on, + I need to add more code to clean the mess up). +- bugfix: rsyslog did not terminate when delayable inputs were blocked + due to unvailable sources. Fixes: + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + Thanks to Marcin M for bringing up this problem and Andre Lorbach + for helping to reproduce and fix it. +- bugfix/tcpflood: sending small test files did not work correctly +--------------------------------------------------------------------------- +Version 5.9.6 [V5-BETA], 2012-04-12 +- added configuration directives to customize queue light delay marks +- permit size modifiers (k,m,g,...) in integer config parameters + Thanks to Jo Rhett for the suggestion. +- bugfix: hostname was not requeried on HUP + Thanks to Per Jessen for reporting this bug and Marius Tomaschewski for + his help in testing the fix. +- bugfix: imklog invalidly computed facility and severity + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=313 +- bugfix: imptcp input name could not be set + config directive was accepted, but had no effect +- added configuration directive to disable octet-counted framing + for imtcp, directive is $InputTCPServerSupportOctetCountedFraming + for imptcp, directive is $InputPTCPServerSupportOctetCountedFraming +- added capability to use a local interface IP address as fromhost-ip for + locally originating messages. New directive $LocalHostIPIF +- added configuration directives to customize queue light delay marks + $MainMsgQueueLightDelayMark, $ActionQueueLightDelayMark; both + specify number of messages starting at which a delay happens. +--------------------------------------------------------------------------- +Version 5.9.5 [V5-DEVEL], 2012-01-27 +- improved impstats subsystem, added many new counters +- enhanced module loader to not rely on PATH_MAX +- refactored imklog linux driver, now combined with BSD driver + The Linux driver no longer supports outdated kernel symbol resolution, + which was disabled by default for very long. Also overall cleanup, + resulting in much smaller code. Linux and BSD are now covered by a + single small driver. +- $IMUXSockRateLimitInterval DEFAULT CHANGED, was 5, now 0 + The new default turns off rate limiting. This was chosen as people + experienced problems with rate-limiting activated by default. Now it + needs an explicit opt-in by setting this parameter. + Thanks to Chris Gaffney for suggesting to make it opt-in; thanks to + many unnamed others who already had complained at the time Chris made + the suggestion ;-) +--------------------------------------------------------------------------- +Version 5.9.4 [V5-DEVEL], 2011-11-29 +- imuxsock: added capability to "annotate" messages with "trusted + information", which contains some properties obtained from the system + and as such sure to not be faked. This is inspired by the similiar idea + introduced in systemd. +- removed dependency on gcrypt for recently-enough GnuTLS + see: http://bugzilla.adiscon.com/show_bug.cgi?id=289 +- bugfix: imuxsock did no longer ignore message-provided timestamp, if + so configured (the *default*). Lead to no longer sub-second timestamps. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=281 +- bugfix: omfile returns fatal error code for things that go really wrong + previously, RS_RET_RESUME was returned, which lead to a loop inside the + rule engine as omfile could not really recover. +- bugfix: rsyslogd -v always said 64 atomics were not present + thanks to mono_matsuko for the patch +--------------------------------------------------------------------------- +Version 5.9.3 [V5-DEVEL], 2011-09-01 +- bugfix/security: off-by-two bug in legacy syslog parser, CVE-2011-3200 +- bugfix: mark message processing did not work correctly +- added capability to emit config error location info for warnings + otherwise, omusrmsg's warning about new config format was not + accompanied by problem location. +- bugfix: potential misadressing in property replacer +- bugfix: MSGID corruption in RFC5424 parser under some circumstances + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=275 +- bugfix: The NUL-Byte for the syslogtag was not copied in MsgDup (msg.c) +--------------------------------------------------------------------------- +Version 5.9.2 [V5-DEVEL] (rgerhards), 2011-07-11 +- systemd support: set stdout/stderr to null - thx to Lennart for the patch +- added support for the ":omusrmsg:" syntax in configuring user messages +- added support for the ":omfile:" syntax for actions +--------------------------------------------------------------------------- +Version 5.9.1 [V5-DEVEL] (rgerhards), 2011-06-30 +- added support for obtaining timestamp for kernel message from message + If the kernel time-stamps messages, time is now take from that + timestamp instead of the system time when the message was read. This + provides much better accuracy. Thanks to Lennart Poettering for + suggesting this feature and his help during implementation. +- added support for obtaining timestamp from system for imuxsock + This permits to read the time a message was submitted to the system + log socket. Most importantly, this is provided in microsecond resolution. + So we are able to obtain high precision timestampis even for messages + that were - as is usual - not formatted with them. This also simplifies + things in regard to local time calculation in chroot environments. + Many thanks to Lennart Poettering for suggesting this feature, + providing some guidance on implementing it and coordinating getting the + necessary support into the Linux kernel. +- bugfix: timestamp was incorrectly calculated for timezones with minute + offset + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=271 +- bugfix: problems in failover action handling + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=270 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=254 +- bugfix: mutex was invalidly left unlocked during action processing + At least one case where this can occur is during thread shutdown, which + may be initiated by lower activity. In most cases, this is quite + unlikely to happen. However, if it does, data structures may be + corrupted which could lead to fatal failure and segfault. I detected + this via a testbench test, not a user report. But I assume that some + users may have had unreproducable aborts that were cause by this bug. +- bugfix: memory leak in imtcp & subsystems under some circumstances + This leak is tied to error conditions which lead to incorrect cleanup + of some data structures. [backport from v6] +- bugfix/improvement:$WorkDirectory now gracefully handles trailing slashes +--------------------------------------------------------------------------- +Version 5.9.0 [V5-DEVEL] (rgerhards), 2011-06-08 +- imfile: added $InputFileMaxLinesAtOnce directive +- enhanced imfile to support input batching +- added capability for imtcp and imptcp to activate keep-alive packets + at the socket layer. This has not been added to imttcp, as the latter is + only an experimental module, and one which did not prove to be useful. + reference: http://kb.monitorware.com/post20791.html +- added support to control KEEPALIVE settings in imptcp + this has not yet been added to imtcp, but could be done on request. +- $ActionName is now also used for naming of queues in impstats + as well as in the debug output +- bugfix: do not open files with full privileges, if privs will be dropped + This make the privilege drop code more bulletproof, but breaks Ubuntu's + work-around for log files created by external programs with the wrong + user and/or group. Note that it was long said that this "functionality" + would break once we go for serious privilege drop code, so hopefully + nobody still depends on it (and, if so, they lost...). +- bugfix: pipes not opened in full priv mode when privs are to be dropped +- this begins a new devel branch for v5 +- better handling of queue i/o errors in disk queues. This is kind of a + bugfix, but a very intrusive one, this it goes into the devel version + first. Right now, "file not found" is handled and leads to the new + emergency mode, in which disk action is stopped and the queue run + in direct mode. An error message is emited if this happens. +- added support for user-level PRI provided via systemd +- added new config directive $InputTCPFlowControl to select if tcp + received messages shall be flagged as light delayable or not. +- enhanced omhdfs to support batching mode. This permits to increase + performance, as we now call the HDFS API with much larger message + sizes and far more infrequently +- bugfix: failover did not work correctly if repeated msg reduction was on + affected directive was: $ActionExecOnlyWhenPreviousIsSuspended on + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=236 +--------------------------------------------------------------------------- +Version 5.8.13 [V5-stable] 2012-08-22 +- bugfix: DA queue could cause abort +- bugfix: "last message repeated n times" message was missing hostname + Thanks to Zdenek Salvet for finding this bug and to Bodik for reporting +- bugfix "$PreserveFQDN on" was not honored in some modules + Thanks to bodik for reporting this bug. +- bugfix: randomized IP option header in omudpspoof caused problems + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=327 + Thanks to Rick Brown for helping to test out the patch. +- bugfix: potential abort if output plugin logged message during shutdown + note that none of the rsyslog-provided plugins does this + Thanks to bodik and Rohit Prasad for alerting us on this bug and + analyzing it. + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=347 +- bugfix: multiple main queues with same queue file name was not detected + This lead to queue file corruption. While the root cause is a config + error, it is a bug that this important and hard to find config error + was not detected by rsyslog. +--------------------------------------------------------------------------- +Version 5.8.12 [V5-stable] 2012-06-06 +- add small delay (50ms) after sending shutdown message + There seem to be cases where the shutdown message is otherwise not + processed, not even on an idle system. Thanks to Marcin for + bringing this problem up. +- support for resolving huge groups + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=310 + Thanks to Alec Warner for the patch +- bugfix: delayble source could block action queue, even if there was + a disk queue associated with it. The root cause of this problem was + that it makes no sense to delay messages once they arrive in the + action queue - the "input" that is being held in that case is the main + queue worker, what makes no sense. + Thanks to Marcin for alerting us on this problem and providing + instructions to reproduce it. +- bugfix: disk queue was not persisted on shutdown, regression of fix to + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + The new code also handles the case of shutdown of blocking light and + full delayable sources somewhat smarter and permits, assuming sufficient + timouts, to persist message up to the max queue capacity. Also some nits + in debug instrumentation have been fixed. +- bugfix/omudpspoof: problems, including abort, happend when run on + multiple threads. Root cause is that libnet is not thread-safe. + omudpspoof now guards libnet calls with their own mutex. +- bugfix: if debug message could end up in log file when forking + if rsyslog was set to auto-background (thus fork, the default) and debug + mode to stdout was enabled, debug messages ended up in the first log file + opened. Currently, stdout logging is completely disabled in forking mode + (but writing to the debug log file is still possible). This is a change + in behaviour, which is under review. If it causes problems to you, + please let us know. + Thanks to Tomas Heinrich for the patch. +- bugfix/tcpflood: sending small test files did not work correctly +- bugfix: potential hang due to mutex deadlock + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=316 + Thanks to Andreas Piesk for reporting&analyzing this bug as well as + providing patches and other help in resolving it. +- bugfix: property PROCID empty instead of proper nilvalue if not present + If it is not present, it must have the nilvalue "-" as of RFC5424 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=332 + Thanks to John N for reporting this issue. +--------------------------------------------------------------------------- +Version 5.8.11 [V5-stable] 2012-05-03 +- bugfix: ommysql did not properly init/exit the mysql runtime library + this could lead to segfaults. Triggering condition: multiple action + instances using ommysql. Thanks to Tomas Heinrich for reporting this + problem and providing an initial patch (which my solution is based on, + I need to add more code to clean the mess up). +- bugfix: rsyslog did not terminate when delayable inputs were blocked + due to unvailable sources. Fixes: + http://bugzilla.adiscon.com/show_bug.cgi?id=299 + Thanks to Marcin M for bringing up this problem and Andre Lorbach + for helping to reproduce and fix it. +- bugfix: active input in "light delay state" could block rsyslog + termination, at least for prolonged period of time +- bugfix: imptcp input name could not be set + config directive was accepted, but had no effect +- bugfix: assigned ruleset was lost when using disk queues + This looked quite hard to diagnose for disk-assisted queues, as the + pure memory part worked well, but ruleset info was lost for messages + stored inside the disk queue. +- bugfix: hostname was not requeried on HUP + Thanks to Per Jessen for reporting this bug and Marius Tomaschewski for + his help in testing the fix. +- bugfix: inside queue.c, some thread cancel states were not correctly + reset. While this is a bug, we assume it did have no practical effect + because the reset as it was done was set to the state the code actually + had at this point. But better fix this... +--------------------------------------------------------------------------- +Version 5.8.10 [V5-stable] 2012-04-05 +- bugfix: segfault on startup if $actionqueuefilename was missing for disk + queue config + Thanks to Tomas Heinrich for the patch. +- bugfix: segfault if disk-queue was started up with old queue file + Thanks to Tomas Heinrich for the patch. +- bugfix: memory leak in array passing output module mode +--------------------------------------------------------------------------- +Version 5.8.9 [V5-stable] 2012-03-15 +- added tool to recover disk queue if .qi file is missing (recover_qi.pl) + Thanks to Kaiwang Chen for contributing this tool +- bugfix: stopped DA queue was never processed after a restart due to a + regression from statistics module +- added better doc for statsobj interface + Thanks to Kaiwang Chen for his suggestions and analysis in regard to the + stats subsystem. +--------------------------------------------------------------------------- +Version 5.8.8 [V5-stable] 2012-03-05 +- added capability to use a local interface IP address as fromhost-ip for + imuxsock imklog + new config directives: $IMUXSockLocalIPIF, $klogLocalIPIF +- added configuration directives to customize queue light delay marks + $MainMsgQueueLightDelayMark, $ActionQueueLightDelayMark; both + specify number of messages starting at which a delay happens. +- bugfix: omprog made rsyslog abort on startup if not binary to + execute was configured +- bugfix: imklog invalidly computed facility and severity + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=313 +--------------------------------------------------------------------------- +Version 5.8.7 [V5-stable] 2012-01-17 +- bugfix: instabilities when using RFC5424 header fields + Thanks to Kaiwang Chen for the patch +- bugfix: imuxsock did truncate part of received message if it did not + contain a proper date. The truncation occured because we removed that + part of the messages that was expected to be the date. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=295 +- bugfix: potential abort after reading invalid X.509 certificate + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=290 + Thanks to Tomas Heinrich for the patch +- bugfix: stats counter were not properly initialized on creation +- FQDN hostname for multihomed host was not always set to the correct name + if multiple aliases existed. Thanks to Tomas Heinreich for the patch. +- re-licensed larger parts of the codebase under the Apache license 2.0 +--------------------------------------------------------------------------- +Version 5.8.6 [V5-stable] 2011-10-21 +- bugfix: missing whitespace after property-based filter was not detected +- bugfix: $OMFileFlushInterval period was doubled - now using correct value +- bugfix: ActionQueue could malfunction due to index error + Thanks to Vlad Grigorescu for the patch +- bugfix: $ActionExecOnlyOnce interval did not work properly + Thanks to Tomas Heinrich for the patch +- bugfix: race condition when extracting program name, APPNAME, structured + data and PROCID (RFC5424 fields) could lead to invalid characters e.g. + in dynamic file names or during forwarding (general malfunction of these + fields in templates, mostly under heavy load) +- bugfix: imuxsock did no longer ignore message-provided timestamp, if + so configured (the *default*). Lead to no longer sub-second timestamps. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=281 +- bugfix: omfile returns fatal error code for things that go really wrong + previously, RS_RET_RESUME was returned, which lead to a loop inside the + rule engine as omfile could not really recover. +- bugfix: imfile did invalid system call under some circumstances + when a file that was to be monitored did not exist BUT the state file + actually existed. Mostly a cosmetic issue. Root cause was incomplete + error checking in stream.c; so patch may affect other code areas. +- bugfix: rsyslogd -v always said 64 atomics were not present + thanks to mono_matsuko for the patch +--------------------------------------------------------------------------- +Version 5.8.5 [V5-stable] (rgerhards/al), 2011-09-01 +- bugfix/security: off-by-two bug in legacy syslog parser, CVE-2011-3200 +- bugfix: mark message processing did not work correctly +- bugfix: potential hang condition during tag emulation +- bugfix: too-early string termination during tag emulation +- bugfix: The NUL-Byte for the syslogtag was not copied in MsgDup (msg.c) +- bugfix: fixed incorrect state handling for Discard Action (transactions) + Note: This caused all messages in a batch to be set to COMMITTED, + even if they were discarded. +--------------------------------------------------------------------------- +Version 5.8.4 [V5-stable] (al), 2011-08-10 +- bugfix: potential misadressing in property replacer +- bugfix: memcpy overflow can occur in allowed sender checkig + if a name is resolved to IPv4-mapped-on-IPv6 address + Found by Ismail Dönmez at suse +- bugfix: potential misadressing in property replacer +- bugfix: MSGID corruption in RFC5424 parser under some circumstances + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=275 +--------------------------------------------------------------------------- +Version 5.8.3 [V5-stable] (rgerhards), 2011-07-11 +- systemd support: set stdout/stderr to null - thx to Lennart for the patch +- added support for the ":omusrmsg:" syntax in configuring user messages +- added support for the ":omfile:" syntax for actions + Note: previous outchannel syntax will generate a warning message. This + may be surprising to some users, but it is quite urgent to alert them + of the new syntax as v6 can no longer support the previous one. +--------------------------------------------------------------------------- +Version 5.8.2 [V5-stable] (rgerhards), 2011-06-21 +- bugfix: problems in failover action handling + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=270 + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=254 +- bugfix: mutex was invalidly left unlocked during action processing + At least one case where this can occur is during thread shutdown, which + may be initiated by lower activity. In most cases, this is quite + unlikely to happen. However, if it does, data structures may be + corrupted which could lead to fatal failure and segfault. I detected + this via a testbench test, not a user report. But I assume that some + users may have had unreproducable aborts that were cause by this bug. +- bugfix: memory leak in imtcp & subsystems under some circumstances + This leak is tied to error conditions which lead to incorrect cleanup + of some data structures. [backport from v6] +- bugfix/improvement:$WorkDirectory now gracefully handles trailing slashes +--------------------------------------------------------------------------- +Version 5.8.1 [V5-stable] (rgerhards), 2011-05-19 +- bugfix: invalid processing in QUEUE_FULL condition + If the the multi-submit interface was used and a QUEUE_FULL condition + occured, the failed message was properly destructed. However, the + rest of the input batch, if it existed, was not processed. So this + lead to potential loss of messages and a memory leak. The potential + loss of messages was IMHO minor, because they would have been dropped + in most cases due to the queue remaining full, but very few lucky ones + from the batch may have made it. Anyhow, this has now been changed so + that the rest of the batch is properly tried to be enqueued and, if + not possible, destructed. +- new module mmsnmptrapd, a sample message modification module + This can be useful to reformat snmptrapd messages and also serves as + a sample for how to write message modification modules using the + output module interface. Note that we introduced this new + functionality directly into the stable release, as it does not + modify the core and as such cannot have any side-effects if it is + not used (and thus the risk is solely on users requiring that + functionality). +- bugfix: rate-limiting inside imuxsock did not work 100% correct + reason was that a global config variable was invalidly accessed where a + listener variable should have been used. + Also performance-improved the case when rate limiting is turned off (this + is a very unintrusive change, thus done directly to the stable version). +- bugfix: $myhostname not available in RainerScript (and no error message) + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=233 +- bugfix: memory and file descriptor leak in stream processing + Leaks could occur under some circumstances if the file stream handler + errored out during the open call. Among others, this could cause very + big memory leaks if there were a problem with unreadable disk queue + files. In regard to the memory leak, this + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=256 +- bugfix: doc for impstats had wrong config statements + also, config statements were named a bit inconsistent, resolved that + problem by introducing an alias and only documenting the consistent + statements + Thanks to Marcin for bringing up this problem. +- bugfix: IPv6-address could not be specified in omrelp + this was due to improper parsing of ":" + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=250 +- bugfix: TCP connection invalidly aborted when messages needed to be + discarded (due to QUEUE_FULL or similar problem) +- bugfix: $LocalHostName was not honored under all circumstances + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=258 +- bugfix(minor): improper template function call in syslogd.c +--------------------------------------------------------------------------- +Version 5.8.0 [V5-stable] (rgerhards), 2011-04-12 + +This is the new v5-stable branch, importing all feature from the 5.7.x +versions. To see what has changed in regard to the previous v5-stable, +check the Changelog for 5.7.x below. + +- bugfix: race condition in deferred name resolution + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=238 + Special thanks to Marcin for his persistence in helping to solve this + bug. +- bugfix: DA queue was never shutdown once it was started + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=241 +--------------------------------------------------------------------------- +Version 5.7.10 [V5-BETA] (rgerhards), 2011-03-29 +- bugfix: ompgsql did not work properly with ANSI SQL strings + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=229 +- bugfix: rsyslog did not build with --disable-regexp configure option + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=243 +- bugfix: PRI was invalid on Solaris for message from local log socket +- enhance: added $BOM system property to ease writing byte order masks +- bugfix: RFC5424 parser confused by empty structured data + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=237 +- bugfix: error return from strgen caused abort, now causes action to be + ignored (just like a failed filter) +- new sample plugin for a strgen to generate sql statement consumable + by a database plugin +- bugfix: strgen could not be used together with database outputs + because the sql/stdsql option could not be specified. This has been + solved by permitting the strgen to include the opton inside its name. + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=195 +--------------------------------------------------------------------------- +Version 5.7.9 [V5-BETA] (rgerhards), 2011-03-16 +- improved testbench + among others, life tests for ommysql (against a test database) have + been added, valgrind-based testing enhanced, ... +- enhance: fallback *at runtime* to epoll_create if epoll_create1 is not + available. Thanks to Michael Biebl for analysis and patch! +- bugfix: failover did not work correctly if repeated msg reduction was on + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=236 + affected directive was: $ActionExecOnlyWhenPreviousIsSuspended on +- bugfix: minor memory leak in omlibdbi (< 1k per instance and run) +- bugfix: (regression) omhdfs did no longer compile +- bugfix: omlibdbi did not use password from rsyslog.conf + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=203 +--------------------------------------------------------------------------- +Version 5.7.8 [V5-BETA] (rgerhards), 2011-03-09 +- systemd support somewhat improved (can now take over existing log sockt) +- bugfix: discard action did not work under some circumstances + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=217 +- bugfix: file descriptor leak in gnutls netstream driver + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=222 +--------------------------------------------------------------------------- +Version 5.7.7 [V5-BETA] (rgerhards), 2011-03-02 +- bugfix: potential abort condition when $RepeatedMsgReduction set to on + as well as potentially in a number of other places where MsgDup() was + used. This only happened when the imudp input module was used and it + depended on name resolution not yet had taken place. In other words, + this was a strange problem that could lead to hard to diagnose + instability. So if you experience instability, chances are good that + this fix will help. +--------------------------------------------------------------------------- +Version 5.7.6 [V5-BETA] (rgerhards), 2011-02-25 +- bugfix: fixed a memory leak and potential abort condition + this could happen if multiple rulesets were used and some output batches + contained messages belonging to more than one ruleset. + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=226 + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=218 +- bugfix: memory leak when $RepeatedMsgReduction on was used + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=225 +--------------------------------------------------------------------------- +Version 5.7.5 [V5-BETA] (rgerhards), 2011-02-23 +- enhance: imfile did not yet support multiple rulesets, now added + we do this directly in the beta because a) it does not affect existing + functionality and b) one may argue that this missing functionality is + close to a bug. +- improved testbench, added tests for imuxsock +- bugfix: imuxsock did no longer sanitize received messages + This was a regression from the imuxsock partial rewrite. Happened + because the message is no longer run through the standard parsers. + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=224 +- bugfix: minor race condition in action.c - considered cosmetic + This is considered cosmetic as multiple threads tried to write exactly + the same value into the same memory location without sync. The method + has been changed so this can no longer happen. +--------------------------------------------------------------------------- +Version 5.7.4 [V5-BETA] (rgerhards), 2011-02-17 +- added pmsnare parser module (written by David Lang) +- enhanced imfile to support non-cancel input termination +- improved systemd socket activation thanks to Marius Tomaschewski +- improved error reporting for $WorkDirectory + non-existance and other detectable problems are now reported, + and the work directory is NOT set in this case +- bugfix: pmsnare causded abort under some conditions +- bugfix: abort if imfile reads file line of more than 64KiB + Thanks to Peter Eisentraut for reporting and analysing this problem. + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=221 +- bugfix: queue engine did not properly slow down inputs in FULL_DELAY mode + when in disk-assisted mode. This especially affected imfile, which + created unnecessarily queue files if a large set of input file data was + to process. +- bugfix: very long running actions could prevent shutdown under some + circumstances. This has now been solved, at least for common + situations. +- bugfix: fixed compile problem due to empty structs + this occured only on some platforms/compilers. thanks to Dražen KaÄar + for the fix +--------------------------------------------------------------------------- +Version 5.7.3 [V5-BETA] (rgerhards), 2011-02-07 +- added support for processing multi-line messages in imfile +- added $IMUDPSchedulingPolicy and $IMUDPSchedulingPriority config settings +- added $LocalHostName config directive +- bugfix: fixed build problems on some platforms + namely those that have 32bit atomic operations but not 64 bit ones +- bugfix: local hostname was pulled too-early, so that some config + directives (namely FQDN settings) did not have any effect +- bugfix: imfile did duplicate messages under some circumstances +- added $OMMySQLConfigFile config directive +- added $OMMySQLConfigSection config directive +--------------------------------------------------------------------------- +Version 5.7.2 [V5-DEVEL] (rgerhards), 2010-11-26 +- bugfix(important): problem in TLS handling could cause rsyslog to loop + in a tight loop, effectively disabling functionality and bearing the + risk of unresponsiveness of the whole system. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=194 +- bugfix: imfile state file was not written when relative file name + for it was specified +- bugfix: compile failed on systems without epoll_create1() + Thanks to David Hill for providing a fix. +- bugfix: atomic increment for msg object may not work correct on all + platforms. Thanks to Chris Metcalf for the patch +- bugfix: replacements for atomic operations for non-int sized types had + problems. At least one instance of that problem could potentially lead + to abort (inside omfile). +--------------------------------------------------------------------------- +Version 5.7.1 [V5-DEVEL] (rgerhards), 2010-10-05 +- support for Hadoop's HDFS added (via omhdfs) +- imuxsock now optionally use SCM_CREDENTIALS to pull the pid from the log + socket itself + (thanks to Lennart Poettering for the suggesting this feature) +- imuxsock now optionally uses per-process input rate limiting, guarding the + user against processes spamming the system log + (thanks to Lennart Poettering for suggesting this feature) +- added new config statements + * $InputUnixListenSocketUsePIDFromSystem + * $SystemLogUsePIDFromSystem + * $SystemLogRateLimitInterval + * $SystemLogRateLimitBurst + * $SystemLogRateLimitSeverity + * $IMUxSockRateLimitInterval + * $IMUxSockRateLimitBurst + * $IMUxSockRateLimitSeverity +- imuxsock now supports up to 50 different sockets for input +- some code cleanup in imuxsock (consider this a release a major + modification, especially if problems show up) +- bugfix: /dev/log was unlinked even when passed in from systemd + in which case it should be preserved as systemd owns it +--------------------------------------------------------------------------- +Version 5.7.0 [V5-DEVEL] (rgerhards), 2010-09-16 +- added module impstat to emit periodic statistics on rsyslog counters +- support for systemd officially added + * acquire /dev/log socket optionally from systemd + thanks to Lennart Poettering for this patch + * sd-systemd API added as part of rsyslog runtime library +--------------------------------------------------------------------------- +Version 5.6.5 [V5-STABLE] (rgerhards), 2011-03-22 +- bugfix: failover did not work correctly if repeated msg reduction was on + affected directive was: $ActionExecOnlyWhenPreviousIsSuspended on + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=236 +- bugfix: omlibdbi did not use password from rsyslog.con + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=203 +- bugfix(kind of): tell users that config graph can currently not be + generated + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=232 +- bugfix: discard action did not work under some circumstances + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=217 + (backport from 5.7.8) +--------------------------------------------------------------------------- +Version 5.6.4 [V5-STABLE] (rgerhards), 2011-03-03 +- bugfix: potential abort condition when $RepeatedMsgReduction set to on + as well as potentially in a number of other places where MsgDup() was + used. This only happened when the imudp input module was used and it + depended on name resolution not yet had taken place. In other words, + this was a strange problem that could lead to hard to diagnose + instability. So if you experience instability, chances are good that + this fix will help. +- bugfix: fixed a memory leak and potential abort condition + this could happen if multiple rulesets were used and some output batches + contained messages belonging to more than one ruleset. + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=226 + fixes: http://bugzilla.adiscon.com/show_bug.cgi?id=218 +- bugfix: memory leak when $RepeatedMsgReduction on was used + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=225 +--------------------------------------------------------------------------- +Version 5.6.3 [V5-STABLE] (rgerhards), 2011-01-26 +- bugfix: action processor released memory too early, resulting in + potential issue in retry cases (but very unlikely due to another + bug, which I also fixed -- only after the fix this problem here + became actually visible). +- bugfix: batch processing flagged invalid message as "bad" under some + circumstances +- bugfix: unitialized variable could cause issues under extreme conditions + plus some minor nits. This was found after a clang static code analyzer + analysis (great tool, and special thanks to Marcin for telling me about + it!) +- bugfix: batches which had actions in error were not properly retried in + all cases +- bugfix: imfile did duplicate messages under some circumstances +- bugfix: testbench was not activated if no Java was present on system + ... what actually was a left-over. Java is no longer required. +--------------------------------------------------------------------------- +Version 5.6.2 [V5-STABLE] (rgerhards), 2010-11-30 +- bugfix: compile failed on systems without epoll_create1() + Thanks to David Hill for providing a fix. +- bugfix: atomic increment for msg object may not work correct on all + platforms. Thanks to Chris Metcalf for the patch +- bugfix: replacements for atomic operations for non-int sized types had + problems. At least one instance of that problem could potentially lead + to abort (inside omfile). +- added the $InputFilePersistStateInterval config directive to imfile +- changed imfile so that the state file is never deleted (makes imfile + more robust in regard to fatal failures) +- bugfix: a slightly more informative error message when a TCP + connections is aborted +--------------------------------------------------------------------------- +Version 5.6.1 [V5-STABLE] (rgerhards), 2010-11-24 +- bugfix(important): problem in TLS handling could cause rsyslog to loop + in a tight loop, effectively disabling functionality and bearing the + risk of unresponsiveness of the whole system. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=194 +- permitted imptcp to work on systems which support epoll(), but not + epoll_create(). + Bug: http://bugzilla.adiscon.com/show_bug.cgi?id=204 + Thanks to Nicholas Brink for reporting this problem. +- bugfix: testbench failed if imptcp was not enabled +- bugfix: segfault when an *empty* template was used + Bug: http://bugzilla.adiscon.com/show_bug.cgi?id=206 + Thanks to David Hill for alerting us. +- bugfix: compile failed with --enable-unlimited-select + thanks varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 5.6.0 [V5-STABLE] (rgerhards), 2010-10-19 + +This release brings all changes and enhancements of the 5.5.x series +to the v5-stable branch. + +- bugfix: a couple of problems that imfile had on some platforms, namely + Ubuntu (not their fault, but occured there) +- bugfix: imfile utilizes 32 bit to track offset. Most importantly, + this problem can not experienced on Fedora 64 bit OS (which has + 64 bit long's!) +--------------------------------------------------------------------------- +Version 5.5.7 [V5-BETA] (rgerhards), 2010-08-09 +- changed omudpspoof default spoof address to simplify typical use case + thanks to David Lang for suggesting this +- doc bugfix: pmlastmsg doc samples had errors +- bugfix[minor]: pmrfc3164sd had invalid name (resided in rsyslog name + space, what should not be the case for a contributed module) +- added omuxsock, which permits to write message to local Unix sockets + this is the counterpart to imuxsock, enabling fast local forwarding +--------------------------------------------------------------------------- +Version 5.5.6 [DEVEL] (rgerhards), 2010-07-21 +- added parser modules + * pmlastmsg, which supports the notoriously malformed "last message + repeated n times" messages from some syslogd's (namely sysklogd) + * pmrfc3164sd (contributed), supports RFC5424 structured data in + RFC3164 messages [untested] +- added new module type "string generator", used to speed up output + processing. Expected speedup for (typical) rsyslog processing is + roughly 5 to 6 percent compared to using string-based templates. + They may also be used to do more complex formatting with custom + C code, what provided greater flexibility and probably far higher + speed, for example if using multiple regular expressions within a + template. +- added 4 string generators for + * RSYSLOG_FileFormat + * RSYSLOG_TraditionalFileFormat + * RSYSLOG_ForwardFormat + * RSYSLOG_TraditionalForwardFormat +- bugfix: mutexes used to simulate atomic instructions were not destructed +- bugfix: regression caused more locking action in msg.c than necessary +- bugfix: "$ActionExecOnlyWhenPreviousIsSuspended on" was broken +- bugfix: segfault on HUP when "HUPIsRestart" was set to "on" + thanks varmojfekoj for the patch +- bugfix: default for $OMFileFlushOnTXEnd was wrong ("off"). + This, in default mode, caused buffered writing to be used, what + means that it looked like no output were written or partial + lines. Thanks to Michael Biebl for pointing out this bug. +- bugfix: programname filter in ! configuration can not be reset + Thanks to Kiss Gabor for the patch. +--------------------------------------------------------------------------- +Version 5.5.5 [DEVEL] (rgerhards), 2010-05-20 +- added new cancel-reduced action thread termination method + We now manage to cancel threads that block inside a retry loop to + terminate without the need to cancel the thread. Avoiding cancellation + helps keep the system complexity minimal and thus provides for better + stability. This also solves some issues with improper shutdown when + inside an action retry loop. +--------------------------------------------------------------------------- +Version 5.5.4 [DEVEL] (rgerhards), 2010-05-03 +- This version offers full support for Solaris on Intel and Sparc +- bugfix: problems with atomic operations emulation + replaced atomic operation emulation with new code. The previous code + seemed to have some issue and also limited concurrency severely. The + whole atomic operation emulation has been rewritten. +- bugfix: netstream ptcp support class was not correctly build on systems + without epoll() support +- bugfix: segfault on Solaris/Sparc +--------------------------------------------------------------------------- +Version 5.5.3 [DEVEL] (rgerhards), 2010-04-09 +- added basic but functional support for Solaris +- imported many bugfixes from 3.6.2/4.6.1 (see ChangeLog below!) +- added new property replacer option "date-rfc3164-buggyday" primarily + to ease migration from syslog-ng. See property replacer doc for + details. +- added capability to turn off standard LF delimiter in TCP server + via new directive "$InputTCPServerDisableLFDelimiter on" +- bugfix: failed to compile on systems without epoll support +- bugfix: comment char ('#') in literal terminated script parsing + and thus could not be used. + but tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=119 + [merged in from v3.22.2] +- imported patches from 4.6.0: + * improved testbench to contain samples for totally malformed messages + which miss parts of the message content + * bugfix: some malformed messages could lead to a missing LF inside files + or some other missing parts of the template content. + * bugfix: if a message ended immediately with a hostname, the hostname + was mistakenly interpreted as TAG, and localhost be used as hostname +--------------------------------------------------------------------------- +Version 5.5.2 [DEVEL] (rgerhards), 2010-02-05 +- applied patches that make rsyslog compile under Apple OS X. + Thanks to trey for providing these. +- replaced data type "bool" by "sbool" because this created some + portability issues. +- added $Escape8BitCharactersOnReceive directive + Thanks to David Lang for suggesting it. +- worked around an issue where omfile failed to compile on 32 bit platforms + under some circumstances (this smells like a gcc problem, but a simple + solution was available). Thanks to Kenneth Marshall for some advice. +- extended testbench +--------------------------------------------------------------------------- +Version 5.5.1 [DEVEL] (rgerhards), 2009-11-27 +- introduced the ablity for netstream drivers to utilize an epoll interface + This offers increased performance and removes the select() FDSET size + limit from imtcp. Note that we fall back to select() if there is no + epoll netstream drivers. So far, an epoll driver has only been + implemented for plain tcp syslog, the rest will follow once the code + proves well in practice AND there is demand. +- re-implemented $EscapeControlCharacterTab config directive + Based on Jonathan Bond-Caron's patch for v4. This now also includes some + automatted tests. +- bugfix: enabling GSSServer crashes rsyslog startup + Thanks to Tomas Kubina for the patch [imgssapi] +- bugfix (kind of): check if TCP connection is still alive if using TLS + Thanks to Jonathan Bond-Caron for the patch. +--------------------------------------------------------------------------- +Version 5.5.0 [DEVEL] (rgerhards), 2009-11-18 +- moved DNS resolution code out of imudp and into the backend processing + Most importantly, DNS resolution now never happens if the resolved name + is not required. Note that this applies to imudp - for the other inputs, + DNS resolution almost comes for free, so we do not do it there. However, + the new method has been implemented in a generic way and as such may + also be used by other modules in the future. +- added option to use unlimited-size select() calls + Thanks to varmjofekoj for the patch + This is not done in imudp, as it natively supports epoll(). +- doc: improved description of what loadable modules can do +--------------------------------------------------------------------------- +Version 5.4.2 [v5-stable] (rgerhards), 2010-03-?? +- bugfix(kind of): output plugin retry behaviour could cause engine to loop + The rsyslog engine did not guard itself against output modules that do + not properly convey back the tryResume() behaviour. This then leads to + what looks like an endless loop. I consider this to be a bug of the + engine not only because it should be hardened against plugin misbehaviour, + but also because plugins may not be totally able to avoid this situation + (depending on the type of and processing done by the plugin). +- bugfix: testbench failed when not executed in UTC+1 timezone + accidently, the time zone information was kept inside some + to-be-checked-for responses +- temporary bugfix replaced by permanent one for + message-induced off-by-one error (potential segfault) (see 4.6.2) + The analysis has been completed and a better fix been crafted and + integrated. +- bugfix(minor): status variable was uninitialized + However, this would have caused harm only if NO parser modules at + all were loaded, which would lead to a defunctional configuration + at all. And, even more important, this is impossible as two parser + modules are built-in and thus can not be "not loaded", so we always + have a minimum of two. +--------------------------------------------------------------------------- +Version 5.4.1 [v5-stable] (rgerhards), 2010-03-?? +- added new property replacer option "date-rfc3164-buggyday" primarily + to ease migration from syslog-ng. See property replacer doc for + details. [backport from 5.5.3 because urgently needed by some] +- imported all bugfixes vom 4.6.2 (see below) +--------------------------------------------------------------------------- +Version 5.4.0 [v5-stable] (rgerhards), 2010-03-08 +*************************************************************************** +* This is a new stable v5 version. It contains all fixes and enhancements * +* made during the 5.3.x phase as well as those listed below. * +* Note that the 5.2.x series was quite buggy and as such all users are * +* strongly advised to upgrade to 5.4.0. * +*************************************************************************** +- bugfix: omruleset failed to work in many cases + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=179 + Thanks to Ryan B. Lynch for reporting this issue. +- bugfix: comment char ('#') in literal terminated script parsing + and thus could not be used. + but tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=119 + [merged in from v3.22.2] +--------------------------------------------------------------------------- +Version 5.3.7 [BETA] (rgerhards), 2010-01-27 +- bugfix: queues in direct mode could case a segfault, especially if an + action failed for action queues. The issue was an invalid increment of + a stack-based pointer which lead to destruction of the stack frame and + thus a segfault on function return. + Thanks to Michael Biebl for alerting us on this problem. +- bugfix: hostname accidently set to IP address for some message sources, + for example imudp. Thanks to Anton for reporting this bug. [imported v4] +- bugfix: ompgsql had problems with transaction support, what actually + rendered it unsuable. Thanks to forum user "horhe" for alerting me + on this bug and helping to debug/fix it! [imported from 5.3.6] +- bugfix: $CreateDirs variable not properly initialized, default thus + was random (but most often "on") [imported from v3] +- bugfix: potential segfaults during queue shutdown + (bugs require certain non-standard settings to appear) + Thanks to varmojfekoj for the patch [imported from 4.5.8] + [backport from 5.5.2] +- bugfix: wrong memory assignment for a config variable (probably + without causing any harm) [backport from 5.2.2] +- bugfix: rsyslog hangs when writing to a named pipe which nobody was + reading. Thanks to Michael Biebl for reporting this bug. + Bugzilla entry: http://bugzilla.adiscon.com/show_bug.cgi?id=169 + [imported from 4.5.8] +--------------------------------------------------------------------------- +Version 5.3.6 [BETA] (rgerhards), 2010-01-13 +- bugfix: ompgsql did not properly check the server connection in + tryResume(), which could lead to rsyslog running in a thight loop +- bugfix: suspension during beginTransaction() was not properly handled + by rsyslog core +- bugfix: omfile output was only written when buffer was full, not at + end of transaction +- bugfix: commit transaction was not properly conveyed to message layer, + potentially resulting in non-message destruction and thus hangs +- bugfix: enabling GSSServer crashes rsyslog startup + Thanks to Tomas Kubina for the patch [imgssapi] +- bugfix (kind of): check if TCP connection is still alive if using TLS + Thanks to Jonathan Bond-Caron for the patch. +- bugfix: $CreateDirs variable not properly initialized, default thus + was random (but most often "on") [imported from v3] +- bugfix: ompgsql had problems with transaction support, what actually + rendered it unsuable. Thanks to forum user "horhe" for alerting me + on this bug and helping to debug/fix it! +- bugfix: memory leak when sending messages in zip-compressed format + Thanks to Naoya Nakazawa for analyzing this issue and providing a patch. +- worked around an issue where omfile failed to compile on 32 bit platforms + under some circumstances (this smells like a gcc problem, but a simple + solution was available). Thanks to Kenneth Marshall for some advice. + [backported from 5.5.x branch] +--------------------------------------------------------------------------- +Version 5.3.5 [BETA] (rgerhards), 2009-11-13 +- some light performance enhancement by replacing time() call with much + faster (at least under linux) gettimeofday() calls. +- some improvement of omfile performance with dynafiles + saved costly time() calls by employing a logical clock, which is + sufficient for the use case +- bugfix: omudpspoof miscalculated source and destination ports + while this was probably not noticed for source ports, it resulted in + almost all destination ports being wrong, except for the default port + of 514, which by virtue of its binary representation was calculated + correct (and probably thus the bug not earlier detected). +- bugfixes imported from earlier releases + * bugfix: named pipes did no longer work (they always got an open error) + this was a regression from the omfile rewrite in 4.5.0 + * bugfix(testbench): sequence check was not always performed correctly, + that could result in tests reporting success when they actually failed +- improved testbench: added tests for UDP forwarding and omudpspoof +- doc bugfix: omudpspoof had wrong config command names ("om" missing) +- bugfix [imported from 4.4.3]: $ActionExecOnlyOnceEveryInterval did + not work. +- [inport v4] improved testbench, contains now tcp and gzip test cases +- [import v4] added a so-called "On Demand Debug" mode, in which debug + output can be generated only after the process has started, but not right + from the beginning. This is assumed to be useful for hard-to-find bugs. + Also improved the doc on the debug system. +- bugfix: segfault on startup when -q or -Q option was given + [imported from v3-stable] +--------------------------------------------------------------------------- +Version 5.3.4 [DEVEL] (rgerhards), 2009-11-04 +- added the ability to create custom message parsers +- added $RulesetParser config directive that permits to bind specific + parsers to specific rulesets +- added omruleset output module, which provides great flexibility in + action processing. THIS IS A VERY IMPORTANT ADDITION, see its doc + for why. +- added the capability to have ruleset-specific main message queues + This offers considerable additional flexibility AND superior performance + (in cases where multiple inputs now can avoid lock contention) +- bugfix: correct default for escape ('#') character restored + This was accidently changed to '\\', thanks to David Lang for reporting +- bugfix(testbench): testcase did not properly wait for rsyslogd shutdown + thus some unpredictable behavior and a false negative test result + could occur. +--------------------------------------------------------------------------- +Version 5.3.3 [DEVEL] (rgerhards), 2009-10-27 +- simplified and thus speeded up the queue engine, also fixed some + potential race conditions (in very unusual shutdown conditions) + along the way. The threading model has seriously changes, so there may + be some regressions. +- enhanced test environment (inlcuding testbench): support for enhancing + probability of memory addressing failure by using non-NULL default + value for malloced memory (optional, only if requested by configure + option). This helps to track down some otherwise undetected issues + within the testbench. +- bugfix: potential abort if inputname property was not set + primarily a problem of imdiag +- bugfix: message processing states were not set correctly in all cases + however, this had no negative effect, as the message processing state + was not evaluated when a batch was deleted, and that was the only case + where the state could be wrong. +--------------------------------------------------------------------------- +Version 5.3.2 [DEVEL] (rgerhards), 2009-10-21 +- enhanced omfile to support transactional interface. This will increase + performance in many cases. +- added multi-ruleset support to imudp +- re-enabled input thread termination handling that does avoid thread + cancellation where possible. This provides a more reliable mode of + rsyslogd termination (canceling threads my result in not properly + freed resouces and potential later hangs, even though we perform + proper cancel handling in our code). This is part of an effort to + reduce thread cancellation as much as possible in rsyslog. + NOTE: the code previously written code for this functionality had a + subtle race condition. The new code solves that. +- enhanced immark to support non-cancel input module termination +- improved imudp so that epoll can be used in more environments, + fixed potential compile time problem if EPOLL_CLOEXEC is not available. +- some cleanup/slight improvement: + * changed imuxsock to no longer use deprecated submitAndParseMsg() IF + * changed submitAndParseMsg() interface to be a wrapper around the new + way of message creation/submission. This enables older plugins to be + used together with the new interface. The removal also enables us to + drop a lot of duplicate code, reducing complexity and increasing + maintainability. +- bugfix: segfault when starting up with an invalid .qi file for a disk queue + Failed for both pure disk as well as DA queues. Now, we emit an error + message and disable disk queueing facility. +- bugfix: potential segfault on messages with empty MSG part. This was a + recently introduced regression. +- bugfix: debug string larger than 1K were improperly displayed. Max size + is now 32K, and if a string is even longer it is meaningfully truncated. +--------------------------------------------------------------------------- +Version 5.3.1 [DEVEL] (rgerhards), 2009-10-05 +- added $AbortOnUncleanConfig directive - permits to prevent startup when + there are problems with the configuration file. See it's doc for + details. +- included some important fixes from v4-stable: + * bugfix: invalid handling of zero-sized messages + * bugfix: zero-sized UDP messages are no longer processed + * bugfix: random data could be appended to message + * bugfix: reverse lookup reduction logic in imudp do DNS queries too often +- bugfixes imported from 4.5.4: + * bugfix: potential segfault in stream writer on destruction + * bugfix: potential race in object loader (obj.c) during use/release + * bugfixes: potential problems in out file zip writer +--------------------------------------------------------------------------- +Version 5.3.0 [DEVEL] (rgerhards), 2009-09-14 +- begun to add simple GUI programs to gain insight into running rsyslogd + instances and help setup and troubleshooting (active via the + --enable-gui ./configure switch) +- changed imudp to utilize epoll(), where available. This shall provide + slightly better performance (just slightly because we called select() + rather infrequently on a busy system) +--------------------------------------------------------------------------- +Version 5.2.2 [v5-stable] (rgerhards), 2009-11-?? +- bugfix: enabling GSSServer crashes rsyslog startup + Thanks to Tomas Kubina for the patch [imgssapi] +--------------------------------------------------------------------------- +Version 5.2.1 [v5-stable] (rgerhards), 2009-11-02 +- bugfix [imported from 4.4.3]: $ActionExecOnlyOnceEveryInterval did + not work. +- bugfix: segfault on startup when -q or -Q option was given + [imported from v3-stable] +--------------------------------------------------------------------------- +Version 5.2.0 [v5-stable] (rgerhards), 2009-11-02 +This is a re-release of version 5.1.6 as stable after we did not get any bug +reports during the whole beta phase. Still, this first v5-stable may not be +as stable as one hopes for, I am not sure if we did not get bug reports +just because nobody tried it. Anyhow, we need to go forward and so we +have the initial v5-stable. +--------------------------------------------------------------------------- +Version 5.1.6 [v5-beta] (rgerhards), 2009-10-15 +- feature imports from v4.5.6 +- bugfix: potential race condition when queue worker threads were + terminated +- bugfix: solved potential (temporary) stall of messages when the queue was + almost empty and few new data added (caused testbench to sometimes hang!) +- fixed some race condition in testbench +- added more elaborate diagnostics to parts of the testbench +- bugfixes imported from 4.5.4: + * bugfix: potential segfault in stream writer on destruction + * bugfix: potential race in object loader (obj.c) during use/release + * bugfixes: potential problems in out file zip writer +- included some important fixes from 4.4.2: + * bugfix: invalid handling of zero-sized messages + * bugfix: zero-sized UDP messages are no longer processed + * bugfix: random data could be appended to message + * bugfix: reverse lookup reduction logic in imudp do DNS queries too often +--------------------------------------------------------------------------- +Version 5.1.5 [v5-beta] (rgerhards), 2009-09-11 +- added new config option $ActionWriteAllMarkMessages + this option permites to process mark messages under all circumstances, + even if an action was recently called. This can be useful to use mark + messages as a kind of heartbeat. +- added new config option $InputUnixListenSocketCreatePath + to permit the auto-creation of pathes to additional log sockets. This + turns out to be useful if they reside on temporary file systems and + rsyslogd starts up before the daemons that create these sockets + (rsyslogd always creates the socket itself if it does not exist). +- added $LogRSyslogStatusMessages configuration directive + permitting to turn off rsyslog start/stop/HUP messages. See Debian + ticket http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=463793 +- bugfix: hostnames with dashes in them were incorrectly treated as + malformed, thus causing them to be treated as TAG (this was a regression + introduced from the "rfc3164 strict" change in 4.5.0). Testbench has been + updated to include a smaple message with a hostname containing a dash. +- bugfix: strings improperly reused, resulting in some message properties + be populated with strings from previous messages. This was caused by + an improper predicate check. +- added new config directive $omfileForceChown [import from 4.7.0] +--------------------------------------------------------------------------- +Version 5.1.4 [DEVEL] (rgerhards), 2009-08-20 +- legacy syslog parser changed so that it now accepts date stamps in + wrong case. Some devices seem to create them and I do not see any harm + in supporting that. +- added $InputTCPMaxListeners directive - permits to specify how many + TCP servers shall be possible (default is 20). +- bugfix: memory leak with some input modules. Those inputs that + use parseAndSubmitMsg() leak two small memory blocks with every message. + Typically, those process only relatively few messages, so the issue + does most probably not have any effect in practice. +- bugfix: if tcp listen port could not be created, no error message was + emitted +- bugfix: discard action did not work (did not discard messages) +- bugfix: discard action caused segfault +- bugfix: potential segfault in output file writer (omfile) + In async write mode, we use modular arithmetic to index the output + buffer array. However, the counter variables accidently were signed, + thus resulting in negative indizes after integer overflow. That in turn + could lead to segfaults, but was depending on the memory layout of + the instance in question (which in turn depended on a number of + variables, like compile settings but also configuration). The counters + are now unsigned (as they always should have been) and so the dangling + mis-indexing does no longer happen. This bug potentially affected all + installations, even if only some may actually have seen a segfault. +--------------------------------------------------------------------------- +Version 5.1.3 [DEVEL] (rgerhards), 2009-07-28 +- architecture change: queue now always has at least one worker thread + if not running in direct mode. Previous versions could run without + any active workers. This simplifies the code at a very small expense. + See v5 compatibility note document for more in-depth discussion. +- enhance: UDP spoofing supported via new output module omudpspoof + See the omudpspoof documentation for details and samples +- bugfix: message could be truncated after TAG, often when forwarding + This was a result of an internal processing error if maximum field + sizes had been specified in the property replacer. +- bugfix: minor static memory leak while reading configuration + did NOT leak based on message volume +- internal: added ability to terminate input modules not via pthread_cancel + but an alternate approach via pthread_kill. This is somewhat safer as we + do not need to think about the cancel-safeness of all libraries we use. + However, not all inputs can easily supported, so this now is a feature + that can be requested by the input module (the most important ones + request it). +--------------------------------------------------------------------------- +Version 5.1.2 [DEVEL] (rgerhards), 2009-07-08 +- bugfix: properties inputname, fromhost, fromhost-ip, msg were lost when + working with disk queues +- some performance enhancements +- bugfix: abort condition when RecvFrom was not set and message reduction + was on. Happend e.g. with imuxsock. +- added $klogConsoleLogLevel directive which permits to set a new + console log level while rsyslog is active +- some internal code cleanup +--------------------------------------------------------------------------- +Version 5.1.1 [DEVEL] (rgerhards), 2009-07-03 +- bugfix: huge memory leak in queue engine (made rsyslogd unusable in + production). Occured if at least one queue was in direct mode + (the default for action queues) +- imported many performance optimizations from v4-devel (4.5.0) +- bugfix: subtle (and usually irrelevant) issue in timout processing + timeout could be one second too early if nanoseconds wrapped +- set a more sensible timeout for shutdow, now 1.5 seconds to complete + processing (this also removes those cases where the shutdown message + was not written because the termination happened before it) +--------------------------------------------------------------------------- +Version 5.1.0 [DEVEL] (rgerhards), 2009-05-29 + +*********************************** NOTE ********************************** +The v5 versions of rsyslog feature a greatly redesigned queue engine. The +major theme for the v5 release is twofold: + +a) greatly improved performance +b) enable audit-grade processing + +Here, audit-grade processing means that rsyslog, if used together with +audit-grade transports and configured correctly, will never lose messages +that already have been acknowledged, not even in fatal failure cases like +sudden loss of power. + +Note that large parts of rsyslog's important core components have been +restructured to support these design goals. As such, early versions of +the engine will probably be less stable than the v3/v4 engine. + +Also note that the initial versions do not cover all and everything. As +usual, the code will evolve toward the final goal as version numbers +increase. +*********************************** NOTE ********************************** + +- redesigned queue engine so that it supports ultra-reliable operations + This resulted in a rewrite of large parts. The new capability can be + used to build audit-grade systems on the basis of rsyslog. +- added $MainMsgQueueDequeueBatchSize and $ActionQueueDequeueBatchSize + configuration directives +- implemented a new transactional output module interface which provides + superior performance (for databases potentially far superior performance) +- increased ompgsql performance by adapting to new transactional + output module interface +--------------------------------------------------------------------------- +Version 4.8.1 [v4-stable], 2011-09-?? +- increased max config file line size to 64k + We now also emit an error message if even 64k is not enough (not + doing so previously may rightfully be considered as a bug) +- bugfix: omprog made rsyslog abort on startup if not binary to + execute was configured +- bugfix: $ActionExecOnlyOnce interval did not work properly + Thanks to Tomas Heinrich for the patch +- bugfix: potential abort if ultra-large file io buffers are used and + dynafile cache exhausts address space (primarily a problem on 32 bit + platforms) +- bugfix: potential abort after reading invalid X.509 certificate + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=290 + Thanks to Tomas Heinrich for the patch. +- bugfix: potential fatal abort in omgssapi + Thanks to Tomas Heinrich for the patch. +- added doc for omprog +- FQDN hostname for multihomed host was not always set to the correct name + if multiple aliases existed. Thanks to Tomas Heinreich for the patch. +- re-licensed larger parts of the codebase under the Apache license 2.0 +--------------------------------------------------------------------------- +Version 4.8.0 [v4-stable] (rgerhards), 2011-09-07 +*************************************************************************** +* This is a new stable v4 version. It contains all fixes and enhancements * +* made during the 4.7.x phase as well as those listed below. * +* Note: major new development to v4 is concluded and will only be done * +* for custom projects. * +*************************************************************************** +There are no changes compared to 4.7.5, just a re-release with the new +version number as new v4-stable. The most important new feature is Solaris +support. +--------------------------------------------------------------------------- +Version 4.7.5 [v4-beta], 2011-09-01 +- bugfix/security: off-by-two bug in legacy syslog parser, CVE-2011-3200 +- bugfix: potential misadressing in property replacer +- bugfix: The NUL-Byte for the syslogtag was not copied in MsgDup (msg.c) +--------------------------------------------------------------------------- +Version 4.7.4 [v4-beta] (rgerhards), 2011-07-11 +- added support for the ":omusrmsg:" syntax in configuring user messages +- added support for the ":omfile:" syntax in configuring user messages +- added $LocalHostName config directive +- bugfix: PRI was invalid on Solaris for message from local log socket +Version 4.7.3 [v4-devel] (rgerhards), 2010-11-25 +- added omuxsock, which permits to write message to local Unix sockets + this is the counterpart to imuxsock, enabling fast local forwarding +- added imptcp, a simplified, Linux-specific and potentielly fast + syslog plain tcp input plugin (NOT supporting TLS!) +- bugfix: a couple of problems that imfile had on some platforms, namely + Ubuntu (not their fault, but occured there) +- bugfix: imfile utilizes 32 bit to track offset. Most importantly, + this problem can not experienced on Fedora 64 bit OS (which has + 64 bit long's!) +- added the $InputFilePersistStateInterval config directive to imfile +- changed imfile so that the state file is never deleted (makes imfile + more robust in regard to fatal failures) +--------------------------------------------------------------------------- +Version 4.7.2 [v4-devel] (rgerhards), 2010-05-03 +- bugfix: problems with atomic operations emulaton + replaced atomic operation emulation with new code. The previous code + seemed to have some issue and also limited concurrency severely. The + whole atomic operation emulation has been rewritten. +- added new $Sleep directive to hold processing for a couple of seconds + during startup +- bugfix: programname filter in ! configuration can not be reset + Thanks to Kiss Gabor for the patch. +--------------------------------------------------------------------------- +Version 4.7.1 [v4-devel] (rgerhards), 2010-04-22 +- Solaris support much improved -- was not truely usable in 4.7.0 + Solaris is no longer supported in imklog, but rather there is a new + plugin imsolaris, which is used to pull local log sources on a Solaris + machine. +- testbench improvement: Java is no longer needed for testing tool creation +--------------------------------------------------------------------------- +Version 4.7.0 [v4-devel] (rgerhards), 2010-04-14 +- new: support for Solaris added (but not yet the Solaris door API) +- added function getenv() to RainerScript +- added new config option $InputUnixListenSocketCreatePath + to permit the auto-creation of pathes to additional log sockets. This + turns out to be useful if they reside on temporary file systems and + rsyslogd starts up before the daemons that create these sockets + (rsyslogd always creates the socket itself if it does not exist). +- added $LogRSyslogStatusMessages configuration directive + permitting to turn off rsyslog start/stop/HUP messages. See Debian + ticket http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=463793 +- added new config directive $omfileForceChown to (try to) fix some broken + system configs. + See ticket for details: http://bugzilla.adiscon.com/show_bug.cgi?id=150 +- added $EscapeControlCharacterTab config directive + Thanks to Jonathan Bond-Caron for the patch. +- added option to use unlimited-size select() calls + Thanks to varmjofekoj for the patch +- debugondemand mode caused backgrounding to fail - close to a bug, but I'd + consider the ability to background in this mode a new feature... +- bugfix (kind of): check if TCP connection is still alive if using TLS + Thanks to Jonathan Bond-Caron for the patch. +- imported changes from 4.5.7 and below +- bugfix: potential segfault when -p command line option was used + Thanks for varmojfekoj for pointing me at this bug. +- imported changes from 4.5.6 and below +--------------------------------------------------------------------------- +Version 4.6.8 [v4-stable] (rgerhards), 2011-09-01 +- bugfix/security: off-by-two bug in legacy syslog parser, CVE-2011-3200 +- bugfix: potential misadressing in property replacer +- bugfix: memcpy overflow can occur in allowed sender checking + if a name is resolved to IPv4-mapped-on-IPv6 address + Found by Ismail Dönmez at suse +- bugfix: The NUL-Byte for the syslogtag was not copied in MsgDup (msg.c) +--------------------------------------------------------------------------- +Version 4.6.7 [v4-stable] (rgerhards), 2011-07-11 +- added support for the ":omusrmsg:" syntax in configuring user messages +- added support for the ":omfile:" syntax for actions +--------------------------------------------------------------------------- +Version 4.6.6 [v4-stable] (rgerhards), 2011-06-24 +- bugfix: memory leak in imtcp & subsystems under some circumstances + This leak is tied to error conditions which lead to incorrect cleanup + of some data structures. [backport from v6, limited testing under v4] +- bugfix: invalid processing in QUEUE_FULL condition + If the the multi-submit interface was used and a QUEUE_FULL condition + occured, the failed message was properly destructed. However, the + rest of the input batch, if it existed, was not processed. So this + lead to potential loss of messages and a memory leak. The potential + loss of messages was IMHO minor, because they would have been dropped + in most cases due to the queue remaining full, but very few lucky ones + from the batch may have made it. Anyhow, this has now been changed so + that the rest of the batch is properly tried to be enqueued and, if + not possible, destructed. +- bugfix: invalid storage type for config variables +- bugfix: stream driver mode was not correctly set on tcp ouput on big + endian systems. + thanks varmojfekoj for the patch +- bugfix: IPv6-address could not be specified in omrelp + this was due to improper parsing of ":" + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=250 +- bugfix: memory and file descriptor leak in stream processing + Leaks could occur under some circumstances if the file stream handler + errored out during the open call. Among others, this could cause very + big memory leaks if there were a problem with unreadable disk queue + files. In regard to the memory leak, this + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=256 +- bugfix: imfile potentially duplicates lines + This can happen when 0 bytes are read from the input file, and some + writer appends data to the file BEFORE we check if a rollover happens. + The check for rollover uses the inode and size as a criterion. So far, + we checked for equality of sizes, which is not given in this scenario, + but that does not indicate a rollover. From the source code comments: + Note that when we check the size, we MUST NOT check for equality. + The reason is that the file may have been written right after we + did try to read (so the file size has increased). That is NOT in + indicator of a rollover (this is an actual bug scenario we + experienced). So we need to check if the new size is smaller than + what we already have seen! + Also, under some circumstances an invalid truncation was detected. This + code has now been removed, a file change (and thus resent) is only + detected if the inode number changes. +- bugfix: a couple of problems that imfile had on some platforms, namely + Ubuntu (not their fault, but occured there) +- bugfix: imfile utilizes 32 bit to track offset. Most importantly, + this problem can not experienced on Fedora 64 bit OS (which has + 64 bit long's!) +- bugfix: abort if imfile reads file line of more than 64KiB + Thanks to Peter Eisentraut for reporting and analysing this problem. + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=221 +- bugfix: omlibdbi did not use password from rsyslog.con + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=203 +- bugfix: TCP connection invalidly aborted when messages needed to be + discarded (due to QUEUE_FULL or similar problem) +- bugfix: a slightly more informative error message when a TCP + connections is aborted +- bugfix: timestamp was incorrectly calculated for timezones with minute + offset + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=271 +- some improvements thanks to clang's static code analyzer + o overall cleanup (mostly unnecessary writes and otherwise unused stuff) + o bugfix: fixed a very remote problem in msg.c which could occur when + running under extremely low memory conditions +--------------------------------------------------------------------------- +Version 4.6.5 [v4-stable] (rgerhards), 2010-11-24 +- bugfix(important): problem in TLS handling could cause rsyslog to loop + in a tight loop, effectively disabling functionality and bearing the + risk of unresponsiveness of the whole system. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=194 +--------------------------------------------------------------------------- +Version 4.6.4 [v4-stable] (rgerhards), 2010-08-05 +- bugfix: zero-sized (empty) messages were processed by imtcp + they are now dropped as they always should have been +- bugfix: programname filter in ! configuration can not be reset + Thanks to Kiss Gabor for the patch. +--------------------------------------------------------------------------- +Version 4.6.3 [v4-stable] (rgerhards), 2010-07-07 +- improvded testbench + - added test with truly random data received via syslog to test + robustness + - added new configure option that permits to disable and enable an + extended testbench +- bugfix: segfault on HUP when "HUPIsRestart" was set to "on" + thanks varmojfekoj for the patch +- bugfix: default for $OMFileFlushOnTXEnd was wrong ("off"). + This, in default mode, caused buffered writing to be used, what + means that it looked like no output were written or partial + lines. Thanks to Michael Biebl for pointing out this bug. +- bugfix: testbench failed when not executed in UTC+1 timezone + accidently, the time zone information was kept inside some + to-be-checked-for responses +- temporary bugfix replaced by permanent one for + message-induced off-by-one error (potential segfault) (see 4.6.2) + The analysis has been completed and a better fix been crafted and + integrated. +- bugfix: the T/P/E config size specifiers did not work properly under + all 32-bit platforms +- bugfix: local unix system log socket was deleted even when it was + not configured +- some doc fixes; incorrect config samples could cause confusion + thanks to Anthony Edwards for pointing the problems out +--------------------------------------------------------------------------- +Version 4.6.2 [v4-stable] (rgerhards), 2010-03-26 +- new feature: "." action type added to support writing files to relative + pathes (this is primarily meant as a debug aid) +- added replacements for atomic instructions on systems that do not + support them. [backport of Stefen Sledz' patch for v5) +- new feature: $OMFileAsyncWriting directive added + it permits to specifiy if asynchronous writing should be done or not +- bugfix(temporary): message-induced off-by-one error (potential segfault) + Some types of malformed messages could trigger an off-by-one error + (for example, \0 or \n as the last character, and generally control + character escaption is questionable). This is due to not strictly + following a the \0 or string counted string paradigm (during the last + optimization on the cstring class). As a temporary fix, we have + introduced a proper recalculation of the size. However, a final + patch is expected in the future. See bug tracker for further details + and when the final patch will be available: + http://bugzilla.adiscon.com/show_bug.cgi?id=184 + Note that the current patch is considered sufficient to solve the + situation, but it requires a bit more runtime than desirable. +- bugfix: potential segfault in dynafile cache + This bug was triggered by an open failure. The the cache was full and + a new entry needed to be placed inside it, a victim for eviction was + selected. That victim was freed, then the open of the new file tried. If + the open failed, the victim entry was still freed, and the function + exited. However, on next invocation and cache search, the victim entry + was used as if it were populated, most probably resulting in a segfault. +- bugfix: race condition during directory creation + If multiple files try to create a directory at (almost) the same time, + some of them may fail. This is a data race and also exists with other + processes that may create the same directory. We do now check for this + condition and gracefully handle it. +- bugfix: potential re-use of free()ed file stream object in omfile + when dynaCache is enabled, the cache is full, a new entry needs to + be allocated, thus the LRU discarded, then a new entry is opend and that + fails. In that case, it looks like the discarded stream may be reused + improperly (based on code analysis, test case and confirmation pending) +- added new property replacer option "date-rfc3164-buggyday" primarily + to ease migration from syslog-ng. See property replacer doc for + details. [backport from 5.5.3 because urgently needed by some] +- improved testbench +- bugfix: invalid buffer write in (file) stream class + currently being accessed buffer could be overwritten with new data. + While this probably did not cause access violations, it could case loss + and/or duplication of some data (definitely a race with no deterministic + outcome) +- bugfix: potential hang condition during filestream close + predicate was not properly checked when waiting for the background file + writer +- bugfix: improper synchronization when "$OMFileFlushOnTXEnd on" was used + Internal data structures were not properly protected due to missing + mutex calls. +- bugfix: potential data loss during file stream shutdown +- bugfix: potential problems during file stream shutdown + The shutdown/close sequence was not clean, what potentially (but + unlikely) could lead to some issues. We have not been able to describe + any fatal cases, but there was some bug potential. Sequence has now + been straighted out. +- bugfix: potential problem (loop, abort) when file write error occured + When a write error occured in stream.c, variable iWritten had the error + code but this was handled as if it were the actual number of bytes + written. That was used in pointer arithmetic later on, and thus could + lead to all sorts of problems. However, this could only happen if the + error was EINTR or the file in question was a tty. All other cases were + handled properly. Now, iWritten is reset to zero in such cases, resulting + in proper retries. +- bugfix: $omfileFlushOnTXEnd was turned on when set to off and vice + versa due to an invalid check +- bugfix: recent patch to fix small memory leak could cause invalid free. + This could only happen during config file parsing. +- bugfix(minor): handling of extremely large strings in dbgprintf() fixed + Previously, it could lead to garbagge output and, in extreme cases, also + to segfaults. Note: this was a problem only when debug output was + actually enabled, so it caused no problem in production use. +- bugfix(minor): BSD_SO_COMPAT query function had some global vars not + properly initialized. However, in practice the loader initializes them + with zero, the desired value, so there were no actual issue in almost + all cases. +--------------------------------------------------------------------------- +Version 4.6.1 [v4-stable] (rgerhards), 2010-03-04 +- re-enabled old pipe output (using new module ompipe, built-in) after + some problems with pipes (and especially in regard to xconsole) were + discovered. Thanks to Michael Biebl for reporting the issues. +- bugfix: potential problems with large file support could cause segfault + ... and other weird problems. This seemed to affect 32bit-platforms + only, but I can not totally outrule there were issues on other + platforms as well. The previous code could cause system data types + to be defined inconsistently, and that could lead to various + troubles. Special thanks go to the Mandriva team for identifying + an initial problem, help discussing it and ultimately a fix they + contributed. +- bugfix: fixed problem that caused compilation on FreeBSD 9.0 to fail. + bugtracker: http://bugzilla.adiscon.com/show_bug.cgi?id=181 + Thanks to Christiano for reporting. +- bugfix: potential segfault in omfile when a dynafile open failed + In that case, a partial cache entry was written, and some internal + pointers (iCurrElt) not correctly updated. In the next iteration, that + could lead to a segfault, especially if iCurrElt then points to the + then-partial record. Not very likely, but could happen in practice. +- bugfix (theoretical): potential segfault in omfile under low memory + condition. This is only a theoretical bug, because it would only + happen when strdup() fails to allocate memory - which is highly + unlikely and will probably lead to all other sorts of errors. +- bugfix: comment char ('#') in literal terminated script parsing + and thus could not be used. + but tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=119 + [merged in from v3.22.2] +--------------------------------------------------------------------------- +Version 4.6.0 [v4-stable] (rgerhards), 2010-02-24 +*************************************************************************** +* This is a new stable v4 version. It contains all fixes and enhancements * +* made during the 4.5.x phase as well as those listed below. * +* Note: this version is scheduled to conclude the v4 development process. * +* Do not expect any more new developments in v4. The focus is now * +* on v5 (what also means we have a single devel branch again). * +* ("development" means new feature development, bug fixes are of * +* course provided for v4-stable) * +*************************************************************************** +- improved testbench to contain samples for totally malformed messages + which miss parts of the message content +- bugfix: some malformed messages could lead to a missing LF inside files + or some other missing parts of the template content. +- bugfix: if a message ended immediately with a hostname, the hostname + was mistakenly interpreted as TAG, and localhost be used as hostname +- bugfix: message without MSG part could case a segfault + [backported from v5 commit 98d1ed504ec001728955a5bcd7916f64cd85f39f] + This actually was a "recent" regression, but I did not realize that it + was introduced by the performance optimization in v4-devel. Shame on + me for having two devel versions at the same time... +--------------------------------------------------------------------------- +Version 4.5.8 [v4-beta] (rgerhards), 2010-02-10 +- enhanced doc for using PostgreSQL + Thanks to Marc Schiffbauer for the new/updated doc +- bugfix: property replacer returned invalid parameters under some (unusual) + conditions. In extreme cases, this could lead to garbled logs and/or + a system failure. +- bugfix: invalid length returned (often) when using regular expressions + inside the property replacer +- bugfix: submatch regex in property replacer did not honor "return 0 on + no match" config case +- bugfix: imuxsock incorrectly stated inputname "imudp" + Thanks to Ryan Lynch for reporting this. +- (slightly) enhanced support for FreeBSD by setting _PATH_MODDIR to + the correct value on FreeBSD. + Thanks to Cristiano for the patch. +- bugfix: -d did not enable display of debug messages + regression from introduction of "debug on demand" mode + Thanks to Michael Biebl for reporting this bug +- bugfix: blanks inside file names did not terminate file name parsing. + This could reslult in the whole rest of a line (including comments) + to be treated as file name in "write to file" actions. + Thanks to Jack for reporting this issue. +- bugfix: rsyslog hang when writing to a named pipe which nobody was + reading. Thanks to Michael Biebl for reporting this bug. + Bugzilla entry: http://bugzilla.adiscon.com/show_bug.cgi?id=169 +- bugfix: potential segfaults during queue shutdown + (bugs require certain non-standard settings to appear) + Thanks to varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 4.5.7 [v4-beta] (rgerhards), 2009-11-18 +- added a so-called "On Demand Debug" mode, in which debug output can + be generated only after the process has started, but not right from + the beginning. This is assumed to be useful for hard-to-find bugs. + Also improved the doc on the debug system. +- bugfix (kind of): check if TCP connection is still alive if using TLS + Thanks to Jonathan Bond-Caron for the patch. +- bugfix: hostname accidently set to IP address for some message sources, + for example imudp. Thanks to Anton for reporting this bug. +- bugfix [imported from 4.4.3]: $ActionExecOnlyOnceEveryInterval did + not work. +--------------------------------------------------------------------------- +Version 4.5.6 [v4-beta] (rgerhards), 2009-11-05 +- bugfix: named pipes did no longer work (they always got an open error) + this was a regression from the omfile rewrite in 4.5.0 +- bugfix(minor): diag function returned wrong queue memeber count + for the main queue if an active DA queue existed. This had no relevance + to real deployments (assuming they are not running the debug/diagnostic + module...), but sometimes caused grief and false alerts in the + testbench. +- included some important fixes from v4-stable: + * bugfix: invalid handling of zero-sized messages + * bugfix: zero-sized UDP messages are no longer processed + * bugfix: random data could be appended to message + * bugfix: reverse lookup reduction logic in imudp do DNS queries too often +- bugfix(testbench): testcase did not properly wait for rsyslod shutdown + thus some unpredictable behavior and a false negative test result + could occur. [BACKPORTED from v5] +- bugfix(testbench): sequence check was not always performed correctly, + that could result in tests reporting success when they actually failed +--------------------------------------------------------------------------- +Version 4.5.5 [v4-beta] (rgerhards), 2009-10-21 +- added $InputTCPServerNotifyOnConnectionClose config directive + see doc for details +- bugfix: debug string larger than 1K were improperly displayed. Max size + is now 32K +- bugfix: invalid storage class selected for some size config parameters. + This resulted in wrong values. The most prominent victim was the + directory creation mode, which was set to zero in some cases. For + details, see related blog post: + http://blog.gerhards.net/2009/10/another-note-on-hard-to-find-bugs.html +--------------------------------------------------------------------------- +Version 4.5.4 [v4-beta] (rgerhards), 2009-09-29 +- bugfix: potential segfault in stream writer on destruction + Most severely affected omfile. The problem was that some buffers were + freed before the asynchronous writer thread was shut down. So the + writer thread accessed invalid data, which may even already be + overwritten. Symptoms (with omfile) were segfaults, grabled data + and files with random names placed around the file system (most + prominently into the root directory). Special thanks to Aaron for + helping to track this down. +- bugfix: potential race in object loader (obj.c) during use/release + of object interface +- bugfixes: potential problems in out file zip writer. Problems could + lead to abort and/or memory leak. The module is now hardened in a very + conservative way, which is sub-optimal from a performance point of view. + This should be improved if it has proven reliable in practice. +--------------------------------------------------------------------------- +Version 4.5.3 [v4-beta] (rgerhards), 2009-09-17 +- bugfix: repeated messages were incorrectly processed + this could lead to loss of the repeated message content. As a side- + effect, it could probably also be possible that some segfault occurs + (quite unlikely). The root cause was that some counters introduced + during the malloc optimizations were not properly duplicated in + MsgDup(). Note that repeated message processing is not enabled + by default. +- bugfix: message sanitation had some issues: + - control character DEL was not properly escaped + - NUL and LF characters were not properly stripped if no control + character replacement was to be done + - NUL characters in the message body were silently dropped (this was + a regeression introduced by some of the recent optimizations) +- bugfix: strings improperly reused, resulting in some message properties + be populated with strings from previous messages. This was caused by + an improper predicate check. [backported from v5] +- fixed some minor portability issues +- bugfix: reverse lookup reduction logic in imudp do DNS queries too often + [imported from 4.4.2] +--------------------------------------------------------------------------- +Version 4.5.2 [v4-beta] (rgerhards), 2009-08-21 +- legacy syslog parser changed so that it now accepts date stamps in + wrong case. Some devices seem to create them and I do not see any harm + in supporting that. +- added $InputTCPMaxListeners directive - permits to specify how many + TCP servers shall be possible (default is 20). +- bugfix: memory leak with some input modules. Those inputs that + use parseAndSubmitMsg() leak two small memory blocks with every message. + Typically, those process only relatively few messages, so the issue + does most probably not have any effect in practice. +- bugfix: if tcp listen port could not be created, no error message was + emitted +- bugfix: potential segfault in output file writer (omfile) + In async write mode, we use modular arithmetic to index the output + buffer array. However, the counter variables accidently were signed, + thus resulting in negative indizes after integer overflow. That in turn + could lead to segfaults, but was depending on the memory layout of + the instance in question (which in turn depended on a number of + variables, like compile settings but also configuration). The counters + are now unsigned (as they always should have been) and so the dangling + mis-indexing does no longer happen. This bug potentially affected all + installations, even if only some may actually have seen a segfault. +- bugfix: hostnames with dashes in them were incorrectly treated as + malformed, thus causing them to be treated as TAG (this was a regression + introduced from the "rfc3164 strict" change in 4.5.0). +--------------------------------------------------------------------------- +Version 4.5.1 [DEVEL] (rgerhards), 2009-07-15 +- CONFIG CHANGE: $HUPisRestart default is now "off". We are doing this + to support removal of restart-type HUP in v5. +- bugfix: fromhost-ip was sometimes truncated +- bugfix: potential segfault when zip-compressed syslog records were + received (double free) +- bugfix: properties inputname, fromhost, fromhost-ip, msg were lost when + working with disk queues +- performance enhancement: much faster, up to twice as fast (depending + on configuration) +- bugfix: abort condition when RecvFrom was not set and message reduction + was on. Happend e.g. with imuxsock. +- added $klogConsoleLogLevel directive which permits to set a new + console log level while rsyslog is active +- bugfix: message could be truncated after TAG, often when forwarding + This was a result of an internal processing error if maximum field + sizes had been specified in the property replacer. +- added ability for the TCP output action to "rebind" its send socket after + sending n messages (actually, it re-opens the connection, the name is + used because this is a concept very similiar to $ActionUDPRebindInterval). + New config directive $ActionSendTCPRebindInterval added for the purpose. + By default, rebinding is disabled. This is considered useful for load + balancers. +- testbench improvements +--------------------------------------------------------------------------- +Version 4.5.0 [DEVEL] (rgerhards), 2009-07-02 +- activation order of inputs changed, they are now activated only after + privileges are dropped. Thanks to Michael Terry for the patch. +- greatly improved performance +- greatly reduced memory requirements of msg object + to around half of the previous demand. This means that more messages can + be stored in core! Due to fewer cache misses, this also means some + performance improvement. +- improved config error messages: now contain a copy of the config line + that (most likely) caused the error +- reduced max value for $DynaFileCacheSize to 1,000 (the former maximum + of 10,000 really made no sense, even 1,000 is very high, but we like + to keep the user in control ;)). +- added capability to fsync() queue disk files for enhanced reliability + (also add's speed, because you do no longer need to run the whole file + system in sync mode) +- more strict parsing of the hostname in rfc3164 mode, hopefully + removes false positives (but may cause some trouble with hostname + parsing). For details, see this bug tracker: + http://bugzilla.adiscon.com/show_bug.cgi?id=126 +- omfile rewrite to natively support zip files (includes large extension + of the stream class) +- added configuration commands (see doc for explanations) + * $OMFileZipLevel + * $OMFileIOBufferSize + * $OMFileFlushOnTXEnd + * $MainMsgQueueSyncQueueFiles + * $ActionQueueSyncQueueFiles +- done some memory accesses explicitely atomic +- bugfix: subtle (and usually irrelevant) issue in timout processing + timeout could be one second too early if nanoseconds wrapped +- set a more sensible timeout for shutdow, now 1.5 seconds to complete + processing (this also removes those cases where the shutdown message + was not written because the termination happened before it) +- internal bugfix: object pointer was only reset to NULL when an object + was actually destructed. This most likely had no effect to existing code, + but it may also have caused trouble in remote cases. Similarly, the fix + may also cause trouble... +- bugfix: missing initialization during timestamp creation + This could lead to timestamps written in the wrong format, but not to + an abort +--------------------------------------------------------------------------- +Version 4.4.3 [v4-stable] (rgerhards), 2009-10-?? +- bugfix: several smaller bugs resolved after flexelint review + Thanks to varmojfekoj for the patch. +- bugfix: $ActionExecOnlyOnceEveryInterval did not work. + This was a regression from the time() optimizations done in v4. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=143 + Thanks to Klaus Tachtler for reporting this bug. +- bugfix: potential segfault on queue shutdown + Thanks to varmojfekoj for the patch. +- bugfix: potential hang condition on queue shutdown + [imported from v3-stable] +- bugfix: segfault on startup when -q or -Q option was given + [imported from v3-stable] +--------------------------------------------------------------------------- +Version 4.4.2 [v4-stable] (rgerhards), 2009-10-09 +- bugfix: invalid handling of zero-sized messages, could lead to mis- + addressing and potential memory corruption/segfault +- bugfix: zero-sized UDP messages are no longer processed + until now, they were forwarded to processing, but this makes no sense + Also, it looks like the system seems to provide a zero return code + on a UDP recvfrom() from time to time for some internal reasons. These + "receives" are now silently ignored. +- bugfix: random data could be appended to message, possibly causing + segfaults +- bugfix: reverse lookup reduction logic in imudp do DNS queries too often + A comparison was done between the current and the former source address. + However, this was done on the full sockaddr_storage structure and not + on the host address only. This has now been changed for IPv4 and IPv6. + The end result of this bug could be a higher UDP message loss rate than + necessary (note that UDP message loss can not totally be avoided due + to the UDP spec) +--------------------------------------------------------------------------- +Version 4.4.1 [v4-stable] (rgerhards), 2009-09-02 +- features requiring Java are automatically disabled if Java is not + present (thanks to Michael Biebl for his help!) +- bugfix: invalid double-quoted PRI, among others in outgoing messages + This causes grief with all receivers. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=147 +- bugfix: Java testing tools were required, even if testbench was disabled + This resulted in build errors if no Java was present on the build system, + even though none of the selected option actually required Java. + (I forgot to backport a similar fix to newer releases). +- bugfix (backport): omfwd segfault + Note that the orginal (higher version) patch states this happens only + when debugging mode is turned on. That statement is wrong: if debug + mode is turned off, the message is not being emitted, but the division + by zero in the actual parameters still happens. +--------------------------------------------------------------------------- +Version 4.4.0 [v4-stable] (rgerhards), 2009-08-21 +- bugfix: stderr/stdout were not closed to be able to emit error messages, + but this caused ssh sessions to hang. Now we close them after the + initial initialization. See forum thread: + http://kb.monitorware.com/controlling-terminal-issues-t9875.html +- bugfix: sending syslog messages with zip compression did not work +--------------------------------------------------------------------------- +Version 4.3.2 [v4-beta] (rgerhards), 2009-06-24 +- removed long-obsoleted property UxTradMsg +- added a generic network stream server (in addition to rather specific + syslog tcp server) +- added ability for the UDP output action to rebind its send socket after + sending n messages. New config directive $ActionSendUDPRebindInterval + added for the purpose. By default, rebinding is disabled. This is + considered useful for load balancers. +- bugfix: imdiag/imtcp had a race condition +- improved testbench (now much better code design and reuse) +- added config switch --enable-testbench=no to turn off testbench +--------------------------------------------------------------------------- +Version 4.3.1 [DEVEL] (rgerhards), 2009-05-25 +- added capability to run multiple tcp listeners (on different ports) +- performance enhancement: imtcp calls parser no longer on input thread + but rather inside on of the potentially many main msg queue worker + threads (an enhancement scheduled for all input plugins where this is + possible) +- added $GenerateConfigGraph configuration command which can be used + to generate nice-looking (and very informative) rsyslog configuration + graphs. +- added $ActionName configuration directive (currently only used for + graph generation, but may find other uses) +- improved doc + * added (hopefully) easier to grasp queue explanation +- improved testbench + * added tests for queue disk-only mode (checks disk queue logic) +- bugfix: light and full delay watermarks had invalid values, badly + affecting performance for delayable inputs +- build system improvements - thanks to Michael Biebl +- added new testing module imdiag, which enables to talk to the + rsyslog core at runtime. The current implementation is only a + beginning, but can be expanded over time +--------------------------------------------------------------------------- +Version 4.3.0 [DEVEL] (rgerhards), 2009-04-17 +- new feature: new output plugin omprog, which permits to start program + and feed it (via its stdin) with syslog messages. If the program + terminates, it is restarted. +- improved internal handling of RainerScript functions, building the + necessary plumbing to support more functions with decent runtime + performance. This is also necessary towards the long-term goal + of loadable library modules. +- added new RainerScript function "tolower" +- improved testbench + * added tests for tcp-based reception + * added tcp-load test (1000 connections, 20,000 messages) +- added $MaxOpenFiles configuration directive +- bugfix: solved potential memory leak in msg processing, could manifest + itself in imtcp +- bugfix: ompgsql did not detect problems in sql command execution + this could cause loss of messages. The handling was correct if the + connection broke, but not if there was a problem with statement + execution. The most probable case for such a case would be invalid + sql inside the template, and this is now much easier to diagnose. +--------------------------------------------------------------------------- +Version 4.2.0 [v4-stable] (rgerhards), 2009-06-23 +- bugfix: light and full delay watermarks had invalid values, badly + affecting performance for delayable inputs +- imported all patches from 3.22.1 as of today (see below) +- bugfix: compile problems in im3195 +--------------------------------------------------------------------------- +Version 4.1.7 [BETA] (rgerhards), 2009-04-22 +- bugfix: $InputTCPMaxSessions config directive was accepted, but not + honored. This resulted in a fixed upper limit of 200 connections. +- bugfix: the default for $DirCreateMode was 0644, and as such wrong. + It has now been changed to 0700. For some background, please see + http://lists.adiscon.net/pipermail/rsyslog/2009-April/001986.html +- bugfix: ompgsql did not detect problems in sql command execution + this could cause loss of messages. The handling was correct if the + connection broke, but not if there was a problem with statement + execution. The most probable case for such a case would be invalid + sql inside the template, and this is now much easier to diagnose. +--------------------------------------------------------------------------- +Version 4.1.6 [DEVEL] (rgerhards), 2009-04-07 +- added new "csv" property replacer options to enable simple creation + of CSV-formatted outputs (format from RFC4180 is used) +- implemented function support in RainerScript. That means the engine + parses and compile functions, as well as executes a few build-in + ones. Dynamic loading and registration of functions is not yet + supported - but we now have a good foundation to do that later on. +- implemented the strlen() RainerScript function +- added a template output module +- added -T rsyslogd command line option, enables to specify a directory + where to chroot() into on startup. This is NOT a security feature but + introduced to support testing. Thus, -T does not make sure chroot() + is used in a secure way. (may be removed later) +- added omstdout module for testing purposes. Spits out all messages to + stdout - no config option, no other features +- added a parser testing suite (still needs to be extended, but a good + start) +- modified $ModLoad statement so that for modules whom's name starts with + a dot, no path is prepended (this enables relative-pathes and should + not break any valid current config) +- fixed a bug that caused action retries not to work correctly + situation was only cleared by a restart +- bugfix: closed dynafile was potentially never written until another + dynafile name was generated - potential loss of messages +- improved omfile so that it properly suspends itself if there is an + i/o or file name generation error. This enables it to be used with + the full high availability features of rsyslog's engine +- bugfix: fixed some segaults on Solaris, where vsprintf() does not + check for NULL pointers +- improved performance of regexp-based filters + Thanks to Arnaud Cornet for providing the idea and initial patch. +- added a new way how output plugins may be passed parameters. This is + more effcient for some outputs. They new can receive fields not only + as a single string but rather in an array where each string is seperated. +- added (some) developer documentation for output plugin interface +- bugfix: potential abort with DA queue after high watermark is reached + There exists a race condition that can lead to a segfault. Thanks + go to vbernetr, who performed the analysis and provided patch, which + I only tweaked a very little bit. +- bugfix: imtcp did incorrectly parse hostname/tag + Thanks to Luis Fernando Muñoz MejÃas for the patch. +--------------------------------------------------------------------------- +Version 4.1.5 [DEVEL] (rgerhards), 2009-03-11 +- bugfix: parser did not correctly parse fields in UDP-received messages +- added ERE support in filter conditions + new comparison operation "ereregex" +- added new config directive $RepeatedMsgContainsOriginalMsg so that the + "last message repeated n times" messages, if generated, may + have an alternate format that contains the message that is being repeated +--------------------------------------------------------------------------- +Version 4.1.4 [DEVEL] (rgerhards), 2009-01-29 +- bugfix: inconsistent use of mutex/atomic operations could cause segfault + details are too many, for full analysis see blog post at: + http://blog.gerhards.net/2009/01/rsyslog-data-race-analysis.html +- bugfix: unitialized mutex was used in msg.c:getPRI + This was subtle, because getPRI is called as part of the debugging code + (always executed) in syslogd.c:logmsg. +- bufgix: $PreserveFQDN was not properly handled for locally emitted + messages +--------------------------------------------------------------------------- +Version 4.1.3 [DEVEL] (rgerhards), 2008-12-17 +- added $InputTCPServerAddtlFrameDelimiter config directive, which + enables to specify an additional, non-standard message delimiter + for processing plain tcp syslog. This is primarily a fix for the invalid + framing used in Juniper's NetScreen products. Credit to forum user + Arv for suggesting this solution. +- added $InputTCPServerInputName property, which enables a name to be + specified that will be available during message processing in the + inputname property. This is considered useful for logic that treats + messages differently depending on which input received them. +- added $PreserveFQDN config file directive + Enables to use FQDNs in sender names where the legacy default + would have stripped the domain part. + Thanks to BlinkMind, Inc. http://www.blinkmind.com for sponsoring this + development. +- bugfix: imudp went into an endless loop under some circumstances + (but could also leave it under some other circumstances...) + Thanks to David Lang and speedfox for reporting this issue. +--------------------------------------------------------------------------- +Version 4.1.2 [DEVEL] (rgerhards), 2008-12-04 +- bugfix: code did not compile without zlib +- security bugfix: $AllowedSender was not honored, all senders were + permitted instead (see http://www.rsyslog.com/Article322.phtml) +- security fix: imudp emitted a message when a non-permitted sender + tried to send a message to it. This behaviour is operator-configurable. + If enabled, a message was emitted each time. That way an attacker could + effectively fill the disk via this facility. The message is now + emitted only once in a minute (this currently is a hard-coded limit, + if someone comes up with a good reason to make it configurable, we + will probably do that). +- doc bugfix: typo in v3 compatibility document directive syntax + thanks to Andrej for reporting +- imported other changes from 3.21.8 and 3.20.1 (see there) +--------------------------------------------------------------------------- +Version 4.1.1 [DEVEL] (rgerhards), 2008-11-26 +- added $PrivDropToGroup, $PrivDropToUser, $PrivDropToGroupID, + $PrivDropToUserID config directives to enable dropping privileges. + This is an effort to provide a security enhancement. For the limits of this + approach, see http://wiki.rsyslog.com/index.php/Security +- re-enabled imklog to compile on FreeBSD (brought in from beta) +--------------------------------------------------------------------------- +Version 4.1.0 [DEVEL] (rgerhards), 2008-11-18 + +********************************* WARNING ********************************* +This version has a slightly different on-disk format for message entries. +As a consequence, old queue files being read by this version may have +an invalid output timestamp, which could result to some malfunction inside +the output driver. It is recommended to drain queues with the previous +version before switching to this one. +********************************* WARNING ********************************* + +- greatly enhanced performance when compared to v3. +- added configuration directive "HUPisRestart" which enables to configure + HUP to be either a full restart or "just" a leightweight way to + close open files. +- enhanced legacy syslog parser to detect year if part of the timestamp + the format is based on what Cisco devices seem to emit. +- added a setting "$OptimizeForUniprocessor" to enable users to turn off + pthread_yield calls which are counter-productive on multiprocessor + machines (but have been shown to be useful on uniprocessors) +- reordered imudp processing. Message parsing is now done as part of main + message queue worker processing (was part of the input thread) + This should also improve performance, as potentially more work is + done in parallel. +- bugfix: compressed syslog messages could be slightly mis-uncompressed + if the last byte of the compressed record was a NUL +- added $UDPServerTimeRequery option which enables to work with + less acurate timestamps in favor of performance. This enables querying + of the time only every n-th time if imudp is running in the tight + receive loop (aka receiving messsages at a high rate) +- doc bugfix: queue doc had wrong parameter name for setting controlling + worker thread shutdown period +- restructured rsyslog.conf documentation +- bugfix: memory leak in ompgsql + Thanks to Ken for providing the patch +--------------------------------------------------------------------------- +Version 3.22.4 [v3-stable] (rgerhards), 2010-??-?? +- bugfix: action resume interval incorrectly handled, thus took longer to + resume +- bugfix: cosmetic: proper constant used instead of number in open call +- bugfix: timestamp was incorrectly calculated for timezones with minute + offset + closes: http://bugzilla.adiscon.com/show_bug.cgi?id=271 +- improved some code based on clang static analyzer results +- bugfix: potential misadressing in property replacer +--------------------------------------------------------------------------- +Version 3.22.3 [v3-stable] (rgerhards), 2010-11-24 +- bugfix(important): problem in TLS handling could cause rsyslog to loop + in a tight loop, effectively disabling functionality and bearing the + risk of unresponsiveness of the whole system. + Bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=194 +--------------------------------------------------------------------------- +Version 3.22.2 [v3-stable] (rgerhards), 2010-08-05 +- bugfix: comment char ('#') in literal terminated script parsing + and thus could not be used. + but tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=119 +- enhance: imrelp now also provides remote peer's IP address + [if librelp != 1.0.0 is used] +- bugfix: sending syslog messages with zip compression did not work +- bugfix: potential hang condition on queue shutdown +- bugfix: segfault on startup when -q or -Q option was given + bug tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=157 + Thanks to Jonas Nogueira for reporting this bug. +- clarified use of $ActionsSendStreamDriver[AuthMode/PermittedPeers] + in doc set (require TLS drivers) +- bugfix: $CreateDirs variable not properly initialized, default thus + was random (but most often "on") +- bugfix: potential segfault when -p command line option was used + thanks to varmojfekoj for pointing me at this bug +- bugfix: programname filter in ! configuration can not be reset + Thanks to Kiss Gabor for the patch. +--------------------------------------------------------------------------- +Version 3.22.1 [v3-stable] (rgerhards), 2009-07-02 +- bugfix: invalid error message issued if $inlcudeConfig was on an empty + set of files (e.g. *.conf, where none such files existed) + thanks to Michael Biebl for reporting this bug +- bugfix: when run in foreground (but not in debug mode), a + debug message ("DoDie called") was emitted at shutdown. Removed. + thanks to Michael Biebl for reporting this bug +- bugfix: some garbagge was emitted to stderr on shutdown. This + garbage consisted of file names, which were written during + startup (key point: not a pointer error) + thanks to Michael Biebl for reporting this bug +- bugfix: startup and shutdown message were emitted to stdout + thanks to Michael Biebl for reporting this bug +- bugfix: error messages were not emitted to stderr in forked mode + (stderr and stdo are now kept open across forks) +- bugfix: internal messages were emitted to whatever file had fd2 when + rsyslogd ran in forked mode (as usual!) + Thanks to varmojfekoj for the patch +- small enhancement: config validation run now exits with code 1 if an + error is detected. This change is considered important but small enough + to apply it directly to the stable version. [But it is a border case, + the change requires more code than I had hoped. Thus I have NOT tried + to actually catch all cases, this is left for the current devel + releases, if necessary] +- bugfix: light and full delay watermarks had invalid values, badly + affecting performance for delayable inputs +- bugfix: potential segfault issue when multiple $UDPServerRun directives + are specified. Thanks to Michael Biebl for helping to debug this one. +- relaxed GnuTLS version requirement to 1.4.0 after confirmation from the + field that this version is sufficient +- bugfix: parser did not properly handle empty structured data +- bugfix: invalid mutex release in msg.c (detected under thread debugger, + seems not to have any impact on actual deployments) +--------------------------------------------------------------------------- +Version 3.22.0 [v3-stable] (rgerhards), 2009-04-21 +This is the first stable release that includes the full functionality +of the 3.21.x version tree. +- bugfix: $InputTCPMaxSessions config directive was accepted, but not + honored. This resulted in a fixed upper limit of 200 connections. +- bugfix: the default for $DirCreateMode was 0644, and as such wrong. + It has now been changed to 0700. For some background, please see + http://lists.adiscon.net/pipermail/rsyslog/2009-April/001986.html +- bugfix: ompgsql did not detect problems in sql command execution + this could cause loss of messages. The handling was correct if the + connection broke, but not if there was a problem with statement + execution. The most probable case for such a case would be invalid + sql inside the template, and this is now much easier to diagnose. +--------------------------------------------------------------------------- +Version 3.21.11 [BETA] (rgerhards), 2009-04-03 +- build system improvements contributed by Michael Biebl - thx! +- all patches from 3.20.5 incorporated (see it's ChangeLog entry) +--------------------------------------------------------------------------- +Version 3.21.10 [BETA] (rgerhards), 2009-02-02 +- bugfix: inconsistent use of mutex/atomic operations could cause segfault + details are too many, for full analysis see blog post at: + http://blog.gerhards.net/2009/01/rsyslog-data-race-analysis.html +- the string "Do Die" was accidently emited upon exit in non-debug mode + This has now been corrected. Thanks to varmojfekoj for the patch. +- some legacy options were not correctly processed. + Thanks to varmojfekoj for the patch. +- doc bugfix: v3-compatiblity document had typo in config directive + thanks to Andrej for reporting this +--------------------------------------------------------------------------- +Version 3.21.9 [BETA] (rgerhards), 2008-12-04 +- re-release of 3.21.8 with an additional fix, that could also lead + to DoS; 3.21.8 has been removed from the official download archives +- security fix: imudp emitted a message when a non-permitted sender + tried to send a message to it. This behaviour is operator-configurable. + If enabled, a message was emitted each time. That way an attacker could + effectively fill the disk via this facility. The message is now + emitted only once in a minute (this currently is a hard-coded limit, + if someone comes up with a good reason to make it configurable, we + will probably do that). +--------------------------------------------------------------------------- +Version 3.21.8 [BETA] (rgerhards), 2008-12-04 +- bugfix: imklog did not compile on FreeBSD +- security bugfix: $AllowedSender was not honored, all senders were + permitted instead (see http://www.rsyslog.com/Article322.phtml) +- merged in all other changes from 3.20.1 (see there) +--------------------------------------------------------------------------- +Version 3.21.7 [BETA] (rgerhards), 2008-11-11 +- this is the new beta branch, based on the former 3.21.6 devel +- new functionality: ZERO property replacer nomatch option (from v3-stable) +--------------------------------------------------------------------------- +Version 3.21.6 [DEVEL] (rgerhards), 2008-10-22 +- consolidated time calls during msg object creation, improves performance + and consistency +- bugfix: solved a segfault condition +- bugfix: subsecond time properties generated by imfile, imklog and + internal messages could be slightly inconsistent +- bugfix: (potentially big) memory leak on HUP if queues could not be + drained before timeout - thanks to David Lang for pointing this out +- added capability to support multiple module search pathes. Thank + to Marius Tomaschewski for providing the patch. +- bugfix: im3195 did no longer compile +- improved "make distcheck" by ensuring everything relevant is recompiled +--------------------------------------------------------------------------- +Version 3.21.5 [DEVEL] (rgerhards), 2008-09-30 +- performance optimization: unnecessary time() calls during message + parsing removed - thanks to David Lang for his excellent performance + analysis +- added new capability to property replacer: multiple immediately + successive field delimiters are treated as a single one. + Thanks to Zhuang Yuyao for the patch. +- added message property "inputname", which contains the name of the + input (module) that generated it. Presence is depending on suport in + each input module (else it is blank). +- added system property "$myhostname", which contains the name of the + local host as it knows itself. +- imported a number of fixes and enhancements from the stable and + devel branches, including a fix to a potential segfault on HUP + when using UDP listners +- re-enabled gcc builtin atomic operations and added a proper + ./configure check +- bugfix: potential race condition when adding messages to queue + There was a wrong order of mutex lock operations. It is hard to + believe that really caused problems, but in theory it could and with + threading we often see that theory becomes practice if something is only + used long enough on a fast enough machine with enough CPUs ;) +- cleaned up internal debug system code and made it behave better + in regard to multi-threading +--------------------------------------------------------------------------- +Version 3.21.4 [DEVEL] (rgerhards), 2008-09-04 +- removed compile time fixed message size limit (was 2K), limit can now + be set via $MaxMessageSize global config directive (finally gotten rid + of MAXLINE ;)) +- enhanced doc for $ActionExecOnlyEveryNthTimeTimeout +- integrated a number of patches from 3.18.4, namely + - bugfix: order-of magnitude issue with base-10 size definitions + in config file parser. Could lead to invalid sizes, constraints + etc for e.g. queue files and any other object whose size was specified + in base-10 entities. Did not apply to binary entities. Thanks to + RB for finding this bug and providing a patch. + - bugfix: action was not called when system time was set backwards + (until the previous time was reached again). There are still some + side-effects when time is rolled back (A time rollback is really a bad + thing to do, ideally the OS should issue pseudo time (like NetWare did) + when the user tries to roll back time). Thanks to varmojfekoj for this + patch. + - doc bugfix: rsyslog.conf man page improved and minor nit fixed + thanks to Lukas Kuklinek for the patch. +--------------------------------------------------------------------------- +Version 3.21.3 [DEVEL] (rgerhards), 2008-08-13 +- added ability to specify flow control mode for imuxsock +- added ability to execute actions only after the n-th call of the action + This also lead to the addition of two new config directives: + $ActionExecOnlyEveryNthTime and $ActionExecOnlyEveryNthTimeTimeout + This feature is useful, for example, for alerting: it permits you to + send an alert only after at least n occurences of a specific message + have been seen by rsyslogd. This protectes against false positives + due to waiting for additional confirmation. +- bugfix: IPv6 addresses could not be specified in forwarding actions + New syntax @[addr]:port introduced to enable that. Root problem was IPv6 + addresses contain colons. +- somewhat enhanced debugging messages +- imported from 3.18.3: + - enhanced ommysql to support custom port to connect to server + Port can be set via new $ActionOmmysqlServerPort config directive + Note: this was a very minor change and thus deemed appropriate to be + done in the stable release. + - bugfix: misspelled config directive, previously was + $MainMsgQueueWorkeTimeoutrThreadShutdown, is now + $MainMsgQueueWorkerTimeoutThreadShutdown. Note that the misspelled + directive is not preserved - if the misspelled directive was used + (which I consider highly unlikely), the config file must be changed. + Thanks to lperr for reporting the bug. +--------------------------------------------------------------------------- +Version 3.21.2 [DEVEL] (rgerhards), 2008-08-04 +- added $InputUnixListenSocketHostName config directive, which permits to + override the hostname being used on a local unix socket. This is useful + for differentiating "hosts" running in several jails. Feature was + suggested by David Darville, thanks for the suggestion. +- enhanced ommail to support multiple email recipients. This is done by + specifying $ActionMailTo multiple times. Note that this introduces a + small incompatibility to previous config file syntax: the recipient + list is now reset for each action (we honestly believe that will + not cause any problem - apologies if it does). +- enhanced troubleshooting documentation +--------------------------------------------------------------------------- +Version 3.21.1 [DEVEL] (rgerhards), 2008-07-30 +- bugfix: no error was reported if the target of a $IncludeConfig + could not be accessed. +- added testbed for common config errors +- added doc for -u option to rsyslogd man page +- enhanced config file checking - no active actions are detected +- added -N rsyslogd command line option for a config validation run + (which does not execute actual syslogd code and does not interfere + with a running instance) +- somewhat improved emergency configuration. It is now also selected + if the config contains no active actions +- rsyslogd error messages are now reported to stderr by default. can be + turned off by the new "$ErrorMessagesToStderr off" directive + Thanks to HKS for suggesting the new features. +--------------------------------------------------------------------------- +Version 3.21.0 [DEVEL] (rgerhards), 2008-07-18 +- starts a new devel branch +- added a generic test driver for RainerScript plus some test cases + to the testbench +- added a small diagnostic tool to obtain result of gethostname() API +- imported all changes from 3.18.1 until today (some quite important, + see below) +--------------------------------------------------------------------------- +Version 3.20.6 [v3-stable] (rgerhards), 2009-04-16 +- this is the last v3-stable for the 3.20.x series +- bugfix: $InputTCPMaxSessions config directive was accepted, but not + honored. This resulted in a fixed upper limit of 200 connections. +- bugfix: the default for $DirCreateMode was 0644, and as such wrong. + It has now been changed to 0700. For some background, please see + http://lists.adiscon.net/pipermail/rsyslog/2009-April/001986.html +--------------------------------------------------------------------------- +Version 3.20.5 [v3-stable] (rgerhards), 2009-04-02 +- bugfix: potential abort with DA queue after high watermark is reached + There exists a race condition that can lead to a segfault. Thanks + go to vbernetr, who performed the analysis and provided patch, which + I only tweaked a very little bit. +- fixed bugs in RainerScript: + o when converting a number and a string to a common type, both were + actually converted to the other variable's type. + o the value of rsCStrConvertToNumber() was miscalculated. + Thanks to varmojfekoj for the patch +- fixed a bug in configure.ac which resulted in problems with + environment detection - thanks to Michael Biebl for the patch +- fixed a potential segfault problem in gssapi code + thanks to varmojfekoj for the patch +- doc enhance: provide standard template for MySQL module and instructions + on how to modify schema +--------------------------------------------------------------------------- +Version 3.20.4 [v3-stable] (rgerhards), 2009-02-09 +- bugfix: inconsistent use of mutex/atomic operations could cause segfault + details are too many, for full analysis see blog post at: + http://blog.gerhards.net/2009/01/rsyslog-data-race-analysis.html +- bugfix: invalid ./configure settings for RFC3195 + thanks to Michael Biebl for the patch +- bugfix: invalid mutex access in msg.c +- doc bugfix: dist tarball missed 2 files, had one extra file that no + longer belongs into it. Thanks to Michael Biebl for pointing this out. +--------------------------------------------------------------------------- +Version 3.20.3 [v3-stable] (rgerhards), 2009-01-19 +- doc bugfix: v3-compatiblity document had typo in config directive + thanks to Andrej for reporting this +- fixed a potential segfault condition with $AllowedSender directive + On HUP, the root pointers were not properly cleaned up. Thanks to + Michael Biebel, olgoat, and Juha Koho for reporting and analyzing + the bug. +--------------------------------------------------------------------------- +Version 3.20.2 [v3-stable] (rgerhards), 2008-12-04 +- re-release of 3.20.1 with an additional fix, that could also lead + to DoS; 3.20.1 has been removed from the official download archives +- security fix: imudp emitted a message when a non-permitted sender + tried to send a message to it. This behaviour is operator-configurable. + If enabled, a message was emitted each time. That way an attacker could + effectively fill the disk via this facility. The message is now + emitted only once in a minute (this currently is a hard-coded limit, + if someone comes up with a good reason to make it configurable, we + will probably do that). +--------------------------------------------------------------------------- +Version 3.20.1 [v3-stable] (rgerhards), 2008-12-04 +- security bugfix: $AllowedSender was not honored, all senders were + permitted instead +- enhance: regex nomatch option "ZERO" has been added + This allows to return the string 0 if a regular expression is + not found. This is probably useful for storing numerical values into + database columns. +- bugfix: memory leak in gtls netstream driver fixed + memory was lost each time a TLS session was torn down. This could + result in a considerable memory leak if it happened quite frequently + (potential system crash condition) +- doc update: documented how to specify multiple property replacer + options + link to new online regex generator tool added +- minor bufgfix: very small memory leak in gtls netstream driver + around a handful of bytes (< 20) for each HUP +- improved debug output for regular expressions inside property replacer + RE's seem to be a big trouble spot and I would like to have more + information inside the debug log. So I decided to add some additional + debug strings permanently. +--------------------------------------------------------------------------- +Version 3.20.0 [v3-stable] (rgerhards), 2008-11-05 +- this is the inital release of the 3.19.x branch as a stable release +- bugfix: double-free in pctp netstream driver. Thank to varmojfeko + for the patch +--------------------------------------------------------------------------- +Version 3.19.12 [BETA] (rgerhards), 2008-10-16 +- bugfix: subseconds where not correctly extracted from a timestamp + if that timestamp did not contain any subsecond information (the + resulting string was garbagge but should have been "0", what it + now is). +- increased maximum size of a configuration statement to 4K (was 1K) +- imported all fixes from the stable branch (quite a lot) +- bugfix: (potentially big) memory leak on HUP if queues could not be + drained before timeout - thanks to David Lang for pointing this out +--------------------------------------------------------------------------- +Version 3.19.11 [BETA] (rgerhards), 2008-08-25 +This is a refresh of the beta. No beta-specific fixes have been added. +- included fixes from v3-stable (most importantly 3.18.3) +--------------------------------------------------------------------------- +Version 3.19.10 [BETA] (rgerhards), 2008-07-15 +- start of a new beta branch based on former 3.19 devel branch +- bugfix: bad memory leak in disk-based queue modes +- bugfix: UDP syslog forwarding did not work on all platforms + the ai_socktype was incorrectly set to 1. On some platforms, this + lead to failing name resolution (e.g. FreeBSD 7). Thanks to HKS for + reporting the bug. +- bugfix: priority was incorrectly calculated on FreeBSD 7, + because the LOG_MAKEPRI() C macro has a different meaning there (it + is just a simple addition of faciltity and severity). I have changed + this to use own, consistent, code for PRI calculation. Thank to HKS + for reporting this bug. +- bugfix (cosmetical): authorization was not checked when gtls handshake + completed immediately. While this sounds scary, the situation can not + happen in practice. We use non-blocking IO only for server-based gtls + session setup. As TLS requires the exchange of multiple frames before + the handshake completes, it simply is impossible to do this in one + step. However, it is useful to have the code path correct even for + this case - otherwise, we may run into problems if the code is changed + some time later (e.g. to use blocking sockets). Thanks to varmojfekoj + for providing the patch. +- important queue bugfix from 3.18.1 imported (see below) +- cleanup of some debug messages +--------------------------------------------------------------------------- +Version 3.19.9 (rgerhards), 2008-07-07 +- added tutorial for creating a TLS-secured syslog infrastructure +- rewritten omusrmsg to no longer fork() a new process for sending messages + this caused some problems with the threading model, e.g. zombies. Also, + it was far less optimal than it is now. +- bugfix: machine certificate was required for client even in TLS anon mode + Reference: http://bugzilla.adiscon.com/show_bug.cgi?id=85 + The fix also slightly improves performance by not storing certificates in + client sessions when there is no need to do so. +- bugfix: RainerScript syntax error was not always detected +--------------------------------------------------------------------------- +Version 3.19.8 (rgerhards), 2008-07-01 +- bugfix: gtls module did not correctly handle EGAIN (and similar) recv() + states. This has been fixed by introducing a new abstraction layer inside + gtls. +- added (internal) error codes to error messages; added redirector to + web description of error codes + closes bug http://bugzilla.adiscon.com/show_bug.cgi?id=20 +- disabled compile warnings caused by third-party libraries +- reduced number of compile warnings in gcc's -pedantic mode +- some minor documentation improvements +- included all fixes from beta 3.17.5 +--------------------------------------------------------------------------- +Version 3.19.7 (rgerhards), 2008-06-11 +- added new property replacer option "date-subseconds" that enables + to query just the subsecond part of a high-precision timestamp +- somewhat improved plain tcp syslog reliability by doing a connection + check before sending. Credits to Martin Schuette for providing the + idea. Details are available at + http://blog.gerhards.net/2008/06/reliable-plain-tcp-syslog-once-again.html +- made rsyslog tickless in the (usual and default) case that repeated + message reduction is turned off. More info: + http://blog.gerhards.net/2008/06/coding-to-save-environment.html +- some build system cleanup, thanks to Michael Biebl +- bugfix: compile under (Free)BSD failed due to some invalid library + definitions - this is fixed now. Thanks to Michael Biebl for the patch. +--------------------------------------------------------------------------- +Version 3.19.6 (rgerhards), 2008-06-06 +- enhanced property replacer to support multiple regex matches +- bugfix: part of permittedPeer structure was not correctly initialized + thanks to varmojfekoj for spotting this +- bugfix: off-by-one bug during certificate check +- bugfix: removed some memory leaks in TLS code +--------------------------------------------------------------------------- +Version 3.19.5 (rgerhards), 2008-05-30 +- enabled Posix ERE expressions inside the property replacer + (previously BRE was permitted only) +- provided ability to specify that a regular expression submatch shall + be used inside the property replacer +- implemented in property replacer: if a regular expression does not match, + it can now either return "**NO MATCH** (default, as before), a blank + property or the full original property text +- enhanced property replacer to support multiple regex matches +--------------------------------------------------------------------------- +Version 3.19.4 (rgerhards), 2008-05-27 +- implemented x509/certvalid gtls auth mode +- implemented x509/name gtls auth mode (including wildcards) +- changed fingerprint gtls auth mode to new format fingerprint +- protected gtls error string function by a mutex. Without it, we + could have a race condition in extreme cases. This was very remote, + but now can no longer happen. +- changed config directive name to reflect different use + $ActionSendStreamDriverCertFingerprint is now + $ActionSendStreamDriverPermittedPeer and can be used both for + fingerprint and name authentication (similar to the input side) +- bugfix: sender information (fromhost et al) was missing in imudp + thanks to sandiso for reporting this bug +- this release fully inplements IETF's syslog-transport-tls-12 plus + the latest text changes Joe Salowey provided via email. Not included + is ipAddress subjectAltName authentication, which I think will be + dropped from the draft. I don't think there is any real need for it. +This release also includes all bug fix up to today from the beta +and stable branches. Most importantly, this means the bugfix for +100% CPU utilization by imklog. +--------------------------------------------------------------------------- +Version 3.19.3 (rgerhards), 2008-05-21 +- added ability to authenticate the server against its certificate + fingerprint +- added ability for client to provide its fingerprint +- added ability for server to obtain client cert's fingerprint +- bugfix: small mem leak in omfwd on exit (strmdriver name was not freed) +- bugfix: $ActionSendStreamDriver had no effect +- bugfix: default syslog port was no longer used if none was + configured. Thanks to varmojfekoj for the patch +- bugfix: missing linker options caused build to fail on some + systems. Thanks to Tiziano Mueller for the patch. +--------------------------------------------------------------------------- +Version 3.19.2 (rgerhards), 2008-05-16 +- bugfix: TCP input modules did incorrectly set fromhost property + (always blank) +- bugfix: imklog did not set fromhost property +- added "fromhost-ip" property + Note that adding this property changes the on-disk format for messages. + However, that should not have any bad effect on existing spool files. + But you will run into trouble if you create a spool file with this + version and then try to process it with an older one (after a downgrade). + Don't do that ;) +- added "RSYSLOG_DebugFormat" canned template +- bugfix: hostname and fromhost were swapped when a persisted message + (in queued mode) was read in +- bugfix: lmtcpclt, lmtcpsrv and lmgssutil did all link to the static + runtime library, resulting in a large size increase (and potential + "interesting" effects). Thanks to Michael Biebel for reporting the size + issue. +- bugfix: TLS server went into an endless loop in some situations. + Thanks to Michael Biebl for reporting the problem. +- fixed potential segfault due to invalid call to cfsysline + thanks to varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 3.19.1 (rgerhards), 2008-05-07 +- configure help for --enable-gnutls wrong - said default is "yes" but + default actually is "no" - thanks to darix for pointing this out +- file dirty.h was missing - thanks to darix for pointing this out +- bugfix: man files were not properly distributed - thanks to + darix for reporting and to Michael Biebl for help with the fix +- some minor cleanup +--------------------------------------------------------------------------- +Version 3.19.0 (rgerhards), 2008-05-06 +- begins new devel branch version +- implemented TLS for plain tcp syslog (this is also the world's first + implementation of IETF's upcoming syslog-transport-tls draft) +- partly rewritten and improved omfwd among others, now loads TCP + code only if this is actually necessary +- split of a "runtime library" for rsyslog - this is not yet a clean + model, because some modularization is still outstanding. In theory, + this shall enable other utilities but rsyslogd to use the same + runtime +- implemented im3195, the RFC3195 input as a plugin +- changed directory structure, files are now better organized +- a lot of cleanup in regard to modularization +- -c option no longer must be the first option - thanks to varmjofekoj + for the patch +--------------------------------------------------------------------------- +Version 3.18.7 (rgerhards), 2008-12-?? +- bugfix: the default for $DirCreateMode was 0644, and as such wrong. + It has now been changed to 0700. For some background, please see + http://lists.adiscon.net/pipermail/rsyslog/2009-April/001986.html +- fixed a potential segfault condition with $AllowedSender directive + On HUP, the root pointers were not properly cleaned up. Thanks to + Michael Biebel, olgoat, and Juha Koho for reporting and analyzing + the bug. +- some legacy options were not correctly processed. + Thanks to varmojfekoj for the patch. +- doc bugfix: some spelling errors in man pages corrected. Thanks to + Geoff Simmons for the patch. +--------------------------------------------------------------------------- +Version 3.18.6 (rgerhards), 2008-12-08 +- security bugfix: $AllowedSender was not honored, all senders were + permitted instead (see http://www.rsyslog.com/Article322.phtml) + (backport from v3-stable, v3.20.9) +- minor bugfix: dual close() call on tcp session closure +--------------------------------------------------------------------------- +Version 3.18.5 (rgerhards), 2008-10-09 +- bugfix: imudp input module could cause segfault on HUP + It did not properly de-init a variable acting as a linked list head. + That resulted in trying to access freed memory blocks after the HUP. +- bugfix: rsyslogd could hang on HUP + because getnameinfo() is not cancel-safe, but was not guarded against + being cancelled. pthread_cancel() is routinely being called during + HUP processing. +- bugfix[minor]: if queue size reached light_delay mark, enqueuing + could potentially be blocked for a longer period of time, which + was not the behaviour desired. +- doc bugfix: $ActionExecOnlyWhenPreviousIsSuspended was still misspelled + as $...OnlyIfPrev... in some parts of the documentation. Thanks to + Lorenzo M. Catucci for reporting this bug. +- added doc on malformed messages, cause and how to work-around, to the + doc set +- added doc on how to build from source repository +--------------------------------------------------------------------------- +Version 3.18.4 (rgerhards), 2008-09-18 +- bugfix: order-of magnitude issue with base-10 size definitions + in config file parser. Could lead to invalid sizes, constraints + etc for e.g. queue files and any other object whose size was specified + in base-10 entities. Did not apply to binary entities. Thanks to + RB for finding this bug and providing a patch. +- bugfix: action was not called when system time was set backwards + (until the previous time was reached again). There are still some + side-effects when time is rolled back (A time rollback is really a bad + thing to do, ideally the OS should issue pseudo time (like NetWare did) + when the user tries to roll back time). Thanks to varmojfekoj for this + patch. +- doc bugfix: rsyslog.conf man page improved and minor nit fixed + thanks to Lukas Kuklinek for the patch. +- bugfix: error code -2025 was used for two different errors. queue full + is now -2074 and -2025 is unique again. (did cause no real problem + except for troubleshooting) +- bugfix: default discard severity was incorrectly set to 4, which lead + to discard-on-queue-full to be enabled by default. That could cause + message loss where non was expected. The default has now been changed + to the correct value of 8, which disables the functionality. This + problem applied both to the main message queue and the action queues. + Thanks to Raoul Bhatia for pointing out this problem. +- bugfix: option value for legacy -a option could not be specified, + resulting in strange operations. Thanks to Marius Tomaschewski + for the patch. +- bugfix: colon after date should be ignored, but was not. This has + now been corrected. Required change to the internal ParseTIMESTAMP3164() + interface. +--------------------------------------------------------------------------- +Version 3.18.3 (rgerhards), 2008-08-18 +- bugfix: imfile could cause a segfault upon rsyslogd HUP and termination + Thanks to lperr for an excellent bug report that helped detect this + problem. +- enhanced ommysql to support custom port to connect to server + Port can be set via new $ActionOmmysqlServerPort config directive + Note: this was a very minor change and thus deemed appropriate to be + done in the stable release. +- bugfix: misspelled config directive, previously was + $MainMsgQueueWorkeTimeoutrThreadShutdown, is now + $MainMsgQueueWorkerTimeoutThreadShutdown. Note that the misspelled + directive is not preserved - if the misspelled directive was used + (which I consider highly unlikely), the config file must be changed. + Thanks to lperr for reporting the bug. +- disabled flow control for imuxsock, as it could cause system hangs + under some circumstances. The devel (3.21.3 and above) will + re-enable it and provide enhanced configurability to overcome the + problems if they occur. +--------------------------------------------------------------------------- +Version 3.18.2 (rgerhards), 2008-08-08 +- merged in IPv6 forwarding address bugfix from v2-stable +--------------------------------------------------------------------------- +Version 3.18.1 (rgerhards), 2008-07-21 +- bugfix: potential segfault in creating message mutex in non-direct queue + mode. rsyslogd segfaults on freeeBSD 7.0 (an potentially other platforms) + if an action queue is running in any other mode than non-direct. The + same problem can potentially be triggered by some main message queue + settings. In any case, it will manifest during rsylog's startup. It is + unlikely to happen after a successful startup (the only window of + exposure may be a relatively seldom executed action running in queued + mode). This has been corrected. Thank to HKS for point out the problem. +- bugfix: priority was incorrectly calculated on FreeBSD 7, + because the LOG_MAKEPRI() C macro has a different meaning there (it + is just a simple addition of faciltity and severity). I have changed + this to use own, consistent, code for PRI calculation. [Backport from + 3.19.10] +- bugfix: remove PRI part from kernel message if it is present + Thanks to Michael Biebl for reporting this bug +- bugfix: mark messages were not correctly written to text log files + the markmessageinterval was not correctly propagated to all places + where it was needed. This resulted in rsyslog using the default + (20 minutes) in some code pathes, what looked to the user like mark + messages were never written. +- added a new property replacer option "sp-if-no-1st-sp" to cover + a problem with RFC 3164 based interpreation of tag separation. While + it is a generic approach, it fixes a format problem introduced in + 3.18.0, where kernel messages no longer had a space after the tag. + This is done by a modifcation of the default templates. + Please note that this may affect some messages where there intentionally + is no space between the tag and the first character of the message + content. If so, this needs to be worked around via a specific + template. However, we consider this scenario to be quite remote and, + even if it exists, it is not expected that it will actually cause + problems with log parsers (instead, we assume the new default template + behaviour may fix previous problems with log parsers due to the + missing space). +- bugfix: imklog module was not correctly compiled for GNU/kFreeBSD. + Thanks to Petr Salinger for the patch +- doc bugfix: property replacer options secpath-replace and + secpath-drop were not documented +- doc bugfix: fixed some typos in rsyslog.conf man page +- fixed typo in source comment - thanks to Rio Fujita +- some general cleanup (thanks to Michael Biebl) +--------------------------------------------------------------------------- +Version 3.18.0 (rgerhards), 2008-07-11 +- begun a new v3-stable based on former 3.17.4 beta plus patches to + previous v3-stable +- bugfix in RainerScript: syntax error was not always detected +--------------------------------------------------------------------------- +Version 3.17.5 (rgerhards), 2008-06-27 +- added doc: howto set up a reliable connection to remote server via + queued mode (and plain tcp protocol) +- bugfix: comments after actions were not properly treated. For some + actions (e.g. forwarding), this could also lead to invalid configuration +--------------------------------------------------------------------------- +Version 3.17.4 (rgerhards), 2008-06-16 +- changed default for $KlogSymbolLookup to "off". The directive is + also scheduled for removal in a later version. This was necessary + because on kernels >= 2.6, the kernel does the symbol lookup itself. The + imklog lookup logic then breaks the log message and makes it unusable. +--------------------------------------------------------------------------- +Version 3.17.3 (rgerhards), 2008-05-28 +- bugfix: imklog went into an endless loop if a PRI value was inside + a kernel log message (unusual case under Linux, frequent under BSD) +--------------------------------------------------------------------------- +Version 3.17.2 (rgerhards), 2008-05-04 +- this version is the new beta, based on 3.17.1 devel feature set +- merged in imklog bug fix from v3-stable (3.16.1) +--------------------------------------------------------------------------- +Version 3.17.1 (rgerhards), 2008-04-15 +- removed dependency on MAXHOSTNAMELEN as much as it made sense. + GNU/Hurd does not define it (because it has no limit), and we have taken + care for cases where it is undefined now. However, some very few places + remain where IMHO it currently is not worth fixing the code. If it is + not defined, we have used a generous value of 1K, which is above IETF + RFC's on hostname length at all. The memory consumption is no issue, as + there are only a handful of this buffers allocated *per run* -- that's + also the main reason why we consider it not worth to be fixed any further. +- enhanced legacy syslog parser to handle slightly malformed messages + (with a space in front of the timestamp) - at least HP procurve is + known to do that and I won't outrule that others also do it. The + change looks quite unintrusive and so we added it to the parser. +- implemented klogd functionality for BSD +- implemented high precision timestamps for the kernel log. Thanks to + Michael Biebl for pointing out that the kernel log did not have them. +- provided ability to discard non-kernel messages if they are present + in the kernel log (seems to happen on BSD) +- implemented $KLogInternalMsgFacility config directive +- implemented $KLogPermitNonKernelFacility config directive +Plus a number of bugfixes that were applied to v3-stable and beta +branches (not mentioned here in detail). +--------------------------------------------------------------------------- +Version 3.17.0 (rgerhards), 2008-04-08 +- added native ability to send mail messages +- removed no longer needed file relptuil.c/.h +- added $ActionExecOnlyOnceEveryInterval config directive +- bugfix: memory leaks in script engine +- bugfix: zero-length strings were not supported in object + deserializer +- properties are now case-insensitive everywhere (script, filters, + templates) +- added the capability to specify a processing (actually dequeue) + timeframe with queues - so things can be configured to be done + at off-peak hours +- We have removed the 32 character size limit (from RFC3164) on the + tag. This had bad effects on existing envrionments, as sysklogd didn't + obey it either (probably another bug in RFC3164...). We now receive + the full size, but will modify the outputs so that only 32 characters + max are used by default. If you need large tags in the output, you need + to provide custom templates. +- changed command line processing. -v, -M, -c options are now parsed + and processed before all other options. Inter-option dependencies + have been relieved. Among others, permits to specify intial module + load path via -M only (not the environment) which makes it much + easier to work with non-standard module library locations. Thanks + to varmojfekoj for suggesting this change. Matches bugzilla bug 55. +- bugfix: some messages were emited without hostname +Plus a number of bugfixes that were applied to v3-stable and beta +branches (not mentioned here in detail). +--------------------------------------------------------------------------- +Version 3.16.3 (rgerhards), 2008-07-11 +- updated information on rsyslog packages +- bugfix: memory leak in disk-based queue modes +--------------------------------------------------------------------------- +Version 3.16.2 (rgerhards), 2008-06-25 +- fixed potential segfault due to invalid call to cfsysline + thanks to varmojfekoj for the patch +- bugfix: some whitespaces where incorrectly not ignored when parsing + the config file. This is now corrected. Thanks to Michael Biebl for + pointing out the problem. +--------------------------------------------------------------------------- +Version 3.16.1 (rgerhards), 2008-05-02 +- fixed a bug in imklog which lead to startup problems (including + segfault) on some platforms under some circumsances. Thanks to + Vieri for reporting this bug and helping to troubleshoot it. +--------------------------------------------------------------------------- +Version 3.16.0 (rgerhards), 2008-04-24 +- new v3-stable (3.16.x) based on beta 3.15.x (RELP support) +- bugfix: omsnmp had a too-small sized buffer for hostname+port. This + could not lead to a segfault, as snprintf() was used, but could cause + some trouble with extensively long hostnames. +- applied patch from Tiziano Müller to remove some compiler warnings +- added gssapi overview/howto thanks to Peter Vrabec +- changed some files to grant LGPLv3 extended persmissions on top of GPLv3 + this also is the first sign of something that will evolve into a + well-defined "rsyslog runtime library" +--------------------------------------------------------------------------- +Version 3.15.1 (rgerhards), 2008-04-11 +- bugfix: some messages were emited without hostname +- disabled atomic operations for the time being because they introduce some + cross-platform trouble - need to see how to fix this in the best + possible way +- bugfix: zero-length strings were not supported in object + deserializer +- added librelp check via PKG_CHECK thanks to Michael Biebl's patch +- file relputil.c deleted, is not actually needed +- added more meaningful error messages to rsyslogd (when some errors + happens during startup) +- bugfix: memory leaks in script engine +- bugfix: $hostname and $fromhost in RainerScript did not work +This release also includes all changes applied to the stable versions +up to today. +--------------------------------------------------------------------------- +Version 3.15.0 (rgerhards), 2008-04-01 +- major new feature: imrelp/omrelp support reliable delivery of syslog + messages via the RELP protocol and librelp (http://www.librelp.com). + Plain tcp syslog, so far the best reliability solution, can lose + messages when something goes wrong or a peer goes down. With RELP, + this can no longer happen. See imrelp.html for more details. +- bugfix: rsyslogd was no longer build by default; man pages are + only installed if corresponding option is selected. Thanks to + Michael Biebl for pointing these problems out. +--------------------------------------------------------------------------- +Version 3.14.2 (rgerhards), 2008-04-09 +- bugfix: segfault with expression-based filters +- bugfix: omsnmp did not deref errmsg object on exit (no bad effects caused) +- some cleanup +- bugfix: imklog did not work well with kernel 2.6+. Thanks to Peter + Vrabec for patching it based on the development in sysklogd - and thanks + to the sysklogd project for upgrading klogd to support the new + functionality +- some cleanup in imklog +- bugfix: potential segfault in imklog when kernel is compiled without + /proc/kallsyms and the file System.map is missing. Thanks to + Andrea Morandi for pointing it out and suggesting a fix. +- bugfixes, credits to varmojfekoj: + * reset errno before printing a warning message + * misspelled directive name in code processing legacy options +- bugfix: some legacy options not correctly interpreted - thanks to + varmojfekoj for the patch +- improved detection of modules being loaded more than once + thanks to varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 3.14.1 (rgerhards), 2008-04-04 +- bugfix: some messages were emited without hostname +- bugfix: rsyslogd was no longer build by default; man pages are + only installed if corresponding option is selected. Thanks to + Michael Biebl for pointing these problems out. +- bugfix: zero-length strings were not supported in object + deserializer +- disabled atomic operations for this stable build as it caused + platform problems +- bugfix: memory leaks in script engine +- bugfix: $hostname and $fromhost in RainerScript did not work +- bugfix: some memory leak when queue is runing in disk mode +- man pages improved thanks to varmofekoj and Peter Vrabec +- We have removed the 32 character size limit (from RFC3164) on the + tag. This had bad effects on existing envrionments, as sysklogd didn't + obey it either (probably another bug in RFC3164...). We now receive + the full size, but will modify the outputs so that only 32 characters + max are used by default. If you need large tags in the output, you need + to provide custom templates. +- bugfix: some memory leak when queue is runing in disk mode +--------------------------------------------------------------------------- +Version 3.14.0 (rgerhards), 2008-04-02 +An interim version was accidently released to the web. It was named 3.14.0. +To avoid confusion, we have not assigned this version number to any +official release. If you happen to use 3.14.0, please update to 3.14.1. +--------------------------------------------------------------------------- +Version 3.13.0-dev0 (rgerhards), 2008-03-31 +- bugfix: accidently set debug option in 3.12.5 reset to production + This option prevented dlclose() to be called. It had no real bad effects, + as the modules were otherwise correctly deinitialized and dlopen() + supports multiple opens of the same module without any memory footprint. +- removed --enable-mudflap, added --enable-valgrind ./configure setting +- bugfix: tcp receiver could segfault due to uninitialized variable +- docfix: queue doc had a wrong directive name that prevented max worker + threads to be correctly set +- worked a bit on atomic memory operations to support problem-free + threading (only at non-intrusive places) +- added a --enable/disable-rsyslogd configure option so that + source-based packaging systems can build plugins without the need + to compile rsyslogd +- some cleanup +- test of potential new version number scheme +--------------------------------------------------------------------------- +Version 3.12.5 (rgerhards), 2008-03-28 +- changed default for "last message repeated n times", which is now + off by default +- implemented backward compatibility commandline option parsing +- automatically generated compatibility config lines are now also + logged so that a user can diagnose problems with them +- added compatibility mode for -a, -o and -p options +- compatibility mode processing finished +- changed default file output format to include high-precision timestamps +- added a buid-in template for previous syslogd file format +- added new $ActionFileDefaultTemplate directive +- added support for high-precision timestamps when receiving legacy + syslog messages +- added new $ActionForwardDefaultTemplate directive +- added new $ActionGSSForwardDefaultTemplate directive +- added build-in templates for easier configuration +- bugfix: fixed small memory leak in tcpclt.c +- bugfix: fixed small memory leak in template regular expressions +- bugfix: regular expressions inside property replacer did not work + properly +- bugfix: QHOUR and HHOUR properties were wrongly calculated +- bugfix: fixed memory leaks in stream class and imfile +- bugfix: $ModDir did invalid bounds checking, potential overlow in + dbgprintf() - thanks to varmojfekoj for the patch +- bugfix: -t and -g legacy options max number of sessions had a wrong + and much too high value +--------------------------------------------------------------------------- +Version 3.12.4 (rgerhards), 2008-03-25 +- Greatly enhanced rsyslogd's file write performance by disabling + file syncing capability of output modules by default. This + feature is usually not required, not useful and an extreme performance + hit (both to rsyslogd as well as the system at large). Unfortunately, + most users enable it by default, because it was most intuitive to enable + it in plain old sysklogd syslog.conf format. There is now the + $ActionFileEnableSync config setting which must be enabled in order to + support syncing. By default it is off. So even if the old-format config + lines request syncing, it is not done unless explicitely enabled. I am + sure this is a very useful change and not a risk at all. I need to think + if I undo it under compatibility mode, but currently this does not + happen (I fear a lot of lazy users will run rsyslogd in compatibility + mode, again bringing up this performance problem...). +- added flow control options to other input sources +- added $HHOUR and $QHOUR system properties - can be used for half- and + quarter-hour logfile rotation +- changed queue's discard severities default value to 8 (do not discard) + to prevent unintentional message loss +- removed a no-longer needed callback from the output module + interface. Results in reduced code complexity. +- bugfix/doc: removed no longer supported -h option from man page +- bugfix: imklog leaked several hundered KB on each HUP. Thanks to + varmojfekoj for the patch +- bugfix: potential segfault on module unload. Thanks to varmojfekoj for + the patch +- bugfix: fixed some minor memory leaks +- bugfix: fixed some slightly invalid memory accesses +- bugfix: internally generated messages had "FROMHOST" property not set +--------------------------------------------------------------------------- +Version 3.12.3 (rgerhards), 2008-03-18 +- added advanced flow control for congestion cases (mode depending on message + source and its capablity to be delayed without bad side effects) +- bugfix: $ModDir should not be reset on $ResetConfig - this can cause a lot + of confusion and there is no real good reason to do so. Also conflicts with + the new -M option and environment setting. +- bugfix: TCP and GSSAPI framing mode variable was uninitialized, leading to + wrong framing (caused, among others, interop problems) +- bugfix: TCP (and GSSAPI) octet-counted frame did not work correctly in all + situations. If the header was split across two packet reads, it was invalidly + processed, causing loss or modification of messages. +- bugfix: memory leak in imfile +- bugfix: duplicate public symbol in omfwd and omgssapi could lead to + segfault. thanks to varmojfekoj for the patch. +- bugfix: rsyslogd aborted on sigup - thanks to varmojfekoj for the patch +- some more internal cleanup ;) +- begun relp modules, but these are not functional yet +- Greatly enhanced rsyslogd's file write performance by disabling + file syncing capability of output modules by default. This + feature is usually not required, not useful and an extreme performance + hit (both to rsyslogd as well as the system at large). Unfortunately, + most users enable it by default, because it was most intuitive to enable + it in plain old sysklogd syslog.conf format. There is now a new config + setting which must be enabled in order to support syncing. By default it + is off. So even if the old-format config lines request syncing, it is + not done unless explicitely enabled. I am sure this is a very useful + change and not a risk at all. I need to think if I undo it under + compatibility mode, but currently this does not happen (I fear a lot of + lazy users will run rsyslogd in compatibility mode, again bringing up + this performance problem...). +--------------------------------------------------------------------------- +Version 3.12.2 (rgerhards), 2008-03-13 +- added RSYSLOGD_MODDIR environment variable +- added -M rsyslogd option (allows to specify module directory location) +- converted net.c into a loadable library plugin +- bugfix: debug module now survives unload of loadable module when + printing out function call data +- bugfix: not properly initialized data could cause several segfaults if + there were errors in the config file - thanks to varmojfekoj for the patch +- bugfix: rsyslogd segfaulted when imfile read an empty line - thanks + to Johnny Tan for an excellent bug report +- implemented dynamic module unload capability (not visible to end user) +- some more internal cleanup +- bugfix: imgssapi segfaulted under some conditions; this fix is actually + not just a fix but a change in the object model. Thanks to varmojfekoj + for providing the bug report, an initial fix and lots of good discussion + that lead to where we finally ended up. +- improved session recovery when outbound tcp connection breaks, reduces + probability of message loss at the price of a highly unlikely potential + (single) message duplication +--------------------------------------------------------------------------- +Version 3.12.1 (rgerhards), 2008-03-06 +- added library plugins, which can be automatically loaded +- bugfix: actions were not correctly retried; caused message loss +- changed module loader to automatically add ".so" suffix if not + specified (over time, this shall also ease portability of config + files) +- improved debugging support; debug runtime options can now be set via + an environment variable +- bugfix: removed debugging code that I forgot to remove before releasing + 3.12.0 (does not cause harm and happened only during startup) +- added support for the MonitorWare syslog MIB to omsnmp +- internal code improvements (more code converted into classes) +- internal code reworking of the imtcp/imgssapi module +- added capability to ignore client-provided timestamp on unix sockets and + made this mode the default; this was needed, as some programs (e.g. sshd) + log with inconsistent timezone information, what messes up the local + logs (which by default don't even contain time zone information). This + seems to be consistent with what sysklogd did for the past four years. + Alternate behaviour may be desirable if gateway-like processes send + messages via the local log slot - in this case, it can be enabled + via the $InputUnixListenSocketIgnoreMsgTimestamp and + $SystemLogSocketIgnoreMsgTimestamp config directives +- added ability to compile on HP UX; verified that imudp worked on HP UX; + however, we are still in need of people trying out rsyslogd on HP UX, + so it can not yet be assumed it runs there +- improved session recovery when outbound tcp connection breaks, reduces + probability of message loss at the price of a highly unlikely potential + (single) message duplication +--------------------------------------------------------------------------- +Version 3.12.0 (rgerhards), 2008-02-28 +- added full expression support for filters; filters can now contain + arbitrary complex boolean, string and arithmetic expressions +--------------------------------------------------------------------------- +Version 3.11.6 (rgerhards), 2008-02-27 +- bugfix: gssapi libraries were still linked to rsyslog core, what should + no longer be necessary. Applied fix by Michael Biebl to solve this. +- enabled imgssapi to be loaded side-by-side with imtcp +- added InputGSSServerPermitPlainTCP config directive +- split imgssapi source code somewhat from imtcp +- bugfix: queue cancel cleanup handler could be called with + invalid pointer if dequeue failed +- bugfix: rsyslogd segfaulted on second SIGHUP + tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=38 +- improved stability of queue engine +- bugfix: queue disk file were not properly persisted when + immediately after closing an output file rsyslog was stopped + or huped (the new output file open must NOT have happend at + that point) - this lead to a sparse and invalid queue file + which could cause several problems to the engine (unpredictable + results). This situation should have happened only in very + rare cases. tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=40 +- bugfix: during queue shutdown, an assert invalidly triggered when + the primary queue's DA worker was terminated while the DA queue's + regular worker was still executing. This could result in a segfault + during shutdown. + tracker: http://bugzilla.adiscon.com/show_bug.cgi?id=41 +- bugfix: queue properties sizeOnDisk, bytesRead were persisted to + disk with wrong data type (long instead of int64) - could cause + problems on 32 bit machines +- bugfix: queue aborted when it was shut down, DA-enabled, DA mode + was just initiated but not fully initialized (a race condition) +- bugfix: imfile could abort under extreme stress conditions + (when it was terminated before it could open all of its + to be monitored files) +- applied patch from varmojfekoj to fix an issue with compatibility + mode and default module directories (many thanks!): + I've also noticed a bug in the compatibility code; the problem is that + options are parsed before configuration file so options which need a + module to be loaded will currently ignore any $moddir directive. This + can be fixed by moving legacyOptsHook() after config file parsing. + (see the attached patch) This goes against the logical order of + processing, but the legacy options are only few and it doesn't seem to + be a problem. +- bugfix: object property deserializer did not handle negative numbers +--------------------------------------------------------------------------- +Version 3.11.5 (rgerhards), 2008-02-25 +- new imgssapi module, changed imtcp module - this enables to load/package + GSSAPI support separately - thanks to varmojfekoj for the patch +- compatibility mode (the -c option series) is now at least partly + completed - thanks to varmojfekoj for the patch +- documentation for imgssapi and imtcp added +- duplicate $ModLoad's for the same module are now detected and + rejected -- thanks to varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 3.11.4 (rgerhards), 2008-02-21 +- bugfix: debug.html was missing from release tarball - thanks to Michael + Biebl for bringing this to my attention +- some internal cleanup on the stringbuf object calling interface +- general code cleanup and further modularization +- $MainMessageQueueDiscardSeverity can now also handle textual severities + (previously only integers) +- bugfix: message object was not properly synchronized when the + main queue had a single thread and non-direct action queues were used +- some documentation improvements +--------------------------------------------------------------------------- +Version 3.11.3 (rgerhards), 2008-02-18 +- fixed a bug in imklog which lead to duplicate message content in + kernel logs +- added support for better plugin handling in libdbi (we contributed + a patch to do that, we just now need to wait for the next libdbi + version) +- bugfix: fixed abort when invalid template was provided to an action + bug http://bugzilla.adiscon.com/show_bug.cgi?id=4 +- re-instantiated SIGUSR1 function; added SIGUSR2 to generate debug + status output +- added some documentation on runtime-debug settings +- slightly improved man pages for novice users +--------------------------------------------------------------------------- +Version 3.11.2 (rgerhards), 2008-02-15 +- added the capability to monitor text files and process their content + as syslog messages (including forwarding) +- added support for libdbi, a database abstraction layer. rsyslog now + also supports the following databases via dbi drivers: + * Firebird/Interbase + * FreeTDS (access to MS SQL Server and Sybase) + * SQLite/SQLite3 + * Ingres (experimental) + * mSQL (experimental) + * Oracle (experimental) + Additional drivers may be provided by the libdbi-drivers project, which + can be used by rsyslog as soon as they become available. +- removed some left-over unnecessary dbgprintf's (cluttered screen, + cosmetic) +- doc bugfix: html documentation for omsnmp was missing +--------------------------------------------------------------------------- +Version 3.11.1 (rgerhards), 2008-02-12 +- SNMP trap sender added thanks to Andre Lorbach (omsnmp) +- added input-plugin interface specification in form of a (copy) template + input module +- applied documentation fix by Michael Biebl -- many thanks! +- bugfix: immark did not have MARK flags set... +- added x-info field to rsyslogd startup/shutdown message. Hopefully + points users to right location for further info (many don't even know + they run rsyslog ;)) +- bugfix: trailing ":" of tag was lost while parsing legacy syslog messages + without timestamp - thanks to Anders Blomdell for providing a patch! +- fixed a bug in stringbuf.c related to STRINGBUF_TRIM_ALLOCSIZE, which + wasn't supposed to be used with rsyslog. Put a warning message up that + tells this feature is not tested and probably not worth the effort. + Thanks to Anders Blomdell fro bringing this to our attention +- somewhat improved performance of string buffers +- fixed bug that caused invalid treatment of tabs (HT) in rsyslog.conf +- bugfix: setting for $EscapeCopntrolCharactersOnReceive was not + properly initialized +- clarified usage of space-cc property replacer option +- improved abort diagnostic handler +- some initial effort for malloc/free runtime debugging support +- bugfix: using dynafile actions caused rsyslogd abort +- fixed minor man errors thanks to Michael Biebl +--------------------------------------------------------------------------- +Version 3.11.0 (rgerhards), 2008-01-31 +- implemented queued actions +- implemented simple rate limiting for actions +- implemented deliberate discarding of lower priority messages over higher + priority ones when a queue runs out of space +- implemented disk quotas for disk queues +- implemented the $ActionResumeRetryCount config directive +- added $ActionQueueFilename config directive +- added $ActionQueueSize config directive +- added $ActionQueueHighWaterMark config directive +- added $ActionQueueLowWaterMark config directive +- added $ActionQueueDiscardMark config directive +- added $ActionQueueDiscardSeverity config directive +- added $ActionQueueCheckpointInterval config directive +- added $ActionQueueType config directive +- added $ActionQueueWorkerThreads config directive +- added $ActionQueueTimeoutshutdown config directive +- added $ActionQueueTimeoutActionCompletion config directive +- added $ActionQueueTimeoutenQueue config directive +- added $ActionQueueTimeoutworkerThreadShutdown config directive +- added $ActionQueueWorkerThreadMinimumMessages config directive +- added $ActionQueueMaxFileSize config directive +- added $ActionQueueSaveonShutdown config directive +- addded $ActionQueueDequeueSlowdown config directive +- addded $MainMsgQueueDequeueSlowdown config directive +- bugfix: added forgotten docs to package +- improved debugging support +- fixed a bug that caused $MainMsgQueueCheckpointInterval to work incorrectly +- when a long-running action needs to be cancelled on shutdown, the message + that was processed by it is now preserved. This finishes support for + guaranteed delivery of messages (if the output supports it, of course) +- fixed bug in output module interface, see + http://sourceforge.net/tracker/index.php?func=detail&aid=1881008&group_id=123448&atid=696552 +- changed the ommysql output plugin so that the (lengthy) connection + initialization now takes place in message processing. This works much + better with the new queued action mode (fast startup) +- fixed a bug that caused a potential hang in file and fwd output module + varmojfekoj provided the patch - many thanks! +- bugfixed stream class offset handling on 32bit platforms +--------------------------------------------------------------------------- +Version 3.10.3 (rgerhards), 2008-01-28 +- fixed a bug with standard template definitions (not a big deal) - thanks + to varmojfekoj for spotting it +- run-time instrumentation added +- implemented disk-assisted queue mode, which enables on-demand disk + spooling if the queue's in-memory queue is exhausted +- implemented a dynamic worker thread pool for processing incoming + messages; workers are started and shut down as need arises +- implemented a run-time instrumentation debug package +- implemented the $MainMsgQueueSaveOnShutdown config directive +- implemented the $MainMsgQueueWorkerThreadMinimumMessages config directive +- implemented the $MainMsgQueueTimeoutWorkerThreadShutdown config directive +--------------------------------------------------------------------------- +Version 3.10.2 (rgerhards), 2008-01-14 +- added the ability to keep stop rsyslogd without the need to drain + the main message queue. In disk queue mode, rsyslog continues to + run from the point where it stopped. In case of a system failure, it + continues to process messages from the last checkpoint. +- fixed a bug that caused a segfault on startup when no $WorkDir directive + was specified in rsyslog.conf +- provided more fine-grain control over shutdown timeouts and added a + way to specify the enqueue timeout when the main message queue is full +- implemented $MainMsgQueueCheckpointInterval config directive +- implemented $MainMsgQueueTimeoutActionCompletion config directive +- implemented $MainMsgQueueTimeoutEnqueue config directive +- implemented $MainMsgQueueTimeoutShutdown config directive +--------------------------------------------------------------------------- +Version 3.10.1 (rgerhards), 2008-01-10 +- implemented the "disk" queue mode. However, it currently is of very + limited use, because it does not support persistence over rsyslogd + runs. So when rsyslogd is stopped, the queue is drained just as with + the in-memory queue modes. Persistent queues will be a feature of + the next release. +- performance-optimized string class, should bring an overall improvement +- fixed a memory leak in imudp -- thanks to varmojfekoj for the patch +- fixed a race condition that could lead to a rsyslogd hang when during + HUP or termination +- done some doc updates +- added $WorkDirectory config directive +- added $MainMsgQueueFileName config directive +- added $MainMsgQueueMaxFileSize config directive +--------------------------------------------------------------------------- +Version 3.10.0 (rgerhards), 2008-01-07 +- implemented input module interface and initial input modules +- enhanced threading for input modules (each on its own thread now) +- ability to bind UDP listeners to specific local interfaces/ports and + ability to run multiple of them concurrently +- added ability to specify listen IP address for UDP syslog server +- license changed to GPLv3 +- mark messages are now provided by loadble module immark +- rklogd is no longer provided. Its functionality has now been taken over + by imklog, a loadable input module. This offers a much better integration + into rsyslogd and makes sure that the kernel logger process is brought + up and down at the appropriate times +- enhanced $IncludeConfig directive to support wildcard characters + (thanks to Michael Biebl) +- all inputs are now implemented as loadable plugins +- enhanced threading model: each input module now runs on its own thread +- enhanced message queue which now supports different queueing methods + (among others, this can be used for performance fine-tuning) +- added a large number of new configuration directives for the new + input modules +- enhanced multi-threading utilizing a worker thread pool for the + main message queue +- compilation without pthreads is no longer supported +- much cleaner code due to new objects and removal of single-threading + mode +--------------------------------------------------------------------------- +Version 2.0.8 V2-STABLE (rgerhards), 2008-??-?? +- bugfix: ompgsql did not detect problems in sql command execution + this could cause loss of messages. The handling was correct if the + connection broke, but not if there was a problem with statement + execution. The most probable case for such a case would be invalid + sql inside the template, and this is now much easier to diagnose. +- doc bugfix: default for $DirCreateMode incorrectly stated +--------------------------------------------------------------------------- +Version 2.0.7 V2-STABLE (rgerhards), 2008-04-14 +- bugfix: the default for $DirCreateMode was 0644, and as such wrong. + It has now been changed to 0700. For some background, please see + http://lists.adiscon.net/pipermail/rsyslog/2009-April/001986.html +- bugfix: "$CreateDirs off" also disabled file creation + Thanks to William Tisater for analyzing this bug and providing a patch. + The actual code change is heavily based on William's patch. +- bugfix: memory leak in ompgsql + Thanks to Ken for providing the patch +- bugfix: potential memory leak in msg.c + This one did not surface yet and the issue was actually found due to + a problem in v4 - but better fix it here, too +--------------------------------------------------------------------------- +Version 2.0.6 V2-STABLE (rgerhards), 2008-08-07 +- bugfix: memory leaks in rsyslogd, primarily in singlethread mode + Thanks to Frederico Nunez for providing the fix +- bugfix: copy&paste error lead to dangling if - this caused a very minor + issue with re-formatting a RFC3164 date when the message was invalidly + formatted and had a colon immediately after the date. This was in the + code for some years (even v1 had it) and I think it never had any + effect at all in practice. Though, it should be fixed - but definitely + nothing to worry about. +--------------------------------------------------------------------------- +Version 2.0.6 V2-STABLE (rgerhards), 2008-08-07 +- bugfix: IPv6 addresses could not be specified in forwarding actions + New syntax @[addr]:port introduced to enable that. Root problem was IPv6 + addresses contain colons. (backport from 3.21.3) +--------------------------------------------------------------------------- +Version 2.0.5 STABLE (rgerhards), 2008-05-15 +- bugfix: regular expressions inside property replacer did not work + properly +- adapted to liblogging 0.7.1+ +--------------------------------------------------------------------------- +Version 2.0.4 STABLE (rgerhards), 2008-03-27 +- bugfix: internally generated messages had "FROMHOST" property not set +- bugfix: continue parsing if tag is oversize (discard oversize part) - thanks + to mclaughlin77@gmail.com for the patch +- added $HHOUR and $QHOUR system properties - can be used for half- and + quarter-hour logfile rotation +--------------------------------------------------------------------------- +Version 2.0.3 STABLE (rgerhards), 2008-03-12 +- bugfix: setting for $EscapeCopntrolCharactersOnReceive was not + properly initialized +- bugfix: resolved potential segfault condition on HUP (extremely + unlikely to happen in practice), for details see tracker: + http://bugzilla.adiscon.com/show_bug.cgi?id=38 +- improved the man pages a bit - thanks to Michael Biebl for the patch +- bugfix: not properly initialized data could cause several segfaults if + there were errors in the config file - thanks to varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 2.0.2 STABLE (rgerhards), 2008-02-12 +- fixed a bug that could cause invalid string handling via strerror_r + varmojfekoj provided the patch - many thanks! +- added x-info field to rsyslogd startup/shutdown message. Hopefully + points users to right location for further info (many don't even know + they run rsyslog ;)) +- bugfix: suspended actions were not always properly resumed + varmojfekoj provided the patch - many thanks! +- bugfix: errno could be changed during mark processing, leading to + invalid error messages when processing inputs. Thank to varmojfekoj for + pointing out this problem. +- bugfix: trailing ":" of tag was lost while parsing legacy syslog messages + without timestamp - thanks to Anders Blomdell for providing a patch! +- bugfix (doc): misspelled config directive, invalid signal info +- applied some doc fixes from Michel Biebl and cleaned up some no longer + needed files suggested by him +- cleaned up stringbuf.c to fix an annoyance reported by Anders Blomdell +- fixed bug that caused invalid treatment of tabs (HT) in rsyslog.conf +--------------------------------------------------------------------------- +Version 2.0.1 STABLE (rgerhards), 2008-01-24 +- fixed a bug in integer conversion - but this function was never called, + so it is not really a useful bug fix ;) +- fixed a bug with standard template definitions (not a big deal) - thanks + to varmojfekoj for spotting it +- fixed a bug that caused a potential hang in file and fwd output module + varmojfekoj provided the patch - many thanks! +--------------------------------------------------------------------------- +Version 2.0.0 STABLE (rgerhards), 2008-01-02 +- re-release of 1.21.2 as STABLE with no modifications except some + doc updates +--------------------------------------------------------------------------- +Version 1.21.2 (rgerhards), 2007-12-28 +- created a gss-api output module. This keeps GSS-API code and + TCP/UDP code separated. It is also important for forward- + compatibility with v3. Please note that this change breaks compatibility + with config files created for 1.21.0 and 1.21.1 - this was considered + acceptable. +- fixed an error in forwarding retry code (could lead to message corruption + but surfaced very seldom) +- increased portability for older platforms (AI_NUMERICSERV moved) +- removed socket leak in omfwd.c +- cross-platform patch for GSS-API compile problem on some platforms + thanks to darix for the patch! +--------------------------------------------------------------------------- +Version 1.21.1 (rgerhards), 2007-12-23 +- small doc fix for $IncludeConfig +- fixed a bug in llDestroy() +- bugfix: fixing memory leak when message queue is full and during + parsing. Thanks to varmojfekoj for the patch. +- bugfix: when compiled without network support, unix sockets were + not properply closed +- bugfix: memory leak in cfsysline.c/doGetWord() fixed +--------------------------------------------------------------------------- +Version 1.21.0 (rgerhards), 2007-12-19 +- GSS-API support for syslog/TCP connections was added. Thanks to + varmojfekoj for providing the patch with this functionality +- code cleanup +- enhanced $IncludeConfig directive to support wildcard filenames +- changed some multithreading synchronization +--------------------------------------------------------------------------- +Version 1.20.1 (rgerhards), 2007-12-12 +- corrected a debug setting that survived release. Caused TCP connections + to be retried unnecessarily often. +- When a hostname ACL was provided and DNS resolution for that name failed, + ACL processing was stopped at that point. Thanks to mildew for the patch. + Fedora Bugzilla: http://bugzilla.redhat.com/show_bug.cgi?id=395911 +- fixed a potential race condition, see link for details: + http://rgerhards.blogspot.com/2007/12/rsyslog-race-condition.html + Note that the probability of problems from this bug was very remote +- fixed a memory leak that happend when PostgreSQL date formats were + used +--------------------------------------------------------------------------- +Version 1.20.0 (rgerhards), 2007-12-07 +- an output module for postgres databases has been added. Thanks to + sur5r for contributing this code +- unloading dynamic modules has been cleaned up, we now have a + real implementation and not just a dummy "good enough for the time + being". +- enhanced platform independence - thanks to Bartosz Kuzma and Michael + Biebl for their very useful contributions +- some general code cleanup (including warnings on 64 platforms, only) +--------------------------------------------------------------------------- +Version 1.19.12 (rgerhards), 2007-12-03 +- cleaned up the build system (thanks to Michael Biebl for the patch) +- fixed a bug where ommysql was still not compiled with -pthread option +--------------------------------------------------------------------------- +Version 1.19.11 (rgerhards), 2007-11-29 +- applied -pthread option to build when building for multi-threading mode + hopefully solves an issue with segfaulting +--------------------------------------------------------------------------- +Version 1.19.10 (rgerhards), 2007-10-19 +- introdcued the new ":modulename:" syntax for calling module actions + in selector lines; modified ommysql to support it. This is primarily + an aid for further modules and a prequisite to actually allow third + party modules to be created. +- minor fix in slackware startup script, "-r 0" is now "-r0" +- updated rsyslogd doc set man page; now in html format +- undid creation of a separate thread for the main loop -- this did not + turn out to be needed or useful, so reduce complexity once again. +- added doc fixes provided by Michael Biebl - thanks +--------------------------------------------------------------------------- +Version 1.19.9 (rgerhards), 2007-10-12 +- now packaging system which again contains all components in a single + tarball +- modularized main() a bit more, resulting in less complex code +- experimentally added an additional thread - will see if that affects + the segfault bug we experience on some platforms. Note that this change + is scheduled to be removed again later. +--------------------------------------------------------------------------- +Version 1.19.8 (rgerhards), 2007-09-27 +- improved repeated message processing +- applied patch provided by varmojfekoj to support building ommysql + in its own way (now also resides in a plugin subdirectory); + ommysql is now a separate package +- fixed a bug in cvthname() that lead to message loss if part + of the source hostname would have been dropped +- created some support for distributing ommysql together with the + main rsyslog package. I need to re-think it in the future, but + for the time being the current mode is best. I now simply include + one additional tarball for ommysql inside the main distribution. + I look forward to user feedback on how this should be done best. In the + long term, a separate project should be spawend for ommysql, but I'd + like to do that only after the plugin interface is fully stable (what + it is not yet). +--------------------------------------------------------------------------- +Version 1.19.7 (rgerhards), 2007-09-25 +- added code to handle situations where senders send us messages ending with + a NUL character. It is now simply removed. This also caused trailing LF + reduction to fail, when it was followed by such a NUL. This is now also + handled. +- replaced some non-thread-safe function calls by their thread-safe + counterparts +- fixed a minor memory leak that occured when the %APPNAME% property was + used (I think nobody used that in practice) +- fixed a bug that caused signal handlers in cvthname() not to be restored when + a malicious pointer record was detected and processing of the message been + stopped for that reason (this should be really rare and can not be related + to the segfault bug we are hunting). +- fixed a bug in cvthname that lead to passing a wrong parameter - in + practice, this had no impact. +- general code cleanup (e.g. compiler warnings, comments) +--------------------------------------------------------------------------- +Version 1.19.6 (rgerhards), 2007-09-11 +- applied patch by varmojfekoj to change signal handling to the new + sigaction API set (replacing the depreciated signal() calls and its + friends. +- fixed a bug that in --enable-debug mode caused an assertion when the + discard action was used +- cleaned up compiler warnings +- applied patch by varmojfekoj to FIX a bug that could cause + segfaults if empty properties were processed using modifying + options (e.g. space-cc, drop-cc) +- fixed man bug: rsyslogd supports -l option +--------------------------------------------------------------------------- +Version 1.19.5 (rgerhards), 2007-09-07 +- changed part of the CStr interface so that better error tracking + is provided and the calling sequence is more intuitive (there were + invalid calls based on a too-weired interface) +- (hopefully) fixed some remaining bugs rooted in wrong use of + the CStr class. These could lead to program abort. +- applied patch by varmojfekoj two fix two potential segfault situations +- added $ModDir config directive +- modified $ModLoad so that an absolute path may be specified as + module name (e.g. /rsyslog/ommysql.so) +--------------------------------------------------------------------------- +Version 1.19.4 (rgerhards/varmojfekoj), 2007-09-04 +- fixed a number of small memory leaks - thanks varmojfekoj for patching +- fixed an issue with CString class that could lead to rsyslog abort + in tplToString() - thanks varmojfekoj for patching +- added a man-version of the config file documenation - thanks to Michel + Samia for providing the man file +- fixed bug: a template like this causes an infinite loop: + $template opts,"%programname:::a,b%" + thanks varmojfekoj for the patch +- fixed bug: case changing options crash freeing the string pointer + because they modify it: $template opts2,"%programname::1:lowercase%" + thanks varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 1.19.3 (mmeckelein/varmojfekoj), 2007-08-31 +- small mem leak fixed (after calling parseSelectorAct) - Thx varmojkekoj +- documentation section "Regular File" und "Blocks" updated +- solved an issue with dynamic file generation - Once again many thanks + to varmojfekoj +- the negative selector for program name filter (Blocks) does not work as + expected - Thanks varmojfekoj for patching +- added forwarding information to sysklogd (requires special template) + to config doc +--------------------------------------------------------------------------- +Version 1.19.2 (mmeckelein/varmojfekoj), 2007-08-28 +- a specifically formed message caused a segfault - Many thanks varmojfekoj + for providing a patch +- a typo and a weird condition are fixed in msg.c - Thanks again + varmojfekoj +- on file creation the file was always owned by root:root. This is fixed + now - Thanks ypsa for solving this issue +--------------------------------------------------------------------------- +Version 1.19.1 (mmeckelein), 2007-08-22 +- a bug that caused a high load when a TCP/UDP connection was closed is + fixed now - Thanks mildew for solving this issue +- fixed a bug which caused a segfault on reinit - Thx varmojfekoj for the + patch +- changed the hardcoded module path "/lib/rsyslog" to $(pkglibdir) in order + to avoid trouble e.g. on 64 bit platforms (/lib64) - many thanks Peter + Vrabec and darix, both provided a patch for solving this issue +- enhanced the unloading of modules - thanks again varmojfekoj +- applied a patch from varmojfekoj which fixes various little things in + MySQL output module +--------------------------------------------------------------------------- +Version 1.19.0 (varmojfekoj/rgerhards), 2007-08-16 +- integrated patch from varmojfekoj to make the mysql module a loadable one + many thanks for the patch, MUCH appreciated +--------------------------------------------------------------------------- +Version 1.18.2 (rgerhards), 2007-08-13 +- fixed a bug in outchannel code that caused templates to be incorrectly + parsed +- fixed a bug in ommysql that caused a wrong ";template" missing message +- added some code for unloading modules; not yet fully complete (and we do + not yet have loadable modules, so this is no problem) +- removed debian subdirectory by request of a debian packager (this is a special + subdir for debian and there is also no point in maintaining it when there + is a debian package available - so I gladly did this) in some cases +- improved overall doc quality (some pages were quite old) and linked to + more of the online resources. +- improved /contrib/delete_mysql script by adding a host option and some + other minor modifications +--------------------------------------------------------------------------- +Version 1.18.1 (rgerhards), 2007-08-08 +- applied a patch from varmojfekoj which solved a potential segfault + of rsyslogd on HUP +- applied patch from Michel Samia to fix compilation when the pthreads + feature is disabled +- some code cleanup (moved action object to its own file set) +- add config directive $MainMsgQueueSize, which now allows to configure the + queue size dynamically +- all compile-time settings are now shown in rsyslogd -v, not just the + active ones +- enhanced performance a little bit more +- added config file directive $ActionResumeInterval +- fixed a bug that prevented compilation under debian sid +- added a contrib directory for user-contributed useful things +--------------------------------------------------------------------------- +Version 1.18.0 (rgerhards), 2007-08-03 +- rsyslog now supports fallback actions when an action did not work. This + is a great feature e.g. for backup database servers or backup syslog + servers +- modified rklogd to only change the console log level if -c is specified +- added feature to use multiple actions inside a single selector +- implemented $ActionExecOnlyWhenPreviousIsSuspended config directive +- error messages during startup are now spit out to the configured log + destinations +--------------------------------------------------------------------------- +Version 1.17.6 (rgerhards), 2007-08-01 +- continued to work on output module modularization - basic stage of + this work is now FINISHED +- fixed bug in OMSRcreate() - always returned SR_RET_OK +- fixed a bug that caused ommysql to always complain about missing + templates +- fixed a mem leak in OMSRdestruct - freeing the object itself was + forgotten - thanks to varmojfekoj for the patch +- fixed a memory leak in syslogd/init() that happend when the config + file could not be read - thanks to varmojfekoj for the patch +- fixed insufficient memory allocation in addAction() and its helpers. + The initial fix and idea was developed by mildew, I fine-tuned + it a bit. Thanks a lot for the fix, I'd probably had pulled out my + hair to find the bug... +- added output of config file line number when a parsing error occured +- fixed bug in objomsr.c that caused program to abort in debug mode with + an invalid assertion (in some cases) +- fixed a typo that caused the default template for MySQL to be wrong. + thanks to mildew for catching this. +- added configuration file command $DebugPrintModuleList and + $DebugPrintCfSysLineHandlerList +- fixed an invalid value for the MARK timer - unfortunately, there was + a testing aid left in place. This resulted in quite frequent MARK messages +- added $IncludeConfig config directive +- applied a patch from mildew to prevent rsyslogd from freezing under heavy + load. This could happen when the queue was full. Now, we drop messages + but rsyslogd remains active. +--------------------------------------------------------------------------- +Version 1.17.5 (rgerhards), 2007-07-30 +- continued to work on output module modularization +- fixed a missing file bug - thanks to Andrea Montanari for reporting + this problem +- fixed a problem with shutting down the worker thread and freeing the + selector_t list - this caused messages to be lost, because the + message queue was not properly drained before the selectors got + destroyed. +--------------------------------------------------------------------------- +Version 1.17.4 (rgerhards), 2007-07-27 +- continued to work on output module modularization +- fixed a situation where rsyslogd could create zombie processes + thanks to mildew for the patch +- applied patch from Michel Samia to fix compilation when NOT + compiled for pthreads +--------------------------------------------------------------------------- +Version 1.17.3 (rgerhards), 2007-07-25 +- continued working on output module modularization +- fixed a bug that caused rsyslogd to segfault on exit (and + probably also on HUP), when there was an unsent message in a selector + that required forwarding and the dns lookup failed for that selector + (yes, it was pretty unlikely to happen;)) + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- fixed a memory leak in config file parsing and die() + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- rsyslogd now checks on startup if it is capable to performa any work + at all. If it cant, it complains and terminates + thanks to Michel Samia for providing the patch! +- fixed a small memory leak when HUPing syslogd. The allowed sender + list now gets freed. thanks to mildew for the patch. +- changed the way error messages in early startup are logged. They + now do no longer use the syslogd code directly but are rather + send to stderr. +--------------------------------------------------------------------------- +Version 1.17.2 (rgerhards), 2007-07-23 +- made the port part of the -r option optional. Needed for backward + compatibility with sysklogd +- replaced system() calls with something more reasonable. Please note that + this might break compatibility with some existing configuration files. + We accept this in favour of the gained security. +- removed a memory leak that could occur if timegenerated was used in + RFC 3164 format in templates +- did some preparation in msg.c for advanced multithreading - placed the + hooks, but not yet any active code +- worked further on modularization +- added $ModLoad MySQL (dummy) config directive +- added DropTrailingLFOnReception config directive +--------------------------------------------------------------------------- +Version 1.17.1 (rgerhards), 2007-07-20 +- fixed a bug that caused make install to install rsyslogd and rklogd under + the wrong names +- fixed bug that caused $AllowedSenders to handle IPv6 scopes incorrectly; + also fixed but that could grabble $AllowedSender wildcards. Thanks to + mildew@gmail.com for the patch +- minor code cleanup - thanks to Peter Vrabec for the patch +- fixed minimal memory leak on HUP (caused by templates) + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- fixed another memory leak on HUPing and on exiting rsyslogd + again thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- code cleanup (removed compiler warnings) +- fixed portability bug in configure.ac - thanks to Bartosz Kuźma for patch +- moved msg object into its own file set +- added the capability to continue trying to write log files when the + file system is full. Functionality based on patch by Martin Schulze + to sysklogd package. +--------------------------------------------------------------------------- +Version 1.17.0 (RGer), 2007-07-17 +- added $RepeatedLineReduction config parameter +- added $EscapeControlCharactersOnReceive config parameter +- added $ControlCharacterEscapePrefix config parameter +- added $DirCreateMode config parameter +- added $CreateDirs config parameter +- added $DebugPrintTemplateList config parameter +- added $ResetConfigVariables config parameter +- added $FileOwner config parameter +- added $FileGroup config parameter +- added $DirOwner config parameter +- added $DirGroup config parameter +- added $FailOnChownFailure config parameter +- added regular expression support to the filter engine + thanks to Michel Samia for providing the patch! +- enhanced $AllowedSender functionality. Credits to mildew@gmail.com for + the patch doing that + - added IPv6 support + - allowed DNS hostnames + - allowed DNS wildcard names +- added new option $DropMsgsWithMaliciousDnsPTRRecords +- added autoconf so that rfc3195d, rsyslogd and klogd are stored to /sbin +- added capability to auto-create directories with dynaFiles +--------------------------------------------------------------------------- +Version 1.16.0 (RGer/Peter Vrabec), 2007-07-13 - The Friday, 13th Release ;) +- build system switched to autotools +- removed SYSV preprocessor macro use, replaced with autotools equivalents +- fixed a bug that caused rsyslogd to segfault when TCP listening was + disabled and it terminated +- added new properties "syslogfacility-text" and "syslogseverity-text" + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- added the -x option to disable hostname dns reslution + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- begun to better modularize syslogd.c - this is an ongoing project; moved + type definitions to a separate file +- removed some now-unused fields from struct filed +- move file size limit fields in struct field to the "right spot" (the file + writing part of the union - f_un.f_file) +- subdirectories linux and solaris are no longer part of the distribution + package. This is not because we cease support for them, but there are no + longer any files in them after the move to autotools +--------------------------------------------------------------------------- +Version 1.15.1 (RGer), 2007-07-10 +- fixed a bug that caused a dynaFile selector to stall when there was + an open error with one file +- improved template processing for dynaFiles; templates are now only + looked up during initialization - speeds up processing +- optimized memory layout in struct filed when compiled with MySQL + support +- fixed a bug that caused compilation without SYSLOG_INET to fail +- re-enabled the "last message repeated n times" feature. This + feature was not taken care of while rsyslogd evolved from sysklogd + and it was more or less defunct. Now it is fully functional again. +- added system properties: $NOW, $YEAR, $MONTH, $DAY, $HOUR, $MINUTE +- fixed a bug in iovAsString() that caused a memory leak under stress + conditions (most probably memory shortage). This was unlikely to + ever happen, but it doesn't hurt doing it right +- cosmetic: defined type "uchar", change all unsigned chars to uchar +--------------------------------------------------------------------------- +Version 1.15.0 (RGer), 2007-07-05 +- added ability to dynamically generate file names based on templates + and thus properties. This was a much-requested feature. It makes + life easy when it e.g. comes to splitting files based on the sender + address. +- added $umask and $FileCreateMode config file directives +- applied a patch from Bartosz Kuzma to compile cleanly under NetBSD +- checks for extra (unexpected) characters in system config file lines + have been added +- added IPv6 documentation - was accidently missing from CVS +- begun to change char to unsigned char +--------------------------------------------------------------------------- +Version 1.14.2 (RGer), 2007-07-03 +** this release fixes all known nits with IPv6 ** +- restored capability to do /etc/service lookup for "syslog" + service when -r 0 was given +- documented IPv6 handling of syslog messages +- integrate patch from Bartosz Kuźma to make rsyslog compile under + Solaris again (the patch replaced a strndup() call, which is not + available under Solaris +- improved debug logging when waiting on select +- updated rsyslogd man page with new options (-46A) +--------------------------------------------------------------------------- +Version 1.14.1 (RGer/Peter Vrabec), 2007-06-29 +- added Peter Vrabec's patch for IPv6 TCP +- prefixed all messages send to stderr in rsyslogd with "rsyslogd: " +--------------------------------------------------------------------------- +Version 1.14.0 (RGer/Peter Vrabec), 2007-06-28 +- Peter Vrabec provided IPv6 for rsyslog, so we are now IPv6 enabled + IPv6 Support is currently for UDP only, TCP is to come soon. + AllowedSender configuration does not yet work for IPv6. +- fixed code in iovCreate() that broke C's strict aliasing rules +- fixed some char/unsigned char differences that forced the compiler + to spit out warning messages +- updated the Red Hat init script to fix a known issue (thanks to + Peter Vrabec) +--------------------------------------------------------------------------- +Version 1.13.5 (RGer), 2007-06-22 +- made the TCP session limit configurable via command line switch + now -t <port>,<max sessions> +- added man page for rklogd(8) (basically a copy from klogd, but now + there is one...) +- fixed a bug that caused internal messages (e.g. rsyslogd startup) to + appear without a tag. +- removed a minor memory leak that occurred when TAG processing requalified + a HOSTNAME to be a TAG (and a TAG already was set). +- removed potential small memory leaks in MsgSet***() functions. There + would be a leak if a property was re-set, something that happened + extremely seldom. +--------------------------------------------------------------------------- +Version 1.13.4 (RGer), 2007-06-18 +- added a new property "PRI-text", which holds the PRI field in + textual form (e.g. "syslog.info") +- added alias "syslogseverity" for "syslogpriority", which is a + misleading property name that needs to stay for historical + reasons (and backward-compatility) +- added doc on how to record PRI value in log file +- enhanced signal handling in klogd, including removal of an unsafe + call to the logging system during signal handling +--------------------------------------------------------------------------- +Version 1.13.3 (RGer), 2007-06-15 +- create a version of syslog.c from scratch. This is now + - highly optimized for rsyslog + - removes an incompatible license problem as the original + version had a BSD license with advertising clause + - fixed in the regard that rklogd will continue to work when + rsysogd has been restarted (the original version, as well + as sysklogd, will remain silent then) + - solved an issue with an extra NUL char at message end that the + original version had +- applied some changes to klogd to care for the new interface +- fixed a bug in syslogd.c which prevented compiling under debian +--------------------------------------------------------------------------- +Version 1.13.2 (RGer), 2007-06-13 +- lib order in makefile patched to facilitate static linking - thanks + to Bennett Todd for providing the patch +- Integrated a patch from Peter Vrabec (pvrabec@redheat.com): + - added klogd under the name of rklogd (remove dependency on + original sysklogd package + - createDB.sql now in UTF + - added additional config files for use on Red Hat +--------------------------------------------------------------------------- +Version 1.13.1 (RGer), 2007-02-05 +- changed the listen backlog limit to a more reasonable value based on + the maximum number of TCP connections configurd (10% + 5) - thanks to Guy + Standen for the hint (actually, the limit was 5 and that was a + left-over from early testing). +- fixed a bug in makefile which caused DB-support to be disabled when + NETZIP support was enabled +- added the -e option to allow transmission of every message to remote + hosts (effectively turns off duplicate message suppression) +- (somewhat) improved memory consumption when compiled with MySQL support +- looks like we fixed an incompatibility with MySQL 5.x and above software + At least in one case, the remote server name was destroyed, leading to + a connection failure. The new, improved code does not have this issue and + so we see this as solved (the new code is generally somewhat better, so + there is a good chance we fixed this incompatibility). +--------------------------------------------------------------------------- +Version 1.13.0 (RGer), 2006-12-19 +- added '$' as ToPos proptery replacer specifier - means "up to the + end of the string" +- property replacer option "escape-cc", "drop-cc" and "space-cc" added +- changed the handling of \0 characters inside syslog messages. We now + consistently escape them to "#000". This is somewhat recommended in + the draft-ietf-syslog-protocol-19 draft. While the real recomendation + is to not escape any characters at all, we can not do this without + considerable modification of the code. So we escape it to "#000", which + is consistent with a sample found in the Internet-draft. +- removed message glue logic (see printchopped() comment for details) + Also caused removal of parts table and thus some improvements in + memory usage. +- changed the default MAXLINE to 2048 to take care of recent syslog + standardization efforts (can easily be changed in syslogd.c) +- added support for byte-counted TCP syslog messages (much like + syslog-transport-tls-05 Internet Draft). This was necessary to + support compression over TCP. +- added support for receiving compressed syslog messages +- added support for sending compressed syslog messages +- fixed a bug where the last message in a syslog/tcp stream was + lost if it was not properly terminated by a LF character +--------------------------------------------------------------------------- +Version 1.12.3 (RGer), 2006-10-04 +- implemented some changes to support Solaris (but support is not + yet complete) +- commented out (via #if 0) some methods that are currently not being use + but should be kept for further us +- added (interim) -u 1 option to turn off hostname and tag parsing +- done some modifications to better support Fedora +- made the field delimiter inside property replace configurable via + template +- fixed a bug in property replacer: if fields were used, the delimitor + became part of the field. Up until now, this was barely noticable as + the delimiter as TAB only and thus invisible to a human. With other + delimiters available now, it quickly showed up. This bug fix might cause + some grief to existing installations if they used the extra TAB for + whatever reasons - sorry folks... Anyhow, a solution is easy: just add + a TAB character contstant into your template. Thus, there has no attempt + been made to do this in a backwards-compatible way. +--------------------------------------------------------------------------- +Version 1.12.2 (RGer), 2006-02-15 +- fixed a bug in the RFC 3339 date formatter. An extra space was added + after the actual timestamp +- added support for providing high-precision RFC3339 timestamps for + (rsyslogd-)internally-generated messages +- very (!) experimental support for syslog-protocol internet draft + added (the draft is experimental, the code is solid ;)) +- added support for field-extracting in the property replacer +- enhanced the legacy-syslog parser so that it can interpret messages + that do not contain a TIMESTAMP +- fixed a bug that caused the default socket (usually /dev/log) to be + opened even when -o command line option was given +- fixed a bug in the Debian sample startup script - it caused rsyslogd + to listen to remote requests, which it shouldn't by default +--------------------------------------------------------------------------- +Version 1.12.1 (RGer), 2005-11-23 +- made multithreading work with BSD. Some signal-handling needed to be + restructured. Also, there might be a slight delay of up to 10 seconds + when huping and terminating rsyslogd under BSD +- fixed a bug where a NULL-pointer was passed to printf() in logmsg(). +- fixed a bug during "make install" where rc3195d was not installed + Thanks to Bennett Todd for spotting this. +- fixed a bug where rsyslogd dumped core when no TAG was found in the + received message +- enhanced message parser so that it can deal with missing hostnames + in many cases (may not be totally fail-safe) +- fixed a bug where internally-generated messages did not have the correct + TAG +--------------------------------------------------------------------------- +Version 1.12.0 (RGer), 2005-10-26 +- moved to a multi-threaded design. single-threading is still optionally + available. Multi-threading is experimental! +- fixed a potential race condition. In the original code, marking was done + by an alarm handler, which could lead to all sorts of bad things. This + has been changed now. See comments in syslogd.c/domark() for details. +- improved debug output for property-based filters +- not a code change, but: I have checked all exit()s to make sure that + none occurs once rsyslogd has started up. Even in unusual conditions + (like low-memory conditions) rsyslogd somehow remains active. Of course, + it might loose a message or two, but at least it does not abort and it + can also recover when the condition no longer persists. +- fixed a bug that could cause loss of the last message received + immediately before rsyslogd was terminated. +- added comments on thread-safety of global variables in syslogd.c +- fixed a small bug: spurios printf() when TCP syslog was used +- fixed a bug that causes rsyslogd to dump core on termination when one + of the selector lines did not receive a message during the run (very + unlikely) +- fixed an one-too-low memory allocation in the TCP sender. Could result + in rsyslogd dumping core. +- fixed a bug with regular expression support (thanks to Andres Riancho) +- a little bit of code restructuring (especially main(), which was + horribly large) +--------------------------------------------------------------------------- +Version 1.11.1 (RGer), 2005-10-19 +- support for BSD-style program name and host blocks +- added a new property "programname" that can be used in templates +- added ability to specify listen port for rfc3195d +- fixed a bug that rendered the "startswith" comparison operation + unusable. +- changed more functions to "static" storage class to help compiler + optimize (should have been static in the first place...) +- fixed a potential memory leak in the string buffer class destructor. + As the destructur was previously never called, the leak did not actually + appear. +- some internal restructuring in anticipation/preparation of minimal + multi-threading support +- rsyslogd still shares some code with the sysklogd project. Some patches + for this shared code have been brought over from the sysklogd CVS. +--------------------------------------------------------------------------- +Version 1.11.0 (RGer), 2005-10-12 +- support for receiving messages via RFC 3195; added rfc3195d for that + purpose +- added an additional guard to prevent rsyslogd from aborting when the + 2gb file size limit is hit. While a user can configure rsyslogd to + handle such situations, it would abort if that was not done AND large + file support was not enabled (ok, this is hopefully an unlikely scenario) +- fixed a bug that caused additional Unix domain sockets to be incorrectly + processed - could lead to message loss in extreme cases +--------------------------------------------------------------------------- +Version 1.10.2 (RGer), 2005-09-27 +- added comparison operations in property-based filters: + * isequal + * startswith +- added ability to negate all property-based filter comparison operations + by adding a !-sign right in front of the operation name +- added the ability to specify remote senders for UDP and TCP + received messages. Allows to block all but well-known hosts +- changed the $-config line directives to be case-INsensitive +- new command line option -w added: "do not display warnings if messages + from disallowed senders are received" +- fixed a bug that caused rsyslogd to dump core when the compare value + was not quoted in property-based filters +- fixed a bug in the new CStr compare function which lead to invalid + results (fortunately, this function was not yet used widely) +- added better support for "debugging" rsyslog.conf property filters + (only if -d switch is given) +- changed some function definitions to static, which eventually enables + some compiler optimizations +- fixed a bug in MySQL code; when a SQL error occured, rsyslogd could + run in a tight loop. This was due to invalid sequence of error reporting + and is now fixed. +--------------------------------------------------------------------------- +Version 1.10.1 (RGer), 2005-09-23 +- added the ability to execute a shell script as an action. + Thanks to Bjoern Kalkbrenner for providing the code! +- fixed a bug in the MySQL code; due to the bug the automatic one-time + retry after an error did not happen - this lead to error message in + cases where none should be seen (e.g. after a MySQL restart) +- fixed a security issue with SQL-escaping in conjunction with + non-(SQL-)standard MySQL features. +--------------------------------------------------------------------------- +Version 1.10.0 (RGer), 2005-09-20 + REMINDER: 1.10 is the first unstable version if the 1.x series! +- added the capability to filter on any property in selector lines + (not just facility and priority) +- changed stringbuf into a new counted string class +- added support for a "discard" action. If a selector line with + discard (~ character) is found, no selector lines *after* that + line will be processed. +- thanks to Andres Riancho, regular expression support has been + added to the template engine +- added the FROMHOST property in the template processor, which could + previously not be obtained. Thanks to Cristian Testa for pointing + this out and even providing a fix. +- added display of compile-time options to -v output +- performance improvement for production build - made some checks + to happen only during debug mode +- fixed a problem with compiling on SUSE and - while doing so - removed + the socket call to set SO_BSDCOMPAT in cases where it is obsolete. +--------------------------------------------------------------------------- +Version 1.0.4 (RGer), 2006-02-01 +- a small but important fix: the tcp receiver had two forgotten printf's + in it that caused a lot of unnecessary output to stdout. This was + important enough to justify a new release +--------------------------------------------------------------------------- +Version 1.0.3 (RGer), 2005-11-14 +- added an additional guard to prevent rsyslogd from aborting when the + 2gb file size limit is hit. While a user can configure rsyslogd to + handle such situations, it would abort if that was not done AND large + file support was not enabled (ok, this is hopefully an unlikely scenario) +- fixed a bug that caused additional Unix domain sockets to be incorrectly + processed - could lead to message loss in extreme cases +- applied some patches available from the sysklogd project to code + shared from there +- fixed a bug that causes rsyslogd to dump core on termination when one + of the selector lines did not receive a message during the run (very + unlikely) +- fixed an one-too-low memory allocation in the TCP sender. Could result + in rsyslogd dumping core. +- fixed a bug in the TCP sender that caused the retry logic to fail + after an error or receiver overrun +- fixed a bug in init() that could lead to dumping core +- fixed a bug that could lead to dumping core when no HOSTNAME or no TAG + was present in the syslog message +--------------------------------------------------------------------------- +Version 1.0.2 (RGer), 2005-10-05 +- fixed an issue with MySQL error reporting. When an error occured, + the MySQL driver went into an endless loop (at least in most cases). +--------------------------------------------------------------------------- +Version 1.0.1 (RGer), 2005-09-23 +- fixed a security issue with SQL-escaping in conjunction with + non-(SQL-)standard MySQL features. +--------------------------------------------------------------------------- +Version 1.0.0 (RGer), 2005-09-12 +- changed install doc to cover daily cron scripts - a trouble source +- added rc script for slackware (provided by Chris Elvidge - thanks!) +- fixed a really minor bug in usage() - the -r option was still + reported as without the port parameter +--------------------------------------------------------------------------- +Version 0.9.8 (RGer), 2005-09-05 +- made startup and shutdown message more consistent and included the + pid, so that they can be easier correlated. Used syslog-protocol + structured data format for this purpose. +- improved config info in startup message, now tells not only + if it is listening remote on udp, but also for tcp. Also includes + the port numbers. The previous startup message was misleading, because + it did not say "remote reception" if rsyslogd was only listening via + tcp (but not via udp). +- added a "how can you help" document to the doc set +--------------------------------------------------------------------------- +Version 0.9.7 (RGer), 2005-08-15 +- some of the previous doc files (like INSTALL) did not properly + reflect the changes to the build process and the new doc. Fixed + that. +- changed syslogd.c so that when compiled without database support, + an error message is displayed when a database action is detected + in the config file (previously this was used as an user rule ;)) +- fixed a bug in the os-specific Makefiles which caused MySQL + support to not be compiled, even if selected +--------------------------------------------------------------------------- +Version 0.9.6 (RGer), 2005-08-09 +- greatly enhanced documentation. Now available in html format in + the "doc" folder and FreeBSD. Finally includes an install howto. +- improved MySQL error messages a little - they now show up as log + messages, too (formerly only in debug mode) +- added the ability to specify the listen port for udp syslog. + WARNING: This introduces an incompatibility. Formerly, udp + syslog was enabled by the -r command line option. Now, it is + "-r [port]", which is consistent with the tcp listener. However, + just -r will now return an error message. +- added sample startup scripts for Debian and FreeBSD +- added support for easy feature selection in the makefile. Un- + fortunately, this also means I needed to spilt the make file + for different OS and distros. There are some really bad syntax + differences between FreeBSD and Linux make. +--------------------------------------------------------------------------- +Version 0.9.5 (RGer), 2005-08-01 +- the "semicolon bug" was actually not (fully) solved in 0.9.4. One + part of the bug was solved, but another still existed. This one + is fixed now, too. +- the "semicolon bug" actually turned out to be a more generic bug. + It appeared whenever an invalid template name was given. With some + selector actions, rsyslogd dumped core, with other it "just" had + a small ressource leak with others all worked well. These anomalies + are now fixed. Note that they only appeared during system initaliziation + once the system was running, nothing bad happened. +- improved error reporting for template errors on startup. They are now + shown on the console and the start-up tty. Formerly, they were only + visible in debug mode. +- support for multiple instances of rsyslogd on a single machine added +- added new option "-o" --> omit local unix domain socket. This option + enables rsyslogd NOT to listen to the local socket. This is most + helpful when multiple instances of rsyslogd (or rsyslogd and another + syslogd) shall run on a single system. +- added new option "-i <pidfile>" which allows to specify the pidfile. + This is needed when multiple instances of rsyslogd are to be run. +- the new project home page is now online at www.rsyslog.com +--------------------------------------------------------------------------- +Version 0.9.4 (RGer), 2005-07-25 +- finally added the TCP sender. It now supports non-blocking mode, no + longer disabling message reception during connect. As it is now, it + is usable in production. The code could be more sophisticated, but + I've kept it short in anticipation of the move to liblogging, which + will lead to the removal of the code just written ;) +- the "exiting on signal..." message still had the "syslogd" name in + it. Changed this to "rsyslogd", as we do not have a large user base + yet, this should pose no problem. +- fixed "the semiconlon" bug. rsyslogd dumped core if a write-db action + was specified but no semicolon was given after the password (an empty + template was ok, but the semicolon needed to be present). +- changed a default for traditional output format. During testing, it + was seen that the timestamp written to file in default format was + the time of message reception, not the time specified in the TIMESTAMP + field of the message itself. Traditionally, the message TIMESTAMP is + used and this has been changed now. +--------------------------------------------------------------------------- +Version 0.9.3 (RGer), 2005-07-19 +- fixed a bug in the message parser. In June, the RFC 3164 timestamp + was not correctly parsed (yes, only in June and some other months, + see the code comment to learn why...) +- added the ability to specify the destination port when forwarding + syslog messages (both for TCP and UDP) +- added an very experimental TCP sender (activated by + @@machine:port in config). This is not yet for production use. If + the receiver is not alive, rsyslogd will wait quite some time until + the connection request times out, which most probably leads to + loss of incoming messages. + +--------------------------------------------------------------------------- +Version 0.9.2 (RGer), around 2005-07-06 +- I intended to change the maxsupported message size to 32k to + support IHE - but given the memory inefficiency in the usual use + cases, I have not done this. I have, however, included very + specific instructions on how to do this in the source code. I have + also done some testing with 32k messages, so you can change the + max size without taking too much risk. +- added a syslog/tcp receiver; we now can receive messages via + plain tcp, but we can still send only via UDP. The syslog/tcp + receiver is the primary enhancement of this release. +- slightly changed some error messages that contained a spurios \n at + the end of the line (which gives empty lines in your log...) + +--------------------------------------------------------------------------- +Version 0.9.1 (RGer) +- fixed code so that it compiles without errors under FreeBSD +- removed now unused function "allocate_log()" from syslogd.c +- changed the make file so that it contains more defines for + different environments (in the long term, we need a better + system for disabling/enabling features...) +- changed some printf's printing off_t types to %lld and + explicit (long long) casts. I tried to figure out the exact type, + but did not succeed in this. In the worst case, ultra-large peta- + byte files will now display funny informational messages on rollover, + something I think we can live with for the neersion 3.11.2 (rgerhards), 2008-02-?? +--------------------------------------------------------------------------- +Version 3.11.1 (rgerhards), 2008-02-12 +- SNMP trap sender added thanks to Andre Lorbach (omsnmp) +- added input-plugin interface specification in form of a (copy) template + input module +- applied documentation fix by Michael Biebl -- many thanks! +- bugfix: immark did not have MARK flags set... +- added x-info field to rsyslogd startup/shutdown message. Hopefully + points users to right location for further info (many don't even know + they run rsyslog ;)) +- bugfix: trailing ":" of tag was lost while parsing legacy syslog messages + without timestamp - thanks to Anders Blomdell for providing a patch! +- fixed a bug in stringbuf.c related to STRINGBUF_TRIM_ALLOCSIZE, which + wasn't supposed to be used with rsyslog. Put a warning message up that + tells this feature is not tested and probably not worth the effort. + Thanks to Anders Blomdell fro bringing this to our attention +- somewhat improved performance of string buffers +- fixed bug that caused invalid treatment of tabs (HT) in rsyslog.conf +- bugfix: setting for $EscapeCopntrolCharactersOnReceive was not + properly initialized +- clarified usage of space-cc property replacer option +- improved abort diagnostic handler +- some initial effort for malloc/free runtime debugging support +- bugfix: using dynafile actions caused rsyslogd abort +- fixed minor man errors thanks to Michael Biebl +--------------------------------------------------------------------------- +Version 3.11.0 (rgerhards), 2008-01-31 +- implemented queued actions +- implemented simple rate limiting for actions +- implemented deliberate discarding of lower priority messages over higher + priority ones when a queue runs out of space +- implemented disk quotas for disk queues +- implemented the $ActionResumeRetryCount config directive +- added $ActionQueueFilename config directive +- added $ActionQueueSize config directive +- added $ActionQueueHighWaterMark config directive +- added $ActionQueueLowWaterMark config directive +- added $ActionQueueDiscardMark config directive +- added $ActionQueueDiscardSeverity config directive +- added $ActionQueueCheckpointInterval config directive +- added $ActionQueueType config directive +- added $ActionQueueWorkerThreads config directive +- added $ActionQueueTimeoutshutdown config directive +- added $ActionQueueTimeoutActionCompletion config directive +- added $ActionQueueTimeoutenQueue config directive +- added $ActionQueueTimeoutworkerThreadShutdown config directive +- added $ActionQueueWorkerThreadMinimumMessages config directive +- added $ActionQueueMaxFileSize config directive +- added $ActionQueueSaveonShutdown config directive +- addded $ActionQueueDequeueSlowdown config directive +- addded $MainMsgQueueDequeueSlowdown config directive +- bugfix: added forgotten docs to package +- improved debugging support +- fixed a bug that caused $MainMsgQueueCheckpointInterval to work incorrectly +- when a long-running action needs to be cancelled on shutdown, the message + that was processed by it is now preserved. This finishes support for + guaranteed delivery of messages (if the output supports it, of course) +- fixed bug in output module interface, see + http://sourceforge.net/tracker/index.php?func=detail&aid=1881008&group_id=123448&atid=696552 +- changed the ommysql output plugin so that the (lengthy) connection + initialization now takes place in message processing. This works much + better with the new queued action mode (fast startup) +- fixed a bug that caused a potential hang in file and fwd output module + varmojfekoj provided the patch - many thanks! +- bugfixed stream class offset handling on 32bit platforms +--------------------------------------------------------------------------- +Version 3.10.3 (rgerhards), 2008-01-28 +- fixed a bug with standard template definitions (not a big deal) - thanks + to varmojfekoj for spotting it +- run-time instrumentation added +- implemented disk-assisted queue mode, which enables on-demand disk + spooling if the queue's in-memory queue is exhausted +- implemented a dynamic worker thread pool for processing incoming + messages; workers are started and shut down as need arises +- implemented a run-time instrumentation debug package +- implemented the $MainMsgQueueSaveOnShutdown config directive +- implemented the $MainMsgQueueWorkerThreadMinimumMessages config directive +- implemented the $MainMsgQueueTimeoutWorkerThreadShutdown config directive +--------------------------------------------------------------------------- +Version 3.10.2 (rgerhards), 2008-01-14 +- added the ability to keep stop rsyslogd without the need to drain + the main message queue. In disk queue mode, rsyslog continues to + run from the point where it stopped. In case of a system failure, it + continues to process messages from the last checkpoint. +- fixed a bug that caused a segfault on startup when no $WorkDir directive + was specified in rsyslog.conf +- provided more fine-grain control over shutdown timeouts and added a + way to specify the enqueue timeout when the main message queue is full +- implemented $MainMsgQueueCheckpointInterval config directive +- implemented $MainMsgQueueTimeoutActionCompletion config directive +- implemented $MainMsgQueueTimeoutEnqueue config directive +- implemented $MainMsgQueueTimeoutShutdown config directive +--------------------------------------------------------------------------- +Version 3.10.1 (rgerhards), 2008-01-10 +- implemented the "disk" queue mode. However, it currently is of very + limited use, because it does not support persistence over rsyslogd + runs. So when rsyslogd is stopped, the queue is drained just as with + the in-memory queue modes. Persistent queues will be a feature of + the next release. +- performance-optimized string class, should bring an overall improvement +- fixed a memory leak in imudp -- thanks to varmojfekoj for the patch +- fixed a race condition that could lead to a rsyslogd hang when during + HUP or termination +- done some doc updates +- added $WorkDirectory config directive +- added $MainMsgQueueFileName config directive +- added $MainMsgQueueMaxFileSize config directive +--------------------------------------------------------------------------- +Version 3.10.0 (rgerhards), 2008-01-07 +- implemented input module interface and initial input modules +- enhanced threading for input modules (each on its own thread now) +- ability to bind UDP listeners to specific local interfaces/ports and + ability to run multiple of them concurrently +- added ability to specify listen IP address for UDP syslog server +- license changed to GPLv3 +- mark messages are now provided by loadble module immark +- rklogd is no longer provided. Its functionality has now been taken over + by imklog, a loadable input module. This offers a much better integration + into rsyslogd and makes sure that the kernel logger process is brought + up and down at the appropriate times +- enhanced $IncludeConfig directive to support wildcard characters + (thanks to Michael Biebl) +- all inputs are now implemented as loadable plugins +- enhanced threading model: each input module now runs on its own thread +- enhanced message queue which now supports different queueing methods + (among others, this can be used for performance fine-tuning) +- added a large number of new configuration directives for the new + input modules +- enhanced multi-threading utilizing a worker thread pool for the + main message queue +- compilation without pthreads is no longer supported +- much cleaner code due to new objects and removal of single-threading + mode +--------------------------------------------------------------------------- +Version 2.0.1 STABLE (rgerhards), 2008-01-24 +- fixed a bug in integer conversion - but this function was never called, + so it is not really a useful bug fix ;) +- fixed a bug with standard template definitions (not a big deal) - thanks + to varmojfekoj for spotting it +- fixed a bug that caused a potential hang in file and fwd output module + varmojfekoj provided the patch - many thanks! +--------------------------------------------------------------------------- +Version 2.0.0 STABLE (rgerhards), 2008-01-02 +- re-release of 1.21.2 as STABLE with no modifications except some + doc updates +--------------------------------------------------------------------------- +Version 1.21.2 (rgerhards), 2007-12-28 +- created a gss-api output module. This keeps GSS-API code and + TCP/UDP code separated. It is also important for forward- + compatibility with v3. Please note that this change breaks compatibility + with config files created for 1.21.0 and 1.21.1 - this was considered + acceptable. +- fixed an error in forwarding retry code (could lead to message corruption + but surfaced very seldom) +- increased portability for older platforms (AI_NUMERICSERV moved) +- removed socket leak in omfwd.c +- cross-platform patch for GSS-API compile problem on some platforms + thanks to darix for the patch! +--------------------------------------------------------------------------- +Version 1.21.1 (rgerhards), 2007-12-23 +- small doc fix for $IncludeConfig +- fixed a bug in llDestroy() +- bugfix: fixing memory leak when message queue is full and during + parsing. Thanks to varmojfekoj for the patch. +- bugfix: when compiled without network support, unix sockets were + not properply closed +- bugfix: memory leak in cfsysline.c/doGetWord() fixed +--------------------------------------------------------------------------- +Version 1.21.0 (rgerhards), 2007-12-19 +- GSS-API support for syslog/TCP connections was added. Thanks to + varmojfekoj for providing the patch with this functionality +- code cleanup +- enhanced $IncludeConfig directive to support wildcard filenames +- changed some multithreading synchronization +--------------------------------------------------------------------------- +Version 1.20.1 (rgerhards), 2007-12-12 +- corrected a debug setting that survived release. Caused TCP connections + to be retried unnecessarily often. +- When a hostname ACL was provided and DNS resolution for that name failed, + ACL processing was stopped at that point. Thanks to mildew for the patch. + Fedora Bugzilla: http://bugzilla.redhat.com/show_bug.cgi?id=395911 +- fixed a potential race condition, see link for details: + http://rgerhards.blogspot.com/2007/12/rsyslog-race-condition.html + Note that the probability of problems from this bug was very remote +- fixed a memory leak that happend when PostgreSQL date formats were + used +--------------------------------------------------------------------------- +Version 1.20.0 (rgerhards), 2007-12-07 +- an output module for postgres databases has been added. Thanks to + sur5r for contributing this code +- unloading dynamic modules has been cleaned up, we now have a + real implementation and not just a dummy "good enough for the time + being". +- enhanced platform independence - thanks to Bartosz Kuzma and Michael + Biebl for their very useful contributions +- some general code cleanup (including warnings on 64 platforms, only) +--------------------------------------------------------------------------- +Version 1.19.12 (rgerhards), 2007-12-03 +- cleaned up the build system (thanks to Michael Biebl for the patch) +- fixed a bug where ommysql was still not compiled with -pthread option +--------------------------------------------------------------------------- +Version 1.19.11 (rgerhards), 2007-11-29 +- applied -pthread option to build when building for multi-threading mode + hopefully solves an issue with segfaulting +--------------------------------------------------------------------------- +Version 1.19.10 (rgerhards), 2007-10-19 +- introdcued the new ":modulename:" syntax for calling module actions + in selector lines; modified ommysql to support it. This is primarily + an aid for further modules and a prequisite to actually allow third + party modules to be created. +- minor fix in slackware startup script, "-r 0" is now "-r0" +- updated rsyslogd doc set man page; now in html format +- undid creation of a separate thread for the main loop -- this did not + turn out to be needed or useful, so reduce complexity once again. +- added doc fixes provided by Michael Biebl - thanks +--------------------------------------------------------------------------- +Version 1.19.9 (rgerhards), 2007-10-12 +- now packaging system which again contains all components in a single + tarball +- modularized main() a bit more, resulting in less complex code +- experimentally added an additional thread - will see if that affects + the segfault bug we experience on some platforms. Note that this change + is scheduled to be removed again later. +--------------------------------------------------------------------------- +Version 1.19.8 (rgerhards), 2007-09-27 +- improved repeated message processing +- applied patch provided by varmojfekoj to support building ommysql + in its own way (now also resides in a plugin subdirectory); + ommysql is now a separate package +- fixed a bug in cvthname() that lead to message loss if part + of the source hostname would have been dropped +- created some support for distributing ommysql together with the + main rsyslog package. I need to re-think it in the future, but + for the time being the current mode is best. I now simply include + one additional tarball for ommysql inside the main distribution. + I look forward to user feedback on how this should be done best. In the + long term, a separate project should be spawend for ommysql, but I'd + like to do that only after the plugin interface is fully stable (what + it is not yet). +--------------------------------------------------------------------------- +Version 1.19.7 (rgerhards), 2007-09-25 +- added code to handle situations where senders send us messages ending with + a NUL character. It is now simply removed. This also caused trailing LF + reduction to fail, when it was followed by such a NUL. This is now also + handled. +- replaced some non-thread-safe function calls by their thread-safe + counterparts +- fixed a minor memory leak that occured when the %APPNAME% property was + used (I think nobody used that in practice) +- fixed a bug that caused signal handlers in cvthname() not to be restored when + a malicious pointer record was detected and processing of the message been + stopped for that reason (this should be really rare and can not be related + to the segfault bug we are hunting). +- fixed a bug in cvthname that lead to passing a wrong parameter - in + practice, this had no impact. +- general code cleanup (e.g. compiler warnings, comments) +--------------------------------------------------------------------------- +Version 1.19.6 (rgerhards), 2007-09-11 +- applied patch by varmojfekoj to change signal handling to the new + sigaction API set (replacing the depreciated signal() calls and its + friends. +- fixed a bug that in --enable-debug mode caused an assertion when the + discard action was used +- cleaned up compiler warnings +- applied patch by varmojfekoj to FIX a bug that could cause + segfaults if empty properties were processed using modifying + options (e.g. space-cc, drop-cc) +- fixed man bug: rsyslogd supports -l option +--------------------------------------------------------------------------- +Version 1.19.5 (rgerhards), 2007-09-07 +- changed part of the CStr interface so that better error tracking + is provided and the calling sequence is more intuitive (there were + invalid calls based on a too-weired interface) +- (hopefully) fixed some remaining bugs rooted in wrong use of + the CStr class. These could lead to program abort. +- applied patch by varmojfekoj two fix two potential segfault situations +- added $ModDir config directive +- modified $ModLoad so that an absolute path may be specified as + module name (e.g. /rsyslog/ommysql.so) +--------------------------------------------------------------------------- +Version 1.19.4 (rgerhards/varmojfekoj), 2007-09-04 +- fixed a number of small memory leaks - thanks varmojfekoj for patching +- fixed an issue with CString class that could lead to rsyslog abort + in tplToString() - thanks varmojfekoj for patching +- added a man-version of the config file documenation - thanks to Michel + Samia for providing the man file +- fixed bug: a template like this causes an infinite loop: + $template opts,"%programname:::a,b%" + thanks varmojfekoj for the patch +- fixed bug: case changing options crash freeing the string pointer + because they modify it: $template opts2,"%programname::1:lowercase%" + thanks varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 1.19.3 (mmeckelein/varmojfekoj), 2007-08-31 +- small mem leak fixed (after calling parseSelectorAct) - Thx varmojkekoj +- documentation section "Regular File" und "Blocks" updated +- solved an issue with dynamic file generation - Once again many thanks + to varmojfekoj +- the negative selector for program name filter (Blocks) does not work as + expected - Thanks varmojfekoj for patching +- added forwarding information to sysklogd (requires special template) + to config doc +--------------------------------------------------------------------------- +Version 1.19.2 (mmeckelein/varmojfekoj), 2007-08-28 +- a specifically formed message caused a segfault - Many thanks varmojfekoj + for providing a patch +- a typo and a weird condition are fixed in msg.c - Thanks again + varmojfekoj +- on file creation the file was always owned by root:root. This is fixed + now - Thanks ypsa for solving this issue +--------------------------------------------------------------------------- +Version 1.19.1 (mmeckelein), 2007-08-22 +- a bug that caused a high load when a TCP/UDP connection was closed is + fixed now - Thanks mildew for solving this issue +- fixed a bug which caused a segfault on reinit - Thx varmojfekoj for the + patch +- changed the hardcoded module path "/lib/rsyslog" to $(pkglibdir) in order + to avoid trouble e.g. on 64 bit platforms (/lib64) - many thanks Peter + Vrabec and darix, both provided a patch for solving this issue +- enhanced the unloading of modules - thanks again varmojfekoj +- applied a patch from varmojfekoj which fixes various little things in + MySQL output module +--------------------------------------------------------------------------- +Version 1.19.0 (varmojfekoj/rgerhards), 2007-08-16 +- integrated patch from varmojfekoj to make the mysql module a loadable one + many thanks for the patch, MUCH appreciated +--------------------------------------------------------------------------- +Version 1.18.2 (rgerhards), 2007-08-13 +- fixed a bug in outchannel code that caused templates to be incorrectly + parsed +- fixed a bug in ommysql that caused a wrong ";template" missing message +- added some code for unloading modules; not yet fully complete (and we do + not yet have loadable modules, so this is no problem) +- removed debian subdirectory by request of a debian packager (this is a special + subdir for debian and there is also no point in maintaining it when there + is a debian package available - so I gladly did this) in some cases +- improved overall doc quality (some pages were quite old) and linked to + more of the online resources. +- improved /contrib/delete_mysql script by adding a host option and some + other minor modifications +--------------------------------------------------------------------------- +Version 1.18.1 (rgerhards), 2007-08-08 +- applied a patch from varmojfekoj which solved a potential segfault + of rsyslogd on HUP +- applied patch from Michel Samia to fix compilation when the pthreads + feature is disabled +- some code cleanup (moved action object to its own file set) +- add config directive $MainMsgQueueSize, which now allows to configure the + queue size dynamically +- all compile-time settings are now shown in rsyslogd -v, not just the + active ones +- enhanced performance a little bit more +- added config file directive $ActionResumeInterval +- fixed a bug that prevented compilation under debian sid +- added a contrib directory for user-contributed useful things +--------------------------------------------------------------------------- +Version 1.18.0 (rgerhards), 2007-08-03 +- rsyslog now supports fallback actions when an action did not work. This + is a great feature e.g. for backup database servers or backup syslog + servers +- modified rklogd to only change the console log level if -c is specified +- added feature to use multiple actions inside a single selector +- implemented $ActionExecOnlyWhenPreviousIsSuspended config directive +- error messages during startup are now spit out to the configured log + destinations +--------------------------------------------------------------------------- +Version 1.17.6 (rgerhards), 2007-08-01 +- continued to work on output module modularization - basic stage of + this work is now FINISHED +- fixed bug in OMSRcreate() - always returned SR_RET_OK +- fixed a bug that caused ommysql to always complain about missing + templates +- fixed a mem leak in OMSRdestruct - freeing the object itself was + forgotten - thanks to varmojfekoj for the patch +- fixed a memory leak in syslogd/init() that happend when the config + file could not be read - thanks to varmojfekoj for the patch +- fixed insufficient memory allocation in addAction() and its helpers. + The initial fix and idea was developed by mildew, I fine-tuned + it a bit. Thanks a lot for the fix, I'd probably had pulled out my + hair to find the bug... +- added output of config file line number when a parsing error occured +- fixed bug in objomsr.c that caused program to abort in debug mode with + an invalid assertion (in some cases) +- fixed a typo that caused the default template for MySQL to be wrong. + thanks to mildew for catching this. +- added configuration file command $DebugPrintModuleList and + $DebugPrintCfSysLineHandlerList +- fixed an invalid value for the MARK timer - unfortunately, there was + a testing aid left in place. This resulted in quite frequent MARK messages +- added $IncludeConfig config directive +- applied a patch from mildew to prevent rsyslogd from freezing under heavy + load. This could happen when the queue was full. Now, we drop messages + but rsyslogd remains active. +--------------------------------------------------------------------------- +Version 1.17.5 (rgerhards), 2007-07-30 +- continued to work on output module modularization +- fixed a missing file bug - thanks to Andrea Montanari for reporting + this problem +- fixed a problem with shutting down the worker thread and freeing the + selector_t list - this caused messages to be lost, because the + message queue was not properly drained before the selectors got + destroyed. +--------------------------------------------------------------------------- +Version 1.17.4 (rgerhards), 2007-07-27 +- continued to work on output module modularization +- fixed a situation where rsyslogd could create zombie processes + thanks to mildew for the patch +- applied patch from Michel Samia to fix compilation when NOT + compiled for pthreads +--------------------------------------------------------------------------- +Version 1.17.3 (rgerhards), 2007-07-25 +- continued working on output module modularization +- fixed a bug that caused rsyslogd to segfault on exit (and + probably also on HUP), when there was an unsent message in a selector + that required forwarding and the dns lookup failed for that selector + (yes, it was pretty unlikely to happen;)) + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- fixed a memory leak in config file parsing and die() + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- rsyslogd now checks on startup if it is capable to performa any work + at all. If it cant, it complains and terminates + thanks to Michel Samia for providing the patch! +- fixed a small memory leak when HUPing syslogd. The allowed sender + list now gets freed. thanks to mildew for the patch. +- changed the way error messages in early startup are logged. They + now do no longer use the syslogd code directly but are rather + send to stderr. +--------------------------------------------------------------------------- +Version 1.17.2 (rgerhards), 2007-07-23 +- made the port part of the -r option optional. Needed for backward + compatibility with sysklogd +- replaced system() calls with something more reasonable. Please note that + this might break compatibility with some existing configuration files. + We accept this in favour of the gained security. +- removed a memory leak that could occur if timegenerated was used in + RFC 3164 format in templates +- did some preparation in msg.c for advanced multithreading - placed the + hooks, but not yet any active code +- worked further on modularization +- added $ModLoad MySQL (dummy) config directive +- added DropTrailingLFOnReception config directive +--------------------------------------------------------------------------- +Version 1.17.1 (rgerhards), 2007-07-20 +- fixed a bug that caused make install to install rsyslogd and rklogd under + the wrong names +- fixed bug that caused $AllowedSenders to handle IPv6 scopes incorrectly; + also fixed but that could grabble $AllowedSender wildcards. Thanks to + mildew@gmail.com for the patch +- minor code cleanup - thanks to Peter Vrabec for the patch +- fixed minimal memory leak on HUP (caused by templates) + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- fixed another memory leak on HUPing and on exiting rsyslogd + again thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- code cleanup (removed compiler warnings) +- fixed portability bug in configure.ac - thanks to Bartosz Kuźma for patch +- moved msg object into its own file set +- added the capability to continue trying to write log files when the + file system is full. Functionality based on patch by Martin Schulze + to sysklogd package. +--------------------------------------------------------------------------- +Version 1.17.0 (RGer), 2007-07-17 +- added $RepeatedLineReduction config parameter +- added $EscapeControlCharactersOnReceive config parameter +- added $ControlCharacterEscapePrefix config parameter +- added $DirCreateMode config parameter +- added $CreateDirs config parameter +- added $DebugPrintTemplateList config parameter +- added $ResetConfigVariables config parameter +- added $FileOwner config parameter +- added $FileGroup config parameter +- added $DirOwner config parameter +- added $DirGroup config parameter +- added $FailOnChownFailure config parameter +- added regular expression support to the filter engine + thanks to Michel Samia for providing the patch! +- enhanced $AllowedSender functionality. Credits to mildew@gmail.com for + the patch doing that + - added IPv6 support + - allowed DNS hostnames + - allowed DNS wildcard names +- added new option $DropMsgsWithMaliciousDnsPTRRecords +- added autoconf so that rfc3195d, rsyslogd and klogd are stored to /sbin +- added capability to auto-create directories with dynaFiles +--------------------------------------------------------------------------- +Version 1.16.0 (RGer/Peter Vrabec), 2007-07-13 - The Friday, 13th Release ;) +- build system switched to autotools +- removed SYSV preprocessor macro use, replaced with autotools equivalents +- fixed a bug that caused rsyslogd to segfault when TCP listening was + disabled and it terminated +- added new properties "syslogfacility-text" and "syslogseverity-text" + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- added the -x option to disable hostname dns reslution + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- begun to better modularize syslogd.c - this is an ongoing project; moved + type definitions to a separate file +- removed some now-unused fields from struct filed +- move file size limit fields in struct field to the "right spot" (the file + writing part of the union - f_un.f_file) +- subdirectories linux and solaris are no longer part of the distribution + package. This is not because we cease support for them, but there are no + longer any files in them after the move to autotools +--------------------------------------------------------------------------- +Version 1.15.1 (RGer), 2007-07-10 +- fixed a bug that caused a dynaFile selector to stall when there was + an open error with one file +- improved template processing for dynaFiles; templates are now only + looked up during initialization - speeds up processing +- optimized memory layout in struct filed when compiled with MySQL + support +- fixed a bug that caused compilation without SYSLOG_INET to fail +- re-enabled the "last message repeated n times" feature. This + feature was not taken care of while rsyslogd evolved from sysklogd + and it was more or less defunct. Now it is fully functional again. +- added system properties: $NOW, $YEAR, $MONTH, $DAY, $HOUR, $MINUTE +- fixed a bug in iovAsString() that caused a memory leak under stress + conditions (most probably memory shortage). This was unlikely to + ever happen, but it doesn't hurt doing it right +- cosmetic: defined type "uchar", change all unsigned chars to uchar +--------------------------------------------------------------------------- +Version 1.15.0 (RGer), 2007-07-05 +- added ability to dynamically generate file names based on templates + and thus properties. This was a much-requested feature. It makes + life easy when it e.g. comes to splitting files based on the sender + address. +- added $umask and $FileCreateMode config file directives +- applied a patch from Bartosz Kuzma to compile cleanly under NetBSD +- checks for extra (unexpected) characters in system config file lines + have been added +- added IPv6 documentation - was accidently missing from CVS +- begun to change char to unsigned char +--------------------------------------------------------------------------- +Version 1.14.2 (RGer), 2007-07-03 +** this release fixes all known nits with IPv6 ** +- restored capability to do /etc/service lookup for "syslog" + service when -r 0 was given +- documented IPv6 handling of syslog messages +- integrate patch from Bartosz Kuźma to make rsyslog compile under + Solaris again (the patch replaced a strndup() call, which is not + available under Solaris +- improved debug logging when waiting on select +- updated rsyslogd man page with new options (-46A) +--------------------------------------------------------------------------- +Version 1.14.1 (RGer/Peter Vrabec), 2007-06-29 +- added Peter Vrabec's patch for IPv6 TCP +- prefixed all messages send to stderr in rsyslogd with "rsyslogd: " +--------------------------------------------------------------------------- +Version 1.14.0 (RGer/Peter Vrabec), 2007-06-28 +- Peter Vrabec provided IPv6 for rsyslog, so we are now IPv6 enabled + IPv6 Support is currently for UDP only, TCP is to come soon. + AllowedSender configuration does not yet work for IPv6. +- fixed code in iovCreate() that broke C's strict aliasing rules +- fixed some char/unsigned char differences that forced the compiler + to spit out warning messages +- updated the Red Hat init script to fix a known issue (thanks to + Peter Vrabec) +--------------------------------------------------------------------------- +Version 1.13.5 (RGer), 2007-06-22 +- made the TCP session limit configurable via command line switch + now -t <port>,<max sessions> +- added man page for rklogd(8) (basically a copy from klogd, but now + there is one...) +- fixed a bug that caused internal messages (e.g. rsyslogd startup) to + appear without a tag. +- removed a minor memory leak that occurred when TAG processing requalified + a HOSTNAME to be a TAG (and a TAG already was set). +- removed potential small memory leaks in MsgSet***() functions. There + would be a leak if a property was re-set, something that happened + extremely seldom. +--------------------------------------------------------------------------- +Version 1.13.4 (RGer), 2007-06-18 +- added a new property "PRI-text", which holds the PRI field in + textual form (e.g. "syslog.info") +- added alias "syslogseverity" for "syslogpriority", which is a + misleading property name that needs to stay for historical + reasons (and backward-compatility) +- added doc on how to record PRI value in log file +- enhanced signal handling in klogd, including removal of an unsafe + call to the logging system during signal handling +--------------------------------------------------------------------------- +Version 1.13.3 (RGer), 2007-06-15 +- create a version of syslog.c from scratch. This is now + - highly optimized for rsyslog + - removes an incompatible license problem as the original + version had a BSD license with advertising clause + - fixed in the regard that rklogd will continue to work when + rsysogd has been restarted (the original version, as well + as sysklogd, will remain silent then) + - solved an issue with an extra NUL char at message end that the + original version had +- applied some changes to klogd to care for the new interface +- fixed a bug in syslogd.c which prevented compiling under debian +--------------------------------------------------------------------------- +Version 1.13.2 (RGer), 2007-06-13 +- lib order in makefile patched to facilitate static linking - thanks + to Bennett Todd for providing the patch +- Integrated a patch from Peter Vrabec (pvrabec@redheat.com): + - added klogd under the name of rklogd (remove dependency on + original sysklogd package + - createDB.sql now in UTF + - added additional config files for use on Red Hat +--------------------------------------------------------------------------- +Version 1.13.1 (RGer), 2007-02-05 +- changed the listen backlog limit to a more reasonable value based on + the maximum number of TCP connections configurd (10% + 5) - thanks to Guy + Standen for the hint (actually, the limit was 5 and that was a + left-over from early testing). +- fixed a bug in makefile which caused DB-support to be disabled when + NETZIP support was enabled +- added the -e option to allow transmission of every message to remote + hosts (effectively turns off duplicate message suppression) +- (somewhat) improved memory consumption when compiled with MySQL support +- looks like we fixed an incompatibility with MySQL 5.x and above software + At least in one case, the remote server name was destroyed, leading to + a connection failure. The new, improved code does not have this issue and + so we see this as solved (the new code is generally somewhat better, so + there is a good chance we fixed this incompatibility). +--------------------------------------------------------------------------- +Version 1.13.0 (RGer), 2006-12-19 +- added '$' as ToPos proptery replacer specifier - means "up to the + end of the string" +- property replacer option "escape-cc", "drop-cc" and "space-cc" added +- changed the handling of \0 characters inside syslog messages. We now + consistently escape them to "#000". This is somewhat recommended in + the draft-ietf-syslog-protocol-19 draft. While the real recomendation + is to not escape any characters at all, we can not do this without + considerable modification of the code. So we escape it to "#000", which + is consistent with a sample found in the Internet-draft. +- removed message glue logic (see printchopped() comment for details) + Also caused removal of parts table and thus some improvements in + memory usage. +- changed the default MAXLINE to 2048 to take care of recent syslog + standardization efforts (can easily be changed in syslogd.c) +- added support for byte-counted TCP syslog messages (much like + syslog-transport-tls-05 Internet Draft). This was necessary to + support compression over TCP. +- added support for receiving compressed syslog messages +- added support for sending compressed syslog messages +- fixed a bug where the last message in a syslog/tcp stream was + lost if it was not properly terminated by a LF character +--------------------------------------------------------------------------- +Version 1.12.3 (RGer), 2006-10-04 +- implemented some changes to support Solaris (but support is not + yet complete) +- commented out (via #if 0) some methods that are currently not being use + but should be kept for further us +- added (interim) -u 1 option to turn off hostname and tag parsing +- done some modifications to better support Fedora +- made the field delimiter inside property replace configurable via + template +- fixed a bug in property replacer: if fields were used, the delimitor + became part of the field. Up until now, this was barely noticable as + the delimiter as TAB only and thus invisible to a human. With other + delimiters available now, it quickly showed up. This bug fix might cause + some grief to existing installations if they used the extra TAB for + whatever reasons - sorry folks... Anyhow, a solution is easy: just add + a TAB character contstant into your template. Thus, there has no attempt + been made to do this in a backwards-compatible way. +--------------------------------------------------------------------------- +Version 1.12.2 (RGer), 2006-02-15 +- fixed a bug in the RFC 3339 date formatter. An extra space was added + after the actual timestamp +- added support for providing high-precision RFC3339 timestamps for + (rsyslogd-)internally-generated messages +- very (!) experimental support for syslog-protocol internet draft + added (the draft is experimental, the code is solid ;)) +- added support for field-extracting in the property replacer +- enhanced the legacy-syslog parser so that it can interpret messages + that do not contain a TIMESTAMP +- fixed a bug that caused the default socket (usually /dev/log) to be + opened even when -o command line option was given +- fixed a bug in the Debian sample startup script - it caused rsyslogd + to listen to remote requests, which it shouldn't by default +--------------------------------------------------------------------------- +Version 1.12.1 (RGer), 2005-11-23 +- made multithreading work with BSD. Some signal-handling needed to be + restructured. Also, there might be a slight delay of up to 10 seconds + when huping and terminating rsyslogd under BSD +- fixed a bug where a NULL-pointer was passed to printf() in logmsg(). +- fixed a bug during "make install" where rc3195d was not installed + Thanks to Bennett Todd for spotting this. +- fixed a bug where rsyslogd dumped core when no TAG was found in the + received message +- enhanced message parser so that it can deal with missing hostnames + in many cases (may not be totally fail-safe) +- fixed a bug where internally-generated messages did not have the correct + TAG +--------------------------------------------------------------------------- +Version 1.12.0 (RGer), 2005-10-26 +- moved to a multi-threaded design. single-threading is still optionally + available. Multi-threading is experimental! +- fixed a potential race condition. In the original code, marking was done + by an alarm handler, which could lead to all sorts of bad things. This + has been changed now. See comments in syslogd.c/domark() for details. +- improved debug output for property-based filters +- not a code change, but: I have checked all exit()s to make sure that + none occurs once rsyslogd has started up. Even in unusual conditions + (like low-memory conditions) rsyslogd somehow remains active. Of course, + it might loose a message or two, but at least it does not abort and it + can also recover when the condition no longer persists. +- fixed a bug that could cause loss of the last message received + immediately before rsyslogd was terminated. +- added comments on thread-safety of global variables in syslogd.c +- fixed a small bug: spurios printf() when TCP syslog was used +- fixed a bug that causes rsyslogd to dump core on termination when one + of the selector lines did not receive a message during the run (very + unlikely) +- fixed an one-too-low memory allocation in the TCP sender. Could result + in rsyslogd dumping core. +- fixed a bug with regular expression support (thanks to Andres Riancho) +- a little bit of code restructuring (especially main(), which was + horribly large) +--------------------------------------------------------------------------- +Version 1.11.1 (RGer), 2005-10-19 +- support for BSD-style program name and host blocks +- added a new property "programname" that can be used in templates +- added ability to specify listen port for rfc3195d +- fixed a bug that rendered the "startswith" comparison operation + unusable. +- changed more functions to "static" storage class to help compiler + optimize (should have been static in the first place...) +- fixed a potential memory leak in the string buffer class destructor. + As the destructur was previously never called, the leak did not actually + appear. +- some internal restructuring in anticipation/preparation of minimal + multi-threading support +- rsyslogd still shares some code with the sysklogd project. Some patches + for this shared code have been brought over from the sysklogd CVS. +--------------------------------------------------------------------------- +Version 1.11.0 (RGer), 2005-10-12 +- support for receiving messages via RFC 3195; added rfc3195d for that + purpose +- added an additional guard to prevent rsyslogd from aborting when the + 2gb file size limit is hit. While a user can configure rsyslogd to + handle such situations, it would abort if that was not done AND large + file support was not enabled (ok, this is hopefully an unlikely scenario) +- fixed a bug that caused additional Unix domain sockets to be incorrectly + processed - could lead to message loss in extreme cases +--------------------------------------------------------------------------- +Version 1.10.2 (RGer), 2005-09-27 +- added comparison operations in property-based filters: + * isequal + * startswith +- added ability to negate all property-based filter comparison operations + by adding a !-sign right in front of the operation name +- added the ability to specify remote senders for UDP and TCP + received messages. Allows to block all but well-known hosts +- changed the $-config line directives to be case-INsensitive +- new command line option -w added: "do not display warnings if messages + from disallowed senders are received" +- fixed a bug that caused rsyslogd to dump core when the compare value + was not quoted in property-based filters +- fixed a bug in the new CStr compare function which lead to invalid + results (fortunately, this function was not yet used widely) +- added better support for "debugging" rsyslog.conf property filters + (only if -d switch is given) +- changed some function definitions to static, which eventually enables + some compiler optimizations +- fixed a bug in MySQL code; when a SQL error occured, rsyslogd could + run in a tight loop. This was due to invalid sequence of error reporting + and is now fixed. +--------------------------------------------------------------------------- +Version 1.10.1 (RGer), 2005-09-23 +- added the ability to execute a shell script as an action. + Thanks to Bjoern Kalkbrenner for providing the code! +- fixed a bug in the MySQL code; due to the bug the automatic one-time + retry after an error did not happen - this lead to error message in + cases where none should be seen (e.g. after a MySQL restart) +- fixed a security issue with SQL-escaping in conjunction with + non-(SQL-)standard MySQL features. +--------------------------------------------------------------------------- +Version 1.10.0 (RGer), 2005-09-20 + REMINDER: 1.10 is the first unstable version if the 1.x series! +- added the capability to filter on any property in selector lines + (not just facility and priority) +- changed stringbuf into a new counted string class +- added support for a "discard" action. If a selector line with + discard (~ character) is found, no selector lines *after* that + line will be processed. +- thanks to Andres Riancho, regular expression support has been + added to the template engine +- added the FROMHOST property in the template processor, which could + previously not be obtained. Thanks to Cristian Testa for pointing + this out and even providing a fix. +- added display of compile-time options to -v output +- performance improvement for production build - made some checks + to happen only during debug mode +- fixed a problem with compiling on SUSE and - while doing so - removed + the socket call to set SO_BSDCOMPAT in cases where it is obsolete. +--------------------------------------------------------------------------- +Version 1.0.4 (RGer), 2006-02-01 +- a small but important fix: the tcp receiver had two forgotten printf's + in it that caused a lot of unnecessary output to stdout. This was + important enough to justify a new release +--------------------------------------------------------------------------- +Version 1.0.3 (RGer), 2005-11-14 +- added an additional guard to prevent rsyslogd from aborting when the + 2gb file size limit is hit. While a user can configure rsyslogd to + handle such situations, it would abort if that was not done AND large + file support was not enabled (ok, this is hopefully an unlikely scenario) +- fixed a bug that caused additional Unix domain sockets to be incorrectly + processed - could lead to message loss in extreme cases +- applied some patches available from the sysklogd project to code + shared from there +- fixed a bug that causes rsyslogd to dump core on termination when one + of the selector lines did not receive a message during the run (very + unlikely) +- fixed an one-too-low memory allocation in the TCP sender. Could result + in rsyslogd dumping core. +- fixed a bug in the TCP sender that caused the retry logic to fail + after an error or receiver overrun +- fixed a bug in init() that could lead to dumping core +- fixed a bug that could lead to dumping core when no HOSTNAME or no TAG + was present in the syslog message +--------------------------------------------------------------------------- +Version 1.0.2 (RGer), 2005-10-05 +- fixed an issue with MySQL error reporting. When an error occured, + the MySQL driver went into an endless loop (at least in most cases). +--------------------------------------------------------------------------- +Version 1.0.1 (RGer), 2005-09-23 +- fixed a security issue with SQL-escaping in conjunction with + non-(SQL-)standard MySQL features. +--------------------------------------------------------------------------- +Version 1.0.0 (RGer), 2005-09-12 +- changed install doc to cover daily cron scripts - a trouble source +- added rc script for slackware (provided by Chris Elvidge - thanks!) +- fixed a really minor bug in usage() - the -r option was still + reported as without the port parameter +--------------------------------------------------------------------------- +Version 0.9.8 (RGer), 2005-09-05 +- made startup and shutdown message more consistent and included the + pid, so that they can be easier correlated. Used syslog-protocol + structured data format for this purpose. +- improved config info in startup message, now tells not only + if it is listening remote on udp, but also for tcp. Also includes + the port numbers. The previous startup message was misleading, because + it did not say "remote reception" if rsyslogd was only listening via + tcp (but not via udp). +- added a "how can you help" document to the doc set +--------------------------------------------------------------------------- +Version 0.9.7 (RGer), 2005-08-15 +- some of the previous doc files (like INSTALL) did not properly + reflect the changes to the build process and the new doc. Fixed + that. +- changed syslogd.c so that when compiled without database support, + an error message is displayed when a database action is detected + in the config file (previously this was used as an user rule ;)) +- fixed a bug in the os-specific Makefiles which caused MySQL + support to not be compiled, even if selected +--------------------------------------------------------------------------- +Version 0.9.6 (RGer), 2005-08-09 +- greatly enhanced documentation. Now available in html format in + the "doc" folder and FreeBSD. Finally includes an install howto. +- improved MySQL error messages a little - they now show up as log + messages, too (formerly only in debug mode) +- added the ability to specify the listen port for udp syslog. + WARNING: This introduces an incompatibility. Formerly, udp + syslog was enabled by the -r command line option. Now, it is + "-r [port]", which is consistent with the tcp listener. However, + just -r will now return an error message. +- added sample startup scripts for Debian and FreeBSD +- added support for easy feature selection in the makefile. Un- + fortunately, this also means I needed to spilt the make file + for different OS and distros. There are some really bad syntax + differences between FreeBSD and Linux make. +--------------------------------------------------------------------------- +Version 0.9.5 (RGer), 2005-08-01 +- the "semicolon bug" was actually not (fully) solved in 0.9.4. One + part of the bug was solved, but another still existed. This one + is fixed now, too. +- the "semicolon bug" actually turned out to be a more generic bug. + It appeared whenever an invalid template name was given. With some + selector actions, rsyslogd dumped core, with other it "just" had + a small ressource leak with others all worked well. These anomalies + are now fixed. Note that they only appeared during system initaliziation + once the system was running, nothing bad happened. +- improved error reporting for template errors on startup. They are now + shown on the console and the start-up tty. Formerly, they were only + visible in debug mode. +- support for multiple instances of rsyslogd on a single machine added +- added new option "-o" --> omit local unix domain socket. This option + enables rsyslogd NOT to listen to the local socket. This is most + helpful when multiple instances of rsyslogd (or rsyslogd and another + syslogd) shall run on a single system. +- added new option "-i <pidfile>" which allows to specify the pidfile. + This is needed when multiple instances of rsyslogd are to be run. +- the new project home page is now online at www.rsyslog.com +--------------------------------------------------------------------------- +Version 0.9.4 (RGer), 2005-07-25 +- finally added the TCP sender. It now supports non-blocking mode, no + longer disabling message reception during connect. As it is now, it + is usable in production. The code could be more sophisticated, but + I've kept it short in anticipation of the move to liblogging, which + will lead to the removal of the code just written ;) +- the "exiting on signal..." message still had the "syslogd" name in + it. Changed this to "rsyslogd", as we do not have a large user base + yet, this should pose no problem. +- fixed "the semiconlon" bug. rsyslogd dumped core if a write-db action + was specified but no semicolon was given after the password (an empty + template was ok, but the semicolon needed to be present). +- changed a default for traditional output format. During testing, it + was seen that the timestamp written to file in default format was + the time of message reception, not the time specified in the TIMESTAMP + field of the message itself. Traditionally, the message TIMESTAMP is + used and this has been changed now. +--------------------------------------------------------------------------- +Version 0.9.3 (RGer), 2005-07-19 +- fixed a bug in the message parser. In June, the RFC 3164 timestamp + was not correctly parsed (yes, only in June and some other months, + see the code comment to learn why...) +- added the ability to specify the destination port when forwarding + syslog messages (both for TCP and UDP) +- added an very experimental TCP sender (activated by + @@machine:port in config). This is not yet for production use. If + the receiver is not alive, rsyslogd will wait quite some time until + the connection request times out, which most probably leads to + loss of incoming messages. + +--------------------------------------------------------------------------- +Version 0.9.2 (RGer), around 2005-07-06 +- I intended to change the maxsupported message size to 32k to + support IHE - but given the memory inefficiency in the usual use + cases, I have not done this. I have, however, included very + specific instructions on how to do this in the source code. I have + also done some testing with 32k messages, so you can change the + max size without taking too much risk. +- added a syslog/tcp receiver; we now can receive messages via + plain tcp, but we can still send only via UDP. The syslog/tcp + receiver is the primary enhancement of this release. +- slightly changed some error messages that contained a spurios \n at + the end of the line (which gives empty lines in your log...) + +--------------------------------------------------------------------------- +Version 0.9.1 (RGer) +- fixed code so that it compiles without errors under FreeBSD +- removed now unused function "allocate_log()" from syslogd.c +- changed the make file so that it contains more defines for + different environments (in the long term, we need a better + system for disabling/enabling features...) +- changed some printf's printing off_t types to %lld and + explicit (long long) casts. I tried to figure out the exact type, + but did not succeed in this. In the worst case, ultra-large peta- + byte files will now display funny informational messages on rollover, + something I think we can live with for the neersion 3.11.2 (rgerhards), 2008-02-?? +--------------------------------------------------------------------------- +Version 3.11.1 (rgerhards), 2008-02-12 +- SNMP trap sender added thanks to Andre Lorbach (omsnmp) +- added input-plugin interface specification in form of a (copy) template + input module +- applied documentation fix by Michael Biebl -- many thanks! +- bugfix: immark did not have MARK flags set... +- added x-info field to rsyslogd startup/shutdown message. Hopefully + points users to right location for further info (many don't even know + they run rsyslog ;)) +- bugfix: trailing ":" of tag was lost while parsing legacy syslog messages + without timestamp - thanks to Anders Blomdell for providing a patch! +- fixed a bug in stringbuf.c related to STRINGBUF_TRIM_ALLOCSIZE, which + wasn't supposed to be used with rsyslog. Put a warning message up that + tells this feature is not tested and probably not worth the effort. + Thanks to Anders Blomdell fro bringing this to our attention +- somewhat improved performance of string buffers +- fixed bug that caused invalid treatment of tabs (HT) in rsyslog.conf +- bugfix: setting for $EscapeCopntrolCharactersOnReceive was not + properly initialized +- clarified usage of space-cc property replacer option +- improved abort diagnostic handler +- some initial effort for malloc/free runtime debugging support +- bugfix: using dynafile actions caused rsyslogd abort +- fixed minor man errors thanks to Michael Biebl +--------------------------------------------------------------------------- +Version 3.11.0 (rgerhards), 2008-01-31 +- implemented queued actions +- implemented simple rate limiting for actions +- implemented deliberate discarding of lower priority messages over higher + priority ones when a queue runs out of space +- implemented disk quotas for disk queues +- implemented the $ActionResumeRetryCount config directive +- added $ActionQueueFilename config directive +- added $ActionQueueSize config directive +- added $ActionQueueHighWaterMark config directive +- added $ActionQueueLowWaterMark config directive +- added $ActionQueueDiscardMark config directive +- added $ActionQueueDiscardSeverity config directive +- added $ActionQueueCheckpointInterval config directive +- added $ActionQueueType config directive +- added $ActionQueueWorkerThreads config directive +- added $ActionQueueTimeoutshutdown config directive +- added $ActionQueueTimeoutActionCompletion config directive +- added $ActionQueueTimeoutenQueue config directive +- added $ActionQueueTimeoutworkerThreadShutdown config directive +- added $ActionQueueWorkerThreadMinimumMessages config directive +- added $ActionQueueMaxFileSize config directive +- added $ActionQueueSaveonShutdown config directive +- addded $ActionQueueDequeueSlowdown config directive +- addded $MainMsgQueueDequeueSlowdown config directive +- bugfix: added forgotten docs to package +- improved debugging support +- fixed a bug that caused $MainMsgQueueCheckpointInterval to work incorrectly +- when a long-running action needs to be cancelled on shutdown, the message + that was processed by it is now preserved. This finishes support for + guaranteed delivery of messages (if the output supports it, of course) +- fixed bug in output module interface, see + http://sourceforge.net/tracker/index.php?func=detail&aid=1881008&group_id=123448&atid=696552 +- changed the ommysql output plugin so that the (lengthy) connection + initialization now takes place in message processing. This works much + better with the new queued action mode (fast startup) +- fixed a bug that caused a potential hang in file and fwd output module + varmojfekoj provided the patch - many thanks! +- bugfixed stream class offset handling on 32bit platforms +--------------------------------------------------------------------------- +Version 3.10.3 (rgerhards), 2008-01-28 +- fixed a bug with standard template definitions (not a big deal) - thanks + to varmojfekoj for spotting it +- run-time instrumentation added +- implemented disk-assisted queue mode, which enables on-demand disk + spooling if the queue's in-memory queue is exhausted +- implemented a dynamic worker thread pool for processing incoming + messages; workers are started and shut down as need arises +- implemented a run-time instrumentation debug package +- implemented the $MainMsgQueueSaveOnShutdown config directive +- implemented the $MainMsgQueueWorkerThreadMinimumMessages config directive +- implemented the $MainMsgQueueTimeoutWorkerThreadShutdown config directive +--------------------------------------------------------------------------- +Version 3.10.2 (rgerhards), 2008-01-14 +- added the ability to keep stop rsyslogd without the need to drain + the main message queue. In disk queue mode, rsyslog continues to + run from the point where it stopped. In case of a system failure, it + continues to process messages from the last checkpoint. +- fixed a bug that caused a segfault on startup when no $WorkDir directive + was specified in rsyslog.conf +- provided more fine-grain control over shutdown timeouts and added a + way to specify the enqueue timeout when the main message queue is full +- implemented $MainMsgQueueCheckpointInterval config directive +- implemented $MainMsgQueueTimeoutActionCompletion config directive +- implemented $MainMsgQueueTimeoutEnqueue config directive +- implemented $MainMsgQueueTimeoutShutdown config directive +--------------------------------------------------------------------------- +Version 3.10.1 (rgerhards), 2008-01-10 +- implemented the "disk" queue mode. However, it currently is of very + limited use, because it does not support persistence over rsyslogd + runs. So when rsyslogd is stopped, the queue is drained just as with + the in-memory queue modes. Persistent queues will be a feature of + the next release. +- performance-optimized string class, should bring an overall improvement +- fixed a memory leak in imudp -- thanks to varmojfekoj for the patch +- fixed a race condition that could lead to a rsyslogd hang when during + HUP or termination +- done some doc updates +- added $WorkDirectory config directive +- added $MainMsgQueueFileName config directive +- added $MainMsgQueueMaxFileSize config directive +--------------------------------------------------------------------------- +Version 3.10.0 (rgerhards), 2008-01-07 +- implemented input module interface and initial input modules +- enhanced threading for input modules (each on its own thread now) +- ability to bind UDP listeners to specific local interfaces/ports and + ability to run multiple of them concurrently +- added ability to specify listen IP address for UDP syslog server +- license changed to GPLv3 +- mark messages are now provided by loadble module immark +- rklogd is no longer provided. Its functionality has now been taken over + by imklog, a loadable input module. This offers a much better integration + into rsyslogd and makes sure that the kernel logger process is brought + up and down at the appropriate times +- enhanced $IncludeConfig directive to support wildcard characters + (thanks to Michael Biebl) +- all inputs are now implemented as loadable plugins +- enhanced threading model: each input module now runs on its own thread +- enhanced message queue which now supports different queueing methods + (among others, this can be used for performance fine-tuning) +- added a large number of new configuration directives for the new + input modules +- enhanced multi-threading utilizing a worker thread pool for the + main message queue +- compilation without pthreads is no longer supported +- much cleaner code due to new objects and removal of single-threading + mode +--------------------------------------------------------------------------- +Version 2.0.1 STABLE (rgerhards), 2008-01-24 +- fixed a bug in integer conversion - but this function was never called, + so it is not really a useful bug fix ;) +- fixed a bug with standard template definitions (not a big deal) - thanks + to varmojfekoj for spotting it +- fixed a bug that caused a potential hang in file and fwd output module + varmojfekoj provided the patch - many thanks! +--------------------------------------------------------------------------- +Version 2.0.0 STABLE (rgerhards), 2008-01-02 +- re-release of 1.21.2 as STABLE with no modifications except some + doc updates +--------------------------------------------------------------------------- +Version 1.21.2 (rgerhards), 2007-12-28 +- created a gss-api output module. This keeps GSS-API code and + TCP/UDP code separated. It is also important for forward- + compatibility with v3. Please note that this change breaks compatibility + with config files created for 1.21.0 and 1.21.1 - this was considered + acceptable. +- fixed an error in forwarding retry code (could lead to message corruption + but surfaced very seldom) +- increased portability for older platforms (AI_NUMERICSERV moved) +- removed socket leak in omfwd.c +- cross-platform patch for GSS-API compile problem on some platforms + thanks to darix for the patch! +--------------------------------------------------------------------------- +Version 1.21.1 (rgerhards), 2007-12-23 +- small doc fix for $IncludeConfig +- fixed a bug in llDestroy() +- bugfix: fixing memory leak when message queue is full and during + parsing. Thanks to varmojfekoj for the patch. +- bugfix: when compiled without network support, unix sockets were + not properply closed +- bugfix: memory leak in cfsysline.c/doGetWord() fixed +--------------------------------------------------------------------------- +Version 1.21.0 (rgerhards), 2007-12-19 +- GSS-API support for syslog/TCP connections was added. Thanks to + varmojfekoj for providing the patch with this functionality +- code cleanup +- enhanced $IncludeConfig directive to support wildcard filenames +- changed some multithreading synchronization +--------------------------------------------------------------------------- +Version 1.20.1 (rgerhards), 2007-12-12 +- corrected a debug setting that survived release. Caused TCP connections + to be retried unnecessarily often. +- When a hostname ACL was provided and DNS resolution for that name failed, + ACL processing was stopped at that point. Thanks to mildew for the patch. + Fedora Bugzilla: http://bugzilla.redhat.com/show_bug.cgi?id=395911 +- fixed a potential race condition, see link for details: + http://rgerhards.blogspot.com/2007/12/rsyslog-race-condition.html + Note that the probability of problems from this bug was very remote +- fixed a memory leak that happend when PostgreSQL date formats were + used +--------------------------------------------------------------------------- +Version 1.20.0 (rgerhards), 2007-12-07 +- an output module for postgres databases has been added. Thanks to + sur5r for contributing this code +- unloading dynamic modules has been cleaned up, we now have a + real implementation and not just a dummy "good enough for the time + being". +- enhanced platform independence - thanks to Bartosz Kuzma and Michael + Biebl for their very useful contributions +- some general code cleanup (including warnings on 64 platforms, only) +--------------------------------------------------------------------------- +Version 1.19.12 (rgerhards), 2007-12-03 +- cleaned up the build system (thanks to Michael Biebl for the patch) +- fixed a bug where ommysql was still not compiled with -pthread option +--------------------------------------------------------------------------- +Version 1.19.11 (rgerhards), 2007-11-29 +- applied -pthread option to build when building for multi-threading mode + hopefully solves an issue with segfaulting +--------------------------------------------------------------------------- +Version 1.19.10 (rgerhards), 2007-10-19 +- introdcued the new ":modulename:" syntax for calling module actions + in selector lines; modified ommysql to support it. This is primarily + an aid for further modules and a prequisite to actually allow third + party modules to be created. +- minor fix in slackware startup script, "-r 0" is now "-r0" +- updated rsyslogd doc set man page; now in html format +- undid creation of a separate thread for the main loop -- this did not + turn out to be needed or useful, so reduce complexity once again. +- added doc fixes provided by Michael Biebl - thanks +--------------------------------------------------------------------------- +Version 1.19.9 (rgerhards), 2007-10-12 +- now packaging system which again contains all components in a single + tarball +- modularized main() a bit more, resulting in less complex code +- experimentally added an additional thread - will see if that affects + the segfault bug we experience on some platforms. Note that this change + is scheduled to be removed again later. +--------------------------------------------------------------------------- +Version 1.19.8 (rgerhards), 2007-09-27 +- improved repeated message processing +- applied patch provided by varmojfekoj to support building ommysql + in its own way (now also resides in a plugin subdirectory); + ommysql is now a separate package +- fixed a bug in cvthname() that lead to message loss if part + of the source hostname would have been dropped +- created some support for distributing ommysql together with the + main rsyslog package. I need to re-think it in the future, but + for the time being the current mode is best. I now simply include + one additional tarball for ommysql inside the main distribution. + I look forward to user feedback on how this should be done best. In the + long term, a separate project should be spawend for ommysql, but I'd + like to do that only after the plugin interface is fully stable (what + it is not yet). +--------------------------------------------------------------------------- +Version 1.19.7 (rgerhards), 2007-09-25 +- added code to handle situations where senders send us messages ending with + a NUL character. It is now simply removed. This also caused trailing LF + reduction to fail, when it was followed by such a NUL. This is now also + handled. +- replaced some non-thread-safe function calls by their thread-safe + counterparts +- fixed a minor memory leak that occured when the %APPNAME% property was + used (I think nobody used that in practice) +- fixed a bug that caused signal handlers in cvthname() not to be restored when + a malicious pointer record was detected and processing of the message been + stopped for that reason (this should be really rare and can not be related + to the segfault bug we are hunting). +- fixed a bug in cvthname that lead to passing a wrong parameter - in + practice, this had no impact. +- general code cleanup (e.g. compiler warnings, comments) +--------------------------------------------------------------------------- +Version 1.19.6 (rgerhards), 2007-09-11 +- applied patch by varmojfekoj to change signal handling to the new + sigaction API set (replacing the depreciated signal() calls and its + friends. +- fixed a bug that in --enable-debug mode caused an assertion when the + discard action was used +- cleaned up compiler warnings +- applied patch by varmojfekoj to FIX a bug that could cause + segfaults if empty properties were processed using modifying + options (e.g. space-cc, drop-cc) +- fixed man bug: rsyslogd supports -l option +--------------------------------------------------------------------------- +Version 1.19.5 (rgerhards), 2007-09-07 +- changed part of the CStr interface so that better error tracking + is provided and the calling sequence is more intuitive (there were + invalid calls based on a too-weired interface) +- (hopefully) fixed some remaining bugs rooted in wrong use of + the CStr class. These could lead to program abort. +- applied patch by varmojfekoj two fix two potential segfault situations +- added $ModDir config directive +- modified $ModLoad so that an absolute path may be specified as + module name (e.g. /rsyslog/ommysql.so) +--------------------------------------------------------------------------- +Version 1.19.4 (rgerhards/varmojfekoj), 2007-09-04 +- fixed a number of small memory leaks - thanks varmojfekoj for patching +- fixed an issue with CString class that could lead to rsyslog abort + in tplToString() - thanks varmojfekoj for patching +- added a man-version of the config file documenation - thanks to Michel + Samia for providing the man file +- fixed bug: a template like this causes an infinite loop: + $template opts,"%programname:::a,b%" + thanks varmojfekoj for the patch +- fixed bug: case changing options crash freeing the string pointer + because they modify it: $template opts2,"%programname::1:lowercase%" + thanks varmojfekoj for the patch +--------------------------------------------------------------------------- +Version 1.19.3 (mmeckelein/varmojfekoj), 2007-08-31 +- small mem leak fixed (after calling parseSelectorAct) - Thx varmojkekoj +- documentation section "Regular File" und "Blocks" updated +- solved an issue with dynamic file generation - Once again many thanks + to varmojfekoj +- the negative selector for program name filter (Blocks) does not work as + expected - Thanks varmojfekoj for patching +- added forwarding information to sysklogd (requires special template) + to config doc +--------------------------------------------------------------------------- +Version 1.19.2 (mmeckelein/varmojfekoj), 2007-08-28 +- a specifically formed message caused a segfault - Many thanks varmojfekoj + for providing a patch +- a typo and a weird condition are fixed in msg.c - Thanks again + varmojfekoj +- on file creation the file was always owned by root:root. This is fixed + now - Thanks ypsa for solving this issue +--------------------------------------------------------------------------- +Version 1.19.1 (mmeckelein), 2007-08-22 +- a bug that caused a high load when a TCP/UDP connection was closed is + fixed now - Thanks mildew for solving this issue +- fixed a bug which caused a segfault on reinit - Thx varmojfekoj for the + patch +- changed the hardcoded module path "/lib/rsyslog" to $(pkglibdir) in order + to avoid trouble e.g. on 64 bit platforms (/lib64) - many thanks Peter + Vrabec and darix, both provided a patch for solving this issue +- enhanced the unloading of modules - thanks again varmojfekoj +- applied a patch from varmojfekoj which fixes various little things in + MySQL output module +--------------------------------------------------------------------------- +Version 1.19.0 (varmojfekoj/rgerhards), 2007-08-16 +- integrated patch from varmojfekoj to make the mysql module a loadable one + many thanks for the patch, MUCH appreciated +--------------------------------------------------------------------------- +Version 1.18.2 (rgerhards), 2007-08-13 +- fixed a bug in outchannel code that caused templates to be incorrectly + parsed +- fixed a bug in ommysql that caused a wrong ";template" missing message +- added some code for unloading modules; not yet fully complete (and we do + not yet have loadable modules, so this is no problem) +- removed debian subdirectory by request of a debian packager (this is a special + subdir for debian and there is also no point in maintaining it when there + is a debian package available - so I gladly did this) in some cases +- improved overall doc quality (some pages were quite old) and linked to + more of the online resources. +- improved /contrib/delete_mysql script by adding a host option and some + other minor modifications +--------------------------------------------------------------------------- +Version 1.18.1 (rgerhards), 2007-08-08 +- applied a patch from varmojfekoj which solved a potential segfault + of rsyslogd on HUP +- applied patch from Michel Samia to fix compilation when the pthreads + feature is disabled +- some code cleanup (moved action object to its own file set) +- add config directive $MainMsgQueueSize, which now allows to configure the + queue size dynamically +- all compile-time settings are now shown in rsyslogd -v, not just the + active ones +- enhanced performance a little bit more +- added config file directive $ActionResumeInterval +- fixed a bug that prevented compilation under debian sid +- added a contrib directory for user-contributed useful things +--------------------------------------------------------------------------- +Version 1.18.0 (rgerhards), 2007-08-03 +- rsyslog now supports fallback actions when an action did not work. This + is a great feature e.g. for backup database servers or backup syslog + servers +- modified rklogd to only change the console log level if -c is specified +- added feature to use multiple actions inside a single selector +- implemented $ActionExecOnlyWhenPreviousIsSuspended config directive +- error messages during startup are now spit out to the configured log + destinations +--------------------------------------------------------------------------- +Version 1.17.6 (rgerhards), 2007-08-01 +- continued to work on output module modularization - basic stage of + this work is now FINISHED +- fixed bug in OMSRcreate() - always returned SR_RET_OK +- fixed a bug that caused ommysql to always complain about missing + templates +- fixed a mem leak in OMSRdestruct - freeing the object itself was + forgotten - thanks to varmojfekoj for the patch +- fixed a memory leak in syslogd/init() that happend when the config + file could not be read - thanks to varmojfekoj for the patch +- fixed insufficient memory allocation in addAction() and its helpers. + The initial fix and idea was developed by mildew, I fine-tuned + it a bit. Thanks a lot for the fix, I'd probably had pulled out my + hair to find the bug... +- added output of config file line number when a parsing error occured +- fixed bug in objomsr.c that caused program to abort in debug mode with + an invalid assertion (in some cases) +- fixed a typo that caused the default template for MySQL to be wrong. + thanks to mildew for catching this. +- added configuration file command $DebugPrintModuleList and + $DebugPrintCfSysLineHandlerList +- fixed an invalid value for the MARK timer - unfortunately, there was + a testing aid left in place. This resulted in quite frequent MARK messages +- added $IncludeConfig config directive +- applied a patch from mildew to prevent rsyslogd from freezing under heavy + load. This could happen when the queue was full. Now, we drop messages + but rsyslogd remains active. +--------------------------------------------------------------------------- +Version 1.17.5 (rgerhards), 2007-07-30 +- continued to work on output module modularization +- fixed a missing file bug - thanks to Andrea Montanari for reporting + this problem +- fixed a problem with shutting down the worker thread and freeing the + selector_t list - this caused messages to be lost, because the + message queue was not properly drained before the selectors got + destroyed. +--------------------------------------------------------------------------- +Version 1.17.4 (rgerhards), 2007-07-27 +- continued to work on output module modularization +- fixed a situation where rsyslogd could create zombie processes + thanks to mildew for the patch +- applied patch from Michel Samia to fix compilation when NOT + compiled for pthreads +--------------------------------------------------------------------------- +Version 1.17.3 (rgerhards), 2007-07-25 +- continued working on output module modularization +- fixed a bug that caused rsyslogd to segfault on exit (and + probably also on HUP), when there was an unsent message in a selector + that required forwarding and the dns lookup failed for that selector + (yes, it was pretty unlikely to happen;)) + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- fixed a memory leak in config file parsing and die() + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- rsyslogd now checks on startup if it is capable to performa any work + at all. If it cant, it complains and terminates + thanks to Michel Samia for providing the patch! +- fixed a small memory leak when HUPing syslogd. The allowed sender + list now gets freed. thanks to mildew for the patch. +- changed the way error messages in early startup are logged. They + now do no longer use the syslogd code directly but are rather + send to stderr. +--------------------------------------------------------------------------- +Version 1.17.2 (rgerhards), 2007-07-23 +- made the port part of the -r option optional. Needed for backward + compatibility with sysklogd +- replaced system() calls with something more reasonable. Please note that + this might break compatibility with some existing configuration files. + We accept this in favour of the gained security. +- removed a memory leak that could occur if timegenerated was used in + RFC 3164 format in templates +- did some preparation in msg.c for advanced multithreading - placed the + hooks, but not yet any active code +- worked further on modularization +- added $ModLoad MySQL (dummy) config directive +- added DropTrailingLFOnReception config directive +--------------------------------------------------------------------------- +Version 1.17.1 (rgerhards), 2007-07-20 +- fixed a bug that caused make install to install rsyslogd and rklogd under + the wrong names +- fixed bug that caused $AllowedSenders to handle IPv6 scopes incorrectly; + also fixed but that could grabble $AllowedSender wildcards. Thanks to + mildew@gmail.com for the patch +- minor code cleanup - thanks to Peter Vrabec for the patch +- fixed minimal memory leak on HUP (caused by templates) + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- fixed another memory leak on HUPing and on exiting rsyslogd + again thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- code cleanup (removed compiler warnings) +- fixed portability bug in configure.ac - thanks to Bartosz Kuźma for patch +- moved msg object into its own file set +- added the capability to continue trying to write log files when the + file system is full. Functionality based on patch by Martin Schulze + to sysklogd package. +--------------------------------------------------------------------------- +Version 1.17.0 (RGer), 2007-07-17 +- added $RepeatedLineReduction config parameter +- added $EscapeControlCharactersOnReceive config parameter +- added $ControlCharacterEscapePrefix config parameter +- added $DirCreateMode config parameter +- added $CreateDirs config parameter +- added $DebugPrintTemplateList config parameter +- added $ResetConfigVariables config parameter +- added $FileOwner config parameter +- added $FileGroup config parameter +- added $DirOwner config parameter +- added $DirGroup config parameter +- added $FailOnChownFailure config parameter +- added regular expression support to the filter engine + thanks to Michel Samia for providing the patch! +- enhanced $AllowedSender functionality. Credits to mildew@gmail.com for + the patch doing that + - added IPv6 support + - allowed DNS hostnames + - allowed DNS wildcard names +- added new option $DropMsgsWithMaliciousDnsPTRRecords +- added autoconf so that rfc3195d, rsyslogd and klogd are stored to /sbin +- added capability to auto-create directories with dynaFiles +--------------------------------------------------------------------------- +Version 1.16.0 (RGer/Peter Vrabec), 2007-07-13 - The Friday, 13th Release ;) +- build system switched to autotools +- removed SYSV preprocessor macro use, replaced with autotools equivalents +- fixed a bug that caused rsyslogd to segfault when TCP listening was + disabled and it terminated +- added new properties "syslogfacility-text" and "syslogseverity-text" + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- added the -x option to disable hostname dns reslution + thanks to varmojfekoj <varmojfekoj@gmail.com> for the patch +- begun to better modularize syslogd.c - this is an ongoing project; moved + type definitions to a separate file +- removed some now-unused fields from struct filed +- move file size limit fields in struct field to the "right spot" (the file + writing part of the union - f_un.f_file) +- subdirectories linux and solaris are no longer part of the distribution + package. This is not because we cease support for them, but there are no + longer any files in them after the move to autotools +--------------------------------------------------------------------------- +Version 1.15.1 (RGer), 2007-07-10 +- fixed a bug that caused a dynaFile selector to stall when there was + an open error with one file +- improved template processing for dynaFiles; templates are now only + looked up during initialization - speeds up processing +- optimized memory layout in struct filed when compiled with MySQL + support +- fixed a bug that caused compilation without SYSLOG_INET to fail +- re-enabled the "last message repeated n times" feature. This + feature was not taken care of while rsyslogd evolved from sysklogd + and it was more or less defunct. Now it is fully functional again. +- added system properties: $NOW, $YEAR, $MONTH, $DAY, $HOUR, $MINUTE +- fixed a bug in iovAsString() that caused a memory leak under stress + conditions (most probably memory shortage). This was unlikely to + ever happen, but it doesn't hurt doing it right +- cosmetic: defined type "uchar", change all unsigned chars to uchar +--------------------------------------------------------------------------- +Version 1.15.0 (RGer), 2007-07-05 +- added ability to dynamically generate file names based on templates + and thus properties. This was a much-requested feature. It makes + life easy when it e.g. comes to splitting files based on the sender + address. +- added $umask and $FileCreateMode config file directives +- applied a patch from Bartosz Kuzma to compile cleanly under NetBSD +- checks for extra (unexpected) characters in system config file lines + have been added +- added IPv6 documentation - was accidently missing from CVS +- begun to change char to unsigned char +--------------------------------------------------------------------------- +Version 1.14.2 (RGer), 2007-07-03 +** this release fixes all known nits with IPv6 ** +- restored capability to do /etc/service lookup for "syslog" + service when -r 0 was given +- documented IPv6 handling of syslog messages +- integrate patch from Bartosz Kuźma to make rsyslog compile under + Solaris again (the patch replaced a strndup() call, which is not + available under Solaris +- improved debug logging when waiting on select +- updated rsyslogd man page with new options (-46A) +--------------------------------------------------------------------------- +Version 1.14.1 (RGer/Peter Vrabec), 2007-06-29 +- added Peter Vrabec's patch for IPv6 TCP +- prefixed all messages send to stderr in rsyslogd with "rsyslogd: " +--------------------------------------------------------------------------- +Version 1.14.0 (RGer/Peter Vrabec), 2007-06-28 +- Peter Vrabec provided IPv6 for rsyslog, so we are now IPv6 enabled + IPv6 Support is currently for UDP only, TCP is to come soon. + AllowedSender configuration does not yet work for IPv6. +- fixed code in iovCreate() that broke C's strict aliasing rules +- fixed some char/unsigned char differences that forced the compiler + to spit out warning messages +- updated the Red Hat init script to fix a known issue (thanks to + Peter Vrabec) +--------------------------------------------------------------------------- +Version 1.13.5 (RGer), 2007-06-22 +- made the TCP session limit configurable via command line switch + now -t <port>,<max sessions> +- added man page for rklogd(8) (basically a copy from klogd, but now + there is one...) +- fixed a bug that caused internal messages (e.g. rsyslogd startup) to + appear without a tag. +- removed a minor memory leak that occurred when TAG processing requalified + a HOSTNAME to be a TAG (and a TAG already was set). +- removed potential small memory leaks in MsgSet***() functions. There + would be a leak if a property was re-set, something that happened + extremely seldom. +--------------------------------------------------------------------------- +Version 1.13.4 (RGer), 2007-06-18 +- added a new property "PRI-text", which holds the PRI field in + textual form (e.g. "syslog.info") +- added alias "syslogseverity" for "syslogpriority", which is a + misleading property name that needs to stay for historical + reasons (and backward-compatility) +- added doc on how to record PRI value in log file +- enhanced signal handling in klogd, including removal of an unsafe + call to the logging system during signal handling +--------------------------------------------------------------------------- +Version 1.13.3 (RGer), 2007-06-15 +- create a version of syslog.c from scratch. This is now + - highly optimized for rsyslog + - removes an incompatible license problem as the original + version had a BSD license with advertising clause + - fixed in the regard that rklogd will continue to work when + rsysogd has been restarted (the original version, as well + as sysklogd, will remain silent then) + - solved an issue with an extra NUL char at message end that the + original version had +- applied some changes to klogd to care for the new interface +- fixed a bug in syslogd.c which prevented compiling under debian +--------------------------------------------------------------------------- +Version 1.13.2 (RGer), 2007-06-13 +- lib order in makefile patched to facilitate static linking - thanks + to Bennett Todd for providing the patch +- Integrated a patch from Peter Vrabec (pvrabec@redheat.com): + - added klogd under the name of rklogd (remove dependency on + original sysklogd package + - createDB.sql now in UTF + - added additional config files for use on Red Hat +--------------------------------------------------------------------------- +Version 1.13.1 (RGer), 2007-02-05 +- changed the listen backlog limit to a more reasonable value based on + the maximum number of TCP connections configurd (10% + 5) - thanks to Guy + Standen for the hint (actually, the limit was 5 and that was a + left-over from early testing). +- fixed a bug in makefile which caused DB-support to be disabled when + NETZIP support was enabled +- added the -e option to allow transmission of every message to remote + hosts (effectively turns off duplicate message suppression) +- (somewhat) improved memory consumption when compiled with MySQL support +- looks like we fixed an incompatibility with MySQL 5.x and above software + At least in one case, the remote server name was destroyed, leading to + a connection failure. The new, improved code does not have this issue and + so we see this as solved (the new code is generally somewhat better, so + there is a good chance we fixed this incompatibility). +--------------------------------------------------------------------------- +Version 1.13.0 (RGer), 2006-12-19 +- added '$' as ToPos proptery replacer specifier - means "up to the + end of the string" +- property replacer option "escape-cc", "drop-cc" and "space-cc" added +- changed the handling of \0 characters inside syslog messages. We now + consistently escape them to "#000". This is somewhat recommended in + the draft-ietf-syslog-protocol-19 draft. While the real recomendation + is to not escape any characters at all, we can not do this without + considerable modification of the code. So we escape it to "#000", which + is consistent with a sample found in the Internet-draft. +- removed message glue logic (see printchopped() comment for details) + Also caused removal of parts table and thus some improvements in + memory usage. +- changed the default MAXLINE to 2048 to take care of recent syslog + standardization efforts (can easily be changed in syslogd.c) +- added support for byte-counted TCP syslog messages (much like + syslog-transport-tls-05 Internet Draft). This was necessary to + support compression over TCP. +- added support for receiving compressed syslog messages +- added support for sending compressed syslog messages +- fixed a bug where the last message in a syslog/tcp stream was + lost if it was not properly terminated by a LF character +--------------------------------------------------------------------------- +Version 1.12.3 (RGer), 2006-10-04 +- implemented some changes to support Solaris (but support is not + yet complete) +- commented out (via #if 0) some methods that are currently not being use + but should be kept for further us +- added (interim) -u 1 option to turn off hostname and tag parsing +- done some modifications to better support Fedora +- made the field delimiter inside property replace configurable via + template +- fixed a bug in property replacer: if fields were used, the delimitor + became part of the field. Up until now, this was barely noticable as + the delimiter as TAB only and thus invisible to a human. With other + delimiters available now, it quickly showed up. This bug fix might cause + some grief to existing installations if they used the extra TAB for + whatever reasons - sorry folks... Anyhow, a solution is easy: just add + a TAB character contstant into your template. Thus, there has no attempt + been made to do this in a backwards-compatible way. +--------------------------------------------------------------------------- +Version 1.12.2 (RGer), 2006-02-15 +- fixed a bug in the RFC 3339 date formatter. An extra space was added + after the actual timestamp +- added support for providing high-precision RFC3339 timestamps for + (rsyslogd-)internally-generated messages +- very (!) experimental support for syslog-protocol internet draft + added (the draft is experimental, the code is solid ;)) +- added support for field-extracting in the property replacer +- enhanced the legacy-syslog parser so that it can interpret messages + that do not contain a TIMESTAMP +- fixed a bug that caused the default socket (usually /dev/log) to be + opened even when -o command line option was given +- fixed a bug in the Debian sample startup script - it caused rsyslogd + to listen to remote requests, which it shouldn't by default +--------------------------------------------------------------------------- +Version 1.12.1 (RGer), 2005-11-23 +- made multithreading work with BSD. Some signal-handling needed to be + restructured. Also, there might be a slight delay of up to 10 seconds + when huping and terminating rsyslogd under BSD +- fixed a bug where a NULL-pointer was passed to printf() in logmsg(). +- fixed a bug during "make install" where rc3195d was not installed + Thanks to Bennett Todd for spotting this. +- fixed a bug where rsyslogd dumped core when no TAG was found in the + received message +- enhanced message parser so that it can deal with missing hostnames + in many cases (may not be totally fail-safe) +- fixed a bug where internally-generated messages did not have the correct + TAG +--------------------------------------------------------------------------- +Version 1.12.0 (RGer), 2005-10-26 +- moved to a multi-threaded design. single-threading is still optionally + available. Multi-threading is experimental! +- fixed a potential race condition. In the original code, marking was done + by an alarm handler, which could lead to all sorts of bad things. This + has been changed now. See comments in syslogd.c/domark() for details. +- improved debug output for property-based filters +- not a code change, but: I have checked all exit()s to make sure that + none occurs once rsyslogd has started up. Even in unusual conditions + (like low-memory conditions) rsyslogd somehow remains active. Of course, + it might loose a message or two, but at least it does not abort and it + can also recover when the condition no longer persists. +- fixed a bug that could cause loss of the last message received + immediately before rsyslogd was terminated. +- added comments on thread-safety of global variables in syslogd.c +- fixed a small bug: spurios printf() when TCP syslog was used +- fixed a bug that causes rsyslogd to dump core on termination when one + of the selector lines did not receive a message during the run (very + unlikely) +- fixed an one-too-low memory allocation in the TCP sender. Could result + in rsyslogd dumping core. +- fixed a bug with regular expression support (thanks to Andres Riancho) +- a little bit of code restructuring (especially main(), which was + horribly large) +--------------------------------------------------------------------------- +Version 1.11.1 (RGer), 2005-10-19 +- support for BSD-style program name and host blocks +- added a new property "programname" that can be used in templates +- added ability to specify listen port for rfc3195d +- fixed a bug that rendered the "startswith" comparison operation + unusable. +- changed more functions to "static" storage class to help compiler + optimize (should have been static in the first place...) +- fixed a potential memory leak in the string buffer class destructor. + As the destructur was previously never called, the leak did not actually + appear. +- some internal restructuring in anticipation/preparation of minimal + multi-threading support +- rsyslogd still shares some code with the sysklogd project. Some patches + for this shared code have been brought over from the sysklogd CVS. +--------------------------------------------------------------------------- +Version 1.11.0 (RGer), 2005-10-12 +- support for receiving messages via RFC 3195; added rfc3195d for that + purpose +- added an additional guard to prevent rsyslogd from aborting when the + 2gb file size limit is hit. While a user can configure rsyslogd to + handle such situations, it would abort if that was not done AND large + file support was not enabled (ok, this is hopefully an unlikely scenario) +- fixed a bug that caused additional Unix domain sockets to be incorrectly + processed - could lead to message loss in extreme cases +--------------------------------------------------------------------------- +Version 1.10.2 (RGer), 2005-09-27 +- added comparison operations in property-based filters: + * isequal + * startswith +- added ability to negate all property-based filter comparison operations + by adding a !-sign right in front of the operation name +- added the ability to specify remote senders for UDP and TCP + received messages. Allows to block all but well-known hosts +- changed the $-config line directives to be case-INsensitive +- new command line option -w added: "do not display warnings if messages + from disallowed senders are received" +- fixed a bug that caused rsyslogd to dump core when the compare value + was not quoted in property-based filters +- fixed a bug in the new CStr compare function which lead to invalid + results (fortunately, this function was not yet used widely) +- added better support for "debugging" rsyslog.conf property filters + (only if -d switch is given) +- changed some function definitions to static, which eventually enables + some compiler optimizations +- fixed a bug in MySQL code; when a SQL error occured, rsyslogd could + run in a tight loop. This was due to invalid sequence of error reporting + and is now fixed. +--------------------------------------------------------------------------- +Version 1.10.1 (RGer), 2005-09-23 +- added the ability to execute a shell script as an action. + Thanks to Bjoern Kalkbrenner for providing the code! +- fixed a bug in the MySQL code; due to the bug the automatic one-time + retry after an error did not happen - this lead to error message in + cases where none should be seen (e.g. after a MySQL restart) +- fixed a security issue with SQL-escaping in conjunction with + non-(SQL-)standard MySQL features. +--------------------------------------------------------------------------- +Version 1.10.0 (RGer), 2005-09-20 + REMINDER: 1.10 is the first unstable version if the 1.x series! +- added the capability to filter on any property in selector lines + (not just facility and priority) +- changed stringbuf into a new counted string class +- added support for a "discard" action. If a selector line with + discard (~ character) is found, no selector lines *after* that + line will be processed. +- thanks to Andres Riancho, regular expression support has been + added to the template engine +- added the FROMHOST property in the template processor, which could + previously not be obtained. Thanks to Cristian Testa for pointing + this out and even providing a fix. +- added display of compile-time options to -v output +- performance improvement for production build - made some checks + to happen only during debug mode +- fixed a problem with compiling on SUSE and - while doing so - removed + the socket call to set SO_BSDCOMPAT in cases where it is obsolete. +--------------------------------------------------------------------------- +Version 1.0.4 (RGer), 2006-02-01 +- a small but important fix: the tcp receiver had two forgotten printf's + in it that caused a lot of unnecessary output to stdout. This was + important enough to justify a new release +--------------------------------------------------------------------------- +Version 1.0.3 (RGer), 2005-11-14 +- added an additional guard to prevent rsyslogd from aborting when the + 2gb file size limit is hit. While a user can configure rsyslogd to + handle such situations, it would abort if that was not done AND large + file support was not enabled (ok, this is hopefully an unlikely scenario) +- fixed a bug that caused additional Unix domain sockets to be incorrectly + processed - could lead to message loss in extreme cases +- applied some patches available from the sysklogd project to code + shared from there +- fixed a bug that causes rsyslogd to dump core on termination when one + of the selector lines did not receive a message during the run (very + unlikely) +- fixed an one-too-low memory allocation in the TCP sender. Could result + in rsyslogd dumping core. +- fixed a bug in the TCP sender that caused the retry logic to fail + after an error or receiver overrun +- fixed a bug in init() that could lead to dumping core +- fixed a bug that could lead to dumping core when no HOSTNAME or no TAG + was present in the syslog message +--------------------------------------------------------------------------- +Version 1.0.2 (RGer), 2005-10-05 +- fixed an issue with MySQL error reporting. When an error occured, + the MySQL driver went into an endless loop (at least in most cases). +--------------------------------------------------------------------------- +Version 1.0.1 (RGer), 2005-09-23 +- fixed a security issue with SQL-escaping in conjunction with + non-(SQL-)standard MySQL features. +--------------------------------------------------------------------------- +Version 1.0.0 (RGer), 2005-09-12 +- changed install doc to cover daily cron scripts - a trouble source +- added rc script for slackware (provided by Chris Elvidge - thanks!) +- fixed a really minor bug in usage() - the -r option was still + reported as without the port parameter +--------------------------------------------------------------------------- +Version 0.9.8 (RGer), 2005-09-05 +- made startup and shutdown message more consistent and included the + pid, so that they can be easier correlated. Used syslog-protocol + structured data format for this purpose. +- improved config info in startup message, now tells not only + if it is listening remote on udp, but also for tcp. Also includes + the port numbers. The previous startup message was misleading, because + it did not say "remote reception" if rsyslogd was only listening via + tcp (but not via udp). +- added a "how can you help" document to the doc set +--------------------------------------------------------------------------- +Version 0.9.7 (RGer), 2005-08-15 +- some of the previous doc files (like INSTALL) did not properly + reflect the changes to the build process and the new doc. Fixed + that. +- changed syslogd.c so that when compiled without database support, + an error message is displayed when a database action is detected + in the config file (previously this was used as an user rule ;)) +- fixed a bug in the os-specific Makefiles which caused MySQL + support to not be compiled, even if selected +--------------------------------------------------------------------------- +Version 0.9.6 (RGer), 2005-08-09 +- greatly enhanced documentation. Now available in html format in + the "doc" folder and FreeBSD. Finally includes an install howto. +- improved MySQL error messages a little - they now show up as log + messages, too (formerly only in debug mode) +- added the ability to specify the listen port for udp syslog. + WARNING: This introduces an incompatibility. Formerly, udp + syslog was enabled by the -r command line option. Now, it is + "-r [port]", which is consistent with the tcp listener. However, + just -r will now return an error message. +- added sample startup scripts for Debian and FreeBSD +- added support for easy feature selection in the makefile. Un- + fortunately, this also means I needed to spilt the make file + for different OS and distros. There are some really bad syntax + differences between FreeBSD and Linux make. +--------------------------------------------------------------------------- +Version 0.9.5 (RGer), 2005-08-01 +- the "semicolon bug" was actually not (fully) solved in 0.9.4. One + part of the bug was solved, but another still existed. This one + is fixed now, too. +- the "semicolon bug" actually turned out to be a more generic bug. + It appeared whenever an invalid template name was given. With some + selector actions, rsyslogd dumped core, with other it "just" had + a small ressource leak with others all worked well. These anomalies + are now fixed. Note that they only appeared during system initaliziation + once the system was running, nothing bad happened. +- improved error reporting for template errors on startup. They are now + shown on the console and the start-up tty. Formerly, they were only + visible in debug mode. +- support for multiple instances of rsyslogd on a single machine added +- added new option "-o" --> omit local unix domain socket. This option + enables rsyslogd NOT to listen to the local socket. This is most + helpful when multiple instances of rsyslogd (or rsyslogd and another + syslogd) shall run on a single system. +- added new option "-i <pidfile>" which allows to specify the pidfile. + This is needed when multiple instances of rsyslogd are to be run. +- the new project home page is now online at www.rsyslog.com +--------------------------------------------------------------------------- +Version 0.9.4 (RGer), 2005-07-25 +- finally added the TCP sender. It now supports non-blocking mode, no + longer disabling message reception during connect. As it is now, it + is usable in production. The code could be more sophisticated, but + I've kept it short in anticipation of the move to liblogging, which + will lead to the removal of the code just written ;) +- the "exiting on signal..." message still had the "syslogd" name in + it. Changed this to "rsyslogd", as we do not have a large user base + yet, this should pose no problem. +- fixed "the semiconlon" bug. rsyslogd dumped core if a write-db action + was specified but no semicolon was given after the password (an empty + template was ok, but the semicolon needed to be present). +- changed a default for traditional output format. During testing, it + was seen that the timestamp written to file in default format was + the time of message reception, not the time specified in the TIMESTAMP + field of the message itself. Traditionally, the message TIMESTAMP is + used and this has been changed now. +--------------------------------------------------------------------------- +Version 0.9.3 (RGer), 2005-07-19 +- fixed a bug in the message parser. In June, the RFC 3164 timestamp + was not correctly parsed (yes, only in June and some other months, + see the code comment to learn why...) +- added the ability to specify the destination port when forwarding + syslog messages (both for TCP and UDP) +- added an very experimental TCP sender (activated by + @@machine:port in config). This is not yet for production use. If + the receiver is not alive, rsyslogd will wait quite some time until + the connection request times out, which most probably leads to + loss of incoming messages. + +--------------------------------------------------------------------------- +Version 0.9.2 (RGer), around 2005-07-06 +- I intended to change the maxsupported message size to 32k to + support IHE - but given the memory inefficiency in the usual use + cases, I have not done this. I have, however, included very + specific instructions on how to do this in the source code. I have + also done some testing with 32k messages, so you can change the + max size without taking too much risk. +- added a syslog/tcp receiver; we now can receive messages via + plain tcp, but we can still send only via UDP. The syslog/tcp + receiver is the primary enhancement of this release. +- slightly changed some error messages that contained a spurios \n at + the end of the line (which gives empty lines in your log...) + +--------------------------------------------------------------------------- +Version 0.9.1 (RGer) +- fixed code so that it compiles without errors under FreeBSD +- removed now unused function "allocate_log()" from syslogd.c +- changed the make file so that it contains more defines for + different environments (in the long term, we need a better + system for disabling/enabling features...) +- changed some printf's printing off_t types to %lld and + explicit (long long) casts. I tried to figure out the exact type, + but did not succeed in this. In the worst case, ultra-large peta- + byte files will now display funny informational messages on rollover, + something I think we can live with for the next 10 years or so... + +--------------------------------------------------------------------------- +Version 0.9.0 (RGer) +- changed the filed structure to be a linked list. Previously, it + was a table - well, for non-SYSV it was defined as linked list, + but from what I see that code did no longer work after my + modifications. I am now using a linked list in general because + that is needed for other upcoming modifications. +- fixed a bug that caused rsyslogd not to listen to anything if + the configuration file could not be read +- pervious versions disabled network logging (send/receive) if + syslog/udp port was not in /etc/services. Now defaulting to + port 514 in this case. +- internal error messages are now supported up to 256 bytes +- error message seen during config file read are now also displayed + to the attached tty and not only the console +- changed some error messages during init to be sent to the console + and/or emergency log. Previously, they were only seen if the + -d (debug) option was present on the command line. +- fixed the "2gb file issue on 32bit systems". If a file grew to + more than 2gb, the syslogd was aborted with "file size exceeded". + Now, defines have been added according to + http://www.daimi.au.dk/~kasperd/comp.os.linux.development.faq.html#LARGEFILE + Testing revealed that they work ;) + HOWEVER, if your file system, glibc, kernel, whatever does not + support files larger 2gb, you need to set a file size limit with + the new output channel mechanism. +- updated man pages to reflect the changes + +--------------------------------------------------------------------------- +Version 0.8.4 + +- improved -d debug output (removed developer-only content) +- now compiles under FreeBSD and NetBSD (only quick testing done on NetBSD) +--------------------------------------------------------------------------- +Version 0.8.3 + +- security model in "make install" changed +- minor doc updates +--------------------------------------------------------------------------- +Version 0.8.2 + +- added man page for rsyslog.conf and rsyslogd +- gave up on the concept of rsyslog being a "drop in" replacement + for syslogd. Now, the user installs rsyslogd and also needs to + adjust his system settings to this specifically. This also lead + to these changes: + * changed Makefile so that install now installs rsyslogd instead + of dealing with syslogd + * changed the default config file name to rsyslog.conf +--------------------------------------------------------------------------- +Version 0.8.1 + +- fixed a nasty memory leak (probably not the last one with this release) +- some enhancements to Makefile as suggested by Bennett Todd +- syslogd-internal messages (like restart) were missing the hostname + this has been corrected +--------------------------------------------------------------------------- +Version 0.8.0 + +Initial testing release. Based on the sysklogd package. Thanks to the +sysklogd maintainers for all their good work! +--------------------------------------------------------------------------- + +---------------------------------------------------------------------- +The following comments were left in the syslogd source. While they provide +not too much detail, the help to date when Rainer started work on the +project (which was 2003, now even surprising for Rainer himself ;)). + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2003-10-17 + * Some initial modifications on the sysklogd package to support + * liblogging. These have actually not yet been merged to the + * source you see currently (but they hopefully will) + * + * \date 2004-10-28 + * Restarted the modifications of sysklogd. This time, we + * focus on a simpler approach first. The initial goal is to + * provide MySQL database support (so that syslogd can log + * to the database). +---------------------------------------------------------------------- +The following comments are from the stock syslogd.c source. They provide +some insight into what happened to the source before we forked +rsyslogd. However, much of the code already has been replaced and more +is to be replaced. So over time, these comments become less valuable. +I have moved them out of the syslogd.c file to shrink it, especially +as a lot of them do no longer apply. For historical reasons and +understanding of how the daemon evolved, they are probably still +helpful. + * Author: Eric Allman + * extensive changes by Ralph Campbell + * more extensive changes by Eric Allman (again) + * + * Steve Lord: Fix UNIX domain socket code, added linux kernel logging + * change defines to + * SYSLOG_INET - listen on a UDP socket + * SYSLOG_UNIXAF - listen on unix domain socket + * SYSLOG_KERNEL - listen to linux kernel + * + * Mon Feb 22 09:55:42 CST 1993: Dr. Wettstein + * Additional modifications to the source. Changed priority scheme + * to increase the level of configurability. In its stock configuration + * syslogd no longer logs all messages of a certain priority and above + * to a log file. The * wildcard is supported to specify all priorities. + * Note that this is a departure from the BSD standard. + * + * Syslogd will now listen to both the inetd and the unixd socket. The + * strategy is to allow all local programs to direct their output to + * syslogd through the unixd socket while the program listens to the + * inetd socket to get messages forwarded from other hosts. + * + * Fri Mar 12 16:55:33 CST 1993: Dr. Wettstein + * Thanks to Stephen Tweedie (dcs.ed.ac.uk!sct) for helpful bug-fixes + * and an enlightened commentary on the prioritization problem. + * + * Changed the priority scheme so that the default behavior mimics the + * standard BSD. In this scenario all messages of a specified priority + * and above are logged. + * + * Add the ability to specify a wildcard (=) as the first character + * of the priority name. Doing this specifies that ONLY messages with + * this level of priority are to be logged. For example: + * + * *.=debug /usr/adm/debug + * + * Would log only messages with a priority of debug to the /usr/adm/debug + * file. + * + * Providing an * as the priority specifies that all messages are to be + * logged. Note that this case is degenerate with specifying a priority + * level of debug. The wildcard * was retained because I believe that + * this is more intuitive. + * + * Thu Jun 24 11:34:13 CDT 1993: Dr. Wettstein + * Modified sources to incorporate changes in libc4.4. Messages from + * syslog are now null-terminated, syslogd code now parses messages + * based on this termination scheme. Linux as of libc4.4 supports the + * fsync system call. Modified code to fsync after all writes to + * log files. + * + * Sat Dec 11 11:59:43 CST 1993: Dr. Wettstein + * Extensive changes to the source code to allow compilation with no + * complaints with -Wall. + * + * Reorganized the facility and priority name arrays so that they + * compatible with the syslog.h source found in /usr/include/syslog.h. + * NOTE that this should really be changed. The reason I do not + * allow the use of the values defined in syslog.h is on account of + * the extensions made to allow the wildcard character in the + * priority field. To fix this properly one should malloc an array, + * copy the contents of the array defined by syslog.h and then + * make whatever modifications that are desired. Next round. + * + * Thu Jan 6 12:07:36 CST 1994: Dr. Wettstein + * Added support for proper decomposition and re-assembly of + * fragment messages on UNIX domain sockets. Lack of this capability + * was causing 'partial' messages to be output. Since facility and + * priority information is encoded as a leader on the messages this + * was causing lines to be placed in erroneous files. + * + * Also added a patch from Shane Alderton (shane@ion.apana.org.au) to + * correct a problem with syslogd dumping core when an attempt was made + * to write log messages to a logged-on user. Thank you. + * + * Many thanks to Juha Virtanen (jiivee@hut.fi) for a series of + * interchanges which lead to the fixing of problems with messages set + * to priorities of none and emerg. Also thanks to Juha for a patch + * to exclude users with a class of LOGIN from receiving messages. + * + * Shane Alderton provided an additional patch to fix zombies which + * were conceived when messages were written to multiple users. + * + * Mon Feb 6 09:57:10 CST 1995: Dr. Wettstein + * Patch to properly reset the single priority message flag. Thanks + * to Christopher Gori for spotting this bug and forwarding a patch. + * + * Wed Feb 22 15:38:31 CST 1995: Dr. Wettstein + * Added version information to startup messages. + * + * Added defines so that paths to important files are taken from + * the definitions in paths.h. Hopefully this will insure that + * everything follows the FSSTND standards. Thanks to Chris Metcalf + * for a set of patches to provide this functionality. Also thanks + * Elias Levy for prompting me to get these into the sources. + * + * Wed Jul 26 18:57:23 MET DST 1995: Martin Schulze + * Linux' gethostname only returns the hostname and not the fqdn as + * expected in the code. But if you call hostname with an fqdn then + * gethostname will return an fqdn, so we have to mention that. This + * has been changed. + * + * The 'LocalDomain' and the hostname of a remote machine is + * converted to lower case, because the original caused some + * inconsistency, because the (at least my) nameserver did respond an + * fqdn containing of upper- _and_ lowercase letters while + * 'LocalDomain' consisted only of lowercase letters and that didn't + * match. + * + * Sat Aug 5 18:59:15 MET DST 1995: Martin Schulze + * Now no messages that were received from any remote host are sent + * out to another. At my domain this missing feature caused ugly + * syslog-loops, sometimes. + * + * Remember that no message is sent out. I can't figure out any + * scenario where it might be useful to change this behavior and to + * send out messages to other hosts than the one from which we + * received the message, but I might be shortsighted. :-/ + * + * Thu Aug 10 19:01:08 MET DST 1995: Martin Schulze + * Added my pidfile.[ch] to it to perform a better handling with + * pidfiles. Now both, syslogd and klogd, can only be started + * once. They check the pidfile. + * + * Sun Aug 13 19:01:41 MET DST 1995: Martin Schulze + * Add an addition to syslog.conf's interpretation. If a priority + * begins with an exclamation mark ('!') the normal interpretation + * of the priority is inverted: ".!*" is the same as ".none", ".!=info" + * don't logs the info priority, ".!crit" won't log any message with + * the priority crit or higher. For example: + * + * mail.*;mail.!=info /usr/adm/mail + * + * Would log all messages of the facility mail except those with + * the priority info to /usr/adm/mail. This makes the syslogd + * much more flexible. + * + * Defined TABLE_ALLPRI=255 and changed some occurrences. + * + * Sat Aug 19 21:40:13 MET DST 1995: Martin Schulze + * Making the table of facilities and priorities while in debug + * mode more readable. + * + * If debugging is turned on, printing the whole table of + * facilities and priorities every hexadecimal or 'X' entry is + * now 2 characters wide. + * + * The number of the entry is prepended to each line of + * facilities and priorities, and F_UNUSED lines are not shown + * anymore. + * + * Corrected some #ifdef SYSV's. + * + * Mon Aug 21 22:10:35 MET DST 1995: Martin Schulze + * Corrected a strange behavior during parsing of configuration + * file. The original BSD syslogd doesn't understand spaces as + * separators between specifier and action. This syslogd now + * understands them. The old behavior caused some confusion over + * the Linux community. + * + * Thu Oct 19 00:02:07 MET 1995: Martin Schulze + * The default behavior has changed for security reasons. The + * syslogd will not receive any remote message unless you turn + * reception on with the "-r" option. + * + * Not defining SYSLOG_INET will result in not doing any network + * activity, i.e. not sending or receiving messages. I changed + * this because the old idea is implemented with the "-r" option + * and the old thing didn't work anyway. + * + * Thu Oct 26 13:14:06 MET 1995: Martin Schulze + * Added another logfile type F_FORW_UNKN. The problem I ran into + * was a name server that runs on my machine and a forwarder of + * kern.crit to another host. The hosts address can only be + * fetched using the nameserver. But named is started after + * syslogd, so syslogd complained. + * + * This logfile type will retry to get the address of the + * hostname ten times and then complain. This should be enough to + * get the named up and running during boot sequence. + * + * Fri Oct 27 14:08:15 1995: Dr. Wettstein + * Changed static array of logfiles to a dynamic array. This + * can grow during process. + * + * Fri Nov 10 23:08:18 1995: Martin Schulze + * Inserted a new tabular sys_h_errlist that contains plain text + * for error codes that are returned from the net subsystem and + * stored in h_errno. I have also changed some wrong lookups to + * sys_errlist. + * + * Wed Nov 22 22:32:55 1995: Martin Schulze + * Added the fabulous strip-domain feature that allows us to + * strip off (several) domain names from the fqdn and only log + * the simple hostname. This is useful if you're in a LAN that + * has a central log server and also different domains. + * + * I have also also added the -l switch do define hosts as + * local. These will get logged with their simple hostname, too. + * + * Thu Nov 23 19:02:56 MET DST 1995: Martin Schulze + * Added the possibility to omit fsyncing of logfiles after every + * write. This will give some performance back if you have + * programs that log in a very verbose manner (like innd or + * smartlist). Thanks to Stephen R. van den Berg <srb@cuci.nl> + * for the idea. + * + * Thu Jan 18 11:14:36 CST 1996: Dr. Wettstein + * Added patche from beta-testers to stop compile error. Also + * added removal of pid file as part of termination cleanup. + * + * Wed Feb 14 12:42:09 CST 1996: Dr. Wettstein + * Allowed forwarding of messages received from remote hosts to + * be controlled by a command-line switch. Specifying -h allows + * forwarding. The default behavior is to disable forwarding of + * messages which were received from a remote host. + * + * Parent process of syslogd does not exit until child process has + * finished initialization process. This allows rc.* startup to + * pause until syslogd facility is up and operating. + * + * Re-arranged the select code to move UNIX domain socket accepts + * to be processed later. This was a contributed change which + * has been proposed to correct the delays sometimes encountered + * when syslogd starts up. + * + * Minor code cleanups. + * + * Thu May 2 15:15:33 CDT 1996: Dr. Wettstein + * Fixed bug in init function which resulted in file descripters + * being orphaned when syslogd process was re-initialized with SIGHUP + * signal. Thanks to Edvard Tuinder + * (Edvard.Tuinder@praseodymium.cistron.nl) for putting me on the + * trail of this bug. I am amazed that we didn't catch this one + * before now. + * + * Tue May 14 00:03:35 MET DST 1996: Martin Schulze + * Corrected a mistake that causes the syslogd to stop logging at + * some virtual consoles under Linux. This was caused by checking + * the wrong error code. Thanks to Michael Nonweiler + * <mrn20@hermes.cam.ac.uk> for sending me a patch. + * + * Mon May 20 13:29:32 MET DST 1996: Miquel van Smoorenburg <miquels@cistron.nl> + * Added continuation line supported and fixed a bug in + * the init() code. + * + * Tue May 28 00:58:45 MET DST 1996: Martin Schulze + * Corrected behaviour of blocking pipes - i.e. the whole system + * hung. Michael Nonweiler <mrn20@hermes.cam.ac.uk> has sent us + * a patch to correct this. A new logfile type F_PIPE has been + * introduced. + * + * Mon Feb 3 10:12:15 MET DST 1997: Martin Schulze + * Corrected behaviour of logfiles if the file can't be opened. + * There was a bug that causes syslogd to try to log into non + * existing files which ate cpu power. + * + * Sun Feb 9 03:22:12 MET DST 1997: Martin Schulze + * Modified syslogd.c to not kill itself which confuses bash 2.0. + * + * Mon Feb 10 00:09:11 MET DST 1997: Martin Schulze + * Improved debug code to decode the numeric facility/priority + * pair into textual information. + * + * Tue Jun 10 12:35:10 MET DST 1997: Martin Schulze + * Corrected freeing of logfiles. Thanks to Jos Vos <jos@xos.nl> + * for reporting the bug and sending an idea to fix the problem. + * + * Tue Jun 10 12:51:41 MET DST 1997: Martin Schulze + * Removed sleep(10) from parent process. This has caused a slow + * startup in former times - and I don't see any reason for this. + * + * Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman + * Some more glibc patches made by <mdorman@debian.org>. + * + * Thu Jan 1 16:04:52 CET 1998: Martin Schulze <joey@infodrom.north.de + * Applied patch from Herbert Thielen <Herbert.Thielen@lpr.e-technik.tu-muenchen.de>. + * This included some balance parentheses for emacs and a bug in + * the exclamation mark handling. + * + * Fixed small bug which caused syslogd to write messages to the + * wrong logfile under some very rare conditions. Thanks to + * Herbert Xu <herbert@gondor.apana.org.au> for fiddling this out. + * + * Thu Jan 8 22:46:35 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Reworked one line of the above patch as it prevented syslogd + * from binding the socket with the result that no messages were + * forwarded to other hosts. + * + * Sat Jan 10 01:33:06 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Fixed small bugs in F_FORW_UNKN meachanism. Thanks to Torsten + * Neumann <torsten@londo.rhein-main.de> for pointing me to it. + * + * Mon Jan 12 19:50:58 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Modified debug output concerning remote receiption. + * + * Mon Feb 23 23:32:35 CET 1998: Topi Miettinen <Topi.Miettinen@ml.tele.fi> + * Re-worked handling of Unix and UDP sockets to support closing / + * opening of them in order to have it open only if it is needed + * either for forwarding to a remote host or by receiption from + * the network. + * + * Wed Feb 25 10:54:09 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Fixed little comparison mistake that prevented the MARK + * feature to work properly. + * + * Wed Feb 25 13:21:44 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Corrected Topi's patch as it prevented forwarding during + * startup due to an unknown LogPort. + * + * Sat Oct 10 20:01:48 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Added support for TESTING define which will turn syslogd into + * stdio-mode used for debugging. + * + * Sun Oct 11 20:16:59 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Reworked the initialization/fork code. Now the parent + * process activates a signal handler which the daughter process + * will raise if it is initialized. Only after that one the + * parent process may exit. Otherwise klogd might try to flush + * its log cache while syslogd can't receive the messages yet. + * + * Mon Oct 12 13:30:35 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Redirected some error output with regard to argument parsing to + * stderr. + * + * Mon Oct 12 14:02:51 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Applied patch provided vom Topi Miettinen with regard to the + * people from OpenBSD. This provides the additional '-a' + * argument used for specifying additional UNIX domain sockets to + * listen to. This is been used with chroot()'ed named's for + * example. See for http://www.psionic.com/papers/dns.html + * + * Mon Oct 12 18:29:44 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Added `ftp' facility which was introduced in glibc version 2. + * It's #ifdef'ed so won't harm with older libraries. + * + * Mon Oct 12 19:59:21 MET DST 1998: Martin Schulze <joey@infodrom.north.de> + * Code cleanups with regard to bsd -> posix transition and + * stronger security (buffer length checking). Thanks to Topi + * Miettinen <tom@medialab.sonera.net> + * . index() --> strchr() + * . sprintf() --> snprintf() + * . bcopy() --> memcpy() + * . bzero() --> memset() + * . UNAMESZ --> UT_NAMESIZE + * . sys_errlist --> strerror() + * + * Mon Oct 12 20:22:59 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Added support for setutent()/getutent()/endutend() instead of + * binary reading the UTMP file. This is the the most portable + * way. This allows /var/run/utmp format to change, even to a + * real database or utmp daemon. Also if utmp file locking is + * implemented in libc, syslog will use it immediately. Thanks + * to Topi Miettinen <tom@medialab.sonera.net>. + * + * Mon Oct 12 20:49:18 MET DST 1998: Martin Schulze <joey@infodrom.north.de> + * Avoid logging of SIGCHLD when syslogd is in the process of + * exiting and closing its files. Again thanks to Topi. + * + * Mon Oct 12 22:18:34 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Modified printline() to support 8bit characters - such as + * russion letters. Thanks to Vladas Lapinskas <lapinskas@mail.iae.lt>. + * + * Sat Nov 14 02:29:37 CET 1998: Martin Schulze <joey@infodrom.north.de> + * ``-m 0'' now turns of MARK logging entirely. + * + * Tue Jan 19 01:04:18 MET 1999: Martin Schulze <joey@infodrom.north.de> + * Finally fixed an error with `-a' processing, thanks to Topi + * Miettinen <tom@medialab.sonera.net>. + * + * Sun May 23 10:08:53 CEST 1999: Martin Schulze <joey@infodrom.north.de> + * Removed superflous call to utmpname(). The path to the utmp + * file is defined in the used libc and should not be hardcoded + * into the syslogd binary referring the system it was compiled on. + * + * Sun Sep 17 20:45:33 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Fixed some bugs in printline() code that did not escape + * control characters '\177' through '\237' and contained a + * single-byte buffer overflow. Thanks to Solar Designer + * <solar@false.com>. + * + * Sun Sep 17 21:26:16 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Don't close open sockets upon reload. Thanks to Bill + * Nottingham. + * + * Mon Sep 18 09:10:47 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Fixed bug in printchopped() that caused syslogd to emit + * kern.emerg messages when splitting long lines. Thanks to + * Daniel Jacobowitz <dan@debian.org> for the fix. + * + * Mon Sep 18 15:33:26 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Removed unixm/unix domain sockets and switch to Datagram Unix + * Sockets. This should remove one possibility to play DoS with + * syslogd. Thanks to Olaf Kirch <okir@caldera.de> for the patch. + * + * Sun Mar 11 20:23:44 CET 2001: Martin Schulze <joey@infodrom.ffis.de> + * Don't return a closed fd if `-a' is called with a wrong path. + * Thanks to Bill Nottingham <notting@redhat.com> for providing + * a patch. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..f604c1cd --- /dev/null +++ b/Makefile.am @@ -0,0 +1,301 @@ +sbin_PROGRAMS = +pkglib_LTLIBRARIES = + +pkgconfigdir = $(libdir)/pkgconfig + +if ENABLE_INET +pkglib_LTLIBRARIES += lmtcpsrv.la lmtcpclt.la +# +# +# TCP (stream) server support +# +lmtcpsrv_la_SOURCES = \ + tcps_sess.c \ + tcps_sess.h \ + tcpsrv.c \ + tcpsrv.h +lmtcpsrv_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmtcpsrv_la_LDFLAGS = -module -avoid-version +lmtcpsrv_la_LIBADD = + +# +# TCP (stream) client support +# +lmtcpclt_la_SOURCES = \ + tcpclt.c \ + tcpclt.h +lmtcpclt_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmtcpclt_la_LDFLAGS = -module -avoid-version +lmtcpclt_la_LIBADD = + +endif # if ENABLE_INET + +# +# gssapi support +# +if ENABLE_GSSAPI +pkglib_LTLIBRARIES += lmgssutil.la +lmgssutil_la_SOURCES = gss-misc.c gss-misc.h +lmgssutil_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmgssutil_la_LDFLAGS = -module -avoid-version +lmgssutil_la_LIBADD = $(GSS_LIBS) +endif + +# +# systemd support +# +if HAVE_SYSTEMD + +nodist_systemdsystemunit_DATA = \ + rsyslog.service + +CLEANFILES = \ + rsyslog.service + +%.service: %.service.in + $(AM_V_GEN)sed -e 's,@sbindir\@,$(sbindir),g' $< > $@ + +endif + +EXTRA_DIST = \ + platform/README \ + platform/freebsd/rsyslogd \ + platform/slackware/rc.rsyslogd \ + platform/redhat/rsyslog.conf \ + contrib/README \ + COPYING \ + COPYING.LESSER \ + COPYING.ASL20 \ + contrib/gnutls/ca.pem \ + contrib/gnutls/cert.pem \ + contrib/gnutls/key.pem \ + rsyslog.service.in + +SUBDIRS = doc compat runtime grammar . plugins/immark plugins/imuxsock plugins/imtcp plugins/imudp plugins/omtesting + +if ENABLE_RSYSLOGD +SUBDIRS += tools +endif + +if ENABLE_IMKLOG +SUBDIRS += plugins/imklog +endif + +if ENABLE_IMKMSG +SUBDIRS += plugins/imkmsg +endif + +if ENABLE_IMPSTATS +SUBDIRS += plugins/impstats +endif + +if ENABLE_IMSOLARIS +SUBDIRS += plugins/imsolaris +endif + +if ENABLE_GSSAPI +SUBDIRS += plugins/omgssapi plugins/imgssapi +endif + +if ENABLE_RELP +SUBDIRS += plugins/omrelp plugins/imrelp +endif + +if ENABLE_MYSQL +SUBDIRS += plugins/ommysql +endif + +if ENABLE_OMLIBDBI +SUBDIRS += plugins/omlibdbi +endif + +if ENABLE_PGSQL +SUBDIRS += plugins/ompgsql +endif + +if ENABLE_SNMP +SUBDIRS += plugins/omsnmp +endif + +if ENABLE_SMCUSTBINDCDR +SUBDIRS += plugins/sm_cust_bindcdr +endif + +if ENABLE_OMSTDOUT +SUBDIRS += plugins/omstdout +endif + +if ENABLE_PMCISCONAMES +SUBDIRS += plugins/pmcisconames +endif + +if ENABLE_PMAIXFORWARDEDFROM +SUBDIRS += plugins/pmaixforwardedfrom +endif + +if ENABLE_PMSNARE +SUBDIRS += plugins/pmsnare +endif + +if ENABLE_PMLASTMSG +SUBDIRS += plugins/pmlastmsg +endif + +if ENABLE_PMRFC3164SD +SUBDIRS += plugins/pmrfc3164sd +endif + +if ENABLE_OMRULESET +SUBDIRS += plugins/omruleset +endif + +if ENABLE_OMUDPSPOOF +SUBDIRS += plugins/omudpspoof +endif + +if ENABLE_OMMONGODB +SUBDIRS += plugins/ommongodb +endif + +if ENABLE_OMHIREDIS +SUBDIRS += plugins/omhiredis +endif + +if ENABLE_OMZMQ3 +SUBDIRS += plugins/omzmq3 +endif + +if ENABLE_OMRABBITMQ +SUBDIRS += plugins/omrabbitmq +endif + +if ENABLE_IMZMQ3 +SUBDIRS += plugins/imzmq3 +endif + +if ENABLE_OMUXSOCK +SUBDIRS += plugins/omuxsock +endif + +if ENABLE_OMHDFS +SUBDIRS += plugins/omhdfs +endif + +if ENABLE_OMJOURNAL +SUBDIRS += plugins/omjournal +endif + +if ENABLE_IMJOURNAL +SUBDIRS += plugins/imjournal +endif + +if ENABLE_ELASTICSEARCH +SUBDIRS += plugins/omelasticsearch +endif + +if ENABLE_MMSNMPTRAPD +SUBDIRS += plugins/mmsnmptrapd +endif + +if ENABLE_IMFILE +SUBDIRS += plugins/imfile +endif + +if ENABLE_IMPTCP +SUBDIRS += plugins/imptcp +endif + +if ENABLE_IMTTCP +SUBDIRS += plugins/imttcp +endif + +if ENABLE_IMDIAG +SUBDIRS += plugins/imdiag +endif + +if ENABLE_MAIL +SUBDIRS += plugins/ommail +endif + +if ENABLE_OMPROG +SUBDIRS += plugins/omprog +endif + +if ENABLE_RFC3195 +SUBDIRS += plugins/im3195 +endif + +if ENABLE_MMNORMALIZE +SUBDIRS += plugins/mmnormalize +endif + +if ENABLE_MMJSONPARSE +SUBDIRS += plugins/mmjsonparse +endif + +if ENABLE_MMAUDIT +SUBDIRS += plugins/mmaudit +endif + +if ENABLE_MMANON +SUBDIRS += plugins/mmanon +endif + +if ENABLE_MMCOUNT +SUBDIRS += plugins/mmcount +endif + +if ENABLE_MMFIELDS +SUBDIRS += plugins/mmfields +endif + +if ENABLE_ORACLE +SUBDIRS += plugins/omoracle +endif + +if ENABLE_GUI +SUBDIRS += java +endif + +# tests are added as last element, because tests may need different +# modules that need to be generated first +SUBDIRS += tests + + +# make sure "make distcheck" tries to build all modules. This means that +# a developer must always have an environment where every supporting library +# is available. If that is not the case, the respective configure option may +# temporarily be removed below. The intent behind forcing everthing to compile +# in a make distcheck is so that we detect code that accidently was not updated +# when some global update happened. +DISTCHECK_CONFIGURE_FLAGS= --enable-gssapi_krb5 \ + --enable-imfile \ + --enable-snmp \ + --enable-libdbi \ + --enable-mysql \ + --enable-relp \ + --enable-rsyslogd \ + --enable-mail \ + --enable-klog \ + --enable-diagtools \ + --enable-gnutls \ + --enable-omstdout \ + --enable-pmlastmsg \ + --enable-omruleset \ + --enable-omprog \ + --enable-imdiag \ + --enable-imptcp \ + --enable-imttcp \ + --enable-omuxsock \ + --enable-impstats \ + --enable-memcheck \ + --enable-pmaixforwardedfrom \ + --enable-pmcisconames \ + --enable-pmsnare \ + --enable-mmsnmptrapd \ + --enable-elasticsearch \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) +# temporarily disable these checks for make distcheck 2012-09-06 rgerhards +# --enable-extended-tests \ +# --enable-pgsql +ACLOCAL_AMFLAGS = -I m4 @@ -0,0 +1 @@ +This file has been superseeded by ChangeLog. Please see there. @@ -0,0 +1,5 @@ +This file has been superseeded by the files in the doc folder. +Please see doc/manual.html for futher details. If you are +looking for install information doc/install.html is for you! +If you do not have the doc set, see + http://www.rsyslog.com/doc diff --git a/action.c b/action.c new file mode 100644 index 00000000..85766941 --- /dev/null +++ b/action.c @@ -0,0 +1,2040 @@ +/* action.c + * + * Implementation of the action object. + * + * File begun on 2007-08-06 by RGerhards (extracted from syslogd.c) + * + * Some notes on processing (this hopefully makes it easier to find + * the right code in question): For performance reasons, this module + * uses different methods of message submission based on the user-selected + * configuration. This code is similar, but can not be abstracted because + * of the performance-affecting differences in it. As such, it is often + * necessary to triple-check that everything works well in *all* modes. + * The different modes (and calling sequence) are: + * + * if set iExecEveryNthOccur > 1 || iSecsExecOnceInterval + * - doSubmitToActionQComplexBatch + * - helperSubmitToActionQComplexBatch + * - doActionCallAction + * handles mark message reduction, but in essence calls + * - actionWriteToAction + * - qqueueEnqObj + * (now queue engine processing) + * if(pThis->bWriteAllMarkMsgs == RSFALSE) - this is the DEFAULT + * - doSubmitToActionQNotAllMarkBatch + * - doSubmitToActionQBatch (and from here like in the else case below!) + * else + * - doSubmitToActionQBatch + * - doSubmitToActionQ + * - qqueueEnqObj + * (now queue engine processing) + * + * Note that bWriteAllMakrMsgs on or off creates almost the same processing. + * The difference ist that if WriteAllMarkMsgs is not set, we need to + * preprocess the batch and drop mark messages which are not yet due for + * writing. + * + * After dequeue, processing is as follows: + * - processBatchMain + * - processAction + * - submitBatch + * - tryDoAction + * - ... + * + * MORE ON PROCESSING, QUEUES and FILTERING + * All filtering needs to be done BEFORE messages are enqueued to an + * action. In previous code, part of the filtering was done at the + * "remote end" of the action queue, which lead to problems in + * non-direct mode (because then things run asynchronously). In order + * to solve this problem once and for all, I have changed the code so + * that all filtering is done before enq, and processing on the + * dequeue side of action processing now always executes whatever is + * enqueued. This is the only way to handle things consistently and + * (as much as possible) in a queue-type agnostic way. However, it is + * a rather radical change, which I unfortunately needed to make from + * stable version 5.8.1 to 5.8.2. If new problems pop up, you now know + * what may be their cause. In any case, the way it is done now is the + * only correct one. + * A problem is that, under fortunate conditions, we use the current + * batch for the output system as well. This is very good from a performance + * point of view, but makes the distinction between enq and deq side of + * the queue a bit hard. The current idea is that the filter condition + * alone is checked at the deq side of the queue (seems to be unavoidable + * to do it that way), but all other complex conditons (like failover + * handling) go into the computation of the filter condition. For + * non-direct queues, we still enqueue only what is acutally necessary. + * Note that in this case the rest of the code must ensure that the filter + * is set to "true". While this is not perfect and not as simple as + * we would like to see it, it looks like the best way to tackle that + * beast. + * rgerhards, 2011-06-15 + * + * Copyright 2007-2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <assert.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <errno.h> +#include <json/json.h> + +#include "dirty.h" +#include "template.h" +#include "action.h" +#include "modules.h" +#include "cfsysline.h" +#include "srUtils.h" +#include "errmsg.h" +#include "batch.h" +#include "wti.h" +#include "rsconf.h" +#include "datetime.h" +#include "unicode-helper.h" +#include "atomic.h" +#include "ruleset.h" +#include "statsobj.h" + +#define NO_TIME_PROVIDED 0 /* indicate we do not provide any cached time */ + +/* forward definitions */ +static rsRetVal processBatchMain(action_t *pAction, batch_t *pBatch, int*); +static rsRetVal doSubmitToActionQComplexBatch(action_t *pAction, batch_t *pBatch); +static rsRetVal doSubmitToActionQNotAllMarkBatch(action_t *pAction, batch_t *pBatch); +static rsRetVal doSubmitToActionQBatch(action_t *pAction, batch_t *pBatch); + +/* object static data (once for all instances) */ +/* TODO: make this an object! DEFobjStaticHelpers -- rgerhards, 2008-03-05 */ +DEFobjCurrIf(obj) +DEFobjCurrIf(datetime) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(statsobj) +DEFobjCurrIf(ruleset) + + +typedef struct configSettings_s { + int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */ + int bActionWriteAllMarkMsgs; /* should all mark messages be unconditionally written? */ + int iActExecOnceInterval; /* execute action once every nn seconds */ + int iActExecEveryNthOccur; /* execute action every n-th occurence (0,1=always) */ + time_t iActExecEveryNthOccurTO; /* timeout for n-occurence setting (in seconds, 0=never) */ + int glbliActionResumeInterval; + int glbliActionResumeRetryCount; /* how often should suspended actions be retried? */ + int bActionRepMsgHasMsg; /* last messsage repeated... has msg fragment in it */ + uchar *pszActionName; /* short name for the action */ + /* action queue and its configuration parameters */ + queueType_t ActionQueType; /* type of the main message queue above */ + int iActionQueueSize; /* size of the main message queue above */ + int iActionQueueDeqBatchSize; /* batch size for action queues */ + int iActionQHighWtrMark; /* high water mark for disk-assisted queues */ + int iActionQLowWtrMark; /* low water mark for disk-assisted queues */ + int iActionQDiscardMark; /* begin to discard messages */ + int iActionQDiscardSeverity; /* by default, discard nothing to prevent unintentional loss */ + int iActionQueueNumWorkers; /* number of worker threads for the mm queue above */ + uchar *pszActionQFName; /* prefix for the main message queue file */ + int64 iActionQueMaxFileSize; + int iActionQPersistUpdCnt; /* persist queue info every n updates */ + int bActionQSyncQeueFiles; /* sync queue files */ + int iActionQtoQShutdown; /* queue shutdown */ + int iActionQtoActShutdown; /* action shutdown (in phase 2) */ + int iActionQtoEnq; /* timeout for queue enque */ + int iActionQtoWrkShutdown; /* timeout for worker thread shutdown */ + int iActionQWrkMinMsgs; /* minimum messages per worker needed to start a new one */ + int bActionQSaveOnShutdown; /* save queue on shutdown (when DA enabled)? */ + int64 iActionQueMaxDiskSpace; /* max disk space allocated 0 ==> unlimited */ + int iActionQueueDeqSlowdown; /* dequeue slowdown (simple rate limiting) */ + int iActionQueueDeqtWinFromHr; /* hour begin of time frame when queue is to be dequeued */ + int iActionQueueDeqtWinToHr; /* hour begin of time frame when queue is to be dequeued */ +} configSettings_t; + +configSettings_t cs; /* our current config settings */ +configSettings_t cs_save; /* our saved (scope!) config settings */ + +/* the counter below counts actions created. It is used to obtain unique IDs for the action. They + * should not be relied on for any long-term activity (e.g. disk queue names!), but they are nice + * to have during one instance of an rsyslogd run. For example, I use them to name actions when there + * is no better name available. Note that I do NOT recover previous numbers on HUP - we simply keep + * counting. -- rgerhards, 2008-01-29 + */ +static int iActionNbr = 0; + +/* tables for interfacing with the v6 config system */ +static struct cnfparamdescr cnfparamdescr[] = { + { "name", eCmdHdlrGetWord, 0 }, /* legacy: actionname */ + { "type", eCmdHdlrString, CNFPARAM_REQUIRED }, /* legacy: actionname */ + { "action.writeallmarkmessages", eCmdHdlrBinary, 0 }, /* legacy: actionwriteallmarkmessages */ + { "action.execonlyeverynthtime", eCmdHdlrInt, 0 }, /* legacy: actionexeconlyeverynthtime */ + { "action.execonlyeverynthtimetimeout", eCmdHdlrInt, 0 }, /* legacy: actionexeconlyeverynthtimetimeout */ + { "action.execonlyonceeveryinterval", eCmdHdlrInt, 0 }, /* legacy: actionexeconlyonceeveryinterval */ + { "action.execonlywhenpreviousissuspended", eCmdHdlrBinary, 0 }, /* legacy: actionexeconlywhenpreviousissuspended */ + { "action.repeatedmsgcontainsoriginalmsg", eCmdHdlrBinary, 0 }, /* legacy: repeatedmsgcontainsoriginalmsg */ + { "action.resumeretrycount", eCmdHdlrInt, 0 }, /* legacy: actionresumeretrycount */ + { "action.resumeinterval", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk pblk = + { CNFPARAMBLK_VERSION, + sizeof(cnfparamdescr)/sizeof(struct cnfparamdescr), + cnfparamdescr + }; + +/* ------------------------------ methods ------------------------------ */ + +/* This function returns the "current" time for this action. Current time + * is not necessarily real-time. In order to enhance performance, current + * system time is obtained the first time an action needs to know the time + * and then kept cached inside the action structure. Later requests will + * always return that very same time. Wile not totally accurate, it is far + * accurate in most cases and considered "acurate enough" for all cases. + * When changing the threading model, please keep in mind that this + * logic needs to be changed should we once allow more than one parallel + * call into the same action (object). As this is currently not supported, + * we simply cache the time inside the action object itself, after it + * is under mutex protection. + * Side-note: the value -1 is used as tActNow, because it also is the + * error return value of time(). So we would do a retry with the next + * invocation if time() failed. Then, of course, we would probably already + * be in trouble, but for the sake of performance we accept this very, + * very slight risk. + * This logic has been added as part of an overall performance improvment + * effort inspired by David Lang. -- rgerhards, 2008-09-16 + * Note: this function does not use the usual iRet call conventions + * because that would provide little to no benefit but complicate things + * a lot. So we simply return the system time. + */ +static inline time_t +getActNow(action_t *pThis) +{ + assert(pThis != NULL); + if(pThis->tActNow == -1) { + pThis->tActNow = datetime.GetTime(NULL); /* good time call - the only one done */ + if(pThis->tLastExec > pThis->tActNow) { + /* if we are traveling back in time, reset tLastExec */ + pThis->tLastExec = (time_t) 0; + } + } + + return pThis->tActNow; +} + + +/* resets action queue parameters to their default values. This happens + * after each action has been created in order to prevent any wild defaults + * to be used. It is somewhat against the original spirit of the config file + * reader, but I think it is a good thing to do. + * rgerhards, 2008-01-29 + */ +static rsRetVal +actionResetQueueParams(void) +{ + DEFiRet; + + cs.ActionQueType = QUEUETYPE_DIRECT; /* type of the main message queue above */ + cs.iActionQueueSize = 1000; /* size of the main message queue above */ + cs.iActionQueueDeqBatchSize = 16; /* default batch size */ + cs.iActionQHighWtrMark = 800; /* high water mark for disk-assisted queues */ + cs.iActionQLowWtrMark = 200; /* low water mark for disk-assisted queues */ + cs.iActionQDiscardMark = 980; /* begin to discard messages */ + cs.iActionQDiscardSeverity = 8; /* discard warning and above */ + cs.iActionQueueNumWorkers = 1; /* number of worker threads for the mm queue above */ + cs.iActionQueMaxFileSize = 1024*1024; + cs.iActionQPersistUpdCnt = 0; /* persist queue info every n updates */ + cs.bActionQSyncQeueFiles = 0; + cs.iActionQtoQShutdown = 0; /* queue shutdown */ + cs.iActionQtoActShutdown = 1000; /* action shutdown (in phase 2) */ + cs.iActionQtoEnq = 50; /* timeout for queue enque */ + cs.iActionQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */ + cs.iActionQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */ + cs.bActionQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ + cs.iActionQueMaxDiskSpace = 0; + cs.iActionQueueDeqSlowdown = 0; + cs.iActionQueueDeqtWinFromHr = 0; + cs.iActionQueueDeqtWinToHr = 25; /* 25 disables time windowed dequeuing */ + + cs.glbliActionResumeRetryCount = 0; /* I guess it is smart to reset this one, too */ + + d_free(cs.pszActionQFName); + cs.pszActionQFName = NULL; /* prefix for the main message queue file */ + + RETiRet; +} + + +/* destructs an action descriptor object + * rgerhards, 2007-08-01 + */ +rsRetVal actionDestruct(action_t *pThis) +{ + DEFiRet; + ASSERT(pThis != NULL); + + if(!strcmp((char*)modGetName(pThis->pMod), "builtin:omdiscard")) { + /* discard actions will be optimized out */ + FINALIZE; + } + + if(pThis->pQueue != NULL) { + qqueueDestruct(&pThis->pQueue); + } + + /* destroy stats object, if we have one (may not always be + * be the case, e.g. if turned off) + */ + if(pThis->statsobj != NULL) + statsobj.Destruct(&pThis->statsobj); + + if(pThis->pMod != NULL) + pThis->pMod->freeInstance(pThis->pModData); + + pthread_mutex_destroy(&pThis->mutAction); + pthread_mutex_destroy(&pThis->mutActExec); + d_free(pThis->pszName); + d_free(pThis->ppTpl); + +finalize_it: + d_free(pThis); + RETiRet; +} + + +/* create a new action descriptor object + * rgerhards, 2007-08-01 + * Note that it is vital to set proper initial values as the v6 config + * system depends on these! + */ +rsRetVal actionConstruct(action_t **ppThis) +{ + DEFiRet; + action_t *pThis; + + ASSERT(ppThis != NULL); + + CHKmalloc(pThis = (action_t*) calloc(1, sizeof(action_t))); + pThis->iResumeInterval = 30; + pThis->iResumeRetryCount = 0; + pThis->pszName = NULL; + pThis->bWriteAllMarkMsgs = RSFALSE; + pThis->iExecEveryNthOccur = 0; + pThis->iExecEveryNthOccurTO = 0; + pThis->iSecsExecOnceInterval = 0; + pThis->bExecWhenPrevSusp = 0; + pThis->bRepMsgHasMsg = 0; + pThis->tLastOccur = datetime.GetTime(NULL); /* done once per action on startup only */ + pthread_mutex_init(&pThis->mutActExec, NULL); + pthread_mutex_init(&pThis->mutAction, NULL); + INIT_ATOMIC_HELPER_MUT(pThis->mutCAS); + + /* indicate we have a new action */ + ++iActionNbr; + +finalize_it: + *ppThis = pThis; + RETiRet; +} + + +/* action construction finalizer + */ +rsRetVal +actionConstructFinalize(action_t *pThis, struct nvlst *lst) +{ + DEFiRet; + uchar pszAName[64]; /* friendly name of our action */ + + ASSERT(pThis != NULL); + + if(!strcmp((char*)modGetName(pThis->pMod), "builtin:omdiscard")) { + /* discard actions will be optimized out */ + FINALIZE; + } + /* generate a friendly name for us action stats */ + if(pThis->pszName == NULL) { + snprintf((char*) pszAName, sizeof(pszAName)/sizeof(uchar), "action %d", iActionNbr); + } else { + ustrncpy(pszAName, pThis->pszName, sizeof(pszAName)); + pszAName[sizeof(pszAName)-1] = '\0'; /* to be on the save side */ + } + + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&pThis->statsobj)); + CHKiRet(statsobj.SetName(pThis->statsobj, pszAName)); + + STATSCOUNTER_INIT(pThis->ctrProcessed, pThis->mutCtrProcessed); + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("processed"), + ctrType_IntCtr, &pThis->ctrProcessed)); + + STATSCOUNTER_INIT(pThis->ctrFail, pThis->mutCtrFail); + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("failed"), + ctrType_IntCtr, &pThis->ctrFail)); + + CHKiRet(statsobj.ConstructFinalize(pThis->statsobj)); + + /* create our queue */ + + /* generate a friendly name for the queue */ + if(pThis->pszName == NULL) { + snprintf((char*) pszAName, sizeof(pszAName)/sizeof(uchar), "action %d queue", + iActionNbr); + } else { + ustrncpy(pszAName, pThis->pszName, sizeof(pszAName)); + pszAName[63] = '\0'; /* to be on the save side */ + } + /* now check if we can run the action in "firehose mode" during stage one of + * its processing (that is before messages are enqueued into the action q). + * This is only possible if some features, which require strict sequence, are + * not used. Thankfully, that is usually the case. The benefit of firehose + * mode is much faster processing (and simpler code) -- rgerhards, 2010-06-08 + */ + if( pThis->iExecEveryNthOccur > 1 + || pThis->iSecsExecOnceInterval + ) { + DBGPRINTF("info: firehose mode disabled for action because " + "iExecEveryNthOccur=%d, iSecsExecOnceInterval=%d\n", + pThis->iExecEveryNthOccur, pThis->iSecsExecOnceInterval); + pThis->submitToActQ = doSubmitToActionQComplexBatch; + } else if(pThis->bWriteAllMarkMsgs == RSFALSE) { + /* nearly full-speed submission mode, default case */ + pThis->submitToActQ = doSubmitToActionQNotAllMarkBatch; + } else { + /* full firehose submission mode */ + pThis->submitToActQ = doSubmitToActionQBatch; + } + + /* create queue */ + /* action queues always (for now) have just one worker. This may change when + * we begin to implement an interface the enable output modules to request + * to be run on multiple threads. So far, this is forbidden by the interface + * spec. -- rgerhards, 2008-01-30 + */ + CHKiRet(qqueueConstruct(&pThis->pQueue, cs.ActionQueType, 1, cs.iActionQueueSize, + (rsRetVal (*)(void*, batch_t*, int*))processBatchMain)); + obj.SetName((obj_t*) pThis->pQueue, pszAName); + qqueueSetpAction(pThis->pQueue, pThis); + + if(lst == NULL) { /* use legacy params? */ + /* ... set some properties ... */ +# define setQPROP(func, directive, data) \ + CHKiRet_Hdlr(func(pThis->pQueue, data)) { \ + errmsg.LogError(0, NO_ERRCODE, "Invalid " #directive ", \ + error %d. Ignored, running with default setting", iRet); \ + } +# define setQPROPstr(func, directive, data) \ + CHKiRet_Hdlr(func(pThis->pQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \ + errmsg.LogError(0, NO_ERRCODE, "Invalid " #directive ", \ + error %d. Ignored, running with default setting", iRet); \ + } + setQPROP(qqueueSetsizeOnDiskMax, "$ActionQueueMaxDiskSpace", cs.iActionQueMaxDiskSpace); + setQPROP(qqueueSetiDeqBatchSize, "$ActionQueueDequeueBatchSize", cs.iActionQueueDeqBatchSize); + setQPROP(qqueueSetMaxFileSize, "$ActionQueueFileSize", cs.iActionQueMaxFileSize); + setQPROPstr(qqueueSetFilePrefix, "$ActionQueueFileName", cs.pszActionQFName); + setQPROP(qqueueSetiPersistUpdCnt, "$ActionQueueCheckpointInterval", cs.iActionQPersistUpdCnt); + setQPROP(qqueueSetbSyncQueueFiles, "$ActionQueueSyncQueueFiles", cs.bActionQSyncQeueFiles); + setQPROP(qqueueSettoQShutdown, "$ActionQueueTimeoutShutdown", cs.iActionQtoQShutdown ); + setQPROP(qqueueSettoActShutdown, "$ActionQueueTimeoutActionCompletion", cs.iActionQtoActShutdown); + setQPROP(qqueueSettoWrkShutdown, "$ActionQueueWorkerTimeoutThreadShutdown", cs.iActionQtoWrkShutdown); + setQPROP(qqueueSettoEnq, "$ActionQueueTimeoutEnqueue", cs.iActionQtoEnq); + setQPROP(qqueueSetiHighWtrMrk, "$ActionQueueHighWaterMark", cs.iActionQHighWtrMark); + setQPROP(qqueueSetiLowWtrMrk, "$ActionQueueLowWaterMark", cs.iActionQLowWtrMark); + setQPROP(qqueueSetiDiscardMrk, "$ActionQueueDiscardMark", cs.iActionQDiscardMark); + setQPROP(qqueueSetiDiscardSeverity, "$ActionQueueDiscardSeverity", cs.iActionQDiscardSeverity); + setQPROP(qqueueSetiMinMsgsPerWrkr, "$ActionQueueWorkerThreadMinimumMessages", cs.iActionQWrkMinMsgs); + setQPROP(qqueueSetbSaveOnShutdown, "$ActionQueueSaveOnShutdown", cs.bActionQSaveOnShutdown); + setQPROP(qqueueSetiDeqSlowdown, "$ActionQueueDequeueSlowdown", cs.iActionQueueDeqSlowdown); + setQPROP(qqueueSetiDeqtWinFromHr, "$ActionQueueDequeueTimeBegin", cs.iActionQueueDeqtWinFromHr); + setQPROP(qqueueSetiDeqtWinToHr, "$ActionQueueDequeueTimeEnd", cs.iActionQueueDeqtWinToHr); + } else { + /* we have v6-style config params */ + qqueueSetDefaultsActionQueue(pThis->pQueue); + qqueueApplyCnfParam(pThis->pQueue, lst); + } + +# undef setQPROP +# undef setQPROPstr + + qqueueDbgPrint(pThis->pQueue); + + DBGPRINTF("Action %p: queue %p created\n", pThis, pThis->pQueue); + + /* and now reset the queue params (see comment in its function header!) */ + actionResetQueueParams(); + +finalize_it: + RETiRet; +} + + + +/* set the global resume interval + */ +rsRetVal actionSetGlobalResumeInterval(int iNewVal) +{ + cs.glbliActionResumeInterval = iNewVal; + return RS_RET_OK; +} + + +/* returns the action state name in human-readable form + * returned string must not be modified. + * rgerhards, 2009-05-07 + */ +static uchar *getActStateName(action_t *pThis) +{ + switch(pThis->eState) { + case ACT_STATE_RDY: + return (uchar*) "rdy"; + case ACT_STATE_ITX: + return (uchar*) "itx"; + case ACT_STATE_RTRY: + return (uchar*) "rtry"; + case ACT_STATE_SUSP: + return (uchar*) "susp"; + case ACT_STATE_DIED: + return (uchar*) "died"; + case ACT_STATE_COMM: + return (uchar*) "comm"; + default: + return (uchar*) "ERROR/UNKNWON"; + } +} + + +/* returns a suitable return code based on action state + * rgerhards, 2009-05-07 + */ +static rsRetVal getReturnCode(action_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + switch(pThis->eState) { + case ACT_STATE_RDY: + iRet = RS_RET_OK; + break; + case ACT_STATE_ITX: + if(pThis->bHadAutoCommit) { + pThis->bHadAutoCommit = 0; /* auto-reset */ + iRet = RS_RET_PREVIOUS_COMMITTED; + } else { + iRet = RS_RET_DEFER_COMMIT; + } + break; + case ACT_STATE_RTRY: + iRet = RS_RET_SUSPENDED; + break; + case ACT_STATE_SUSP: + case ACT_STATE_DIED: + iRet = RS_RET_ACTION_FAILED; + break; + default: + DBGPRINTF("Invalid action engine state %d, program error\n", + (int) pThis->eState); + iRet = RS_RET_ERR; + break; + } + + RETiRet; +} + + +/* set the action to a new state + * rgerhards, 2007-08-02 + */ +static inline void actionSetState(action_t *pThis, action_state_t newState) +{ + pThis->eState = newState; + DBGPRINTF("Action %p transitioned to state: %s\n", pThis, getActStateName(pThis)); +} + +/* Handles the transient commit state. So far, this is + * mostly a dummy... + * rgerhards, 2007-08-02 + */ +static void actionCommitted(action_t *pThis) +{ + actionSetState(pThis, ACT_STATE_RDY); +} + + +/* set action to "rtry" state. + * rgerhards, 2007-08-02 + */ +static void actionRetry(action_t *pThis) +{ + actionSetState(pThis, ACT_STATE_RTRY); + pThis->iResumeOKinRow++; +} + + +/* Disable action, this means it will never again be usable + * until rsyslog is reloaded. Use only as a last resort, but + * depends on output module. + * rgerhards, 2007-08-02 + */ +static void actionDisable(action_t *pThis) +{ + actionSetState(pThis, ACT_STATE_DIED); +} + + +/* Suspend action, this involves changing the acton state as well + * as setting the next retry time. + * if we have more than 10 retries, we prolong the + * retry interval. If something is really stalled, it will + * get re-tried only very, very seldom - but that saves + * CPU time. TODO: maybe a config option for that? + * rgerhards, 2007-08-02 + */ +static inline void actionSuspend(action_t *pThis) +{ + time_t ttNow; + + /* note: we can NOT use a cached timestamp, as time may have evolved + * since caching, and this would break logic (and it actually did so!) + */ + datetime.GetTime(&ttNow); + pThis->ttResumeRtry = ttNow + pThis->iResumeInterval * (pThis->iNbrResRtry / 10 + 1); + actionSetState(pThis, ACT_STATE_SUSP); + DBGPRINTF("action suspended, earliest retry=%d\n", (int) pThis->ttResumeRtry); +} + + +/* actually do retry processing. Note that the function receives a timestamp so + * that we do not need to call the (expensive) time() API. + * Note that we do the full retry processing here, doing the configured number of + * iterations. -- rgerhards, 2009-05-07 + * We need to guard against module which always return RS_RET_OK from their tryResume() + * entry point. This is invalid, but has harsh consequences: it will cause the rsyslog + * engine to go into a tight loop. That obviously is not acceptable. As such, we track the + * count of iterations that a tryResume returning RS_RET_OK is immediately followed by + * an unsuccessful call to doAction(). If that happens more than 1,000 times, we assume + * the return acutally is a RS_RET_SUSPENDED. In order to go through the various + * resumption stages, we do this for every 1000 requests. This magic number 1000 may + * not be the most appropriate, but it should be thought of a "if nothing else helps" + * kind of facility: in the first place, the module should return a proper indication + * of its inability to recover. -- rgerhards, 2010-04-26. + */ +static inline rsRetVal +actionDoRetry(action_t *pThis, int *pbShutdownImmediate) +{ + int iRetries; + int iSleepPeriod; + int bTreatOKasSusp; + DEFiRet; + + ASSERT(pThis != NULL); + + iRetries = 0; + while((*pbShutdownImmediate == 0) && pThis->eState == ACT_STATE_RTRY) { + DBGPRINTF("actionDoRetry: enter loop, iRetries=%d\n", iRetries); + iRet = pThis->pMod->tryResume(pThis->pModData); + DBGPRINTF("actionDoRetry: action->tryResume returned %d\n", iRet); + if((pThis->iResumeOKinRow > 9) && (pThis->iResumeOKinRow % 10 == 0)) { + bTreatOKasSusp = 1; + pThis->iResumeOKinRow = 0; + } else { + bTreatOKasSusp = 0; + } + if((iRet == RS_RET_OK) && (!bTreatOKasSusp)) { + DBGPRINTF("actionDoRetry: had success RDY again (iRet=%d)\n", iRet); + actionSetState(pThis, ACT_STATE_RDY); + } else if(iRet == RS_RET_SUSPENDED || bTreatOKasSusp) { + /* max retries reached? */ + DBGPRINTF("actionDoRetry: check for max retries, iResumeRetryCount %d, iRetries %d\n", + pThis->iResumeRetryCount, iRetries); + if((pThis->iResumeRetryCount != -1 && iRetries >= pThis->iResumeRetryCount)) { + actionSuspend(pThis); + } else { + ++pThis->iNbrResRtry; + ++iRetries; + iSleepPeriod = pThis->iResumeInterval; + srSleep(iSleepPeriod, 0); + if(*pbShutdownImmediate) { + ABORT_FINALIZE(RS_RET_FORCE_TERM); + } + } + } else if(iRet == RS_RET_DISABLE_ACTION) { + actionDisable(pThis); + } + } + + if(pThis->eState == ACT_STATE_RDY) { + pThis->iNbrResRtry = 0; + } + +finalize_it: + RETiRet; +} + + +/* try to resume an action -- rgerhards, 2007-08-02 + * changed to new action state engine -- rgerhards, 2009-05-07 + */ +static rsRetVal actionTryResume(action_t *pThis, int *pbShutdownImmediate) +{ + DEFiRet; + time_t ttNow = NO_TIME_PROVIDED; + + ASSERT(pThis != NULL); + + if(pThis->eState == ACT_STATE_SUSP) { + /* if we are suspended, we need to check if the timeout expired. + * for this handling, we must always obtain a fresh timestamp. We used + * to use the action timestamp, but in this case we will never reach a + * point where a resumption is actually tried, because the action timestamp + * is always in the past. So we can not avoid doing a fresh time() call + * here. -- rgerhards, 2009-03-18 + */ + datetime.GetTime(&ttNow); /* cache "now" */ + if(ttNow >= pThis->ttResumeRtry) { + actionSetState(pThis, ACT_STATE_RTRY); /* back to retries */ + } + } + + if(pThis->eState == ACT_STATE_RTRY) { + if(ttNow == NO_TIME_PROVIDED) /* use cached result if we have it */ + datetime.GetTime(&ttNow); + CHKiRet(actionDoRetry(pThis, pbShutdownImmediate)); + } + + if(Debug && (pThis->eState == ACT_STATE_RTRY ||pThis->eState == ACT_STATE_SUSP)) { + DBGPRINTF("actionTryResume: action %p state: %s, next retry (if applicable): %u [now %u]\n", + pThis, getActStateName(pThis), (unsigned) pThis->ttResumeRtry, (unsigned) ttNow); + } + +finalize_it: + RETiRet; +} + + +/* prepare an action for performing work. This involves trying to recover it, + * depending on its current state. + * rgerhards, 2009-05-07 + */ +static inline rsRetVal actionPrepare(action_t *pThis, int *pbShutdownImmediate) +{ + DEFiRet; + + assert(pThis != NULL); + CHKiRet(actionTryResume(pThis, pbShutdownImmediate)); + + /* if we are now ready, we initialize the transaction and advance + * action state accordingly + */ + if(pThis->eState == ACT_STATE_RDY) { + iRet = pThis->pMod->mod.om.beginTransaction(pThis->pModData); + switch(iRet) { + case RS_RET_OK: + actionSetState(pThis, ACT_STATE_ITX); + break; + case RS_RET_SUSPENDED: + actionRetry(pThis); + break; + case RS_RET_DISABLE_ACTION: + actionDisable(pThis); + break; + default:FINALIZE; + } + } + +finalize_it: + RETiRet; +} + + +/* debug-print the contents of an action object + * rgerhards, 2007-08-02 + */ +rsRetVal actionDbgPrint(action_t *pThis) +{ + DEFiRet; + char *sz; + + dbgprintf("%s: ", module.GetStateName(pThis->pMod)); + pThis->pMod->dbgPrintInstInfo(pThis->pModData); + dbgprintf("\n"); + dbgprintf("\tInstance data: 0x%lx\n", (unsigned long) pThis->pModData); + dbgprintf("\tResume Interval: %d\n", pThis->iResumeInterval); + if(pThis->eState == ACT_STATE_SUSP) { + dbgprintf("\tresume next retry: %u, number retries: %d", + (unsigned) pThis->ttResumeRtry, pThis->iNbrResRtry); + } + dbgprintf("\tState: %s\n", getActStateName(pThis)); + dbgprintf("\tExec only when previous is suspended: %d\n", pThis->bExecWhenPrevSusp); + if(pThis->submitToActQ == doSubmitToActionQComplexBatch) { + sz = "slow, but feature-rich"; + } else if(pThis->submitToActQ == doSubmitToActionQNotAllMarkBatch) { + sz = "fast, but supports partial mark messages"; + } else if(pThis->submitToActQ == doSubmitToActionQBatch) { + sz = "firehose (fastest)"; + } else { + sz = "unknown (need to update debug display?)"; + } + dbgprintf("\tsubmission mode: %s\n", sz); + dbgprintf("\n"); + + RETiRet; +} + + +/* prepare the calling parameters for doAction() + * rgerhards, 2009-05-07 + */ +static rsRetVal +prepareDoActionParams(action_t *pAction, batch_obj_t *pElem, struct syslogTime *ttNow) +{ + int i; + msg_t *pMsg; + struct json_object *json; + DEFiRet; + + ASSERT(pAction != NULL); + ASSERT(pElem != NULL); + + pMsg = pElem->pMsg; + /* here we must loop to process all requested strings */ + for(i = 0 ; i < pAction->iNumTpls ; ++i) { + switch(pAction->eParamPassing) { + case ACT_STRING_PASSING: + CHKiRet(tplToString(pAction->ppTpl[i], pMsg, &(pElem->staticActStrings[i]), + &pElem->staticLenStrings[i], ttNow)); + pElem->staticActParams[i] = pElem->staticActStrings[i]; + break; + case ACT_ARRAY_PASSING: + CHKiRet(tplToArray(pAction->ppTpl[i], pMsg, (uchar***) &(pElem->staticActParams[i]), ttNow)); + break; + case ACT_MSG_PASSING: + pElem->staticActParams[i] = (void*) pMsg; + break; + case ACT_JSON_PASSING: + CHKiRet(tplToJSON(pAction->ppTpl[i], pMsg, &json, ttNow)); + pElem->staticActParams[i] = (void*) json; + break; + default:dbgprintf("software bug/error: unknown pAction->eParamPassing %d in prepareDoActionParams\n", + (int) pAction->eParamPassing); + assert(0); /* software bug if this happens! */ + break; + } + } + +finalize_it: + RETiRet; +} + + +/* free a batches ressources, but not string buffers (because they will + * most probably be reused). String buffers are only deleted upon final + * destruction of the batch. + * This function here must be called only when the batch is actually no + * longer used, also not for retrying actions or such. It invalidates + * buffers. + * rgerhards, 2010-12-17 + */ +static rsRetVal releaseBatch(action_t *pAction, batch_t *pBatch) +{ + int jArr; + int i, j; + batch_obj_t *pElem; + uchar ***ppMsgs; + DEFiRet; + + ASSERT(pAction != NULL); + + if(pAction->eParamPassing == ACT_STRING_PASSING || pAction->eParamPassing == ACT_MSG_PASSING) + goto done; /* we need to do nothing with these types! */ + + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + pElem = &(pBatch->pElem[i]); + if(batchIsValidElem(pBatch, i)) { + switch(pAction->eParamPassing) { + case ACT_ARRAY_PASSING: + ppMsgs = (uchar***) pElem->staticActParams; + for(j = 0 ; j < pAction->iNumTpls ; ++j) { + if(((uchar**)ppMsgs)[j] != NULL) { + jArr = 0; + while(ppMsgs[j][jArr] != NULL) { + d_free(ppMsgs[j][jArr]); + ppMsgs[j][jArr] = NULL; + ++jArr; + } + d_free(((uchar**)ppMsgs)[j]); + ((uchar**)ppMsgs)[j] = NULL; + } + } + break; + case ACT_JSON_PASSING: + for(j = 0 ; j < pAction->iNumTpls ; ++j) { + json_object_put((struct json_object*) + pElem->staticActParams[j]); + pElem->staticActParams[j] = NULL; + } + break; + case ACT_STRING_PASSING: + case ACT_MSG_PASSING: + /* can never happen, just to keep compiler happy! */ + break; + } + } + } + +done: RETiRet; +} + + +/* call the DoAction output plugin entry point + * rgerhards, 2008-01-28 + */ +rsRetVal +actionCallDoAction(action_t *pThis, msg_t *pMsg, void *actParams) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ISOBJ_TYPE_assert(pMsg, msg); + + DBGPRINTF("entering actionCalldoAction(), state: %s\n", getActStateName(pThis)); + + pThis->bHadAutoCommit = 0; + iRet = pThis->pMod->mod.om.doAction(actParams, pMsg->msgFlags, pThis->pModData); + switch(iRet) { + case RS_RET_OK: + actionCommitted(pThis); + pThis->iResumeOKinRow = 0; /* we had a successful call! */ + break; + case RS_RET_DEFER_COMMIT: + pThis->iResumeOKinRow = 0; /* we had a successful call! */ + /* we are done, action state remains the same */ + break; + case RS_RET_PREVIOUS_COMMITTED: + /* action state remains the same, but we had a commit. */ + pThis->bHadAutoCommit = 1; + pThis->iResumeOKinRow = 0; /* we had a successful call! */ + break; + case RS_RET_SUSPENDED: + actionRetry(pThis); + break; + case RS_RET_DISABLE_ACTION: + actionDisable(pThis); + break; + default:/* permanent failure of this message - no sense in retrying. This is + * not yet handled (but easy TODO) + */ + FINALIZE; + } + iRet = getReturnCode(pThis); + +finalize_it: + RETiRet; +} + + +/* process a message + * this readies the action and then calls doAction() + * rgerhards, 2008-01-28 + */ +static inline rsRetVal +actionProcessMessage(action_t *pThis, msg_t *pMsg, void *actParams, int *pbShutdownImmediate) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ISOBJ_TYPE_assert(pMsg, msg); + + CHKiRet(actionPrepare(pThis, pbShutdownImmediate)); + if(pThis->pMod->mod.om.SetShutdownImmdtPtr != NULL) + pThis->pMod->mod.om.SetShutdownImmdtPtr(pThis->pModData, pbShutdownImmediate); + if(pThis->eState == ACT_STATE_ITX) + CHKiRet(actionCallDoAction(pThis, pMsg, actParams)); + + iRet = getReturnCode(pThis); +finalize_it: + RETiRet; +} + + +/* finish processing a batch. Most importantly, that means we commit if we + * need to do so. + * rgerhards, 2008-01-28 + */ +static rsRetVal +finishBatch(action_t *pThis, batch_t *pBatch) +{ + int i; + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->eState == ACT_STATE_RDY) { + /* we just need to flag the batch as commited */ + FINALIZE; /* nothing to do */ + } + + CHKiRet(actionPrepare(pThis, pBatch->pbShutdownImmediate)); + if(pThis->eState == ACT_STATE_ITX) { + iRet = pThis->pMod->mod.om.endTransaction(pThis->pModData); + switch(iRet) { + case RS_RET_OK: + actionCommitted(pThis); + /* flag messages as committed */ + for(i = 0 ; i < pBatch->nElem ; ++i) { + batchSetElemState(pBatch, i, BATCH_STATE_COMM); + pBatch->pElem[i].bPrevWasSuspended = 0; /* we had success! */ + } + break; + case RS_RET_SUSPENDED: + actionRetry(pThis); + break; + case RS_RET_DISABLE_ACTION: + actionDisable(pThis); + break; + case RS_RET_DEFER_COMMIT: + DBGPRINTF("output plugin error: endTransaction() returns RS_RET_DEFER_COMMIT " + "- ignored\n"); + actionCommitted(pThis); + break; + case RS_RET_PREVIOUS_COMMITTED: + DBGPRINTF("output plugin error: endTransaction() returns RS_RET_PREVIOUS_COMMITTED " + "- ignored\n"); + actionCommitted(pThis); + break; + default:/* permanent failure of this message - no sense in retrying. This is + * not yet handled (but easy TODO) + */ + FINALIZE; + } + } + iRet = getReturnCode(pThis); + +finalize_it: + RETiRet; +} + + +/* try to submit a partial batch of elements. + * rgerhards, 2009-05-12 + */ +static inline rsRetVal +tryDoAction(action_t *pAction, batch_t *pBatch, int *pnElem) +{ + int i; + int iElemProcessed; + int iCommittedUpTo; + msg_t *pMsg; + rsRetVal localRet; + DEFiRet; + + assert(pBatch != NULL); + assert(pnElem != NULL); + + i = pBatch->iDoneUpTo; /* all messages below that index are processed */ + iElemProcessed = 0; + iCommittedUpTo = i; + DBGPRINTF("tryDoAction %p, pnElem %d, nElem %d\n", pAction, *pnElem, pBatch->nElem); + while(iElemProcessed <= *pnElem && i < pBatch->nElem) { + if(*(pBatch->pbShutdownImmediate)) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + /* NOTE: do NOT extend the filter below! Anything else must be done on the + * enq side of the queue (see file header comment)! -- rgerhards, 2011-06-15 + */ + if(batchIsValidElem(pBatch, i)) { + pMsg = pBatch->pElem[i].pMsg; + localRet = actionProcessMessage(pAction, pMsg, pBatch->pElem[i].staticActParams, + pBatch->pbShutdownImmediate); + DBGPRINTF("action %p call returned %d\n", pAction, localRet); + /* Note: we directly modify the batch object state, because we know that + * wo do not overwrite BATCH_STATE_DISC indicators! + */ + if(localRet == RS_RET_OK) { + /* mark messages as committed */ + while(iCommittedUpTo <= i) { + pBatch->pElem[iCommittedUpTo].bPrevWasSuspended = 0; /* we had success! */ + batchSetElemState(pBatch, iCommittedUpTo, BATCH_STATE_COMM); + ++iCommittedUpTo; + //pBatch->pElem[iCommittedUpTo++].state = BATCH_STATE_COMM; + } + } else if(localRet == RS_RET_PREVIOUS_COMMITTED) { + /* mark messages as committed */ + while(iCommittedUpTo < i) { + pBatch->pElem[iCommittedUpTo].bPrevWasSuspended = 0; /* we had success! */ + batchSetElemState(pBatch, iCommittedUpTo, BATCH_STATE_COMM); + ++iCommittedUpTo; + //pBatch->pElem[iCommittedUpTo++].state = BATCH_STATE_COMM; + } + pBatch->eltState[i] = BATCH_STATE_SUB; + } else if(localRet == RS_RET_DEFER_COMMIT) { + pBatch->eltState[i] = BATCH_STATE_SUB; + } else if(localRet == RS_RET_DISCARDMSG) { + pBatch->eltState[i] = BATCH_STATE_DISC; + } else { + dbgprintf("tryDoAction: unexpected error code %d[nElem %d, Commited UpTo %d], finalizing\n", + localRet, *pnElem, iCommittedUpTo); + iRet = localRet; + FINALIZE; + } + } + ++i; + ++iElemProcessed; + } + +finalize_it: + if(pBatch->iDoneUpTo != iCommittedUpTo) { + pBatch->iDoneUpTo = iCommittedUpTo; + } + RETiRet; +} + +/* submit a batch for actual action processing. + * The first nElem elements are processed. This function calls itself + * recursively if it needs to handle errors. + * Note: we don't need the number of the first message to be processed as a parameter, + * because this is kept track of inside the batch itself (iDoneUpTo). + * rgerhards, 2009-05-12 + */ +static rsRetVal +submitBatch(action_t *pAction, batch_t *pBatch, int nElem) +{ + int i; + int bDone; + rsRetVal localRet; + int wasDoneTo; + DEFiRet; + + assert(pBatch != NULL); + + DBGPRINTF("submitBatch: enter, nElem %d\n", nElem); + wasDoneTo = pBatch->iDoneUpTo; + bDone = 0; + do { + localRet = tryDoAction(pAction, pBatch, &nElem); + if(localRet == RS_RET_FORCE_TERM) { + ABORT_FINALIZE(RS_RET_FORCE_TERM); + } + if( localRet == RS_RET_OK + || localRet == RS_RET_PREVIOUS_COMMITTED + || localRet == RS_RET_DEFER_COMMIT) { + /* try commit transaction, once done, we can simply do so as if + * that return state was returned from tryDoAction(). + */ + localRet = finishBatch(pAction, pBatch); + } + + if( localRet == RS_RET_OK + || localRet == RS_RET_PREVIOUS_COMMITTED + || localRet == RS_RET_DEFER_COMMIT) { + bDone = 1; + } else if(localRet == RS_RET_SUSPENDED) { + DBGPRINTF("action ret RS_RET_SUSPENDED - retry full batch\n"); + /* do nothing, this will retry the full batch */ + } else if(localRet == RS_RET_ACTION_FAILED) { + /* in this case, everything not yet committed is BAD */ + for(i = pBatch->iDoneUpTo ; i < wasDoneTo + nElem ; ++i) { + if( pBatch->eltState[i] != BATCH_STATE_DISC + && pBatch->eltState[i] != BATCH_STATE_COMM ) { + pBatch->eltState[i] = BATCH_STATE_BAD; + pBatch->pElem[i].bPrevWasSuspended = 1; + STATSCOUNTER_INC(pAction->ctrFail, pAction->mutCtrFail); + } + } + bDone = 1; + } else { + if(nElem == 1) { + batchSetElemState(pBatch, pBatch->iDoneUpTo, BATCH_STATE_BAD); + bDone = 1; + } else { + /* retry with half as much. Depth is log_2 batchsize, so recursion is not too deep */ + DBGPRINTF("submitBatch recursing trying to find and exclude the culprit " + "for iRet %d\n", localRet); + submitBatch(pAction, pBatch, nElem / 2); + submitBatch(pAction, pBatch, nElem - (nElem / 2)); + bDone = 1; + } + } + } while(!bDone && !*(pBatch->pbShutdownImmediate)); /* do .. while()! */ + + if(*(pBatch->pbShutdownImmediate)) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + +finalize_it: + RETiRet; +} + + +/* copy "active" array of batch, as we need to modify it. The caller + * must make sure the new array is freed and the orginal batch + * pointer is restored (thus the caller must save it). If active + * is currently NULL, this is properly handled. + * Note: the batches active pointer is modified, so it must be + * saved BEFORE calling this function! + * rgerhards, 2012-09-12 + */ +static rsRetVal +copyActive(batch_t *pBatch) +{ + sbool *active; + DEFiRet; + + CHKmalloc(active = malloc(batchNumMsgs(pBatch) * sizeof(sbool))); + if(pBatch->active == NULL) + memset(active, 1, batchNumMsgs(pBatch)); + else + memcpy(active, pBatch->active, batchNumMsgs(pBatch)); + pBatch->active = active; +finalize_it: + RETiRet; +} + +/* The following function prepares a batch for processing, that it is + * reinitializes batch states, generates strings and does everything else + * that needs to be done in order to make the batch ready for submission to + * the actual output module. Note that we look at the precomputed + * filter OK condition and process only those messages, that actually matched + * the filter. + * rgerhards, 2010-06-14 + */ +static inline rsRetVal +prepareBatch(action_t *pAction, batch_t *pBatch, sbool **activeSave, int *bMustRestoreActivePtr) +{ + int i; + batch_obj_t *pElem; + struct syslogTime ttNow; + DEFiRet; + + /* indicate we have not yet read the date */ + ttNow.year = 0; + + pBatch->iDoneUpTo = 0; + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + pElem = &(pBatch->pElem[i]); + if(batchIsValidElem(pBatch, i)) { + pBatch->eltState[i] = BATCH_STATE_RDY; + if(prepareDoActionParams(pAction, pElem, &ttNow) != RS_RET_OK) { + /* make sure we have our copy of "active" array */ + if(!*bMustRestoreActivePtr) { + *activeSave = pBatch->active; + copyActive(pBatch); + } + pBatch->active[i] = RSFALSE; + } + } + } + RETiRet; +} + + +/* receive a batch and process it. This includes retry handling. + * rgerhards, 2009-05-12 + */ +static inline rsRetVal +processAction(action_t *pAction, batch_t *pBatch) +{ + DEFiRet; + + assert(pBatch != NULL); + CHKiRet(submitBatch(pAction, pBatch, pBatch->nElem)); + iRet = finishBatch(pAction, pBatch); + +finalize_it: + RETiRet; +} + + +#pragma GCC diagnostic ignored "-Wempty-body" +/* receive an array of to-process user pointers and submit them + * for processing. + * rgerhards, 2009-04-22 + */ +static rsRetVal +processBatchMain(action_t *pAction, batch_t *pBatch, int *pbShutdownImmediate) +{ + int *pbShutdownImmdtSave; + sbool *activeSave; + int bMustRestoreActivePtr = 0; + rsRetVal localRet; + DEFiRet; + + assert(pBatch != NULL); + + if(pbShutdownImmediate != NULL) { + pbShutdownImmdtSave = pBatch->pbShutdownImmediate; + pBatch->pbShutdownImmediate = pbShutdownImmediate; + } + CHKiRet(prepareBatch(pAction, pBatch, &activeSave, &bMustRestoreActivePtr)); + + /* We now must guard the output module against execution by multiple threads. The + * plugin interface specifies that output modules must not be thread-safe (except + * if they notify us they are - functionality not yet implemented...). + * rgerhards, 2008-01-30 + */ + d_pthread_mutex_lock(&pAction->mutActExec); + pthread_cleanup_push(mutexCancelCleanup, &pAction->mutActExec); + + iRet = processAction(pAction, pBatch); + + pthread_cleanup_pop(1); /* unlock mutex */ + + /* even if processAction failed, we need to release the batch (else we + * have a memory leak). So we do this first, and then check if we need to + * return an error code. If so, the code from processAction has priority. + * rgerhards, 2010-12-17 + */ + localRet = releaseBatch(pAction, pBatch); + + if(iRet == RS_RET_OK) + iRet = localRet; + + if(bMustRestoreActivePtr) { + free(pBatch->active); + pBatch->active = activeSave; + } + +finalize_it: + if(pbShutdownImmediate != NULL) + pBatch->pbShutdownImmediate = pbShutdownImmdtSave; + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* call the HUP handler for a given action, if such a handler is defined. The + * action mutex is locked, because the HUP handler most probably needs to modify + * some internal state information. + * rgerhards, 2008-10-22 + */ +#pragma GCC diagnostic ignored "-Wempty-body" +rsRetVal +actionCallHUPHdlr(action_t *pAction) +{ + DEFiRet; + + ASSERT(pAction != NULL); + DBGPRINTF("Action %p checks HUP hdlr: %p\n", pAction, pAction->pMod->doHUP); + + if(pAction->pMod->doHUP == NULL) { + FINALIZE; /* no HUP handler, so we are done ;) */ + } + + d_pthread_mutex_lock(&pAction->mutActExec); + pthread_cleanup_push(mutexCancelCleanup, &pAction->mutActExec); + CHKiRet(pAction->pMod->doHUP(pAction->pModData)); + pthread_cleanup_pop(1); /* unlock mutex */ + +finalize_it: + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* set the action message queue mode + * TODO: probably move this into queue object, merge with MainMsgQueue! + * rgerhards, 2008-01-28 + */ +static rsRetVal setActionQueType(void __attribute__((unused)) *pVal, uchar *pszType) +{ + DEFiRet; + + if (!strcasecmp((char *) pszType, "fixedarray")) { + cs.ActionQueType = QUEUETYPE_FIXED_ARRAY; + DBGPRINTF("action queue type set to FIXED_ARRAY\n"); + } else if (!strcasecmp((char *) pszType, "linkedlist")) { + cs.ActionQueType = QUEUETYPE_LINKEDLIST; + DBGPRINTF("action queue type set to LINKEDLIST\n"); + } else if (!strcasecmp((char *) pszType, "disk")) { + cs.ActionQueType = QUEUETYPE_DISK; + DBGPRINTF("action queue type set to DISK\n"); + } else if (!strcasecmp((char *) pszType, "direct")) { + cs.ActionQueType = QUEUETYPE_DIRECT; + DBGPRINTF("action queue type set to DIRECT (no queueing at all)\n"); + } else { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "unknown actionqueue parameter: %s", (char *) pszType); + iRet = RS_RET_INVALID_PARAMS; + } + d_free(pszType); /* no longer needed */ + + RETiRet; +} + + +/* This submits the message to the action queue in case we do NOT need to handle repeat + * message processing. That case permits us to gain lots of freedom during processing + * and thus speed. This is also utilized to submit messages in complex case once + * the complex logic has been applied ;) + * rgerhards, 2010-06-08 + */ +static inline rsRetVal +doSubmitToActionQ(action_t *pAction, msg_t *pMsg) +{ + DEFiRet; + + if(pAction->eState == ACT_STATE_DIED) { + DBGPRINTF("action %p died, do NOT execute\n", pAction); + FINALIZE; + } + + STATSCOUNTER_INC(pAction->ctrProcessed, pAction->mutCtrProcessed); + if(pAction->pQueue->qType == QUEUETYPE_DIRECT) + iRet = qqueueEnqMsgDirect(pAction->pQueue, MsgAddRef(pMsg)); + else + iRet = qqueueEnqMsg(pAction->pQueue, eFLOWCTL_NO_DELAY, MsgAddRef(pMsg)); + +finalize_it: + RETiRet; +} + + +/* This function builds up a batch of messages to be (later) + * submitted to the action queue. + * Important: this function MUST not be called with messages that are to + * be discarded due to their "prevWasSuspended" state. It will not check for + * this and submit all messages to the queue for execution. So these must + * be filtered out before calling us (what is done currently!). + */ +rsRetVal +actionWriteToAction(action_t *pAction, msg_t *pMsg) +{ + DEFiRet; + + /* first, we check if the action should actually be called. The action-specific + * $ActionExecOnlyEveryNthTime permits us to execute an action only every Nth + * time. So we need to check if we need to drop the (otherwise perfectly executable) + * action for this reason. Note that in case we need to drop it, we return RS_RET_OK + * as the action was properly "passed to execution" from the upper layer's point + * of view. -- rgerhards, 2008-08-07. + */ + if(pAction->iExecEveryNthOccur > 1) { + /* we need to care about multiple occurences */ + if( pAction->iExecEveryNthOccurTO > 0 + && (getActNow(pAction) - pAction->tLastOccur) > pAction->iExecEveryNthOccurTO) { + DBGPRINTF("n-th occurence handling timed out (%d sec), restarting from 0\n", + (int) (getActNow(pAction) - pAction->tLastOccur)); + pAction->iNbrNoExec = 0; + pAction->tLastOccur = getActNow(pAction); + } + if(pAction->iNbrNoExec < pAction->iExecEveryNthOccur - 1) { + ++pAction->iNbrNoExec; + DBGPRINTF("action %p passed %d times to execution - less than neded - discarding\n", + pAction, pAction->iNbrNoExec); + FINALIZE; + } else { + pAction->iNbrNoExec = 0; /* we execute the action now, so the number of no execs is down to */ + } + } + + DBGPRINTF("Called action(complex case), logging to %s\n", module.GetStateName(pAction->pMod)); + + /* now check if we need to drop the message because otherwise the action would be too + * frequently called. -- rgerhards, 2008-04-08 + * Note that the check for "pAction->iSecsExecOnceInterval > 0" is not necessary from + * a purely logical point of view. However, if safes us to check the system time in + * (those common) cases where ExecOnceInterval is not used. -- rgerhards, 2008-09-16 + */ + if(pAction->iSecsExecOnceInterval > 0 && + pAction->iSecsExecOnceInterval + pAction->tLastExec > getActNow(pAction)) { + /* in this case we need to discard the message - its not yet time to exec the action */ + DBGPRINTF("action not yet ready again to be executed, onceInterval %d, tCurr %d, tNext %d\n", + (int) pAction->iSecsExecOnceInterval, (int) getActNow(pAction), + (int) (pAction->iSecsExecOnceInterval + pAction->tLastExec)); + FINALIZE; + } + + /* we use reception time, not dequeue time - this is considered more appropriate and also faster ;) + * rgerhards, 2008-09-17 */ + pAction->tLastExec = getActNow(pAction); /* re-init time flags */ + pAction->f_time = pMsg->ttGenTime; + + /* When we reach this point, we have a valid, non-disabled action. + * So let's enqueue our message for execution. -- rgerhards, 2007-07-24 + */ + iRet = doSubmitToActionQ(pAction, pMsg); + +finalize_it: + RETiRet; +} + + +/* helper to actonCallAction, mostly needed because of this damn + * pthread_cleanup_push() POSIX macro... + */ +static inline rsRetVal +doActionCallAction(action_t *pAction, batch_t *pBatch, int idxBtch) +{ + msg_t *pMsg; + DEFiRet; + + pMsg = pBatch->pElem[idxBtch].pMsg; + pAction->tActNow = -1; /* we do not yet know our current time (clear prev. value) */ + + /* don't output marks to recently written outputs */ + if(pAction->bWriteAllMarkMsgs == RSFALSE + && (pMsg->msgFlags & MARK) && (getActNow(pAction) - pAction->f_time) < MarkInterval / 2) { + ABORT_FINALIZE(RS_RET_OK); + } + + /* call the output driver */ + iRet = actionWriteToAction(pAction, pMsg); + +finalize_it: + /* we need to update the batch to handle failover processing correctly */ + if(iRet == RS_RET_OK) { + pBatch->pElem[idxBtch].bPrevWasSuspended = 0; + } else if(iRet == RS_RET_ACTION_FAILED) { + pBatch->pElem[idxBtch].bPrevWasSuspended = 1; + } + + RETiRet; +} + + +/* helper to activateActions, it activates a specific action. + */ +DEFFUNC_llExecFunc(doActivateActions) +{ + rsRetVal localRet; + action_t *pThis = (action_t*) pData; + BEGINfunc + localRet = qqueueStart(pThis->pQueue); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "error starting up action queue"); + if(localRet == RS_RET_FILE_PREFIX_MISSING) { + errmsg.LogError(0, localRet, "file prefix (work directory?) " + "is missing"); + } + actionDisable(pThis); + } + DBGPRINTF("Action %s[%p]: queue %p started\n", modGetName(pThis->pMod), + pThis, pThis->pQueue); + ENDfunc + return RS_RET_OK; /* we ignore errors, we can not do anything either way */ +} + + +/* This function "activates" the action after privileges have been dropped. Currently, + * this means that the queues are started. + * rgerhards, 2011-05-02 + */ +rsRetVal +activateActions(void) +{ + DEFiRet; + iRet = ruleset.IterateAllActions(ourConf, doActivateActions, NULL); + RETiRet; +} + + + +/* This submits the message to the action queue in case where we need to handle + * bWriteAllMarkMessage == RSFALSE only. Note that we use a non-blocking CAS loop + * for the synchronization. Here, we just modify the filter condition to be false when + * a mark message must not be written. However, in this case we must save the previous + * filter as we may need it in the next action (potential future optimization: check if this is + * the last action TODO). + * rgerhards, 2010-06-08 + */ +static rsRetVal +doSubmitToActionQNotAllMarkBatch(action_t *pAction, batch_t *pBatch) +{ + time_t now = 0; + time_t lastAct; + int i; + sbool *activeSave; + DEFiRet; + + activeSave = pBatch->active; + copyActive(pBatch); + + for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) { + if((pBatch->eltState[i] == BATCH_STATE_DISC) || !pBatch->active[i]) + continue; + if(now == 0) { + now = datetime.GetTime(NULL); /* good time call - the only one done */ + } + /* CAS loop, we write back a bit early, but that's OK... */ + /* we use reception time, not dequeue time - this is considered more appropriate and + * also faster ;) -- rgerhards, 2008-09-17 */ + do { + lastAct = pAction->f_time; + if(pBatch->pElem[i].pMsg->msgFlags & MARK) { + if((now - lastAct) < MarkInterval / 2) { + pBatch->active[i] = 0; + DBGPRINTF("batch item %d: action was recently called, ignoring " + "mark message\n", i); + break; /* do not update timestamp for non-written mark messages */ + } + } + } while(ATOMIC_CAS_time_t(&pAction->f_time, lastAct, + pBatch->pElem[i].pMsg->ttGenTime, &pAction->mutCAS) == 0); + if(pBatch->active[i]) { + DBGPRINTF("Called action(NotAllMark), processing batch[%d] via '%s'\n", + i, module.GetStateName(pAction->pMod)); + } + } + + iRet = doSubmitToActionQBatch(pAction, pBatch); + + free(pBatch->active); + pBatch->active = activeSave; + + RETiRet; +} + +static inline void +countStatsBatchEnq(action_t *pAction, batch_t *pBatch) +{ + int i; + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + if( batchIsValidElem(pBatch, i)) { + STATSCOUNTER_INC(pAction->ctrProcessed, pAction->mutCtrProcessed); + } + } +} + + +/* enqueue a batch in direct mode. We have put this into its own function just to avoid + * cluttering the actual submit function. + * rgerhards, 2011-06-16 + */ +static inline rsRetVal +doQueueEnqObjDirectBatch(action_t *pAction, batch_t *pBatch) +{ + sbool bNeedSubmit; + sbool *activeSave; + int i; + DEFiRet; + + activeSave = pBatch->active; + copyActive(pBatch); + + /* note: for direct mode, we need to adjust the filter property. For non-direct + * this is not necessary, because in that case we enqueue only what actually needs + * to be processed. + */ + if(pAction->bExecWhenPrevSusp) { + bNeedSubmit = 0; + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + if(!pBatch->pElem[i].bPrevWasSuspended) { + DBGPRINTF("action enq stage: change active to 0 due to " + "failover case in elem %d\n", i); + pBatch->active[i] = 0; + } + if(batchIsValidElem(pBatch, i)) { + STATSCOUNTER_INC(pAction->ctrProcessed, pAction->mutCtrProcessed); + bNeedSubmit = 1; + } + DBGPRINTF("action %p[%d]: valid:%d state:%d execWhenPrev:%d prevWasSusp:%d\n", + pAction, i, batchIsValidElem(pBatch, i), pBatch->eltState[i], + pAction->bExecWhenPrevSusp, pBatch->pElem[i].bPrevWasSuspended); + } + if(bNeedSubmit) { + /* note: stats were already computed above */ + iRet = qqueueEnqObjDirectBatch(pAction->pQueue, pBatch); + } else { + DBGPRINTF("no need to submit batch, all invalid\n"); + } + } else { + if(GatherStats) + countStatsBatchEnq(pAction, pBatch); + iRet = qqueueEnqObjDirectBatch(pAction->pQueue, pBatch); + } + + free(pBatch->active); + pBatch->active = activeSave; + RETiRet; +} + +/* This submits the message to the action queue in case we do NOT need to handle repeat + * message processing. That case permits us to gain lots of freedom during processing + * and thus speed. + * rgerhards, 2010-06-08 + */ +static rsRetVal +doSubmitToActionQBatch(action_t *pAction, batch_t *pBatch) +{ + int i; + DEFiRet; + + DBGPRINTF("Called action(Batch), logging to %s\n", module.GetStateName(pAction->pMod)); + + if(pAction->pQueue->qType == QUEUETYPE_DIRECT) { + iRet = doQueueEnqObjDirectBatch(pAction, pBatch); + } else {/* in this case, we do single submits to the queue. + * TODO: optimize this, we may do at least a multi-submit! + */ + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + DBGPRINTF("action %p: valid:%d state:%d execWhenPrev:%d prevWasSusp:%d\n", + pAction, batchIsValidElem(pBatch, i), pBatch->eltState[i], + pAction->bExecWhenPrevSusp, pBatch->pElem[i].bPrevWasSuspended); + if( batchIsValidElem(pBatch, i) + && (pAction->bExecWhenPrevSusp == 0 || pBatch->pElem[i].bPrevWasSuspended == 1)) { + doSubmitToActionQ(pAction, pBatch->pElem[i].pMsg); + } + } + } + + RETiRet; +} + + + +/* Helper to submit a batch of actions to the engine. Note that we have rather + * complicated processing here, so we need to do this one message after another. + * rgerhards, 2010-06-23 + */ +static inline rsRetVal +helperSubmitToActionQComplexBatch(action_t *pAction, batch_t *pBatch) +{ + int i; + DEFiRet; + + DBGPRINTF("Called action %p (complex case), logging to %s\n", + pAction, module.GetStateName(pAction->pMod)); + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + DBGPRINTF("action %p: valid:%d state:%d execWhenPrev:%d prevWasSusp:%d\n", + pAction, batchIsValidElem(pBatch, i), pBatch->eltState[i], + pAction->bExecWhenPrevSusp, pBatch->pElem[i].bPrevWasSuspended); + if( batchIsValidElem(pBatch, i) + && ((pAction->bExecWhenPrevSusp == 0) || pBatch->pElem[i].bPrevWasSuspended) ) { + doActionCallAction(pAction, pBatch, i); + } + } + + RETiRet; +} + +/* Call configured action, most complex case with all features supported (and thus slow). + * rgerhards, 2010-06-08 + */ +#pragma GCC diagnostic ignored "-Wempty-body" +static rsRetVal +doSubmitToActionQComplexBatch(action_t *pAction, batch_t *pBatch) +{ + DEFiRet; + + d_pthread_mutex_lock(&pAction->mutAction); + pthread_cleanup_push(mutexCancelCleanup, &pAction->mutAction); + iRet = helperSubmitToActionQComplexBatch(pAction, pBatch); + d_pthread_mutex_unlock(&pAction->mutAction); + pthread_cleanup_pop(0); /* remove mutex cleanup handler */ + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* apply all params from param block to action. This supports the v6 config system. + * Defaults must have been set appropriately during action construct! + * rgerhards, 2011-08-01 + */ +static rsRetVal +actionApplyCnfParam(action_t *pAction, struct cnfparamvals *pvals) +{ + int i; + + for(i = 0 ; i < pblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblk.descr[i].name, "name")) { + pAction->pszName = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblk.descr[i].name, "type")) { + continue; /* this is handled seperately during module select! */ + } else if(!strcmp(pblk.descr[i].name, "action.writeallmarkmessages")) { + pAction->bWriteAllMarkMsgs = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.execonlyeverynthtime")) { + pAction->iExecEveryNthOccur = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.execonlyeverynthtimetimeout")) { + pAction->iExecEveryNthOccurTO = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.execonlyonceeveryinterval")) { + pAction->iSecsExecOnceInterval = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.execonlywhenpreviousissuspended")) { + pAction->bExecWhenPrevSusp = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.repeatedmsgcontainsoriginalmsg")) { + pAction->bRepMsgHasMsg = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.resumeretrycount")) { + pAction->iResumeRetryCount = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "action.resumeinterval")) { + pAction->iResumeInterval = pvals[i].val.d.n; + } else { + dbgprintf("action: program error, non-handled " + "param '%s'\n", pblk.descr[i].name); + } + } + return RS_RET_OK; +} + + +/* add an Action to the current selector + * The pOMSR is freed, as it is not needed after this function. + * Note: this function pulls global data that specifies action config state. + * rgerhards, 2007-07-27 + */ +rsRetVal +addAction(action_t **ppAction, modInfo_t *pMod, void *pModData, + omodStringRequest_t *pOMSR, struct cnfparamvals *actParams, + struct nvlst *lst, int bSuspended) +{ + DEFiRet; + int i; + int iTplOpts; + uchar *pTplName; + action_t *pAction; + char errMsg[512]; + + assert(ppAction != NULL); + assert(pMod != NULL); + assert(pOMSR != NULL); + DBGPRINTF("Module %s processes this action.\n", module.GetName(pMod)); + + CHKiRet(actionConstruct(&pAction)); /* create action object first */ + pAction->pMod = pMod; + pAction->pModData = pModData; + if(actParams == NULL) { /* use legacy systemn */ + pAction->pszName = cs.pszActionName; + pAction->iResumeInterval = cs.glbliActionResumeInterval; + pAction->iResumeRetryCount = cs.glbliActionResumeRetryCount; + pAction->bWriteAllMarkMsgs = cs.bActionWriteAllMarkMsgs; + pAction->bExecWhenPrevSusp = cs.bActExecWhenPrevSusp; + pAction->iSecsExecOnceInterval = cs.iActExecOnceInterval; + pAction->iExecEveryNthOccur = cs.iActExecEveryNthOccur; + pAction->iExecEveryNthOccurTO = cs.iActExecEveryNthOccurTO; + pAction->bRepMsgHasMsg = cs.bActionRepMsgHasMsg; + cs.iActExecEveryNthOccur = 0; /* auto-reset */ + cs.iActExecEveryNthOccurTO = 0; /* auto-reset */ + cs.bActionWriteAllMarkMsgs = RSFALSE; /* auto-reset */ + cs.pszActionName = NULL; /* free again! */ + } else { + actionApplyCnfParam(pAction, actParams); + } + + /* check if we can obtain the template pointers - TODO: move to separate function? */ + pAction->iNumTpls = OMSRgetEntryCount(pOMSR); + assert(pAction->iNumTpls >= 0); /* only debug check because this "can not happen" */ + /* please note: iNumTpls may validly be zero. This is the case if the module + * does not request any templates. This sounds unlikely, but an actual example is + * the discard action, which does not require a string. -- rgerhards, 2007-07-30 + */ + if(pAction->iNumTpls > 0) { + /* we first need to create the template pointer array */ + CHKmalloc(pAction->ppTpl = (struct template **)calloc(pAction->iNumTpls, sizeof(struct template *))); + } + + for(i = 0 ; i < pAction->iNumTpls ; ++i) { + CHKiRet(OMSRgetEntry(pOMSR, i, &pTplName, &iTplOpts)); + /* Ok, we got everything, so it now is time to look up the template + * (Hint: templates MUST be defined before they are used!) + */ + if( !(iTplOpts & OMSR_TPL_AS_MSG) + && (pAction->ppTpl[i] = + tplFind(ourConf, (char*)pTplName, strlen((char*)pTplName))) == NULL) { + snprintf(errMsg, sizeof(errMsg) / sizeof(char), + " Could not find template '%s' - action disabled", + pTplName); + errno = 0; + errmsg.LogError(0, RS_RET_NOT_FOUND, "%s", errMsg); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + /* check required template options */ + if( (iTplOpts & OMSR_RQD_TPL_OPT_SQL) + && (pAction->ppTpl[i]->optFormatEscape == 0)) { + errno = 0; + errmsg.LogError(0, RS_RET_RQD_TPLOPT_MISSING, "Action disabled. To use this action, you have to specify " + "the SQL or stdSQL option in your template!\n"); + ABORT_FINALIZE(RS_RET_RQD_TPLOPT_MISSING); + } + + /* set parameter-passing mode */ + if(iTplOpts & OMSR_TPL_AS_ARRAY) { + pAction->eParamPassing = ACT_ARRAY_PASSING; + } else if(iTplOpts & OMSR_TPL_AS_MSG) { + pAction->eParamPassing = ACT_MSG_PASSING; + } else if(iTplOpts & OMSR_TPL_AS_JSON) { + pAction->eParamPassing = ACT_JSON_PASSING; + } else { + pAction->eParamPassing = ACT_STRING_PASSING; + } + + DBGPRINTF("template: '%s' assigned\n", pTplName); + } + + pAction->pMod = pMod; + pAction->pModData = pModData; + /* check if the module is compatible with select features (currently no such features exist) */ + pAction->eState = ACT_STATE_RDY; /* action is enabled */ + + if(bSuspended) + actionSuspend(pAction); + + CHKiRet(actionConstructFinalize(pAction, lst)); + + /* TODO: if we exit here, we have a memory leak... */ + + *ppAction = pAction; /* finally store the action pointer */ + +finalize_it: + if(iRet == RS_RET_OK) + iRet = OMSRdestruct(pOMSR); + else { + /* do not overwrite error state! */ + OMSRdestruct(pOMSR); + if(pAction != NULL) + actionDestruct(pAction); + } + + RETiRet; +} + + +/* Reset config variables to default values. + * rgerhards, 2009-11-12 + */ +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.iActExecOnceInterval = 0; + cs.bActExecWhenPrevSusp = 0; + return RS_RET_OK; +} + + +/* initialize (current) config variables. + * Used at program start and when a new scope is created. + */ +static inline void +initConfigVariables(void) +{ + cs.bActionWriteAllMarkMsgs = RSFALSE; + cs.glbliActionResumeRetryCount = 0; + cs.bActExecWhenPrevSusp = 0; + cs.iActExecOnceInterval = 0; + cs.iActExecEveryNthOccur = 0; + cs.iActExecEveryNthOccurTO = 0; + cs.glbliActionResumeInterval = 30; + cs.glbliActionResumeRetryCount = 0; + cs.bActionRepMsgHasMsg = 0; + if(cs.pszActionName != NULL) { + free(cs.pszActionName); + cs.pszActionName = NULL; + } + actionResetQueueParams(); +} + + +rsRetVal +actionNewInst(struct nvlst *lst, action_t **ppAction) +{ + struct cnfparamvals *paramvals; + modInfo_t *pMod; + uchar *cnfModName = NULL; + omodStringRequest_t *pOMSR; + void *pModData; + action_t *pAction; + int typeIdx; + DEFiRet; + + paramvals = nvlstGetParams(lst, &pblk, NULL); + if(paramvals == NULL) { + ABORT_FINALIZE(RS_RET_ERR); + } + dbgprintf("action param blk after actionNewInst:\n"); + cnfparamsPrint(&pblk, paramvals); + typeIdx = cnfparamGetIdx(&pblk, "type"); + if(paramvals[typeIdx].bUsed == 0) { + errmsg.LogError(0, RS_RET_CONF_RQRD_PARAM_MISSING, "action type missing"); + ABORT_FINALIZE(RS_RET_CONF_RQRD_PARAM_MISSING); // TODO: move this into rainerscript handlers + } + cnfModName = (uchar*)es_str2cstr(paramvals[cnfparamGetIdx(&pblk, ("type"))].val.d.estr, NULL); + if((pMod = module.FindWithCnfName(loadConf, cnfModName, eMOD_OUT)) == NULL) { + errmsg.LogError(0, RS_RET_MOD_UNKNOWN, "module name '%s' is unknown", cnfModName); + ABORT_FINALIZE(RS_RET_MOD_UNKNOWN); + } + iRet = pMod->mod.om.newActInst(cnfModName, lst, &pModData, &pOMSR); + // TODO: check if RS_RET_SUSPENDED is still valid in v6! + if(iRet != RS_RET_OK && iRet != RS_RET_SUSPENDED) { + FINALIZE; /* iRet is already set to error state */ + } + + if((iRet = addAction(&pAction, pMod, pModData, pOMSR, paramvals, lst, + (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) { + /* check if the module is compatible with select features + * (currently no such features exist) */ + pAction->eState = ACT_STATE_RDY; /* action is enabled */ + loadConf->actions.nbrActions++; /* one more active action! */ + } + *ppAction = pAction; + +finalize_it: + free(cnfModName); + cnfparamvalsDestruct(paramvals, &pblk); + RETiRet; +} + +/* TODO: we are not yet a real object, the ClassInit here just looks like it is.. + */ +rsRetVal actionClassInit(void) +{ + DEFiRet; + /* request objects we use */ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + + CHKiRet(regCfSysLineHdlr((uchar *)"actionname", 0, eCmdHdlrGetWord, NULL, &cs.pszActionName, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuefilename", 0, eCmdHdlrGetWord, NULL, &cs.pszActionQFName, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuesize", 0, eCmdHdlrInt, NULL, &cs.iActionQueueSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionwriteallmarkmessages", 0, eCmdHdlrBinary, NULL, &cs.bActionWriteAllMarkMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeuebatchsize", 0, eCmdHdlrInt, NULL, &cs.iActionQueueDeqBatchSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuemaxdiskspace", 0, eCmdHdlrSize, NULL, &cs.iActionQueMaxDiskSpace, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuehighwatermark", 0, eCmdHdlrInt, NULL, &cs.iActionQHighWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuelowwatermark", 0, eCmdHdlrInt, NULL, &cs.iActionQLowWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuediscardmark", 0, eCmdHdlrInt, NULL, &cs.iActionQDiscardMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuediscardseverity", 0, eCmdHdlrInt, NULL, &cs.iActionQDiscardSeverity, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuecheckpointinterval", 0, eCmdHdlrInt, NULL, &cs.iActionQPersistUpdCnt, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuesyncqueuefiles", 0, eCmdHdlrBinary, NULL, &cs.bActionQSyncQeueFiles, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetype", 0, eCmdHdlrGetWord, setActionQueType, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueueworkerthreads", 0, eCmdHdlrInt, NULL, &cs.iActionQueueNumWorkers, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetimeoutshutdown", 0, eCmdHdlrInt, NULL, &cs.iActionQtoQShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetimeoutactioncompletion", 0, eCmdHdlrInt, NULL, &cs.iActionQtoActShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetimeoutenqueue", 0, eCmdHdlrInt, NULL, &cs.iActionQtoEnq, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueueworkertimeoutthreadshutdown", 0, eCmdHdlrInt, NULL, &cs.iActionQtoWrkShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueueworkerthreadminimummessages", 0, eCmdHdlrInt, NULL, &cs.iActionQWrkMinMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuemaxfilesize", 0, eCmdHdlrSize, NULL, &cs.iActionQueMaxFileSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuesaveonshutdown", 0, eCmdHdlrBinary, NULL, &cs.bActionQSaveOnShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeueslowdown", 0, eCmdHdlrInt, NULL, &cs.iActionQueueDeqSlowdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeuetimebegin", 0, eCmdHdlrInt, NULL, &cs.iActionQueueDeqtWinFromHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeuetimeend", 0, eCmdHdlrInt, NULL, &cs.iActionQueueDeqtWinToHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyeverynthtime", 0, eCmdHdlrInt, NULL, &cs.iActExecEveryNthOccur, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyeverynthtimetimeout", 0, eCmdHdlrInt, NULL, &cs.iActExecEveryNthOccurTO, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyonceeveryinterval", 0, eCmdHdlrInt, NULL, &cs.iActExecOnceInterval, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgcontainsoriginalmsg", 0, eCmdHdlrBinary, NULL, &cs.bActionRepMsgHasMsg, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlywhenpreviousissuspended", 0, eCmdHdlrBinary, NULL, &cs.bActExecWhenPrevSusp, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeretrycount", 0, eCmdHdlrInt, NULL, &cs.glbliActionResumeRetryCount, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); + + initConfigVariables(); /* first-time init of config setings */ + +finalize_it: + RETiRet; +} + +/* vi:set ai: + */ diff --git a/action.h b/action.h new file mode 100644 index 00000000..54cdb54c --- /dev/null +++ b/action.h @@ -0,0 +1,107 @@ +/* action.h + * Header file for the action object + * + * File begun on 2007-08-06 by RGerhards (extracted from syslogd.c, which + * was under BSD license at the time of rsyslog fork) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ACTION_H_INCLUDED +#define ACTION_H_INCLUDED 1 + +#include "syslogd-types.h" +#include "queue.h" + +/* external data - this is to be removed when we change the action + * object interface (will happen some time..., at latest when the + * config file format is changed). -- rgerhards, 2008-01-28 + */ +extern int glbliActionResumeRetryCount; + + +typedef enum { + ACT_STATE_DIED = 0, /* action permanently failed and now disabled - MUST BE ZERO! */ + ACT_STATE_RDY = 1, /* action ready, waiting for new transaction */ + ACT_STATE_ITX = 2, /* transaction active, waiting for new data or commit */ + ACT_STATE_COMM = 3, /* transaction finished (a transient state) */ + ACT_STATE_RTRY = 4, /* failure occured, trying to restablish ready state */ + ACT_STATE_SUSP = 5 /* suspended due to failure (return fail until timeout expired) */ +} action_state_t; + +/* the following struct defines the action object data structure + */ +struct action_s { + time_t f_time; /* used for "max. n messages in m seconds" processing */ + time_t tActNow; /* the current time for an action execution. Initially set to -1 and + populated on an as-needed basis. This is a performance optimization. */ + time_t tLastExec; /* time this action was last executed */ + sbool bExecWhenPrevSusp;/* execute only when previous action is suspended? */ + sbool bWriteAllMarkMsgs;/* should all mark msgs be written (not matter how recent the action was executed)? */ + int iSecsExecOnceInterval; /* if non-zero, minimum seconds to wait until action is executed again */ + action_state_t eState; /* current state of action */ + sbool bHadAutoCommit; /* did an auto-commit happen during doAction()? */ + time_t ttResumeRtry; /* when is it time to retry the resume? */ + int iResumeOKinRow; /* number of times in a row that resume said OK with an immediate failure following */ + int iResumeInterval;/* resume interval for this action */ + int iResumeRetryCount;/* how often shall we retry a suspended action? (-1 --> eternal) */ + int iNbrResRtry; /* number of retries since last suspend */ + int iNbrNoExec; /* number of matches that did not yet yield to an exec */ + int iExecEveryNthOccur;/* execute this action only every n-th occurence (with n=0,1 -> always) */ + int iExecEveryNthOccurTO;/* timeout for n-th occurence feature */ + time_t tLastOccur; /* time last occurence was seen (for timing them out) */ + struct modInfo_s *pMod;/* pointer to output module handling this selector */ + void *pModData; /* pointer to module data - content is module-specific */ + sbool bRepMsgHasMsg; /* "message repeated..." has msg fragment in it (0-no, 1-yes) */ + rsRetVal (*submitToActQ)(action_t *, batch_t *);/* function submit message to action queue */ + rsRetVal (*qConstruct)(struct queue_s *pThis); + enum { ACT_STRING_PASSING = 0, ACT_ARRAY_PASSING = 1, ACT_MSG_PASSING = 2, + ACT_JSON_PASSING = 3} + eParamPassing; /* mode of parameter passing to action */ + int iNumTpls; /* number of array entries for template element below */ + struct template **ppTpl;/* array of template to use - strings must be passed to doAction + * in this order. */ + qqueue_t *pQueue; /* action queue */ + pthread_mutex_t mutAction; /* primary action mutex */ + pthread_mutex_t mutActExec; /* mutex to guard actual execution of doAction for single-threaded modules */ + uchar *pszName; /* action name (for documentation) */ + DEF_ATOMIC_HELPER_MUT(mutCAS); + /* for statistics subsystem */ + statsobj_t *statsobj; + STATSCOUNTER_DEF(ctrProcessed, mutCtrProcessed); + STATSCOUNTER_DEF(ctrFail, mutCtrFail); +}; + + +/* function prototypes + */ +rsRetVal actionConstruct(action_t **ppThis); +rsRetVal actionConstructFinalize(action_t *pThis, struct nvlst *lst); +rsRetVal actionDestruct(action_t *pThis); +rsRetVal actionDbgPrint(action_t *pThis); +rsRetVal actionSetGlobalResumeInterval(int iNewVal); +rsRetVal actionDoAction(action_t *pAction); +rsRetVal actionWriteToAction(action_t *pAction, msg_t *pMsg); +rsRetVal actionCallHUPHdlr(action_t *pAction); +rsRetVal actionClassInit(void); +rsRetVal addAction(action_t **ppAction, modInfo_t *pMod, void *pModData, omodStringRequest_t *pOMSR, struct cnfparamvals *actParams, struct nvlst *lst, int bSuspended); +rsRetVal activateActions(void); +rsRetVal actionNewInst(struct nvlst *lst, action_t **ppAction); +rsRetVal actionProcessCnf(struct cnfobj *o); + +#endif /* #ifndef ACTION_H_INCLUDED */ diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..c4055c50 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +(test -f $srcdir/configure.ac) || { + echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" + echo " top-level package directory" + exit 1 +} + +if test -z "$*"; then + echo "**Warning**: I am going to run \`configure' with no arguments." + echo "If you wish to pass any to it, please specify them on the" + echo \`$0\'" command line." + echo +fi + +(cd $srcdir && autoreconf --verbose --force --install) || exit 1 + +conf_flags="--cache-file=config.cache" + +if test x$NOCONFIGURE = x; then + echo Running $srcdir/configure $conf_flags "$@" ... + $srcdir/configure $conf_flags "$@" \ + && echo Now type \`make\' to compile. || exit 1 +else + echo Skipping configure process. +fi + + diff --git a/build/rhel/README b/build/rhel/README new file mode 100644 index 00000000..4c4ead76 --- /dev/null +++ b/build/rhel/README @@ -0,0 +1,17 @@ +This directory contains a series of spec files to help build rsyslog +and its dependencies on a RHEL 5 or 6 (or compatible variant) system. + +Build order: +(dependencies for rsyslog's mmnormalize) +1. libestr +2. libee +3. liblognorm + +(dependency for rsyslog) +4. librelp + +(external dependencies for zmq modules) +5. zeromq +6. czmq + +7. rsyslog diff --git a/build/rhel/dependencies/czmq.spec b/build/rhel/dependencies/czmq.spec new file mode 100644 index 00000000..9f0ff908 --- /dev/null +++ b/build/rhel/dependencies/czmq.spec @@ -0,0 +1,107 @@ +Name: czmq +Version: 1.2.0 +Release: 1%{?dist} +Summary: High-level C binding for 0MQ (ZeroMQ). + +Group: Development/Libraries +License: LGPL +URL: http://czmq.zeromq.org/ +Source0: %{name}-%{version}.tar.gz +BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) + +BuildRequires: zeromq-devel +BuildRequires: xmlto, xmltoman +BuildRequires: asciidoc +Requires: zeromq +Requires(post): /sbin/ldconfig + +%if %{?rhel}%{!?rhel:0} >= 6 +BuildRequires: libuuid-devel +Requires: libuuid +%elseif %{?rhel}%{!?rhel:0} >= 5 +BuildRequires: e2fsprogs-devel +Requires: e2fsprogs +%else +BuildRequires: uuid-devel +Requires: uuid +%endif + +%description +%{summary} + +%package devel +Summary: Development bindings for 0MQ (ZeroMQ). +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} +Requires: pkgconfig + +%description devel +Development bindings for 0MQ (ZeroMQ). + +%prep +%setup -q + +%build +export CFLAGS="-Wno-unused-variable" +%configure +make %{?_smp_mflags} + +%install +rm -rf $RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(0644,root,root,0755) +%doc AUTHORS COPYING* INSTALL README NEWS + +%{_mandir}/man7/zloop.7.gz +%{_mandir}/man7/zsockopt.7.gz +%{_mandir}/man7/zhash.7.gz +%{_mandir}/man7/zctx.7.gz +%{_mandir}/man7/zsocket.7.gz +%{_mandir}/man7/zframe.7.gz +%{_mandir}/man7/zfile.7.gz +%{_mandir}/man7/zthread.7.gz +%{_mandir}/man7/czmq.7.gz +%{_mandir}/man7/zstr.7.gz +%{_mandir}/man7/zclock.7.gz +%{_mandir}/man7/zmsg.7.gz +%{_mandir}/man7/zlist.7.gz +%{_libdir}/libczmq.so* +%attr(0755,root,root) %{_bindir}/czmq_selftest + +%files devel +%defattr(0644,root,root,0755) +%{_libdir}/libczmq.la +%{_libdir}/pkgconfig/libczmq.pc +%{_libdir}/libczmq.a +%{_includedir}/zsocket.h +%{_includedir}/czmq.h +%{_includedir}/zstr.h +%{_includedir}/zlist.h +%{_includedir}/zsockopt.h +%{_includedir}/zmsg.h +%{_includedir}/zclock.h +%{_includedir}/zthread.h +%{_includedir}/zloop.h +%{_includedir}/zframe.h +%{_includedir}/czmq_prelude.h +%{_includedir}/zhash.h +%{_includedir}/zctx.h +%{_includedir}/zfile.h + +%changelog + +* Tue Jun 12 2012 Abby Edwards <abby.lina.edwards@gmail.com> 1.2.0-1 +- updated to 1.2.0, split devel package out +- updated CFLAGS to be compatible with gcc 4+ + +* Thu Sep 29 2011 Lars Kellogg-Stedman <lars@seas.harvard.edu> - 1.1.0-1 +- initial package build + diff --git a/build/rhel/dependencies/zeromq.spec b/build/rhel/dependencies/zeromq.spec new file mode 100644 index 00000000..d1aa5f4c --- /dev/null +++ b/build/rhel/dependencies/zeromq.spec @@ -0,0 +1,142 @@ +Name: zeromq +Version: 2.2.0 +Release: 1%{?dist} +Summary: The ZeroMQ messaging library +Group: Applications/Internet +License: LGPLv3+ +URL: http://www.zeromq.org/ +Source: http://download.zeromq.org/%{name}-%{version}.tar.gz +Prefix: %{_prefix} +Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildRequires: gcc, make, gcc-c++, libstdc++-devel +Requires: libstdc++ + +%if %{?rhel}%{!?rhel:0} >= 6 +BuildRequires: libuuid-devel +Requires: libuuid +%elseif %{?rhel}%{!?rhel:0} >= 5 +BuildRequires: e2fsprogs-devel +Requires: e2fsprogs +%else +BuildRequires: uuid-devel +Requires: uuid +%endif + +# Build pgm only on supported archs +%ifarch pentium3 pentium4 athlon i386 i486 i586 i686 x86_64 +BuildRequires: glib2-devel +Requires: glib2 +%endif + +%description +The 0MQ lightweight messaging kernel is a library which extends the +standard socket interfaces with features traditionally provided by +specialised messaging middleware products. 0MQ sockets provide an +abstraction of asynchronous message queues, multiple messaging +patterns, message filtering (subscriptions), seamless access to +multiple transport protocols and more. + +This package contains the ZeroMQ shared library. + +%package devel +Summary: Development files and static library for the ZeroMQ library +Group: Development/Libraries +Requires: %{name} = %{version}-%{release}, pkgconfig + +%description devel +The 0MQ lightweight messaging kernel is a library which extends the +standard socket interfaces with features traditionally provided by +specialised messaging middleware products. 0MQ sockets provide an +abstraction of asynchronous message queues, multiple messaging +patterns, message filtering (subscriptions), seamless access to +multiple transport protocols and more. + +This package contains ZeroMQ related development libraries and header files. + +%prep +%setup -q + +%build +%ifarch pentium3 pentium4 athlon i386 i486 i586 i686 x86_64 + %configure --with-pgm +%else + %configure +%endif + +%{__make} %{?_smp_mflags} + +%install +[ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} + +# Install the package to build area +%{__make} check +%makeinstall + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%clean +[ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} + +%files +%defattr(-,root,root,-) + +# docs in the main package +%doc AUTHORS ChangeLog COPYING COPYING.LESSER NEWS README + +# libraries +%{_libdir}/libzmq.so.1 +%{_libdir}/libzmq.so.1.0.1 + +%{_mandir}/man7/zmq.7.gz + +%files devel +%defattr(-,root,root,-) +%{_includedir}/zmq.h +%{_includedir}/zmq.hpp +%{_includedir}/zmq_utils.h + +%{_libdir}/libzmq.la +%{_libdir}/libzmq.a +%{_libdir}/pkgconfig/libzmq.pc +%{_libdir}/libzmq.so + +%{_mandir}/man3/zmq_bind.3.gz +%{_mandir}/man3/zmq_close.3.gz +%{_mandir}/man3/zmq_connect.3.gz +%{_mandir}/man3/zmq_errno.3.gz +%{_mandir}/man3/zmq_device.3.gz +%{_mandir}/man3/zmq_getsockopt.3.gz +%{_mandir}/man3/zmq_init.3.gz +%{_mandir}/man3/zmq_msg_close.3.gz +%{_mandir}/man3/zmq_msg_copy.3.gz +%{_mandir}/man3/zmq_msg_data.3.gz +%{_mandir}/man3/zmq_msg_init.3.gz +%{_mandir}/man3/zmq_msg_init_data.3.gz +%{_mandir}/man3/zmq_msg_init_size.3.gz +%{_mandir}/man3/zmq_msg_move.3.gz +%{_mandir}/man3/zmq_msg_size.3.gz +%{_mandir}/man3/zmq_poll.3.gz +%{_mandir}/man3/zmq_recv.3.gz +%{_mandir}/man3/zmq_send.3.gz +%{_mandir}/man3/zmq_setsockopt.3.gz +%{_mandir}/man3/zmq_socket.3.gz +%{_mandir}/man3/zmq_strerror.3.gz +%{_mandir}/man3/zmq_term.3.gz +%{_mandir}/man3/zmq_version.3.gz +%{_mandir}/man7/zmq_cpp.7.gz +%{_mandir}/man7/zmq_epgm.7.gz +%{_mandir}/man7/zmq_inproc.7.gz +%{_mandir}/man7/zmq_ipc.7.gz +%{_mandir}/man7/zmq_pgm.7.gz +%{_mandir}/man7/zmq_tcp.7.gz + +%changelog +* Tue Jun 12 2012 Abby Edwards <abby.lina.edwards@gmail.com> 2.2.0-1 +- updated version + +* Sat Apr 10 2010 Mikko Koppanen <mkoppanen@php.net> 2.0.7-1 +- Initial packaging diff --git a/build/rhel/libee/libee.spec b/build/rhel/libee/libee.spec new file mode 100644 index 00000000..8bb3a378 --- /dev/null +++ b/build/rhel/libee/libee.spec @@ -0,0 +1,90 @@ +Summary: libee - an event expression library inspired by CEE +Name: libee +Version: 0.4.1 +Release: 1%{?dist} +License: GPL +Group: Networking/Admin +Source: %{name}-%{version}.tar.gz +BuildRoot: /var/tmp/%{name}-build +BuildRequires: libestr-devel +Requires: /sbin/ldconfig + +%description +CEE is an upcoming standard used to describe network events in a number of +normalized formats. It's goal is to unify they currently many different +representations that exist in the industry. + +The core idea of libee is to provide a small but hopefully convenient API layer +above the CEE standard. However, CEE is not finished. At the time of this writing, +CEE is under heavy development and even some of its core data structures (like +the data dictionary and taxonmy) have not been fully specified. + +libee should be thought of as a useful library that helps you get your events +normalized. If you program cleanly to libee, chances are not bad that only +relatively little effort is required to move your app over to be CEE compliant +(once the standard is out). + +%package devel +Summary: include files for libee +Group: Networking/Admin +Requires: %name = %version-%release +Requires: /usr/bin/pkg-config + +%description devel +This package provides include files and pkg-config settings for libee. + +%prep +%setup -q -n %{name}-%{version} + +%build +%configure CFLAGS="%{optflags}" --prefix=%{_prefix} --mandir=%{_mandir} --infodir=%{_infodir} +%{__make} + +%install +rm -rf $RPM_BUILD_ROOT +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(644,root,root,755) +%{_libdir}/libee.so +%{_libdir}/libee.so.0 +%{_libdir}/libee.so.0.0.0 + +%files devel +%defattr(644,root,root,755) +%{_libdir}/pkgconfig/libee.pc +%{_includedir}/libee/apache.h +%{_includedir}/libee/ctx.h +%{_includedir}/libee/event.h +%{_includedir}/libee/field.h +%{_includedir}/libee/fieldbucket.h +%{_includedir}/libee/fieldset.h +%{_includedir}/libee/fieldtype.h +%{_includedir}/libee/int.h +%{_includedir}/libee/internal.h +%{_includedir}/libee/libee.h +%{_includedir}/libee/namelist.h +%{_includedir}/libee/obj.h +%{_includedir}/libee/parser.h +%{_includedir}/libee/primitivetype.h +%{_includedir}/libee/tag.h +%{_includedir}/libee/tagbucket.h +%{_includedir}/libee/tagset.h +%{_includedir}/libee/timestamp.h +%{_includedir}/libee/valnode.h +%{_includedir}/libee/value.h +%{_includedir}/libee/valuetype.h +%{_sbindir}/libee-convert +%{_libdir}/libee.a +%{_libdir}/libee.la + +%changelog + +* Tue Jun 12 2012 Abby Edwards <abby.lina.edwards@gmail.com> 0.4.1-1 +- initial version, used to build latest git master diff --git a/build/rhel/libestr/libestr.spec b/build/rhel/libestr/libestr.spec new file mode 100644 index 00000000..10589d88 --- /dev/null +++ b/build/rhel/libestr/libestr.spec @@ -0,0 +1,56 @@ +Summary: libestr - some essentials for string handling (and a bit more) +Name: libestr +Version: 0.1.2 +Release: 1%{?dist} +License: GPL +Group: Networking/Admin +Source: %{name}-%{version}.tar.gz +BuildRoot: /var/tmp/%{name}-build +Requires: /sbin/ldconfig + +%description +libestr is a string handling library used in rsyslog. + +%package devel +Summary: includes for compilation against libestr +Group: Networking/Admin +Requires: %name = %version-%release +Requires: /usr/bin/pkg-config + +%description devel +This package provides the include files and pkg-config information for +compiling against libestr. + +%prep +%setup -q -n %{name}-%{version} + +%build +%configure CFLAGS="%{optflags}" --prefix=%{_prefix} --mandir=%{_mandir} --infodir=%{_infodir} +%{__make} + +%install +rm -rf $RPM_BUILD_ROOT +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(644,root,root,755) +%{_libdir}/libestr.so +%{_libdir}/libestr.so.0 +%{_libdir}/libestr.so.0.0.0 + +%files devel +%defattr(644,root,root,755) +%{_includedir}/libestr.h +%{_libdir}/pkgconfig/libestr.pc +%{_libdir}/libestr.a +%{_libdir}/libestr.la + +%changelog +* Tue Jun 12 2012 Abby Edwards <abby.lina.edwards@gmail.com> 0.1.2-1 +- initial version, used to build latest git master diff --git a/build/rhel/liblognorm/liblognorm.spec b/build/rhel/liblognorm/liblognorm.spec new file mode 100644 index 00000000..2047525e --- /dev/null +++ b/build/rhel/liblognorm/liblognorm.spec @@ -0,0 +1,70 @@ +Summary: liblognorm - a tool to normalize log data +Name: liblognorm +Version: 0.3.4 +Release: 1%{?dist} +License: GPL +Group: Networking/Admin +Source: %{name}-%{version}.tar.gz +BuildRoot: /var/tmp/%{name}-build +BuildRequires: libestr-devel, libee-devel +Requires: /sbin/ldconfig + +%description +Briefly described, liblognorm is a tool to normalize log data. + +People who need to take a look at logs often have a common problem. Logs from +different machines (from different vendors) usually have different formats for +their logs. Even if it is the same type of log (e.g. from firewalls), the log +entries are so different, that it is pretty hard to read these. This is where +liblognorm comes into the game. With this tool you can normalize all your logs. +All you need is liblognorm and its dependencies and a sample database that fits +the logs you want to normalize. + +%package devel +Summary: includes for compiling against liblognorm +Group: Networking/Admin +Requires: %name = %version-%release +Requires: /usr/bin/pkg-config + +%description devel +This package provides the include and pkg-config files for compiling +against liblognorm. + +%prep +%setup -q -n %{name}-%{version} + +%build +%configure CFLAGS="%{optflags}" --prefix=%{_prefix} --mandir=%{_mandir} --infodir=%{_infodir} +%{__make} + +%install +rm -rf $RPM_BUILD_ROOT +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(644,root,root,755) +%{_libdir}/liblognorm.so +%{_libdir}/liblognorm.so.0 +%{_libdir}/liblognorm.so.0.0.0 + +%files devel +%defattr(644,root,root,755) +%{_libdir}/pkgconfig/lognorm.pc +%{_includedir}/annot.h +%{_includedir}/liblognorm.h +%{_includedir}/lognorm.h +%{_includedir}/ptree.h +%{_includedir}/samp.h +%{_bindir}/normalizer +%{_libdir}/liblognorm.a +%{_libdir}/liblognorm.la + +%changelog +* Tue Jun 12 2012 Abby Edwards <abby.lina.edwards@gmail.com> 0.3.4-1 +- initial version, used to build latest git master diff --git a/build/rhel/librelp/librelp.spec b/build/rhel/librelp/librelp.spec new file mode 100644 index 00000000..04f78dc0 --- /dev/null +++ b/build/rhel/librelp/librelp.spec @@ -0,0 +1,65 @@ +Summary: librelp - a reliable logging library +Name: librelp +Version: 1.0.1 +Release: 1%{?dist} +License: GPL +Group: Networking/Admin +Source: %{name}-%{version}.tar.gz +BuildRoot: /var/tmp/%{name}-build +Requires: /sbin/ldconfig + +%description +librelp is an easy to use library for the RELP protocol. RELP in turn provides reliable +event logging over the network (and consequently RELP stands for Reliable Event Logging +Protocol). RELP was initiated by Rainer Gerhards after he was finally upset by the lossy +nature of plain tcp syslog and wanted a cure for all these dangling issues. + +RELP (and hence) librelp assures that no message is lost, not even when connections +break and a peer becomes unavailable. The current version of RELP has a minimal window +of opportunity for message duplication after a session has been broken due to network +problems. In this case, a few messages may be duplicated (a problem that also exists +with plain tcp syslog). Future versions of RELP will address this shortcoming. + +%package devel +Summary: includes for compilation against librelp +Group: Networking/Admin +Requires: %name = %version-%release +Requires: /usr/bin/pkg-config + +%description devel +This package provides include and pkg-config files for compiling against +librelp. + +%prep +%setup -q -n %{name}-%{version} + +%build +%configure CFLAGS="%{optflags}" --prefix=%{_prefix} --mandir=%{_mandir} --infodir=%{_infodir} +%{__make} + +%install +rm -rf $RPM_BUILD_ROOT +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(644,root,root,755) +%{_libdir}/librelp.so.0.0.0 +%{_libdir}/librelp.so.0 +%{_libdir}/librelp.so + +%files devel +%defattr(644,root,root,755) +%{_includedir}/librelp.h +%{_libdir}/pkgconfig/relp.pc +%{_libdir}/librelp.a +%{_libdir}/librelp.la + +%changelog +* Tue Jun 12 2012 Abby Edwards <abby.lina.edwards@gmail.com> 1.0.1-1 +- initial version, used to build latest git master diff --git a/build/rhel/rsyslog/rsyslog.conf b/build/rhel/rsyslog/rsyslog.conf new file mode 100644 index 00000000..36cea98f --- /dev/null +++ b/build/rhel/rsyslog/rsyslog.conf @@ -0,0 +1,80 @@ +# rsyslog v5 configuration file + +# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html +# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html + +#### MODULES #### + +$ModLoad imuxsock # provides support for local system logging (e.g. via logger command) +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# Provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 + +# Provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +#### GLOBAL DIRECTIVES #### + +# Use default timestamp format +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# File syncing capability is disabled by default. This feature is usually not required, +# not useful and an extreme performance hit +#$ActionFileEnableSync on + +# Include all config files in /etc/rsyslog.d/ +$IncludeConfig /etc/rsyslog.d/*.conf + + +#### RULES #### + +# Log all kernel messages to the console. +# Logging much else clutters up the screen. +#kern.* /dev/console + +# Log anything (except mail) of level info or higher. +# Don't log private authentication messages! +*.info;mail.none;authpriv.none;cron.none /var/log/messages + +# The authpriv file has restricted access. +authpriv.* /var/log/secure + +# Log all the mail messages in one place. +mail.* -/var/log/maillog + + +# Log cron stuff +cron.* /var/log/cron + +# Everybody gets emergency messages +*.emerg * + +# Save news errors of level crit and higher in a special file. +uucp,news.crit /var/log/spooler + +# Save boot messages also to boot.log +local7.* /var/log/boot.log + + +# ### begin forwarding rule ### +# The statement between the begin ... end define a SINGLE forwarding +# rule. They belong together, do NOT split them. If you create multiple +# forwarding rules, duplicate the whole block! +# Remote Logging (we use TCP for reliable delivery) +# +# An on-disk queue is created for this action. If the remote host is +# down, messages are spooled to disk and sent when it is up again. +#$WorkDirectory /var/lib/rsyslog # where to place spool files +#$ActionQueueFileName fwdRule1 # unique name prefix for spool files +#$ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible) +#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown +#$ActionQueueType LinkedList # run asynchronously +#$ActionResumeRetryCount -1 # infinite retries if host is down +# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional +#*.* @@remote-host:514 +# ### end of the forwarding rule ### diff --git a/build/rhel/rsyslog/rsyslog.init b/build/rhel/rsyslog/rsyslog.init new file mode 100644 index 00000000..ed4b1b1f --- /dev/null +++ b/build/rhel/rsyslog/rsyslog.init @@ -0,0 +1,121 @@ +#!/bin/bash +# +# rsyslog Startup script for rsyslog. +# +# chkconfig: 2345 12 88 +# description: Syslog is the facility by which many daemons use to log \ +# messages to various system log files. It is a good idea to always \ +# run rsyslog. +### BEGIN INIT INFO +# Provides: $syslog +# Required-Start: $local_fs +# Required-Stop: $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Enhanced system logging and kernel message trapping daemons +# Description: Rsyslog is an enhanced multi-threaded syslogd supporting, +# among others, MySQL, syslog/tcp, RFC 3195, permitted +# sender lists, filtering on any message part, and fine +# grain output format control. +### END INIT INFO + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 +PIDFILE=/var/run/syslogd.pid + +prog=rsyslog +exec=/sbin/rsyslogd +lockfile=/var/lock/subsys/$prog + +# Source config +if [ -f /etc/sysconfig/$prog ] ; then + . /etc/sysconfig/$prog +fi + +start() { + [ -x $exec ] || exit 5 + + umask 077 + + syntaxcheck + + RETVAL=$? + if [ $RETVAL -ne 0 ] ; then + echo "Refusing to start system logger due to syntax errors; correct to continue." + return $RETVAL + fi + + echo -n $"Starting rsyslog: " + daemon --pidfile="${PIDFILE}" $exec -i "${PIDFILE}" $SYSLOGD_OPTIONS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch $lockfile + return $RETVAL +} +stop() { + echo -n $"Shutting down system logger: " + killproc -p "${PIDFILE}" $exec + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f $lockfile + return $RETVAL +} +rhstatus() { + status -p "${PIDFILE}" -l $prog $exec +} +restart() { + stop + start +} + +syntaxcheck() { + [ -x $exec ] || exit 5 + + echo "Checking rsyslog configuration syntax: " + $exec $SYSLOGD_OPTIONS -n -N1 + echo + + RETVAL=$? + + if [ $RETVAL -eq 0 ] ; then + echo "syntax is okay" + else + echo "configuration has syntax errors" + fi + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload) + exit 3 + ;; + force-reload) + restart + ;; + status) + rhstatus + ;; + condrestart|try-restart) + rhstatus >/dev/null 2>&1 || exit 0 + restart + ;; + syntaxcheck) + syntaxcheck + ;; + *) + echo $"Usage: $0 {start|stop|restart|condrestart|try-restart|reload|force-reload|status}" + exit 3 +esac + +exit $? diff --git a/build/rhel/rsyslog/rsyslog.log b/build/rhel/rsyslog/rsyslog.log new file mode 100644 index 00000000..0e3fc160 --- /dev/null +++ b/build/rhel/rsyslog/rsyslog.log @@ -0,0 +1,8 @@ +/var/log/cron /var/log/maillog /var/log/messages /var/log/secure /var/log/spooler +{ + missingok + sharedscripts + postrotate + /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true + endscript +} diff --git a/build/rhel/rsyslog/rsyslog.spec b/build/rhel/rsyslog/rsyslog.spec new file mode 100644 index 00000000..7bb367d4 --- /dev/null +++ b/build/rhel/rsyslog/rsyslog.spec @@ -0,0 +1,801 @@ +%global _exec_prefix %{nil} +%global _libdir %{_exec_prefix}/%{_lib} +%define rsyslog_statedir %{_sharedstatedir}/rsyslog +%define rsyslog_pkidir %{_sysconfdir}/pki/rsyslog + +Summary: Enhanced system logging and kernel message trapping daemon +Name: rsyslog +Version: 6.5.0 +Release: 1%{?dist} +License: (GPLv3+ and ASL 2.0) +Group: System Environment/Daemons +URL: http://www.rsyslog.com/ +Source0: %{name}-%{version}.tar.gz +Source1: rsyslog.init +Source2: rsyslog.conf +Source3: rsyslog.sysconfig +Source4: rsyslog.log + +BuildRequires: zlib-devel +BuildRequires: pkgconfig +BuildRequires: flex +BuildRequires: bison + +%if %{?rhel}%{!?rhel:0} >= 6 +BuildRequires: libcurl-devel +Requires: libcurl +%elseif %{?rhel}%{!?rhel:0} >= 5 +BuildRequires: curl-devel +Requires: curl +%endif + +Requires: logrotate >= 3.5.2 +Requires: bash >= 2.0 +Requires(post): /sbin/chkconfig coreutils +Requires(post): /sbin/service +Requires(preun): /sbin/chkconfig +Requires(preun): /sbin/service +Requires(postun): /sbin/service + +Provides: syslog +Obsoletes: sysklogd < 1.5-11 + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%package elasticsearch +Summary: ElasticSearch output module for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release + +%package mmcount +Summary: Message counting support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release + +%package mmjsonparse +Summary: JSON enhanced logging support +Group: System Environment/Daemons +Requires: %name = %version-%release + +%package mmnormalize +Summary: Log normalization support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: libestr-devel libee-devel liblognorm-devel + +%package imzmq3 +Summary: ZMQ input module support +Group: Systems Environment/Daemons +Requires: %name = %version-%release +BuildRequires: czmq-devel +BuildRequires: zeromq-devel +%if %{?rhel}%{!?rhel:0} >= 6 +BuildRequires: libuuid-devel +Requires: libuuid +%elseif %{?rhel}%{!?rhel:0} >= 5 +BuildRequires: e2fsprogs-devel +Requires: e2fsprogs +%else +BuildRequires: uuid-devel +Requires: uuid +%endif + + +%package omzmq3 +Summary: ZMQ output module support +Group: Systems Environment/Daemons +Requires: %name = %version-%release +BuildRequires: czmq-devel +BuildRequires: zeromq-devel +%if %{?rhel}%{!?rhel:0} >= 6 +BuildRequires: libuuid-devel +Requires: libuuid +%elseif %{?rhel}%{!?rhel:0} >= 5 +BuildRequires: e2fsprogs-devel +Requires: e2fsprogs +%else +BuildRequires: uuid-devel +Requires: uuid +%endif + + +%package libdbi +Summary: libdbi database support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: libdbi-devel + +%package mysql +Summary: MySQL support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: mysql-devel >= 4.0 + +%package pgsql +Summary: PostgresSQL support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: postgresql-devel + +%package gssapi +Summary: GSSAPI authentication and encryption support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: krb5-devel + +%package relp +Summary: RELP protocol support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: librelp-devel + +%package gnutls +Summary: TLS protocol support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: gnutls-devel + +%package snmp +Summary: SNMP protocol support for rsyslog +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: net-snmp-devel + +%package udpspoof +Summary: Provides the omudpspoof module +Group: System Environment/Daemons +Requires: %name = %version-%release +BuildRequires: libnet-devel + +%description +Rsyslog is an enhanced, multi-threaded syslog daemon. It supports MySQL, +syslog/TCP, RFC 3195, permitted sender lists, filtering on any message part, +and fine grain output format control. It is compatible with stock sysklogd +and can be used as a drop-in replacement. Rsyslog is simple to set up, with +advanced features suitable for enterprise-class, encryption-protected syslog +relay chains. + +%description elasticsearch +This module provides the capability for rsyslog to feed logs directly into +Elasticsearch. + +%description mmcount +This module provides the capability to count log messages by severity +or json property of given app-name. The count value is added into the +log message in json property named 'mmcount' + +%description mmjsonparse +This module provides the capability to recognize and parse JSON enhanced +syslog messages. + +%description mmnormalize +This module provides the capability to normalize log messages via liblognorm. + +%description imzmq3 +This module provides the capability to use a zeromq socket for input. + +%description omzmq3 +This module provides the capability to use a zeromq socket for output. + +%description libdbi +This module supports a large number of database systems via +libdbi. Libdbi abstracts the database layer and provides drivers for +many systems. Drivers are available via the libdbi-drivers project. + +%description mysql +The rsyslog-mysql package contains a dynamic shared object that will add +MySQL database support to rsyslog. + +%description pgsql +The rsyslog-pgsql package contains a dynamic shared object that will add +PostgreSQL database support to rsyslog. + +%description gssapi +The rsyslog-gssapi package contains the rsyslog plugins which support GSSAPI +authentication and secure connections. GSSAPI is commonly used for Kerberos +authentication. + +%description relp +The rsyslog-relp package contains the rsyslog plugins that provide +the ability to receive syslog messages via the reliable RELP +protocol. + +%description gnutls +The rsyslog-gnutls package contains the rsyslog plugins that provide the +ability to receive syslog messages via upcoming syslog-transport-tls +IETF standard protocol. + +%description snmp +The rsyslog-snmp package contains the rsyslog plugin that provides the +ability to send syslog messages as SNMPv1 and SNMPv2c traps. + +%description udpspoof +This module is similar to the regular UDP forwarder, but permits to +spoof the sender address. Also, it enables to circle through a number +of source ports. + +%prep +%setup -q +#%patch0 -p1 +#%patch1 -p1 +#%patch2 -p1 +#%patch3 -p1 +#%patch4 -p1 +#%patch5 -p1 + +%build +%ifarch sparc64 +#sparc64 need big PIE +export CFLAGS="$RPM_OPT_FLAGS -fPIE -DSYSLOGD_PIDNAME=\\\"syslogd.pid\\\"" +export LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now" +%else +export CFLAGS="$RPM_OPT_FLAGS -fpie -DSYSLOGD_PIDNAME=\\\"syslogd.pid\\\"" +export LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now" +%endif +export PKG_CONFIG=/usr/bin/pkg-config +%configure --disable-static \ + --disable-testbench \ + --enable-elasticsearch \ + --enable-mmcount \ + --enable-mmjsonparse \ + --enable-mmnormalize \ + --enable-imzmq3 \ + --enable-omzmq3 \ + --enable-gnutls \ + --enable-gssapi-krb5 \ + --enable-imfile \ + --enable-impstats \ + --enable-imptcp \ + --enable-libdbi \ + --enable-mail \ + --enable-mysql \ + --enable-omprog \ + --enable-omudpspoof \ + --enable-omuxsock \ + --enable-pgsql \ + --enable-pmlastmsg \ + --enable-relp \ + --enable-snmp \ + --enable-unlimited-select +make + +%install +rm -rf $RPM_BUILD_ROOT + +make install DESTDIR=$RPM_BUILD_ROOT + +install -d -m 755 $RPM_BUILD_ROOT%{_initrddir} +install -d -m 755 $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig +install -d -m 755 $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d +install -d -m 755 $RPM_BUILD_ROOT%{_sysconfdir}/rsyslog.d +install -d -m 700 $RPM_BUILD_ROOT%{rsyslog_statedir} +install -d -m 700 $RPM_BUILD_ROOT%{rsyslog_pkidir} + +install -p -m 755 %{SOURCE1} $RPM_BUILD_ROOT%{_initrddir}/rsyslog +install -p -m 644 %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}/rsyslog.conf +install -p -m 644 %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/rsyslog +install -p -m 644 %{SOURCE4} $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/syslog + +#get rid of *.la +rm $RPM_BUILD_ROOT/%{_libdir}/rsyslog/*.la + +%clean +rm -rf $RPM_BUILD_ROOT + +%post +for n in /var/log/{messages,secure,maillog,spooler} +do + [ -f $n ] && continue + umask 066 && touch $n +done +if [ $1 -eq 1 ]; then + # On install (not upgrade), enable (but don't start) + /sbin/chkconfig --add rsyslog + /sbin/chkconfig rsyslog on +fi + +%preun +if [ $1 = 0 ]; then + # On uninstall (not upgrade), disable and stop the units + /sbin/chkconfig --del rsyslog >/dev/null 2>&1 || : + /sbin/service rsyslog stop >/dev/null 2>&1 || : +fi + +%postun +if [ $1 -ge 1 ] ; then + # On upgrade (not uninstall), optionally, restart the daemon + /sbin/service rsyslog restart >/dev/null 2>&1 || : +fi + +%files +%defattr(-,root,root,-) +%doc AUTHORS COPYING* NEWS README ChangeLog doc/*html +%dir %{_libdir}/rsyslog +%{_libdir}/rsyslog/imfile.so +%{_libdir}/rsyslog/imklog.so +%{_libdir}/rsyslog/immark.so +%{_libdir}/rsyslog/impstats.so +%{_libdir}/rsyslog/imptcp.so +%{_libdir}/rsyslog/imtcp.so +%{_libdir}/rsyslog/imudp.so +%{_libdir}/rsyslog/imuxsock.so +%{_libdir}/rsyslog/lmnet.so +%{_libdir}/rsyslog/lmnetstrms.so +%{_libdir}/rsyslog/lmnsd_ptcp.so +%{_libdir}/rsyslog/lmregexp.so +%{_libdir}/rsyslog/lmstrmsrv.so +%{_libdir}/rsyslog/lmtcpclt.so +%{_libdir}/rsyslog/lmtcpsrv.so +%{_libdir}/rsyslog/lmzlibw.so +%{_libdir}/rsyslog/omtesting.so +%{_libdir}/rsyslog/ommail.so +%{_libdir}/rsyslog/omprog.so +%{_libdir}/rsyslog/omruleset.so +%{_libdir}/rsyslog/omuxsock.so +%{_libdir}/rsyslog/pmlastmsg.so +%{_initrddir}/rsyslog + +%config(noreplace) %{_sysconfdir}/rsyslog.conf +%config(noreplace) %{_sysconfdir}/sysconfig/rsyslog +%config(noreplace) %{_sysconfdir}/logrotate.d/syslog +%dir %{_sysconfdir}/rsyslog.d +%dir %{rsyslog_statedir} +%dir %{rsyslog_pkidir} +%{_sbindir}/rsyslogd +%{_mandir}/*/* + +%attr(0755,root,root) %{_initrddir}/rsyslog + +%files elasticsearch +%defattr(-,root,root) +%{_libdir}/rsyslog/omelasticsearch.so + +%files mmcount +%defattr(-,root,root) +%{_libdir}/rsyslog/mmcount.so + +%files mmjsonparse +%defattr(-,root,root) +%{_libdir}/rsyslog/mmjsonparse.so + +%files mmnormalize +%defattr(-,root,root) +%{_libdir}/rsyslog/mmnormalize.so + +%files imzmq3 +%defattr(-,root,root) +%{_libdir}/rsyslog/imzmq3.so + +%files omzmq3 +%defattr(-,root,root) +%{_libdir}/rsyslog/omzmq3.so + +%files libdbi +%defattr(-,root,root) +%{_libdir}/rsyslog/omlibdbi.so + +%files mysql +%defattr(-,root,root) +%doc plugins/ommysql/createDB.sql +%{_libdir}/rsyslog/ommysql.so + +%files pgsql +%defattr(-,root,root) +%doc plugins/ompgsql/createDB.sql +%{_libdir}/rsyslog/ompgsql.so + +%files gssapi +%defattr(-,root,root) +%{_libdir}/rsyslog/lmgssutil.so +%{_libdir}/rsyslog/imgssapi.so +%{_libdir}/rsyslog/omgssapi.so + +%files relp +%defattr(-,root,root) +%{_libdir}/rsyslog/imrelp.so +%{_libdir}/rsyslog/omrelp.so + +%files gnutls +%defattr(-,root,root) +%{_libdir}/rsyslog/lmnsd_gtls.so + +%files snmp +%defattr(-,root,root) +%{_libdir}/rsyslog/omsnmp.so + +%files udpspoof +%defattr(-,root,root) +%{_libdir}/rsyslog/omudpspoof.so + +%changelog + +* Wed Jun 06 2012 Abigail Edwards <abby.lina.edwards@gmail.com> 6.5.0-1 +- convert Fedora spec into RHEL 6 spec, as far as init scripts are concerned +- removed references to all Fedora specific patches, as they were mostly + version specific +- added syntax check option to the init script, made a successful check + a prequisite to starting the daemon +- added several "--enable-*" directives to configure to create as many + subpackages as possible; enduser will need to have requisite dependencies + installed, some of which I had to write separate specs for. + +* Wed May 23 2012 Tomas Heinrich <theinric@redhat.com> 5.8.11-1 +- upgrade to new upstream stable version 5.8.11 +- add impstats and imptcp modules +- include new license text files +- consider lock file in 'status' action +- add patch to update information on debugging in the man page +- add patch to prevent debug output to stdout after forking +- add patch to support ssl certificates with domain names longer than 128 chars + +* Fri Mar 30 2012 Jon Ciesla <limburgher@gmail.com> 5.8.7-2 +- libnet rebuild. + +* Mon Jan 23 2012 Tomas Heinrich <theinric@redhat.com> 5.8.7-1 +- upgrade to new upstream version 5.8.7 +- change license from 'GPLv3+' to '(GPLv3+ and ASL 2.0)' + http://blog.gerhards.net/2012/01/rsyslog-licensing-update.html +- use a specific version for obsoleting sysklogd +- add patches for better sysklogd compatibility (taken from upstream) + +* Sat Jan 14 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 5.8.6-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Tue Oct 25 2011 Tomas Heinrich <theinric@redhat.com> 5.8.6-1 +- upgrade to new upstream version 5.8.6 +- obsolete sysklogd + Resolves: #748495 + +* Tue Oct 11 2011 Tomas Heinrich <theinric@redhat.com> 5.8.5-3 +- modify logrotate configuration to omit boot.log + Resolves: #745093 + +* Mon Sep 06 2011 Tomas Heinrich <theinric@redhat.com> 5.8.5-2 +- add systemd-units to BuildRequires for the _unitdir macro definition + +* Mon Sep 05 2011 Tomas Heinrich <theinric@redhat.com> 5.8.5-1 +- upgrade to new upstream version (CVE-2011-3200) + +* Fri Jul 22 2011 Tomas Heinrich <theinric@redhat.com> 5.8.2-3 +- move the SysV init script into a subpackage +- Resolves: 697533 + +* Mon Jul 11 2011 Tomas Heinrich <theinric@redhat.com> 5.8.2-2 +- rebuild for net-snmp-5.7 (soname bump in libnetsnmp) + +* Mon Jun 27 2011 Tomas Heinrich <theinric@redhat.com> 5.8.2-1 +- upgrade to new upstream version 5.8.2 + +* Mon Jun 13 2011 Tomas Heinrich <theinric@redhat.com> 5.8.1-2 +- scriptlet correction +- use macro in unit file's path + +* Fri May 20 2011 Tomas Heinrich <theinric@redhat.com> 5.8.1-1 +- upgrade to new upstream version +- correct systemd scriptlets (#705829) + +* Mon May 16 2011 Bill Nottingham <notting@redhat.com> - 5.7.9-3 +- combine triggers (as rpm will only execute one) - fixes upgrades (#699198) + +* Tue Apr 05 2011 Tomas Heinrich <theinric@redhat.com> 5.7.10-1 +- upgrade to new upstream version 5.7.10 + +* Wed Mar 23 2011 Dan Horák <dan@danny.cz> - 5.7.9-2 +- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) + +* Fri Mar 18 2011 Tomas Heinrich <theinric@redhat.com> 5.7.9-1 +- upgrade to new upstream version 5.7.9 +- enable compilation of several new modules, + create new subpackages for some of them +- integrate changes from Lennart Poettering + to add support for systemd + - add rsyslog-5.7.9-systemd.patch to tweak the upstream + service file to honour configuration from /etc/sysconfig/rsyslog + +* Fri Mar 18 2011 Dennis Gilmore <dennis@ausil.us> - 5.6.2-3 +- sparc64 needs big PIE + +* Wed Feb 09 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 5.6.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Mon Dec 20 2010 Tomas Heinrich <theinric@redhat.com> 5.6.2-1 +- upgrade to new upstream stable version 5.6.2 +- drop rsyslog-5.5.7-remove_include.patch; applied upstream +- provide omsnmp module +- use correct name for lock file (#659398) +- enable specification of the pid file (#579411) +- init script adjustments + +* Wed Oct 06 2010 Tomas Heinrich <theinric@redhat.com> 5.5.7-1 +- upgrade to upstream version 5.5.7 +- update configuration and init files for the new major version +- add several directories for storing auxiliary data +- add ChangeLog to documentation +- drop unlimited-select.patch; integrated upstream +- add rsyslog-5.5.7-remove_include.patch to fix compilation + +* Tue Sep 07 2010 Tomas Heinrich <theinric@redhat.com> 4.6.3-2 +- build rsyslog with PIE and RELRO + +* Thu Jul 15 2010 Tomas Heinrich <theinric@redhat.com> 4.6.3-1 +- upgrade to new upstream stable version 4.6.3 + +* Wed Apr 07 2010 Tomas Heinrich <theinric@redhat.com> 4.6.2-1 +- upgrade to new upstream stable version 4.6.2 +- correct the default value of the OMFileFlushOnTXEnd directive + +* Thu Feb 11 2010 Tomas Heinrich <theinric@redhat.com> 4.4.2-6 +- modify rsyslog-4.4.2-unlimited-select.patch so that + running autoreconf is not needed +- remove autoconf, automake, libtool from BuildRequires +- change exec-prefix to nil + +* Wed Feb 10 2010 Tomas Heinrich <theinric@redhat.com> 4.4.2-5 +- remove '_smp_mflags' make argument as it seems to be + producing corrupted builds + +* Mon Feb 08 2010 Tomas Heinrich <theinric@redhat.com> 4.4.2-4 +- redefine _libdir as it doesn't use _exec_prefix + +* Thu Dec 17 2009 Tomas Heinrich <theinric@redhat.com> 4.4.2-3 +- change exec-prefix to / + +* Wed Dec 09 2009 Robert Scheck <robert@fedoraproject.org> 4.4.2-2 +- run libtoolize to avoid errors due mismatching libtool version + +* Thu Dec 03 2009 Tomas Heinrich <theinric@redhat.com> 4.4.2-1 +- upgrade to new upstream stable version 4.4.2 +- add support for arbitrary number of open file descriptors + +* Mon Sep 14 2009 Tomas Heinrich <theinric@redhat.com> 4.4.1-2 +- adjust init script according to guidelines (#522071) + +* Thu Sep 03 2009 Tomas Heinrich <theinric@redhat.com> 4.4.1-1 +- upgrade to new upstream stable version + +* Fri Aug 21 2009 Tomas Mraz <tmraz@redhat.com> - 4.2.0-3 +- rebuilt with new openssl + +* Sun Jul 26 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 4.2.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + +* Tue Jul 14 2009 Tomas Heinrich <theinric@redhat.com> 4.2.0-1 +- upgrade + +* Mon Apr 13 2009 Tomas Heinrich <theinric@redhat.com> 3.21.11-1 +- upgrade + +* Tue Mar 31 2009 Lubomir Rintel <lkundrak@v3.sk> 3.21.10-4 +- Backport HUPisRestart option + +* Wed Mar 18 2009 Tomas Heinrich <theinric@redhat.com> 3.21.10-3 +- fix variables' type conversion in expression-based filters (#485937) + +* Wed Feb 25 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.21.10-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild + +* Tue Feb 10 2009 Tomas Heinrich <theinric@redhat.com> 3.21.10-1 +- upgrade + +* Sat Jan 24 2009 Caolán McNamara <caolanm@redhat.com> 3.21.9-3 +- rebuild for dependencies + +* Tue Jan 07 2009 Tomas Heinrich <theinric@redhat.com> 3.21.9-2 +- fix several legacy options handling +- fix internal message output (#478612) + +* Mon Dec 15 2008 Peter Vrabec <pvrabec@redhat.com> 3.21.9-1 +- update is fixing $AllowedSender security issue + +* Mon Sep 15 2008 Peter Vrabec <pvrabec@redhat.com> 3.21.3-4 +- use RPM_OPT_FLAGS +- use same pid file and logrotate file as syslog-ng (#441664) +- mark config files as noreplace (#428155) + +* Mon Sep 01 2008 Tomas Heinrich <theinric@redhat.com> 3.21.3-3 +- fix a wrong module name in the rsyslog.conf manual page (#455086) +- expand the rsyslog.conf manual page (#456030) + +* Thu Aug 28 2008 Tomas Heinrich <theinric@redhat.com> 3.21.3-2 +- fix clock rollback issue (#460230) + +* Wed Aug 20 2008 Peter Vrabec <pvrabec@redhat.com> 3.21.3-1 +- upgrade to bugfix release + +* Wed Jul 23 2008 Peter Vrabec <pvrabec@redhat.com> 3.21.0-1 +- upgrade + +* Mon Jul 14 2008 Peter Vrabec <pvrabec@redhat.com> 3.19.9-2 +- adjust default config file + +* Fri Jul 11 2008 Lubomir Rintel <lkundrak@v3.sk> 3.19.9-1 +- upgrade + +* Wed Jun 25 2008 Peter Vrabec <pvrabec@redhat.com> 3.19.7-3 +- rebuild because of new gnutls + +* Fri Jun 13 2008 Peter Vrabec <pvrabec@redhat.com> 3.19.7-2 +- do not translate Oopses (#450329) + +* Fri Jun 13 2008 Peter Vrabec <pvrabec@redhat.com> 3.19.7-1 +- upgrade + +* Wed May 28 2008 Peter Vrabec <pvrabec@redhat.com> 3.19.4-1 +- upgrade + +* Mon May 26 2008 Peter Vrabec <pvrabec@redhat.com> 3.19.3-1 +- upgrade to new upstream release + +* Wed May 14 2008 Tomas Heinrich <theinric@redhat.com> 3.16.1-1 +- upgrade + +* Tue Apr 08 2008 Peter Vrabec <pvrabec@redhat.com> 3.14.1-5 +- prevent undesired error description in legacy + warning messages + +* Tue Apr 08 2008 Peter Vrabec <pvrabec@redhat.com> 3.14.1-4 +- adjust symbol lookup method to 2.6 kernel + +* Tue Apr 08 2008 Peter Vrabec <pvrabec@redhat.com> 3.14.1-3 +- fix segfault of expression based filters + +* Mon Apr 07 2008 Peter Vrabec <pvrabec@redhat.com> 3.14.1-2 +- init script fixes (#441170,#440968) + +* Fri Apr 04 2008 Peter Vrabec <pvrabec@redhat.com> 3.14.1-1 +- upgrade + +* Mon Mar 25 2008 Peter Vrabec <pvrabec@redhat.com> 3.12.4-1 +- upgrade + +* Wed Mar 19 2008 Peter Vrabec <pvrabec@redhat.com> 3.12.3-1 +- upgrade +- fix some significant memory leaks + +* Tue Mar 11 2008 Peter Vrabec <pvrabec@redhat.com> 3.12.1-2 +- init script fixes (#436854) +- fix config file parsing (#436722) + +* Thu Mar 06 2008 Peter Vrabec <pvrabec@redhat.com> 3.12.1-1 +- upgrade + +* Wed Mar 05 2008 Peter Vrabec <pvrabec@redhat.com> 3.12.0-1 +- upgrade + +* Mon Feb 25 2008 Peter Vrabec <pvrabec@redhat.com> 3.11.5-1 +- upgrade + +* Fri Feb 01 2008 Peter Vrabec <pvrabec@redhat.com> 3.11.0-1 +- upgrade to the latests development release +- provide PostgresSQL support +- provide GSSAPI support + +* Mon Jan 21 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-7 +- change from requires sysklogd to conflicts sysklogd + +* Fri Jan 18 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-6 +- change logrotate file +- use rsyslog own pid file + +* Thu Jan 17 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-5 +- fixing bad descriptor (#428775) + +* Wed Jan 16 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-4 +- rename logrotate file + +* Wed Jan 16 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-3 +- fix post script and init file + +* Wed Jan 16 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-2 +- change pid filename and use logrotata script from sysklogd + +* Tue Jan 15 2008 Peter Vrabec <pvrabec@redhat.com> 2.0.0-1 +- upgrade to stable release +- spec file clean up + +* Wed Jan 02 2008 Peter Vrabec <pvrabec@redhat.com> 1.21.2-1 +- new upstream release + +* Thu Dec 06 2007 Release Engineering <rel-eng at fedoraproject dot org> - 1.19.11-2 +- Rebuild for deps + +* Thu Nov 29 2007 Peter Vrabec <pvrabec@redhat.com> 1.19.11-1 +- new upstream release +- add conflicts (#400671) + +* Mon Nov 19 2007 Peter Vrabec <pvrabec@redhat.com> 1.19.10-1 +- new upstream release + +* Wed Oct 03 2007 Peter Vrabec <pvrabec@redhat.com> 1.19.6-3 +- remove NUL character from recieved messages + +* Tue Sep 25 2007 Tomas Heinrich <theinric@redhat.com> 1.19.6-2 +- fix message suppression (303341) + +* Tue Sep 25 2007 Tomas Heinrich <theinric@redhat.com> 1.19.6-1 +- upstream bugfix release + +* Tue Aug 28 2007 Peter Vrabec <pvrabec@redhat.com> 1.19.2-1 +- upstream bugfix release +- support for negative app selector, patch from + theinric@redhat.com + +* Fri Aug 17 2007 Peter Vrabec <pvrabec@redhat.com> 1.19.0-1 +- new upstream release with MySQL support(as plugin) + +* Wed Aug 08 2007 Peter Vrabec <pvrabec@redhat.com> 1.18.1-1 +- upstream bugfix release + +* Mon Aug 06 2007 Peter Vrabec <pvrabec@redhat.com> 1.18.0-1 +- new upstream release + +* Thu Aug 02 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.6-1 +- upstream bugfix release + +* Mon Jul 30 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.5-1 +- upstream bugfix release +- fix typo in provides + +* Wed Jul 25 2007 Jeremy Katz <katzj@redhat.com> - 1.17.2-4 +- rebuild for toolchain bug + +* Tue Jul 24 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.2-3 +- take care of sysklogd configuration files in %%post + +* Tue Jul 24 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.2-2 +- use EVR in provides/obsoletes sysklogd + +* Mon Jul 23 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.2-1 +- upstream bug fix release + +* Fri Jul 20 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.1-1 +- upstream bug fix release +- include html docs (#248712) +- make "-r" option compatible with sysklogd config (248982) + +* Tue Jul 17 2007 Peter Vrabec <pvrabec@redhat.com> 1.17.0-1 +- feature rich upstream release + +* Thu Jul 12 2007 Peter Vrabec <pvrabec@redhat.com> 1.15.1-2 +- use obsoletes and hadle old config files + +* Wed Jul 11 2007 Peter Vrabec <pvrabec@redhat.com> 1.15.1-1 +- new upstream bugfix release + +* Tue Jul 10 2007 Peter Vrabec <pvrabec@redhat.com> 1.15.0-1 +- new upstream release introduce capability to generate output + file names based on templates + +* Tue Jul 03 2007 Peter Vrabec <pvrabec@redhat.com> 1.14.2-1 +- new upstream bugfix release + +* Mon Jul 02 2007 Peter Vrabec <pvrabec@redhat.com> 1.14.1-1 +- new upstream release with IPv6 support + +* Tue Jun 26 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.5-3 +- add BuildRequires for zlib compression feature + +* Mon Jun 25 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.5-2 +- some spec file adjustments. +- fix syslog init script error codes (#245330) + +* Fri Jun 22 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.5-1 +- new upstream release + +* Fri Jun 22 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.4-2 +- some spec file adjustments. + +* Mon Jun 18 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.4-1 +- upgrade to new upstream release + +* Wed Jun 13 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.2-2 +- DB support off + +* Tue Jun 12 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.2-1 +- new upstream release based on redhat patch + +* Fri Jun 08 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.1-2 +- rsyslog package provides its own kernel log. daemon (rklogd) + +* Mon Jun 04 2007 Peter Vrabec <pvrabec@redhat.com> 1.13.1-1 +- Initial rpm build diff --git a/build/rhel/rsyslog/rsyslog.sysconfig b/build/rhel/rsyslog/rsyslog.sysconfig new file mode 100644 index 00000000..04c6c533 --- /dev/null +++ b/build/rhel/rsyslog/rsyslog.sysconfig @@ -0,0 +1,7 @@ +# Options for rsyslogd +# Syslogd options are deprecated since rsyslog v3. +# If you want to use them, switch to compatibility mode 2 by "-c 2" +# See rsyslogd(8) for more details +# +# Assume compatibily with version 5 or greater +SYSLOGD_OPTIONS="-c5 -f /etc/rsyslog.conf" diff --git a/compat/Makefile.am b/compat/Makefile.am new file mode 100644 index 00000000..f580a380 --- /dev/null +++ b/compat/Makefile.am @@ -0,0 +1,6 @@ +noinst_LTLIBRARIES = compat.la + +compat_la_SOURCES = getifaddrs.c ifaddrs.h +compat_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +compat_la_LDFLAGS = -module -avoid-version +compat_la_LIBADD = $(IMUDP_LIBS) diff --git a/compat/getifaddrs.c b/compat/getifaddrs.c new file mode 100755 index 00000000..25d04d4d --- /dev/null +++ b/compat/getifaddrs.c @@ -0,0 +1,283 @@ +#include "config.h" +#ifndef HAVE_GETIFADDRS +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include <netdb.h> +#include <nss_dbdefs.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <string.h> +#include <stdio.h> +#include <sys/sockio.h> +#include <sys/types.h> +#include <stdlib.h> +#include <net/if.h> +#include <ifaddrs.h> + +/* Normally this is defined in <net/if.h> but was new for Solaris 11 */ +#ifndef LIFC_ENABLED +#define LIFC_ENABLED 0x20 +#endif + +int getallifaddrs(sa_family_t af, struct ifaddrs **ifap, int64_t flags); +int getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs, + int64_t lifc_flags); + +/* + * Create a linked list of `struct ifaddrs' structures, one for each + * address that is UP. If successful, store the list in *ifap and + * return 0. On errors, return -1 and set `errno'. + * + * The storage returned in *ifap is allocated dynamically and can + * only be properly freed by passing it to `freeifaddrs'. + */ +int +getifaddrs(struct ifaddrs **ifap) +{ + int err; + char *cp; + struct ifaddrs *curr; + + if (ifap == NULL) { + errno = EINVAL; + return (-1); + } + *ifap = NULL; + err = getallifaddrs(AF_UNSPEC, ifap, LIFC_ENABLED); + if (err == 0) { + for (curr = *ifap; curr != NULL; curr = curr->ifa_next) { + if ((cp = strchr(curr->ifa_name, ':')) != NULL) + *cp = '\0'; + } + } + return (err); +} + +void +freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *curr; + + while (ifa != NULL) { + curr = ifa; + ifa = ifa->ifa_next; + free(curr->ifa_name); + free(curr->ifa_addr); + free(curr->ifa_netmask); + free(curr->ifa_dstaddr); + free(curr); + } +} + +/* + * Returns all addresses configured on the system. If flags contain + * LIFC_ENABLED, only the addresses that are UP are returned. + * Address list that is returned by this function must be freed + * using freeifaddrs(). + */ +int +getallifaddrs(sa_family_t af, struct ifaddrs **ifap, int64_t flags) +{ + struct lifreq *buf = NULL; + struct lifreq *lifrp; + struct lifreq lifrl; + int ret; + int s, n, numifs; + struct ifaddrs *curr, *prev; + sa_family_t lifr_af; + int sock4; + int sock6; + int err; + + if ((sock4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return (-1); + if ((sock6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { + err = errno; + close(sock4); + errno = err; + return (-1); + } + +retry: + /* Get all interfaces from SIOCGLIFCONF */ + ret = getallifs(sock4, af, &buf, &numifs, (flags & ~LIFC_ENABLED)); + if (ret != 0) + goto fail; + + /* + * Loop through the interfaces obtained from SIOCGLIFCOMF + * and retrieve the addresses, netmask and flags. + */ + prev = NULL; + lifrp = buf; + *ifap = NULL; + for (n = 0; n < numifs; n++, lifrp++) { + + /* Prepare for the ioctl call */ + (void) strncpy(lifrl.lifr_name, lifrp->lifr_name, + sizeof (lifrl.lifr_name)); + lifr_af = lifrp->lifr_addr.ss_family; + if (af != AF_UNSPEC && lifr_af != af) + continue; + + s = (lifr_af == AF_INET ? sock4 : sock6); + + if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0) + goto fail; + if ((flags & LIFC_ENABLED) && !(lifrl.lifr_flags & IFF_UP)) + continue; + + /* + * Allocate the current list node. Each node contains data + * for one ifaddrs structure. + */ + curr = calloc(1, sizeof (struct ifaddrs)); + if (curr == NULL) + goto fail; + + if (prev != NULL) { + prev->ifa_next = curr; + } else { + /* First node in the linked list */ + *ifap = curr; + } + prev = curr; + + curr->ifa_flags = lifrl.lifr_flags; + if ((curr->ifa_name = strdup(lifrp->lifr_name)) == NULL) + goto fail; + + curr->ifa_addr = malloc(sizeof (struct sockaddr_storage)); + if (curr->ifa_addr == NULL) + goto fail; + (void) memcpy(curr->ifa_addr, &lifrp->lifr_addr, + sizeof (struct sockaddr_storage)); + + /* Get the netmask */ + if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifrl) < 0) + goto fail; + curr->ifa_netmask = malloc(sizeof (struct sockaddr_storage)); + if (curr->ifa_netmask == NULL) + goto fail; + (void) memcpy(curr->ifa_netmask, &lifrl.lifr_addr, + sizeof (struct sockaddr_storage)); + + /* Get the destination for a pt-pt interface */ + if (curr->ifa_flags & IFF_POINTOPOINT) { + if (ioctl(s, SIOCGLIFDSTADDR, (caddr_t)&lifrl) < 0) + goto fail; + curr->ifa_dstaddr = malloc( + sizeof (struct sockaddr_storage)); + if (curr->ifa_dstaddr == NULL) + goto fail; + (void) memcpy(curr->ifa_dstaddr, &lifrl.lifr_addr, + sizeof (struct sockaddr_storage)); + } else if (curr->ifa_flags & IFF_BROADCAST) { + if (ioctl(s, SIOCGLIFBRDADDR, (caddr_t)&lifrl) < 0) + goto fail; + curr->ifa_broadaddr = malloc( + sizeof (struct sockaddr_storage)); + if (curr->ifa_broadaddr == NULL) + goto fail; + (void) memcpy(curr->ifa_broadaddr, &lifrl.lifr_addr, + sizeof (struct sockaddr_storage)); + } + + } + free(buf); + close(sock4); + close(sock6); + return (0); +fail: + err = errno; + free(buf); + freeifaddrs(*ifap); + *ifap = NULL; + if (err == ENXIO) + goto retry; + close(sock4); + close(sock6); + errno = err; + return (-1); +} + +/* + * Do a SIOCGLIFCONF and store all the interfaces in `buf'. + */ +int +getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs, + int64_t lifc_flags) +{ + struct lifnum lifn; + struct lifconf lifc; + size_t bufsize; + char *tmp; + caddr_t *buf = (caddr_t *)lifr; + + lifn.lifn_family = af; + lifn.lifn_flags = lifc_flags; + + *buf = NULL; +retry: + if (ioctl(s, SIOCGLIFNUM, &lifn) < 0) + goto fail; + + /* + * When calculating the buffer size needed, add a small number + * of interfaces to those we counted. We do this to capture + * the interface status of potential interfaces which may have + * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF. + */ + bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq); + + if ((tmp = realloc(*buf, bufsize)) == NULL) + goto fail; + + *buf = tmp; + lifc.lifc_family = af; + lifc.lifc_flags = lifc_flags; + lifc.lifc_len = bufsize; + lifc.lifc_buf = *buf; + if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) + goto fail; + + *numifs = lifc.lifc_len / sizeof (struct lifreq); + if (*numifs >= (lifn.lifn_count + 4)) { + /* + * If every entry was filled, there are probably + * more interfaces than (lifn.lifn_count + 4). + * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to + * get all the interfaces. + */ + goto retry; + } + return (0); +fail: + free(*buf); + *buf = NULL; + return (-1); +} +#endif /* HAVE_GETIFADDRS */ diff --git a/compat/ifaddrs.h b/compat/ifaddrs.h new file mode 100755 index 00000000..ec359c13 --- /dev/null +++ b/compat/ifaddrs.h @@ -0,0 +1,89 @@ +#include "config.h" +#ifndef HAVE_GETIFADDRS +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ +#ifndef _IFADDRS_H +#define _IFADDRS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/* + * The `getifaddrs' function generates a linked list of these structures. + * Each element of the list describes one network interface. + */ +#if defined(_INT64_TYPE) +struct ifaddrs { + struct ifaddrs *ifa_next; /* Pointer to the next structure. */ + char *ifa_name; /* Name of this network interface. */ + uint64_t ifa_flags; /* Flags as from SIOCGLIFFLAGS ioctl. */ + struct sockaddr *ifa_addr; /* Network address of this interface. */ + struct sockaddr *ifa_netmask; /* Netmask of this interface. */ + union { + /* + * At most one of the following two is valid. If the + * IFF_BROADCAST bit is set in `ifa_flags', then + * `ifa_broadaddr' is valid. If the IFF_POINTOPOINT bit is + * set, then `ifa_dstaddr' is valid. It is never the case that + * both these bits are set at once. + */ + struct sockaddr *ifu_broadaddr; + struct sockaddr *ifu_dstaddr; + } ifa_ifu; + void *ifa_data; /* Address-specific data (may be unused). */ +/* + * This may have been defined in <net/if.h>. + */ +#ifndef ifa_broadaddr +#define ifa_broadaddr ifa_ifu.ifu_broadaddr /* broadcast address */ +#endif +#ifndef ifa_dstaddr +#define ifa_dstaddr ifa_ifu.ifu_dstaddr /* other end of p-to-p link */ +#endif +}; +#endif + +/* + * Create a linked list of `struct ifaddrs' structures, one for each + * network interface on the host machine. If successful, store the + * list in *ifap and return 0. On errors, return -1 and set `errno'. + * + * The storage returned in *ifap is allocated dynamically and can + * only be properly freed by passing it to `freeifaddrs'. + */ +extern int getifaddrs(struct ifaddrs **); + +/* Reclaim the storage allocated by a previous `getifaddrs' call. */ +extern void freeifaddrs(struct ifaddrs *); + + +#ifdef __cplusplus +} +#endif + +#endif /* _IFADDRS_H */ +#endif /* HAVE_GETIFADDRS */ diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..56457c05 --- /dev/null +++ b/configure.ac @@ -0,0 +1,1601 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT([rsyslog],[7.5.0],[rsyslog@lists.adiscon.com]) +AM_INIT_AUTOMAKE + +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_CONFIG_SRCDIR([ChangeLog]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) + +AC_GNU_SOURCE + +# check if valgrind is present +AC_CHECK_PROG(have_valgrind, [valgrind], [yes]) +AM_CONDITIONAL(HAVE_VALGRIND, test x$have_valgrind = xyes) + +# Checks for programs. +AC_PROG_LEX +AC_PROG_YACC +AC_PROG_CC +AM_PROG_CC_C_O +if test "$GCC" = "yes" +then CFLAGS="$CFLAGS -W -Wall -Wformat-security -Wshadow -Wcast-align -Wpointer-arith -Wmissing-format-attribute -g" +fi +AC_DISABLE_STATIC +AC_PROG_LIBTOOL +AC_CANONICAL_HOST + +PKG_PROG_PKG_CONFIG + +# modules we require +PKG_CHECK_MODULES(LIBESTR, libestr >= 0.1.5) +PKG_CHECK_MODULES([JSON_C], [json]) + +case "${host}" in + *-*-linux*) + AC_DEFINE([OS_LINUX], [1], [Indicator for a Linux OS]) + os_type="linux" + ;; + *-*-*darwin*|*-*-dragonfly*|*-*-freebsd*|*-*-netbsd*|*-*-openbsd*) + AC_DEFINE([OS_BSD], [1], [Indicator for a BSD OS]) + os_type="bsd" + ;; + *-*-kfreebsd*) + # kernel is FreeBSD, but userspace is glibc - i.e. like linux + # do not DEFINE OS_BSD + os_type="bsd" + ;; + *-*-solaris*) + os_type="solaris" + AC_DEFINE([OS_SOLARIS], [1], [Indicator for a Solaris OS]) + AC_DEFINE([_POSIX_PTHREAD_SEMANTICS], [1], [Use POSIX pthread semantics]) + SOL_LIBS="-lsocket -lnsl" + # Solaris libuuid does not ship with a pkgconfig file so override the appropriate + # variables (but only if they have not been set by the user). + LIBUUID_CFLAGS=${LIBUUID_CFLAGS:= } + LIBUUID_LIBS=${LIBUUID_LIBS:=-luuid} + AC_SUBST(SOL_LIBS) + ;; +esac + +AC_DEFINE_UNQUOTED([HOSTENV], "$host", [the host environment, can be queried via a system variable]) + +# Checks for libraries. +save_LIBS=$LIBS +LIBS= +AC_SEARCH_LIBS(clock_gettime, rt) +RT_LIBS=$LIBS +AC_SEARCH_LIBS(mq_getattr, rt) +RT_LIBS="$RT_LIBS $LIBS" +LIBS= +AC_SEARCH_LIBS(dlopen, dl) +DL_LIBS=$LIBS +LIBS=$save_LIBS + +AC_SUBST(RT_LIBS) +AC_SUBST(DL_LIBS) + +# Checks for header files. +AC_HEADER_RESOLV +AC_HEADER_STDC +AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([arpa/inet.h libgen.h malloc.h fcntl.h locale.h netdb.h netinet/in.h paths.h stddef.h stdlib.h string.h sys/file.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h sys/stat.h syslog.h unistd.h utmp.h utmpx.h sys/epoll.h sys/prctl.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_MODE_T +AC_TYPE_UID_T +AC_TYPE_UINT8_T +AC_HEADER_TIME +AC_STRUCT_TM +AC_C_VOLATILE +AC_C_TYPEOF + +sa_includes="\ +$ac_includes_default +#if HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif +" +AC_CHECK_MEMBERS([struct sockaddr.sa_len],,,[$sa_includes]) + +# Checks for library functions. +AC_FUNC_CHOWN +AC_FUNC_FORK +AC_PROG_GCC_TRADITIONAL +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_FUNC_SELECT_ARGTYPES +AC_TYPE_SIGNAL +AC_FUNC_STAT +AC_FUNC_STRERROR_R +AC_FUNC_VPRINTF +AC_CHECK_FUNCS([flock basename alarm clock_gettime gethostbyname gethostname gettimeofday localtime_r memset mkdir regcomp select setid socket strcasecmp strchr strdup strerror strndup strnlen strrchr strstr strtol strtoul uname ttyname_r getline malloc_trim prctl epoll_create epoll_create1 fdatasync syscall lseek64]) + +# getifaddrs is in libc (mostly) or in libsocket (eg Solaris 11) or not defined (eg Solaris 10) +AC_SEARCH_LIBS([getifaddrs], [socket], [AC_DEFINE(HAVE_GETIFADDRS, [1], [set define])]) + +# the check below is probably ugly. If someone knows how to do it in a better way, please +# let me know! -- rgerhards, 2010-10-06 +AC_CHECK_DECL([SCM_CREDENTIALS], [AC_DEFINE(HAVE_SCM_CREDENTIALS, [1], [set define])], [], [#include <sys/types.h> +#include <sys/socket.h>]) +AC_CHECK_DECL([SO_TIMESTAMP], [AC_DEFINE(HAVE_SO_TIMESTAMP, [1], [set define])], [], [#include <sys/types.h> +#include <sys/socket.h>]) +AC_CHECK_DECL([SYS_gettid], [AC_DEFINE(HAVE_SYS_gettid, [1], [set define])], [], [#include <sys/syscall.h>]) +AC_CHECK_MEMBER([struct sysinfo.uptime], [AC_DEFINE(HAVE_SYSINFO_UPTIME, [1], [set define])], [], [#include <sys/sysinfo.h>]) +AC_CHECK_DECL([GLOB_NOMAGIC], [AC_DEFINE(HAVE_GLOB_NOMAGIC, [1], [set define])], [], [#include <glob.h>]) + +# Check for MAXHOSTNAMELEN +AC_MSG_CHECKING(for MAXHOSTNAMELEN) +AC_TRY_COMPILE([ + #include <sys/param.h> + ], [ + return MAXHOSTNAMELEN; + ] + , + AC_MSG_RESULT(yes) + , + # note: we use 1024 here, which should be far more than needed by any system. If that's too low, we simply + # life with the need to change it. Most of the code doesn't need it anyways, but there are a few places + # where it actually is needed and it makes no sense to change them. + AC_DEFINE(MAXHOSTNAMELEN, 1024, [Define with a value if your <sys/param.h> does not define MAXHOSTNAMELEN]) + AC_MSG_RESULT(no; defined as 64) +) + +# check for availability of atomic operations +RS_ATOMIC_OPERATIONS +RS_ATOMIC_OPERATIONS_64BIT + +# fall back to POSIX sems for atomic operations (cpu expensive) +AC_CHECK_HEADERS([semaphore.h sys/syscall.h]) + + +# Additional module directories +AC_ARG_WITH(moddirs, + [AS_HELP_STRING([--with-moddirs=DIRS],[Additional module search paths appended to @<:@$libdir/rsyslog@:>@])], + [_save_IFS=$IFS ; IFS=$PATH_SEPARATOR ; moddirs="" + for w in ${with_moddirs} ; + do + case $w in + "") continue ;; */) ;; *) w="${w}/" ;; + esac + for m in ${moddirs} ; + do + test "x$w" = "x${libdir}/${PACKAGE}/" || \ + test "x$w" = "x$m" || test "x$w" = "x/" && \ + continue 2 + done + case $moddirs in + "") moddirs="$w" ;; *) moddirs="${moddirs}:${w}" ;; + esac + done ; IFS=$_save_IFS],[moddirs=""] +) +AM_CONDITIONAL(WITH_MODDIRS, test x$moddirs != x) +AC_SUBST(moddirs) + + +# Large file support +# http://www.gnu.org/software/autoconf/manual/html_node/System-Services.html#index-AC_005fSYS_005fLARGEFILE-1028 +AC_SYS_LARGEFILE +case "${enable_largefile}" in + no) ;; + *) enable_largefile="yes" ;; +esac + +# Regular expressions +AC_ARG_ENABLE(regexp, + [AS_HELP_STRING([--enable-regexp],[Enable regular expressions support @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_regexp="yes" ;; + no) enable_regexp="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-regexp) ;; + esac], + [enable_regexp=yes] +) +AM_CONDITIONAL(ENABLE_REGEXP, test x$enable_regexp = xyes) +if test "$enable_regexp" = "yes"; then + AC_DEFINE(FEATURE_REGEXP, 1, [Regular expressions support enabled.]) +fi + + + +# zlib compression +AC_ARG_ENABLE(zlib, + [AS_HELP_STRING([--enable-zlib],[Enable zlib compression support @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_zlib="yes" ;; + no) enable_zlib="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-zlib) ;; + esac], + [enable_zlib=yes] +) +AM_CONDITIONAL(ENABLE_ZLIB, test x$enable_zlib = xyes) +if test "$enable_zlib" = "yes"; then + AC_CHECK_HEADER(zlib.h, [zlib_header="yes"], [zlib_header="no" enable_zlib="false"]) + if test "$zlib_header" = "yes"; then + AC_CHECK_LIB(z, deflate, + [AC_DEFINE(USE_NETZIP, 1, [Define if you want to enable zlib support]) + ZLIB_LIBS="-lz" + AC_SUBST(ZLIB_LIBS)], enable_zlib="false") + fi +fi + + +#gssapi +AC_ARG_ENABLE(gssapi_krb5, + [AS_HELP_STRING([--enable-gssapi-krb5],[Enable GSSAPI Kerberos 5 support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_gssapi_krb5="yes" ;; + no) enable_gssapi_krb5="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-gssapi-krb5) ;; + esac], + [enable_gssapi_krb5=no] +) +if test $enable_gssapi_krb5 = yes; then + AC_CHECK_LIB(gssapi_krb5, gss_acquire_cred, [ + AC_CHECK_HEADER(gssapi/gssapi.h, [ + AC_DEFINE(USE_GSSAPI,, + Define if you want to use GSSAPI) + GSS_LIBS="-lgssapi_krb5" + AC_SUBST(GSS_LIBS) + ]) + ]) +fi +AM_CONDITIONAL(ENABLE_GSSAPI, test x$enable_gssapi_krb5 = xyes) + + +# multithreading via pthreads +AC_CHECK_HEADERS( + [pthread.h], + [ + AC_CHECK_LIB( + [pthread], + [pthread_create], + [ + AC_DEFINE([USE_PTHREADS], [1], [Multithreading support enabled.]) + PTHREADS_LIBS="-lpthread" + case "${os_type}" in + solaris) PTHREADS_CFLAGS="-pthreads" ;; + *) PTHREADS_CFLAGS="-pthread" ;; + esac + AC_SUBST(PTHREADS_LIBS) + AC_SUBST(PTHREADS_CFLAGS) + ], + [AC_MSG_FAILURE([pthread is missing])], + [-lpthread] + ) + ], + [AC_MSG_FAILURE([pthread is missing])] +) + +AC_CHECK_FUNCS( + [pthread_setschedparam], + [ + rsyslog_have_pthread_setschedparam=yes + ], + [ + rsyslog_have_pthread_setschedparam=no + ] +) +AC_CHECK_HEADERS( + [sched.h], + [ + rsyslog_have_sched_h=yes + ], + [ + rsyslog_have_sched_h=no + ] +) +if test "$rsyslog_have_pthread_setschedparam" = "yes" -a "$rsyslog_have_sched_h" = "yes"; then + save_LIBS=$LIBS + LIBS= + AC_SEARCH_LIBS(sched_get_priority_max, rt) + if test "x$ac_cv_search" != "xno"; then + AC_CHECK_FUNCS(sched_get_priority_max) + fi + IMUDP_LIBS=$LIBS + AC_SUBST(IMUDP_LIBS) + LIBS=$save_LIBS +fi + + +# klog +AC_ARG_ENABLE(klog, + [AS_HELP_STRING([--enable-klog],[Integrated klog functionality @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_klog="yes" ;; + no) enable_klog="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-klog) ;; + esac], + [enable_klog="yes"] +) +AM_CONDITIONAL(ENABLE_IMKLOG, test x$enable_klog = xyes) +AM_CONDITIONAL(ENABLE_IMKLOG_BSD, test x$os_type = xbsd) +AM_CONDITIONAL(ENABLE_IMKLOG_LINUX, test x$os_type = xlinux) +AM_CONDITIONAL(ENABLE_IMKLOG_SOLARIS, test x$os_type = xsolaris) + +# kmsg +AC_ARG_ENABLE(kmsg, + [AS_HELP_STRING([--enable-kmsg],[Kmsg structured kernel logs functionality @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_kmsg="yes" ;; + no) enable_kmsg="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-kmsg) ;; + esac], + [enable_kmsg="no"] +) +AM_CONDITIONAL(ENABLE_IMKMSG, test x$enable_kmsg = xyes) + +# imjournal +AC_ARG_ENABLE(imjournal, + [AS_HELP_STRING([--enable-imjournal],[Systemd journal message import @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imjournal="yes" ;; + no) enable_imjournal="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imjournal) ;; + esac], + [enable_imjournal="no"] +) +if test "x$enable_imjournal" = "xyes"; then + PKG_CHECK_MODULES([LIBSYSTEMD_JOURNAL], [libsystemd-journal >= 197]) +fi +AM_CONDITIONAL(ENABLE_IMJOURNAL, test x$enable_imjournal = xyes) + +# inet +AC_ARG_ENABLE(inet, + [AS_HELP_STRING([--enable-inet],[Enable networking support @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_inet="yes" ;; + no) enable_inet="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-inet) ;; + esac], + [enable_inet="yes"] +) +AM_CONDITIONAL(ENABLE_INET, test x$enable_inet = xyes) +if test "$enable_inet" = "yes"; then + AC_DEFINE(SYSLOG_INET, 1, [network support is integrated.]) +fi + + +# +# The following define determines whether the package adheres to the +# file system standard. +# +AC_MSG_CHECKING(for FSSTND support) +AC_ARG_ENABLE([fsstnd], + [AS_HELP_STRING([--disable-fsstnd], [Disable support for FSSTND])], + [ + if test "x${enableval}" = "xyes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([FSSTND], [1], [Description]) + else + AC_MSG_RESULT([no]) + fi + ], + [ + # enabled by default + AC_MSG_RESULT([yes]) + AC_DEFINE([FSSTND], [1], [Description]) + ]) + + +# support for unlimited select() syscall +AC_ARG_ENABLE(unlimited_select, + [AS_HELP_STRING([--enable-unlimited-select],[Enable unlimited select() syscall @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_unlimited_select="yes" ;; + no) enable_unlimited_select="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-unlimited-select) ;; + esac], + [enable_unlimited_select="no"] +) +if test "$enable_unlimited_select" = "yes"; then + AC_DEFINE(USE_UNLIMITED_SELECT, 1, [If defined, the select() syscall won't be limited to a particular number of file descriptors.]) +fi + + +# support for systemd unit files +AC_ARG_WITH([systemdsystemunitdir], + AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), + [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]) +if test "x$with_systemdsystemunitdir" != xno; then + AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) +fi +AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ]) + + +# debug +AC_ARG_ENABLE(debug, + [AS_HELP_STRING([--enable-debug],[Enable debug mode @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_debug="yes" ;; + no) enable_debug="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; + esac], + [enable_debug="no"] +) +if test "$enable_debug" = "yes"; then + AC_DEFINE(DEBUG, 1, [Defined if debug mode is enabled (its easier to check).]) +fi +if test "$enable_debug" = "no"; then + AC_DEFINE(NDEBUG, 1, [Defined if debug mode is disabled.]) +fi + + +# runtime instrumentation +AC_ARG_ENABLE(rtinst, + [AS_HELP_STRING([--enable-rtinst],[Enable runtime instrumentation mode @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_rtinst="yes" ;; + no) enable_rtinst="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-rtinst) ;; + esac], + [enable_rtinst="no"] +) +if test "$enable_rtinst" = "yes"; then + AC_DEFINE(RTINST, 1, [Defined if runtime instrumentation mode is enabled.]) +fi + + +# total debugless: highest performance, but no way at all to enable debug +# logging +AC_ARG_ENABLE(debugless, + [AS_HELP_STRING([--enable-debugless],[Enable runtime instrumentation mode @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_debugless="yes" ;; + no) enable_debugless="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-debugless) ;; + esac], + [enable_debugless="no"] +) +if test "$enable_debugless" = "yes"; then + AC_DEFINE(DEBUGLESS, 1, [Defined if debugless mode is enabled.]) +fi + + +# valgrind +AC_ARG_ENABLE(valgrind, + [AS_HELP_STRING([--enable-valgrind],[Enable valgrind support settings @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_valgrind="yes" ;; + no) enable_valgrind="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-valgrind) ;; + esac], + [enable_valgrind="no"] +) +if test "$enable_valgrind" = "yes"; then + AC_DEFINE(VALGRIND, 1, [Defined if valgrind support settings are to be enabled (e.g. prevents dlclose()).]) +fi + + +# memcheck +AC_ARG_ENABLE(memcheck, + [AS_HELP_STRING([--enable-memcheck],[Enable extended memory check support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_memcheck="yes" ;; + no) enable_memcheck="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-memcheck) ;; + esac], + [enable_memcheck="no"] +) +if test "$enable_memcheck" = "yes"; then + AC_DEFINE(MEMCHECK, 1, [Defined if memcheck support settings are to be enabled (e.g. prevents dlclose()).]) +fi + + +# compile diagnostic tools (small helpers usually not needed) +AC_ARG_ENABLE(diagtools, + [AS_HELP_STRING([--enable-diagtools],[Enable diagnostic tools @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_diagtools="yes" ;; + no) enable_diagtools="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-diagtools) ;; + esac], + [enable_diagtools=no] +) +AM_CONDITIONAL(ENABLE_DIAGTOOLS, test x$enable_diagtools = xyes) + + +# compile end-user tools +AC_ARG_ENABLE(usertools, + [AS_HELP_STRING([--enable-usertools],[Enable end user tools @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_usertools="yes" ;; + no) enable_usertools="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-usertools) ;; + esac], + [enable_usertools=no] +) +AM_CONDITIONAL(ENABLE_USERTOOLS, test x$enable_usertools = xyes) + + +# MySQL support +AC_ARG_ENABLE(mysql, + [AS_HELP_STRING([--enable-mysql],[Enable MySql database support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mysql="yes" ;; + no) enable_mysql="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mysql) ;; + esac], + [enable_mysql=no] +) +if test "x$enable_mysql" = "xyes"; then + AC_CHECK_PROG( + [HAVE_MYSQL_CONFIG], + [mysql_config], + [yes],,, + ) + if test "x${HAVE_MYSQL_CONFIG}" != "xyes"; then + AC_MSG_FAILURE([mysql_config not found in PATH - usually a package named mysql-dev, libmysql-dev or similar, is missing - install it to fix this issue]) + fi + AC_CHECK_LIB( + [mysqlclient], + [mysql_init], + [MYSQL_CFLAGS=`mysql_config --cflags` + MYSQL_LIBS=`mysql_config --libs` + ], + [AC_MSG_FAILURE([MySQL library is missing])], + [`mysql_config --libs`] + ) + AC_MSG_CHECKING(if we have mysql_library_init) + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $MYSQL_CFLAGS" + save_LIBS="$LIBS" + LIBS="$LIBS $MYSQL_LIBS" + AC_TRY_LINK( + [#include <mysql.h> + #include <stdio.h>], + [mysql_library_init(0, NULL, NULL)], + [have_mysql_library_init=yes], + [have_mysql_library_init=no]) + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" +fi +AM_CONDITIONAL(ENABLE_MYSQL, test x$enable_mysql = xyes) +if test "$have_mysql_library_init" = "yes"; then + AC_DEFINE([HAVE_MYSQL_LIBRARY_INIT], [1], [mysql_library_init available]) +fi +AC_SUBST(MYSQL_CFLAGS) +AC_SUBST(MYSQL_LIBS) + + +# PostgreSQL support +AC_ARG_ENABLE(pgsql, + [AS_HELP_STRING([--enable-pgsql],[Enable PostgreSQL database support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_pgsql="yes" ;; + no) enable_pgsql="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pgsql) ;; + esac], + [enable_pgsql=no] +) +if test "x$enable_pgsql" = "xyes"; then + AC_CHECK_PROG( + [HAVE_PGSQL_CONFIG], + [pg_config], + [yes],,, + ) + if test "x${HAVE_PGSQL_CONFIG}" != "xyes"; then + AC_MSG_FAILURE([pg_config not found in PATH]) + fi + AC_CHECK_LIB( + [pq], + [PQconnectdb], + [PGSQL_CFLAGS="-I`pg_config --includedir`" + PGSQL_LIBS="-L`pg_config --libdir` -lpq" + ], + [AC_MSG_FAILURE([PgSQL library is missing])], + [-L`pg_config --libdir`] + ) +fi +AM_CONDITIONAL(ENABLE_PGSQL, test x$enable_pgsql = xyes) +AC_SUBST(PGSQL_CFLAGS) +AC_SUBST(PGSQL_LIBS) + + +# oracle (OCI) support +AC_ARG_ENABLE(oracle, + [AS_HELP_STRING([--enable-oracle],[Enable native Oracle database support @<:@default=no@:>@]. (Check your ORACLE_HOME environment variable!))], + [case "${enableval}" in + yes) enable_oracle="yes" ;; + no) enable_oracle="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-oracle) ;; + esac], + [enable_oracle=no] +) +if test "x$enable_oracle" = "xyes"; then + if test -d "$ORACLE_HOME" + then + AC_CHECK_LIB([occi], [OCIEnvCreate], + [ORACLE_CFLAGS=-I$ORACLE_HOME/rdbms/public] + [ORACLE_LIBS=-L$ORACLE_HOME/lib -locci], + [AC_MSG_FAILURE([Oracle (OCI) library is missing: wrong oracle home])], + [-I$ORACLE_HOME/rdbms/public/ -L$ORACLE_HOME/lib -locci -lclntsh ] + ) + elif test -d "$ORACLE_LIB_PATH" -a -d "$ORACLE_INCLUDE_PATH" + then + AC_CHECK_LIB([occi], [OCIEnvCreate], + [ORACLE_CFLAGS=-I$ORACLE_INCLUDE_PATH] + [ORACLE_LIBS=-L$ORACLE_LIB_PATH -locci], + [AC_MSG_FAILURE([Oracle (OCI) library is missing: wrong oracle directories])], + [-I$ORACLE_INCLUDE_PATH -L$ORACLE_LIB_PATH -locci -lclntsh ] + ) + else + AC_CHECK_PROG( + [HAVE_ORACLE_CONFIG], + [oracle-instantclient-config], + [yes],,, + ) + if test "x${HAVE_ORACLE_CONFIG}" != "xyes"; then + AC_MSG_FAILURE([oracle-instantclient-config not found in PATH]) + fi + AC_CHECK_LIB( + [occi], + [OCIEnvCreate], + [ORACLE_CFLAGS="`oracle-instantclient-config --cflags`" + ORACLE_LIBS="`oracle-instantclient-config --libs`" + ], + [AC_MSG_FAILURE([Oracle (OCI) libraray is missing])], + [`oracle-instantclient-config --libs --cflags`] + ) + fi +fi +AM_CONDITIONAL(ENABLE_ORACLE, test x$enable_oracle = xyes) +AC_SUBST(ORACLE_CFLAGS) +AC_SUBST(ORACLE_LIBS) + + +# libdbi support +AC_ARG_ENABLE(libdbi, + [AS_HELP_STRING([--enable-libdbi],[Enable libdbi database support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_libdbi="yes" ;; + no) enable_libdbi="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-libdbi) ;; + esac], + [enable_libdbi=no] +) +if test "x$enable_libdbi" = "xyes"; then + AC_CHECK_HEADERS( + [dbi/dbi.h],, + [AC_MSG_FAILURE([libdbi is missing])] + ) + AC_CHECK_LIB( + [dbi], + [dbi_initialize], + [LIBDBI_CFLAGS="" + LIBDBI_LIBS="-ldbi" + ], + [AC_MSG_FAILURE([libdbi library is missing])] + ) + AC_CHECK_LIB( + [dbi], + [dbi_initialize_r], + [AC_DEFINE([HAVE_DBI_R], [1], [Define to 1 if libdbi supports the new plugin-safe interface])] + ) + AC_CHECK_LIB( + [dbi], + [dbi_conn_transaction_begin], + [AC_DEFINE([HAVE_DBI_TXSUPP], [1], [Define to 1 if libdbi supports transactions])] + ) +fi +AM_CONDITIONAL(ENABLE_OMLIBDBI, test x$enable_libdbi = xyes) +AC_SUBST(LIBDBI_CFLAGS) +AC_SUBST(LIBDBI_LIBS) + +# SNMP support +AC_ARG_ENABLE(snmp, + [AS_HELP_STRING([--enable-snmp],[Enable SNMP support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_snmp="yes" ;; + no) enable_snmp="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-snmp) ;; + esac], + [enable_snmp=no] +) +if test "x$enable_snmp" = "xyes"; then + AC_CHECK_HEADERS( + [net-snmp/net-snmp-config.h],, + [AC_MSG_FAILURE([Net-SNMP is missing])] + ) + AC_CHECK_LIB( + [netsnmp], + [snmp_timeout], + [SNMP_CFLAGS="" + SNMP_LIBS="-lnetsnmp" + ], + [AC_MSG_FAILURE([Net-SNMP library is missing])] + ) +fi +AM_CONDITIONAL(ENABLE_SNMP, test x$enable_snmp = xyes) +AC_SUBST(SNMP_CFLAGS) +AC_SUBST(SNMP_LIBS) + + +# uuid support +AC_ARG_ENABLE(uuid, + [AS_HELP_STRING([--enable-uuid],[Enable support for uuid generation @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_uuid="yes" ;; + no) enable_uuid="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-uuid) ;; + esac], + [enable_uuid=yes] +) +if test "x$enable_uuid" = "xyes"; then + PKG_CHECK_MODULES([LIBUUID], [uuid]) + AC_DEFINE(USE_LIBUUID, 1, [Define if you want to enable libuuid support]) +fi +AM_CONDITIONAL(ENABLE_UUID, test x$enable_uuid = xyes) + + +# elasticsearch support +AC_ARG_ENABLE(elasticsearch, + [AS_HELP_STRING([--enable-elasticsearch],[Enable elasticsearch output module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_elasticsearch="yes" ;; + no) enable_elasticsearch="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-elasticsearch) ;; + esac], + [enable_elasticsearch=no] +) +if test "x$enable_elasticsearch" = "xyes"; then + PKG_CHECK_MODULES([CURL], [libcurl]) + LT_LIB_M +fi +AM_CONDITIONAL(ENABLE_ELASTICSEARCH, test x$enable_elasticsearch = xyes) + + +# GnuTLS support +AC_ARG_ENABLE(gnutls, + [AS_HELP_STRING([--enable-gnutls],[Enable GNU TLS support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_gnutls="yes" ;; + no) enable_gnutls="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-gnutls) ;; + esac], + [enable_gnutls=no] +) +if test "x$enable_gnutls" = "xyes"; then + PKG_CHECK_MODULES(GNUTLS, gnutls >= 1.4.0) + AC_DEFINE([ENABLE_GNUTLS], [1], [Indicator that GnuTLS is present]) +fi +AM_CONDITIONAL(ENABLE_GNUTLS, test x$enable_gnutls = xyes) + +# libgcrypt support +AC_ARG_ENABLE(libgcrypt, + [AS_HELP_STRING([--enable-libgcrypt],[Enable log file encryption support (libgcrypt) @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_libgcrypt="yes" ;; + no) enable_libgcrypt="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-libgcrypt) ;; + esac], + [enable_libgcrypt=yes] +) +if test "x$enable_libgcrypt" = "xyes"; then + AC_CHECK_PROG( + [HAVE_LIBGCRYPT_CONFIG], + [libgcrypt-config], + [yes],,, + ) + if test "x${HAVE_LIBGCRYPT_CONFIG}" != "xyes"; then + AC_MSG_FAILURE([libgcrypt-config not found in PATH]) + fi + AC_CHECK_LIB( + [gcrypt], + [gcry_cipher_open], + [LIBGCRYPT_CFLAGS="`libgcrypt-config --cflags`" + LIBGCRYPT_LIBS="`libgcrypt-config --libs`" + ], + [AC_MSG_FAILURE([libgcrypt is missing])], + [`libgcrypt-config --libs --cflags`] + ) + AC_DEFINE([ENABLE_LIBGCRYPT], [1], [Indicator that LIBGCRYPT is present]) +fi +AM_CONDITIONAL(ENABLE_LIBGCRYPT, test x$enable_libgcrypt = xyes) +AC_SUBST(LIBGCRYPT_CFLAGS) +AC_SUBST(LIBGCRYPT_LIBS) + + +# support for building the rsyslogd runtime +AC_ARG_ENABLE(rsyslogrt, + [AS_HELP_STRING([--enable-rsyslogrt],[Build rsyslogrt @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_rsyslogrt="yes" ;; + no) enable_rsyslogrt="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-rsyslogrt) ;; + esac], + [enable_rsyslogrt=yes] +) +if test "x$enable_rsyslogrt" = "xyes"; then + RSRT_CFLAGS1="-I\$(top_srcdir)/runtime -I\$(top_srcdir) -I\$(top_srcdir)/grammar" + RSRT_LIBS1="\$(top_builddir)/runtime/librsyslog.la" +fi +AM_CONDITIONAL(ENABLE_RSYSLOGRT, test x$enable_rsyslogrt = xyes) +RSRT_CFLAGS="\$(RSRT_CFLAGS1) \$(LIBESTR_CFLAGS) \$(JSON_C_FLAGS)" +RSRT_LIBS="\$(RSRT_LIBS1) \$(LIBESTR_LIBS) \$(JSON_C_LIBS)" +AC_SUBST(RSRT_CFLAGS1) +AC_SUBST(RSRT_LIBS1) +AC_SUBST(RSRT_CFLAGS) +AC_SUBST(RSRT_LIBS) + + +# support for NOT building rsyslogd (useful for source-based packaging systems) +AC_ARG_ENABLE(rsyslogd, + [AS_HELP_STRING([--enable-rsyslogd],[Build rsyslogd @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_rsyslogd="yes" ;; + no) enable_rsyslogd="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-rsyslogd) ;; + esac], + [enable_rsyslogd=yes] +) +AM_CONDITIONAL(ENABLE_RSYSLOGD, test x$enable_rsyslogd = xyes) + + +# capability to enable an extended testbench. By default, this is off. The reason +# for this switch is that some test simply take too long to execute them on a regular +# basis. So we enable to skip them, while the majority of tests can still be used. The +# idea is that at least "make distcheck" executes the extended testbench, and also +# developers should explicitely enable it after important changes. -- rgerhards, 2010-04-12 +AC_ARG_ENABLE(extended_tests, + [AS_HELP_STRING([--enable-extended-tests],[extended testbench @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_rsyslogd="yes" ;; + no) enable_rsyslogd="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-extended-tests) ;; + esac], + [enable_extended_tests=no] +) +AM_CONDITIONAL(ENABLE_EXTENDED_TESTS, test x$enable_extended_tests = xyes) + + +# capability to enable MySQL testbench tests. This requries that a Syslog database +# with the default schema has been created on the local (127.0.0.1) MySQL server and +# a user "rsyslog" with password "testbench" exists, is able to login with default +# parameters and has sufficient (read: all) privileges on that database. +# rgerhards, 2011-03-09 +AC_ARG_ENABLE(mysql_tests, + [AS_HELP_STRING([--enable-mysql-tests],[enable MySQL specific tests in testbench @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mysql_tests="yes" ;; + no) enable_mysql_tests="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mysql-tests) ;; + esac], + [enable_mysql_tests=no] +) +AM_CONDITIONAL(ENABLE_MYSQL_TESTS, test x$enable_mysql_tests = xyes) + + +# Mail support (so far we do not need a library, but we need to turn this on and off) +AC_ARG_ENABLE(mail, + [AS_HELP_STRING([--enable-mail],[Enable mail support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mail="yes" ;; + no) enable_mail="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mail) ;; + esac], + [enable_mail=no] +) +AM_CONDITIONAL(ENABLE_MAIL, test x$enable_mail = xyes) + + +# imdiag support (so far we do not need a library, but we need to turn this on and off) +# note that we enable this be default, because an important point is to make +# it available to users who do not know much about how to handle things. It +# would complicate things if we first needed to tell them how to enable imdiag. +# rgerhards, 2008-07-25 +AC_ARG_ENABLE(imdiag, + [AS_HELP_STRING([--enable-imdiag],[Enable imdiag @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imdiag="yes" ;; + no) enable_imdiag="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imdiag) ;; + esac], + [enable_imdiag=no] +) +AM_CONDITIONAL(ENABLE_IMDIAG, test x$enable_imdiag = xyes) + + +# mmnormalize +AC_ARG_ENABLE(mmnormalize, + [AS_HELP_STRING([--enable-mmnormalize],[Enable building mmnormalize support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mmnormalize="yes" ;; + no) enable_mmnormalize="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmnormalize) ;; + esac], + [enable_mmnormalize=no] +) +if test "x$enable_mmnormalize" = "xyes"; then + PKG_CHECK_MODULES(LIBEE, libee >= 0.4.0) + PKG_CHECK_MODULES(LIBLOGNORM, lognorm >= 0.3.1) +fi +AM_CONDITIONAL(ENABLE_MMNORMALIZE, test x$enable_mmnormalize = xyes) + + +# mmnjsonparse +AC_ARG_ENABLE(mmjsonparse, + [AS_HELP_STRING([--enable-mmjsonparse],[Enable building mmjsonparse support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mmjsonparse="yes" ;; + no) enable_mmjsonparse="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmjsonparse) ;; + esac], + [enable_mmjsonparse=no] +) +AM_CONDITIONAL(ENABLE_MMJSONPARSE, test x$enable_mmjsonparse = xyes) + + +# mmaudit +AC_ARG_ENABLE(mmaudit, + [AS_HELP_STRING([--enable-mmaudit],[Enable building mmaudit support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mmaudit="yes" ;; + no) enable_mmaudit="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmaudit) ;; + esac], + [enable_mmaudit=no] +) +AM_CONDITIONAL(ENABLE_MMAUDIT, test x$enable_mmaudit = xyes) + + +# mmanon +AC_ARG_ENABLE(mmanon, + [AS_HELP_STRING([--enable-mmanon],[Enable building mmanon support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mmanon="yes" ;; + no) enable_mmanon="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmanon) ;; + esac], + [enable_mmanon=no] +) +AM_CONDITIONAL(ENABLE_MMANON, test x$enable_mmanon = xyes) + + +# mmcount +AC_ARG_ENABLE(mmcount, + [AS_HELP_STRING([--enable-mmcount],[Enable message counting @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_xmpp="yes" ;; + no) enable_xmpp="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmcount) ;; + esac], + [enable_mmcount=no] +) +AM_CONDITIONAL(ENABLE_MMCOUNT, test x$enable_mmcount = xyes) + + +# mmfields +AC_ARG_ENABLE(mmfields, + [AS_HELP_STRING([--enable-mmfields],[Enable building mmfields support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mmfields="yes" ;; + no) enable_mmfields="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmfields) ;; + esac], + [enable_mmfields=no] +) +AM_CONDITIONAL(ENABLE_MMFIELDS, test x$enable_mmfields = xyes) + + +# RELP support +AC_ARG_ENABLE(relp, + [AS_HELP_STRING([--enable-relp],[Enable RELP support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_relp="yes" ;; + no) enable_relp="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-relp) ;; + esac], + [enable_relp=no] +) +if test "x$enable_relp" = "xyes"; then + PKG_CHECK_MODULES(RELP, relp >= 1.1.2) +fi +AM_CONDITIONAL(ENABLE_RELP, test x$enable_relp = xyes) + + +# GuardTime support +AC_ARG_ENABLE(guardtime, + [AS_HELP_STRING([--enable-guardtime],[Enable log file signing support (via GuardTime) @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_guardtime="yes" ;; + no) enable_guardtime="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-guardtime) ;; + esac], + [enable_guardtime=no] +) +if test "x$enable_guardtime" = "xyes"; then + PKG_CHECK_MODULES(GUARDTIME, libgt >= 0.3.1) +fi +AM_CONDITIONAL(ENABLE_GUARDTIME, test x$enable_guardtime = xyes) + + +# Support using cached man file copies, to avoid the need for rst2man +# in the build environment +AC_ARG_ENABLE(cached_man_pages, + [AS_HELP_STRING([--enable-cached-man-pages],[Enable using cached versions of man files (avoid rst2man) @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_cached_man_pages="yes" ;; + no) enable_cached_man_pages="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-cached-man-pages) ;; + esac], + [enable_cached_man_pages=no] +) +if test "x$enable_cached_man_pages" = "xno"; then +# obtain path for rst2man + if test "x$enable_libgcrypt" = "xyes" || \ + "x$enable_guardtime" = "xyes"; then + AC_PATH_PROG([RST2MAN], [rst2man]) + if test "x${RST2MAN}" == "x"; then + AC_MSG_FAILURE([rst2man not found in PATH]) + fi + fi +fi + + + +# RFC 3195 support +AC_ARG_ENABLE(rfc3195, + [AS_HELP_STRING([--enable-rfc3195],[Enable RFC3195 support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_rfc3195="yes" ;; + no) enable_rfc3195="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-rfc3195) ;; + esac], + [enable_rfc3195=no] +) +if test "x$enable_rfc3195" = "xyes"; then + PKG_CHECK_MODULES(LIBLOGGING, liblogging >= 0.7.1) +fi +AM_CONDITIONAL(ENABLE_RFC3195, test x$enable_rfc3195 = xyes) + + +# enable/disable the testbench (e.g. because some important parts +# are missing) +AC_ARG_ENABLE(testbench, + [AS_HELP_STRING([--enable-testbench],[testbench enabled @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_testbench="yes" ;; + no) enable_testbench="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-testbench) ;; + esac], + [enable_testbench=yes] +) +AM_CONDITIONAL(ENABLE_TESTBENCH, test x$enable_testbench = xyes) + + +# settings for the file input module +AC_ARG_ENABLE(imfile, + [AS_HELP_STRING([--enable-imfile],[file input module enabled @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imfile="yes" ;; + no) enable_imfile="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imfile) ;; + esac], + [enable_imfile=no] +) +AM_CONDITIONAL(ENABLE_IMFILE, test x$enable_imfile = xyes) + + +# settings for the door input module (under solaris, thus default off) +AC_ARG_ENABLE(imsolaris, + [AS_HELP_STRING([--enable-imsolaris],[solaris input module enabled @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imsolaris="yes" ;; + no) enable_imsolaris="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imsolaris) ;; + esac], + [enable_imsolaris=no] +) +AM_CONDITIONAL(ENABLE_IMSOLARIS, test x$enable_imsolaris = xyes) + +# settings for the ptcp input module +AC_ARG_ENABLE(imptcp, + [AS_HELP_STRING([--enable-imptcp],[plain tcp input module enabled @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imptcp="yes" ;; + no) enable_imptcp="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imptcp) ;; + esac], + [enable_imptcp=no] +) +AM_CONDITIONAL(ENABLE_IMPTCP, test x$enable_imptcp = xyes) + + +# settings for the ttcp input module +AC_ARG_ENABLE(imttcp, + [AS_HELP_STRING([--enable-imttcp],[threaded plain tcp input module enabled @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imttcp="yes" ;; + no) enable_imttcp="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imttcp) ;; + esac], + [enable_imttcp=no] +) +AM_CONDITIONAL(ENABLE_IMTTCP, test x$enable_imttcp = xyes) + + +# settings for the pstats input module +AC_ARG_ENABLE(impstats, + [AS_HELP_STRING([--enable-impstats],[periodic statistics module enabled @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_impstats="yes" ;; + no) enable_impstats="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-impstats) ;; + esac], + [enable_impstats=no] +) +AM_CONDITIONAL(ENABLE_IMPSTATS, test x$enable_impstats = xyes) + + +# settings for the omprog output module +AC_ARG_ENABLE(omprog, + [AS_HELP_STRING([--enable-omprog],[Compiles omprog module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omprog="yes" ;; + no) enable_omprog="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omprog) ;; + esac], + [enable_omprog=no] +) +AM_CONDITIONAL(ENABLE_OMPROG, test x$enable_omprog = xyes) + + +# settings for omudpspoof +AC_ARG_ENABLE(omudpspoof, + [AS_HELP_STRING([--enable-omudpspoof],[Compiles omudpspoof module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omudpspoof="yes" ;; + no) enable_omudpspoof="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omudpspoof) ;; + esac], + [enable_omudpspoof=no] +) + +if test "x$enable_omudpspoof" = "xyes"; then + AC_CHECK_HEADERS( + [libnet.h],, + [AC_MSG_FAILURE([libnet is missing])] + ) + AC_CHECK_LIB( + [net], + [libnet_init], + [UDPSPOOF_CFLAGS="" + UDPSPOOF_LIBS="-lnet" + ], + [AC_MSG_FAILURE([libnet is missing])] + ) +fi +AM_CONDITIONAL(ENABLE_OMUDPSPOOF, test x$enable_omudpspoof = xyes) +AC_SUBST(UDPSPOOF_CFLAGS) +AC_SUBST(UDPSPOOF_LIBS) + + +# settings for omstdout +AC_ARG_ENABLE(omstdout, + [AS_HELP_STRING([--enable-omstdout],[Compiles stdout module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omstdout="yes" ;; + no) enable_omstdout="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omstdout) ;; + esac], + [enable_omstdout=no] +) +AM_CONDITIONAL(ENABLE_OMSTDOUT, test x$enable_omstdout = xyes) + +# settings for omjournal +AC_ARG_ENABLE(omjournal, + [AS_HELP_STRING([--enable-omjournal],[Compiles omjournal @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omjournal="yes" ;; + no) enable_omjournal="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omjournal) ;; + esac], + [enable_omjournal=no] +) +if test "x$enable_omjournal" = "xyes"; then + PKG_CHECK_MODULES([LIBSYSTEMD_JOURNAL], [libsystemd-journal >= 197]) +fi +AM_CONDITIONAL(ENABLE_OMJOURNAL, test x$enable_omjournal = xyes) + + +# settings for pmlastmsg +AC_ARG_ENABLE(pmlastmsg, + [AS_HELP_STRING([--enable-pmlastmsg],[Compiles lastmsg parser module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_pmlastmsg="yes" ;; + no) enable_pmlastmsg="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pmlastmsg) ;; + esac], + [enable_pmlastmsg=no] +) +AM_CONDITIONAL(ENABLE_PMLASTMSG, test x$enable_pmlastmsg = xyes) + + +# settings for pmcisconames +AC_ARG_ENABLE(pmcisconames, + [AS_HELP_STRING([--enable-pmcisconames],[Compiles cisconames parser module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_pmcisconames="yes" ;; + no) enable_pmcisconames="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pmcisconames) ;; + esac], + [enable_pmcisconames=no] +) +AM_CONDITIONAL(ENABLE_PMCISCONAMES, test x$enable_pmcisconames = xyes) + + +# settings for pmaixforwardedfrom +AC_ARG_ENABLE(pmaixforwardedfrom, + [AS_HELP_STRING([--enable-pmaixforwardedfrom],[Compiles aixforwardedfrom parser module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_pmaixforwardedfrom="yes" ;; + no) enable_pmaixforwardedfrom="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pmaixforwardedfrom) ;; + esac], + [enable_pmaixforwardedfrom=no] +) +AM_CONDITIONAL(ENABLE_PMAIXFORWARDEDFROM, test x$enable_pmaixforwardedfrom = xyes) + + +# settings for pmsnare +AC_ARG_ENABLE(pmsnare, + [AS_HELP_STRING([--enable-pmsnare],[Compiles snare parser module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_pmsnare="yes" ;; + no) enable_pmsnare="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pmsnare) ;; + esac], + [enable_pmsnare=no] +) +AM_CONDITIONAL(ENABLE_PMSNARE, test x$enable_pmsnare = xyes) + + +# settings for pmrfc3164sd +AC_ARG_ENABLE(pmrfc3164sd, + [AS_HELP_STRING([--enable-pmrfc3164sd],[Compiles rfc3164sd parser module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_pmrfc3164sd="yes" ;; + no) enable_pmrfc3164sd="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pmrfc3164sd) ;; + esac], + [enable_pmrfc3164sd=no] +) +AM_CONDITIONAL(ENABLE_PMRFC3164SD, test x$enable_pmrfc3164sd = xyes) + + +# settings for omruleset +AC_ARG_ENABLE(omruleset, + [AS_HELP_STRING([--enable-omruleset],[Compiles ruleset forwarding module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omruleset="yes" ;; + no) enable_omruleset="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omruleset) ;; + esac], + [enable_omruleset=yes] +) +AM_CONDITIONAL(ENABLE_OMRULESET, test x$enable_omruleset = xyes) + + +# building the GUI (mostly for diagnostic reasons) +AC_ARG_ENABLE(gui, + [AS_HELP_STRING([--enable-gui],[Enable GUI programs @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_gui="yes" ;; + no) enable_gui="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-gui) ;; + esac], + [enable_gui=no] +) +if test "x$enable_gui" = "xyes"; then + if test x$HAVE_JAVAC = x; then + AC_MSG_ERROR([GUI components need Java, but Java development system is not installed on this system]) + fi +fi +AM_CONDITIONAL(ENABLE_GUI, test x$enable_gui = xyes) + + +# settings for omuxsock +AC_ARG_ENABLE(omuxsock, + [AS_HELP_STRING([--enable-omuxsock],[Compiles omuxsock module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omuxsock="yes" ;; + no) enable_omuxsock="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omuxsock) ;; + esac], + [enable_omuxsock=no] +) +AM_CONDITIONAL(ENABLE_OMUXSOCK, test x$enable_omuxsock = xyes) + + +# A custom strgen that also serves as a sample of how to do +# SQL-generating strgen's +AC_ARG_ENABLE(sm_cust_bindcdr, + [AS_HELP_STRING([--enable-sm_cust_bindcdr],[Compiles sm_cust_bindcdr module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_sm_cust_bindcdr="yes" ;; + no) enable_sm_cust_bindcdr="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-sm_cust_bindcdr) ;; + esac], + [enable_sm_cust_bindcdr=no] +) +AM_CONDITIONAL(ENABLE_SMCUSTBINDCDR, test x$enable_sm_cust_bindcdr = xyes) + + +# settings for mmsnmptrapd message modification module +AC_ARG_ENABLE(mmsnmptrapd, + [AS_HELP_STRING([--enable-mmsnmptrapd],[Compiles mmsnmptrapd module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_mmsnmptrapd="yes" ;; + no) enable_mmsnmptrapd="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-mmsnmptrapd) ;; + esac], + [enable_mmsnmptrapd=no] +) +AM_CONDITIONAL(ENABLE_MMSNMPTRAPD, test x$enable_mmsnmptrapd = xyes) + + +# settings for the omhdfs; +AC_ARG_ENABLE(omhdfs, + [AS_HELP_STRING([--enable-omhdfs],[Compiles omhdfs template module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omhdfs="yes" ;; + no) enable_omhdfs="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omhdfs) ;; + esac], + [enable_omhdfs=no] +) +# +# you may want to do some library checks here - see snmp, mysql, pgsql modules +# for samples +# +AM_CONDITIONAL(ENABLE_OMHDFS, test x$enable_omhdfs = xyes) + + +#MONGODB SUPPORT + +AC_ARG_ENABLE(ommongodb, + [AS_HELP_STRING([--enable-ommongodb],[Compiles ommongodb template module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_ommongodb="yes" ;; + no) enable_ommongodb="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ommongodb) ;; + esac], + [enable_ommongodb=no] +) +if test "x$enable_ommongodb" = "xyes"; then + PKG_CHECK_MODULES(LIBMONGO_CLIENT, libmongo-client >= 0.1.4) +fi +AM_CONDITIONAL(ENABLE_OMMONGODB, test x$enable_ommongodb = xyes) +# end of mongodb code + +# BEGIN ZMQ3 INPUT SUPPORT +AC_ARG_ENABLE(imzmq3, + [AS_HELP_STRING([--enable-imzmq3],[Compiles imzmq3 output module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imzmq3="yes" ;; + no) enable_imzmq3="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imzmq3) ;; + esac], + [enable_imzmq3=no] +) +if test "x$enable_imzmq3" = "xyes"; then + PKG_CHECK_MODULES(CZMQ, libczmq >= 1.1.0) +fi +AM_CONDITIONAL(ENABLE_IMZMQ3, test x$enable_imzmq3 = xyes) + +# END ZMQ3 INPUT SUPPORT + +# BEGIN ZMQ3 OUTPUT SUPPORT +AC_ARG_ENABLE(omzmq3, + [AS_HELP_STRING([--enable-omzmq3],[Compiles omzmq3 output module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omzmq3="yes" ;; + no) enable_omzmq3="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omzmq3) ;; + esac], + [enable_omzmq3=no] +) +if test "x$enable_omzmq3" = "xyes"; then + PKG_CHECK_MODULES(CZMQ, libczmq >= 1.1.0) +fi +AM_CONDITIONAL(ENABLE_OMZMQ3, test x$enable_omzmq3 = xyes) + +# END ZMQ3 SUPPORT + +# BEGIN RABBITMQ OUTPUT SUPPORT + +AC_ARG_ENABLE(omrabbitmq, + [AS_HELP_STRING([--enable-omrabbitmq],[Compiles omrabbitmq output module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omrabbitmq="yes" ;; + no) enable_omrabbitmq="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omrabbitmq) ;; + esac], + [enable_omrabbitmq=no] +) +if test "x$enable_omrabbitmq" = "xyes"; then + PKG_CHECK_MODULES(RABBITMQ, librabbitmq >= 0.2.0) + AC_SUBST(RABBITMQ_CFLAGS) + AC_SUBST(RABBITMQ_LIBS) +fi +AM_CONDITIONAL(ENABLE_OMRABBITMQ, test x$enable_omrabbitmq = xyes) + +# END RABBITMQ SUPPORT + +# HIREDIS SUPPORT + +AC_ARG_ENABLE(omhiredis, + [AS_HELP_STRING([--enable-omhiredis],[Compiles omhiredis template module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_omhiredis="yes" ;; + no) enable_omhiredis="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-omhiredis) ;; + esac], + [enable_omhiredis=no] +) +# +if test "x$enable_omhiredis" = "xyes"; then + PKG_CHECK_MODULES(HIREDIS, hiredis >= 0.10.1) +fi +AM_CONDITIONAL(ENABLE_OMHIREDIS, test x$enable_omhiredis = xyes) + +# END HIREDIS SUPPORT + +AC_CONFIG_FILES([Makefile \ + runtime/Makefile \ + compat/Makefile \ + grammar/Makefile \ + tools/Makefile \ + doc/Makefile \ + plugins/imudp/Makefile \ + plugins/imtcp/Makefile \ + plugins/im3195/Makefile \ + plugins/imgssapi/Makefile \ + plugins/imuxsock/Makefile \ + plugins/imjournal/Makefile \ + plugins/immark/Makefile \ + plugins/imklog/Makefile \ + plugins/imkmsg/Makefile \ + plugins/omhdfs/Makefile \ + plugins/omprog/Makefile \ + plugins/omstdout/Makefile \ + plugins/omjournal/Makefile \ + plugins/pmrfc3164sd/Makefile \ + plugins/pmlastmsg/Makefile \ + plugins/pmcisconames/Makefile \ + plugins/pmsnare/Makefile \ + plugins/pmaixforwardedfrom/Makefile \ + plugins/omruleset/Makefile \ + plugins/omuxsock/Makefile \ + plugins/imfile/Makefile \ + plugins/imsolaris/Makefile \ + plugins/imptcp/Makefile \ + plugins/imttcp/Makefile \ + plugins/impstats/Makefile \ + plugins/imrelp/Makefile \ + plugins/imdiag/Makefile \ + plugins/imzmq3/Makefile \ + plugins/omtesting/Makefile \ + plugins/omgssapi/Makefile \ + plugins/ommysql/Makefile \ + plugins/ompgsql/Makefile \ + plugins/omrelp/Makefile \ + plugins/omlibdbi/Makefile \ + plugins/ommail/Makefile \ + plugins/omsnmp/Makefile \ + plugins/omoracle/Makefile \ + plugins/omudpspoof/Makefile \ + plugins/ommongodb/Makefile \ + plugins/omhiredis/Makefile \ + plugins/omzmq3/Makefile \ + plugins/omrabbitmq/Makefile \ + plugins/mmnormalize/Makefile \ + plugins/mmjsonparse/Makefile \ + plugins/mmaudit/Makefile \ + plugins/mmanon/Makefile \ + plugins/mmcount/Makefile \ + plugins/mmfields/Makefile \ + plugins/omelasticsearch/Makefile \ + plugins/sm_cust_bindcdr/Makefile \ + plugins/mmsnmptrapd/Makefile \ + java/Makefile \ + tests/Makefile]) +AC_OUTPUT + +echo "****************************************************" +echo "rsyslog will be compiled with the following settings:" +echo +echo " Large file support enabled: $enable_largefile" +echo " Networking support enabled: $enable_inet" +echo " Regular expressions support enabled: $enable_regexp" +echo " Zlib compression support enabled: $enable_zlib" +echo " rsyslog runtime will be built: $enable_rsyslogrt" +echo " rsyslogd will be built: $enable_rsyslogd" +echo " GUI components will be built: $enable_gui" +echo " cached man files will be used: $enable_cached_man_pages" +echo " Unlimited select() support enabled: $enable_unlimited_select" +echo " uuid support enabled: $enable_uuid" +echo " Log file signing support: $enable_guardtime" +echo " Log file encryption support: $enable_libgcrypt" +echo " anonymization support enabled: $enable_mmanon" +echo " message counting support enabled: $enable_mmcount" +echo " mmfields enabled: $enable_mmfields" +echo +echo "---{ input plugins }---" +echo " Klog functionality enabled: $enable_klog ($os_type)" +echo " /dev/kmsg functionality enabled: $enable_kmsg" +echo " plain tcp input module enabled: $enable_imptcp" +echo " threaded plain tcp input module enabled: $enable_imttcp" +echo " imdiag enabled: $enable_imdiag" +echo " file input module enabled: $enable_imfile" +echo " Solaris input module enabled: $enable_imsolaris" +echo " periodic statistics module enabled: $enable_impstats" +echo " imzmq3 input module enabled: $enable_imzmq3" +echo " imjournal input module enabled: $enable_imjournal" +echo +echo "---{ output plugins }---" +echo " Mail support enabled: $enable_mail" +echo " omprog module will be compiled: $enable_omprog" +echo " omstdout module will be compiled: $enable_omstdout" +echo " omjournal module will be compiled: $enable_omjournal" +echo " omhdfs module will be compiled: $enable_omhdfs" +echo " omelasticsearch module will be compiled: $enable_elasticsearch" +echo " omruleset module will be compiled: $enable_omruleset" +echo " omudpspoof module will be compiled: $enable_omudpspoof" +echo " omuxsock module will be compiled: $enable_omuxsock" +echo " omzmq3 module will be compiled: $enable_omzmq3" +echo " omrabbitmq module will be compiled: $enable_omrabbitmq" +echo +echo "---{ parser modules }---" +echo " pmrfc3164sd module will be compiled: $enable_pmrfc3164sd" +echo " pmlastmsg module will be compiled: $enable_pmlastmsg" +echo " pmcisconames module will be compiled: $enable_pmcisconames" +echo " pmaixforwardedfrom module w.be compiled: $enable_pmaixforwardedfrom" +echo " pmsnare module will be compiled: $enable_pmsnare" +echo +echo "---{ message modification modules }---" +echo " mmnormalize module will be compiled: $enable_mmnormalize" +echo " mmjsonparse module will be compiled: $enable_mmjsonparse" +echo " mmjaduit module will be compiled: $enable_mmaudit" +echo " mmsnmptrapd module will be compiled: $enable_mmsnmptrapd" +echo +echo "---{ strgen modules }---" +echo " sm_cust_bindcdr module will be compiled: $enable_sm_cust_bindcdr" +echo +echo "---{ database support }---" +echo " MySql support enabled: $enable_mysql" +echo " libdbi support enabled: $enable_libdbi" +echo " PostgreSQL support enabled: $enable_pgsql" +echo " mongodb support enabled: $enable_ommongodb" +echo " hiredis support enabled: $enable_omhiredis" +echo " Oracle (OCI) support enabled: $enable_oracle" +echo +echo "---{ protocol support }---" +echo " GnuTLS network stream driver enabled: $enable_gnutls" +echo " GSSAPI Kerberos 5 support enabled: $enable_gssapi_krb5" +echo " RELP support enabled: $enable_relp" +echo " SNMP support enabled: $enable_snmp" +echo +echo "---{ debugging support }---" +echo " Testbench enabled: $enable_testbench" +echo " Extended Testbench enabled: $enable_extended_tests" +echo " MySQL Tests enabled: $enable_mysql_tests" +echo " Debug mode enabled: $enable_debug" +echo " Runtime Instrumentation enabled: $enable_rtinst" +echo " (total) debugless mode enabled: $enable_debugless" +echo " Diagnostic tools enabled: $enable_diagtools" +echo " End-User tools enabled: $enable_usertools" +echo " Enhanced memory checking enabled: $enable_memcheck" +echo " Valgrind support settings enabled: $enable_valgrind" +echo diff --git a/contrib/README b/contrib/README new file mode 100644 index 00000000..d8ef3dd2 --- /dev/null +++ b/contrib/README @@ -0,0 +1,12 @@ +This directory contains a number of possibly useful things that do not +directly relate to rsyslog. They are not actively supported, but as +I said often helpful. Use them with some care, as they may be outdated +in respect to the current release of rsyslog. + +At least some of this stuff has been found by our users and been +included after a brief check and possibly an adapation. If you have +something useful you would like to see in contrib, just drop us a +note (see http://www.rsyslog.com for how to do that at the time your +are reading this document). + +rgerhards, 2007-08-08 diff --git a/contrib/gnutls/ca-key.pem b/contrib/gnutls/ca-key.pem new file mode 100644 index 00000000..181a8ad9 --- /dev/null +++ b/contrib/gnutls/ca-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDDaz5X5YIruPH0wukMPik7xIKqrpCcr8Gm28oz5h4GtX253eWr +piBuk2a/f/CKDjeuqmiWqTs90PFNb+Z1c+Yzvagqv80VzZwDI4RcrwlNaKrBz/9X +iowCcoV8s7GvV2vtZEPSThNzz4FYkxCMvbOYZeJIYQVhZggUcuadfhmDIwIDAQAB +AoGAIG5AUD2jmYDzD+UhiultVgtkifyNaEtsuQsZu/zbt85P2VQ0z4SINlbvrXvc +iJ9tEzzEPa3udHGj/MTDe3OAB4TK5tImX1pe2gw+zaOB/DaH5i4QhXeltU7epCHF +oUv9EVNzL8Bl00MFiWcLY0LisQVfHeW5rcN9U7EbvTlWbRkCQQDR2/Qn1ceavwDU +qYt2TbEicJVC8aQMYYyc6Xvi4mZaNa8gGCpWpurgQop0Ln0QE8vA0601UVs6N3tm +g8FJ8rXpAkEA7mKCtp2MXCbHMdkZCyQt6drUYCyU9N/HtmBEtFGpqt1PdMyUI07m +rlVFDwUH9JFmg18RP1X2ufj7+ZbJzaMtKwJBAJgbw1Z0P19Mfj+mPC2dlnyN+cIx +/2Px+Mdq/J6w1tsf+jVbDqUMC0ZNNKmNYJycnJzBUNRKicMin9DoQttkjrECQQCC +s/aRY+6adBSRi0QE7NBTwUzicm81mCDrKPtilsfdTDyNgMHUXiVy/oO/yXVkLfi0 +HQLa5CpEK3UUkw2Qt2BDAkA0XXvQzW0+tEHiktLNljIluhiyOAx2bBywY/9Qmn6C +hv4sOSCzTR39jNmuNZ0X6ZZvt4VsWTHhpche/ud1+3p6 +-----END RSA PRIVATE KEY----- diff --git a/contrib/gnutls/ca.pem b/contrib/gnutls/ca.pem new file mode 100644 index 00000000..6324c7d5 --- /dev/null +++ b/contrib/gnutls/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYjCCAc2gAwIBAgIBATALBgkqhkiG9w0BAQUwWDELMAkGA1UEBhMCREUxHTAb +BgNVBAoTFHJzeXNsb2cgdGVzdCByb290IENBMQswCQYDVQQLEwJDQTEdMBsGA1UE +AxMUcnN5c2xvZy10ZXN0LXJvb3QtY2EwHhcNMDgwNTIwMTI1ODEyWhcNMTgwNTE4 +MTI1ODI0WjBYMQswCQYDVQQGEwJERTEdMBsGA1UEChMUcnN5c2xvZyB0ZXN0IHJv +b3QgQ0ExCzAJBgNVBAsTAkNBMR0wGwYDVQQDExRyc3lzbG9nLXRlc3Qtcm9vdC1j +YTCBnDALBgkqhkiG9w0BAQEDgYwAMIGIAoGAw2s+V+WCK7jx9MLpDD4pO8SCqq6Q +nK/BptvKM+YeBrV9ud3lq6YgbpNmv3/wig43rqpolqk7PdDxTW/mdXPmM72oKr/N +Fc2cAyOEXK8JTWiqwc//V4qMAnKFfLOxr1dr7WRD0k4Tc8+BWJMQjL2zmGXiSGEF +YWYIFHLmnX4ZgyMCAwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8E +BQMDBwYAMB0GA1UdDgQWBBQzYQQgUm0YLNdarJnc2c1LxYVClDALBgkqhkiG9w0B +AQUDgYEAuGWtH7Jkpa0n/izqQ5ddDQP/LT6taivCwlpEYEU9aumpQPWWxtYywKaP +RfM1JTMLAiYd8MS7TJ8TYRvvR32Y02Y+OhXn11xERkWvBT2M9yzqX6hDfRueN7RT +fPWsfm/NBTVojzjaECcTFenZid7PC5JiFbcU6PSUMZ49/JPhxAo= +-----END CERTIFICATE----- diff --git a/contrib/gnutls/cert.pem b/contrib/gnutls/cert.pem new file mode 100644 index 00000000..6b5b13cd --- /dev/null +++ b/contrib/gnutls/cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIChjCCAfGgAwIBAgIBADALBgkqhkiG9w0BAQUwWDELMAkGA1UEBhMCREUxHTAb +BgNVBAoTFHJzeXNsb2cgdGVzdCByb290IENBMQswCQYDVQQLEwJDQTEdMBsGA1UE +AxMUcnN5c2xvZy10ZXN0LXJvb3QtY2EwHhcNMDgwNTIwMTMwNDE5WhcNMTgwNTE4 +MTMwNDI2WjA6MQswCQYDVQQGEwJERTEQMA4GA1UEChMHcnN5c2xvZzEZMBcGA1UE +CxMQdGVzdCBjZXJ0aWZpY2F0ZTCBnDALBgkqhkiG9w0BAQEDgYwAMIGIAoGAxmHe +fztJgaGxFYEceiUg0hdMlRVWBqoZelJ8BeXTDnXcu/5F2HtM+l+QDyDaGjKlx+NI +K4rkj7d6Wd3AKPgOYS0VSDZe3a1xf9rRYzOthWTv7tYi4/LTqPXqN5lKE71dgrB/ +/gOmvV/1YD776FIxVGCSAT0hHwkFC3slmpJSwD8CAwEAAaOBhDCBgTAMBgNVHRMB +Af8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHREECzAJ +ggdyc3lzbG9nMB0GA1UdDgQWBBQYu6eC9UALvC+5K5VOnFRi5OC98TAfBgNVHSME +GDAWgBQzYQQgUm0YLNdarJnc2c1LxYVClDALBgkqhkiG9w0BAQUDgYEAXaymqsG9 +PNBhhWIRFvXCDMaDM71vUtgSFoNUbxIV607ua2HQosPPM4EHIda6N6hdBK1bMQoG +yqBwhvw0JVaVaO70Kbs2m2Ypk3YcpJtRqyp8q8+2y/w1Mk1QazFZC29aYgX2iNVf +X4/x38YEL7Gu5vqPrTn++agnV4ZXECKuvLQ= +-----END CERTIFICATE----- diff --git a/contrib/gnutls/key.pem b/contrib/gnutls/key.pem new file mode 100644 index 00000000..3ff507f0 --- /dev/null +++ b/contrib/gnutls/key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDGYd5/O0mBobEVgRx6JSDSF0yVFVYGqhl6UnwF5dMOddy7/kXY +e0z6X5APINoaMqXH40griuSPt3pZ3cAo+A5hLRVINl7drXF/2tFjM62FZO/u1iLj +8tOo9eo3mUoTvV2CsH/+A6a9X/VgPvvoUjFUYJIBPSEfCQULeyWaklLAPwIDAQAB +AoGARIwKqmHc+0rYenq7UUVE+vMMBjNyHyllVkvsCMmpzMRS+i5ZCf1I0vZ0O5X5 +ZrX7bH8PL+R1J2eZgjXKMR3NMZBuyKHewItD9t2rIC0eD/ITlwq3VybbaMsw666e +INxSmax+dS5CEcLevHHP3c+Q7S7QAFiWV43TdFUGXWJktIkCQQDPQ5WAZ+/Tvv0Q +vtRjXMeTVaw/bSuKNUeDzFkmGyePnFeCReNFtJLE9PFSQWcPuYcbZgU59JTfA5ac +Un+cHm31AkEA9Qek+q7PcJ+kON9E6SNodCZn6gLyHjnWrq4tf8pZO3NvoX2QiuD4 +rwF7KWjr6q1JzADpLtwXnuYEhyiLFjJA4wJAcElMCEnG2y+ASH8p7z7HfKGQdLg/ +O1wMB3JA5e0WLK5lllUogI4IaZ3N02NNY25+rLBDqpc/w+ZcxQnIypqNtQJATs9p +ofON5wSB1oUBbhckZo9fxuWxqEUkJsUA/2Q+9R843XE8h166vdc1HOmRT8bywHne +hmLl+gazmCFTMw1wzwJAHng+3zGUl4D8Ov3MPFD6hwYYK6/pEdtz/NUsCSazF7eK +XuuP+DXPHNhXOuF1A3tP74pfc/fC1uCUH2G5z3Fy0Q== +-----END RSA PRIVATE KEY----- diff --git a/dirty.h b/dirty.h new file mode 100644 index 00000000..e0a6e26e --- /dev/null +++ b/dirty.h @@ -0,0 +1,44 @@ +/* This file is an aid to support non-modular object accesses + * while we do not have fully modularized everything. Once this is + * done, this file can (and should) be deleted. Presence of it + * also somewhat indicates that the runtime library is not really + * yet a runtime library, because it depends on some functionality + * residing somewhere else. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef DIRTY_H_INCLUDED +#define DIRTY_H_INCLUDED 1 + +rsRetVal __attribute__((deprecated)) multiSubmitMsg(multi_submit_t *pMultiSub); +rsRetVal multiSubmitMsg2(multi_submit_t *pMultiSub); /* friends only! */ +rsRetVal submitMsg2(msg_t *pMsg); +rsRetVal __attribute__((deprecated)) submitMsg(msg_t *pMsg); +rsRetVal multiSubmitFlush(multi_submit_t *pMultiSub); +rsRetVal logmsgInternal(int iErr, int pri, uchar *msg, int flags); +rsRetVal __attribute__((deprecated)) parseAndSubmitMessage(uchar *hname, uchar *hnameIP, uchar *msg, int len, int flags, flowControl_t flowCtlTypeu, prop_t *pInputName, struct syslogTime *stTime, time_t ttGenTime, ruleset_t *pRuleset); +rsRetVal diagGetMainMsgQSize(int *piSize); /* for imdiag */ +rsRetVal createMainQueue(qqueue_t **ppQueue, uchar *pszQueueName, struct nvlst *lst); + +extern int MarkInterval; +extern qqueue_t *pMsgQueue; /* the main message queue */ +extern int iConfigVerify; /* is this just a config verify run? */ +extern int bHaveMainQueue; +#endif /* #ifndef DIRTY_H_INCLUDED */ diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..e1757644 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,176 @@ +html_files = \ + index.html \ + bugs.html \ + debug.html \ + features.html \ + generic_design.html \ + expression.html \ + droppriv.html \ + history.html \ + how2help.html \ + install.html \ + build_from_repo.html \ + ipv6.html \ + log_rotation_fix_size.html \ + manual.html \ + modules.html \ + property_replacer.html \ + rsyslog_ng_comparison.html \ + rsyslog_conf.html \ + rsyslog-example.conf \ + rsyslog_mysql.html \ + rsyslog_pgsql.html \ + rsyslog_packages.html \ + rsyslog_high_database_rate.html \ + rsyslog_php_syslog_ng.html \ + rsyslog_recording_pri.html \ + rsyslog_tls.html \ + rsyslog_reliable_forwarding.html \ + rsyslog_stunnel.html \ + syslog_protocol.html \ + version_naming.html \ + contributors.html \ + dev_queue.html \ + ompipe.html \ + omfwd.html \ + omfile.html \ + omjournal.html \ + imjournal.html \ + mmanon.html \ + omusrmsg.html \ + omstdout.html \ + omudpspoof.html \ + omruleset.html \ + omsnmp.html \ + sigprov_gt.html \ + ommysql.html \ + omoracle.html \ + omlibdbi.html \ + imfile.html \ + imtcp.html \ + imptcp.html \ + impstats.html \ + imgssapi.html \ + imrelp.html \ + imsolaris.html \ + imuxsock.html \ + imklog.html \ + pmlastmsg.html \ + mmsnmptrapd.html \ + queues.html \ + src/queueWorkerLogic.dia \ + queueWorkerLogic.jpg \ + queueWorkerLogic_small.jpg \ + tls_cert_100.jpg \ + tls_cert_ca.jpg \ + tls_cert.jpg \ + tls_cert_errmsgs.html \ + rsyslog_secure_tls.html \ + tls_cert_server.html \ + tls_cert_ca.html \ + tls_cert_summary.html \ + tls_cert_machine.html \ + tls_cert_udp_relay.html \ + tls_cert_client.html \ + tls_cert_scenario.html \ + rainerscript.html \ + lookup_tables.html \ + rscript_abnf.html \ + rsconf1_actionexeconlywhenpreviousissuspended.html \ + rsconf1_actionresumeinterval.html \ + rsconf1_allowedsender.html \ + rsconf1_controlcharacterescapeprefix.html \ + rsconf1_escape8bitcharsonreceive.html \ + rsconf1_debugprintcfsyslinehandlerlist.html \ + rsconf1_debugprintmodulelist.html \ + rsconf1_debugprinttemplatelist.html \ + rsconf1_dircreatemode.html \ + rsconf1_dirgroup.html \ + rsconf1_dirowner.html \ + rsconf1_dropmsgswithmaliciousdnsptrrecords.html \ + rsconf1_droptrailinglfonreception.html \ + rsconf1_dynafilecachesize.html \ + rsconf1_escapecontrolcharactersonreceive.html \ + rsconf1_failonchownfailure.html \ + rsconf1_filecreatemode.html \ + rsconf1_filegroup.html \ + rsconf1_fileowner.html \ + rsconf1_generateconfiggraph.html \ + rsconf1_gssforwardservicename.html \ + rsconf1_gsslistenservicename.html \ + rsconf1_gssmode.html \ + rsconf1_includeconfig.html \ + rsconf1_mainmsgqueuesize.html \ + rsconf1_markmessageperiod.html \ + rsconf1_modload.html \ + rsconf1_moddir.html \ + rsconf1_repeatedmsgreduction.html \ + rsconf1_resetconfigvariables.html \ + rsconf1_rulesetcreatemainqueue.html \ + rsconf1_umask.html \ + rsconf1_rulesetparser.html \ + v3compatibility.html \ + v4compatibility.html \ + v5compatibility.html \ + im3195.html \ + netstream.html \ + ns_gtls.html \ + ns_ptcp.html \ + src/tls_cert.dia \ + gssapi.html \ + licensing.html \ + mmnormalize.html \ + mmjsonparse.html \ + ommail.html \ + omuxsock.html \ + omrelp.html \ + syslog_parsing.html \ + troubleshoot.html \ + rsyslog_conf_actions.html \ + rsyslog_conf_filter.html \ + rsyslog_conf_global.html \ + rsyslog_conf_modules.html \ + rsyslog_conf_output.html \ + rsyslog_conf_templates.html \ + rsyslog_conf_nomatch.html \ + queues_analogy.html \ + multi_ruleset.html \ + multi_ruleset_legacy_format.html \ + dev_oplugins.html \ + free_support.html \ + imudp.html \ + messageparser.html \ + omhdfs.html \ + omprog.html \ + queue_msg_state.jpeg \ + rsconf1_abortonuncleanconfig.html \ + rsconf1_maxopenfiles.html \ + rsconf1_omfileforcechown.html \ + rsyslog_queue_pointers.jpeg \ + rsyslog_queue_pointers2.jpeg \ + v6compatibility.html \ + v7compatibility.html \ + rsyslog_conf_basic_structure.html \ + rsyslog_conf_sysklogd_compatibility.html \ + imkmsg.html \ + src/classes.dia + +grfx_files = \ + rsyslog_confgraph_complex.png\ + rsyslog_confgraph_std.png \ + module_workflow.png \ + direct_queue0.png \ + direct_queue1.png \ + direct_queue2.png \ + direct_queue3.png \ + direct_queue_rsyslog.png \ + direct_queue_rsyslog2.png \ + direct_queue_directq.png \ + dataflow.png \ + queue_analogy_tv.png \ + gssapi.png \ + rfc5424layers.png \ + src/rfc5424layers.dia \ + rsyslog-vers.png + +EXTRA_DIST = $(html_files) $(grfx_files) diff --git a/doc/action-call.dot b/doc/action-call.dot new file mode 100644 index 00000000..86c6834d --- /dev/null +++ b/doc/action-call.dot @@ -0,0 +1,33 @@ +// This file is part of rsyslog. +// +// rsyslog action call state diagram +// +// see http://www.graphviz.org for how to obtain the graphviz processor +// which is used to build the actual graph. +// +// generate the graph with +// $ dot action-call.dot -Tpng >action-call.png + +digraph G { + label="\n\nrsyslog message states during action processing\nhttp://www.rsyslog.com"; + //fontsize=20; + + ok [label="ready for processing" color="green"]; + mpf [label="message permanent failure" color="red"]; + tf [label="temporary failure"] + cPen [label="commit pending"]; + com [label="committed" color="red"]; + + tf -> tf [label="retry fails, i < n"]; + tf -> mpf [label="retry fails, i = n"]; + tf -> ok [label="retry succeeds"]; + ok -> com [label="doAction RS_RET_OK"]; + ok -> cPen [label="doAction COMMIT_PENDING"]; + ok -> tf [label="doAction RS_RET_SUSPENDED"]; + ok -> mpf [label="doAction RS_RET_DISABLED"]; + cPen -> com [label="endTransaction RS_RET_OK"]; + cPen -> tf [label="endTransaction _SUSPENDED"]; + + //{rank=same; tf cPen} + {rank=same; com mpf} +} diff --git a/doc/action_state.dot b/doc/action_state.dot new file mode 100644 index 00000000..2f36d8da --- /dev/null +++ b/doc/action_state.dot @@ -0,0 +1,34 @@ +// This file is part of rsyslog. +// +// rsyslog message state diagram +// +// see http://www.graphviz.org for how to obtain the graphviz processor +// which is used to build the actual graph. +// +// generate the graph with +// $ dot file.dot -Tpng >file.png + +digraph msgState { + compound=true; nodesep=1.0 + //label="\n\nrsyslog action transaction states\nhttp://www.rsyslog.com"; + //fontsize=20; + + rdy [label="ready" group="main"]; + itx [label="in Tx" group="main"]; + comm [label="commit"] + rtry [label="retry"] + susp [label="suspended"] + + rdy -> itx [label="transaction begins"] + rdy -> rtry [label="begin tx\nerror"] + itx -> itx [label="success"] + itx -> comm [label="commit\n(caller or auto)"] + itx -> rtry [label="error"] + comm -> rdy [label="success"] + comm -> rtry [label="error"] + rtry -> rdy [label="recovered"] + rtry -> susp [label="could not\nrecover"] + susp -> rtry [label="timeout expired"] + + {rank=same; comm rtry} +} diff --git a/doc/batch_state.dot b/doc/batch_state.dot new file mode 100644 index 00000000..0dd48b47 --- /dev/null +++ b/doc/batch_state.dot @@ -0,0 +1,28 @@ +// This file is part of rsyslog. +// +// rsyslog batch state diagram +// +// see http://www.graphviz.org for how to obtain the graphviz processor +// which is used to build the actual graph. +// +// generate the graph with +// $ dot file.dot -Tpng >file.png + +digraph msgState { + compound=true; nodesep=1.0 + //label="\n\nrsyslog batch states\nhttp://www.rsyslog.com"; + rankdir=LR + + rdy [label="ready"]; + bad [label="message-caused\nfailure"]; + sub [label="submitted"] + disc [label="discarded" color="red"] + + rdy -> sub [label="submitted to action"] + rdy -> bad [label="permanent fail"] + rdy -> disc [label="action requests discarding"] + sub -> rdy [label="next action or\naction-caused failure"] + bad -> rdy [label="next action"] + + //{rank=same; comm rtry } +} diff --git a/doc/bugs.html b/doc/bugs.html new file mode 100644 index 00000000..a12c43f3 --- /dev/null +++ b/doc/bugs.html @@ -0,0 +1,32 @@ +<html> +<head> +<title>rsyslog bugs and annoyances</title> +</head> +<body> +<h1>rsyslog bugs and annoyances</h1> +<p><b>This page lists the known bugs rsyslog has to offer.</b> It lists +old and esoteric bugs. A live list of bugs is contained in our bugzilla. <b> +<font color="#FF0000">Please visit </font><a href="http://www.rsyslog.com/bugs"> +<font color="#FF0000">http://www.rsyslog.com/bugs</font></a></b> to see what we +have. There, you can also open your own bug report if you think you found one.</p> +<p>This list has last been updated on 2008-02-12 by +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a>.</p> +<h1>rsyslogd</h1> +<h2>EQUALLY-NAMED TEMPLATES</h2> +<p>If multiple templates with the SAME name are created, all but the + first definition is IGNORED. So you can NOT (yet) replace a + template definition. I also strongly doubt I will ever support this, because +it does not make an awful lot of sense (after all, why not use two template +names...).</p> +<h2>WALLMSG FORMAT (* selector) + </h2> +<p>This format is actually not 100% compatible with stock syslogd - the + date is missing. Will be fixed soon and can also be fixed just via + the proper template. Anyone up for this? ;)</p> +<h2>MULTIPLE INSTANCES</h2> +<p>If multiple instances are running on a single machine, the one with + the -r switch must start first. Also, UDP-based syslog forwarding between the +instances does not work. Use TCP instead.</p> + +</body> +</html> diff --git a/doc/build_from_repo.html b/doc/build_from_repo.html new file mode 100644 index 00000000..a06863e1 --- /dev/null +++ b/doc/build_from_repo.html @@ -0,0 +1,79 @@ +<html><head> +<title>Building rsyslog from the source repository</title> +</head> +<body> +<h1>Building rsyslog from the source repository</h1> +<p>In most cases, people install rsyslog either via a package or use an "official" +distribution tarball to generate it. But there may be situations where it is desirable +to build directly from the source repository. This is useful for people who would like to +participate in development or who would like to use the latest, not-yet-released code. +The later may especially be the case if you are asked to try out an experimental version. +<p>Building from the repsitory is not much different than building from the source +tarball, but some files are missing because they are output files and thus do not +belong into the repository. +<h2>Obtaining the Source</h2> +<p>First of all, you need to download the sources. Rsyslog is currently kept in a git +repository. You can clone this repository either via http or git protocol (with the later +being much faster. URLS are: +<ul> +<li>git://git.adiscon.com/git/rsyslog.git +<li>http://git.adiscon.com/git/rsyslog.git +</ul> +<p>There is also a browsable version (gitweb) available at +<a href="http://git.adiscon.com/?p=rsyslog.git;a=summary">http://git.adiscon.com/?p=rsyslog.git;a=summary</a>. +This version also offers snapshots of each commit for easy download. You can use these if +you do not have git present on your system. +<p>After you have cloned the repository, you are in the master branch by default. This +is where we keep the devel branch. If you need any other branch, you need to do +a "git checkout --track -b branch origin/branch". For example, the command to check out +the beta branch is "git checkout --track -b beta origin/beta". +<h2>Prequisites</h2> +<p>To build the compilation system, you need the <b>pkg-config</b> package (an utility for +autotools) present on your system. Otherwise, configure will fail with something like +<pre><code> +checking for SYSLOG_UNIXAF support... yes +checking for FSSTND support... yes +./configure: line 25895: syntax error near unexpected token `RELP,' +./configure: line 25895: ` PKG_CHECK_MODULES(RELP, relp >= 0.1.1)' +</code></pre> +<h2>Creating the Build Environment</h2> +<p>This is fairly easy: just issue "<b>autoreconf -fvi</b>", which should do everything you need. +Once this is done, you can follow the usual ./configure steps just like when +you downloaded an official distribution tarball (see the +<a href="install.html">rsyslog install guide</a>, starting at step 2, +for further details about that). + +<h2>Special Compile-Time Options</h2> +<p>On some platforms, compile-time issues occur, like the one shown below: +<p><pre><code> +make[2]: Entering directory `/home/az/RSyslog/rsyslog-5.5.0/tools' + CCLD rsyslogd +rsyslogd-omfile.o: In function `getClockFileAccess': +/home/az/RSyslog/rsyslog-5.5.0/tools/omfile.c:91: undefined reference to `__sync_fetch_and_add_8' +/home/az/RSyslog/rsyslog-5.5.0/tools/omfile.c:91: undefined reference to `__sync_fetch_and_add_8' +/home/az/RSyslog/rsyslog-5.5.0/tools/omfile.c:91: undefined reference to `__sync_fetch_and_add_8' +</code></pre> +<p>Note that the exact error messages can be different. These type of errors stem down to +atomic instruction support in GCC, which is somewhat depending on the machine architecture it +compiles code for. Very old machines (like the original i386) do not even at all provide support +for these instructions. +<p>The availability of atomic instructions is vital for rsyslog - it can not be built without them. +Consequently, there is a configure check included for them. But under some circumstances, +GCC seems to report they are available, but does not provide implementations for +all of them (at least this is my observation...). The simple cure is to make sure that +GCC generates code for a modern-enough architecture. This, for example, can be done as +follows: +<p><pre><code> +./configure CFLAGS="-march=i586 -mcpu=i686" --enable-imfile ... (whatever you need) +</code></pre> +<p>These settings should resolve the issue . + +<p>[<a href="manual.html">manual index</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008, 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/confsamples/normalization.conf b/doc/confsamples/normalization.conf new file mode 100644 index 00000000..7cfd92ef --- /dev/null +++ b/doc/confsamples/normalization.conf @@ -0,0 +1,187 @@ +# this is a config sample for log normalization, but can +# be used as a more complex general sample. +# It is based on a plain standard rsyslog.conf for Red Hat systems. +# +# NOTE: Absolute path names for modules are used in this config +# so that we can run a different rsyslog version alongside the +# regular system-installed rsyslogd. Remove these path names +# for production environment. + +#### MODULES #### + +# we do not run imuxsock as we don't want to mess with the main system logger +#module(load="/home/rger/proj/rsyslog/plugins/imuxsock/.libs/imuxsock") # provides support for local system logging (e.g. via logger command) +#module(load="imklog") # provides kernel logging support (previously done by rklogd) +module(load="/home/rger/proj/rsyslog/plugins/imudp/.libs/imudp") # Provides UDP syslog reception +module(load="/home/rger/proj/rsyslog/plugins/imtcp/.libs/imtcp") +module(load="/home/rger/proj/rsyslog/plugins/mmjsonparse/.libs/mmjsonparse") +module(load="/home/rger/proj/rsyslog/plugins/mmnormalize/.libs/mmnormalize") + +/* We assume to have all TCP logging (for simplicity) + * Note that we use different ports to point different sources + * to the right rule sets for normalization. While there are + * other methods (e.g. based on tag or source), using multiple + * ports is both the easiest as well as the fastest. + */ +input(type="imtcp" port="13514" Ruleset="WindowsRsyslog") +input(type="imtcp" port="13515" Ruleset="LinuxPlainText") +input(type="imtcp" port="13516" Ruleset="WindowsSnare") + +#debug: +action(type="omfile" file="/home/rger/proj/rsyslog/logfile") + +/* This ruleset handles structured logging. + * It is the only one ever called for remote machines + * but executed in addition to the standard action for + * the local machine. The ultimate goal is to forward + * to some Vendor's analysis tool (which digests a + * structured log format, here we use Lumberjack). + */ +template(name="lumberjack" type="string" string="%$!all-json%\n") + + +/* the rsyslog Windows Agent uses native Lumberjack format + * (better said: is configured to use it) + */ +ruleset(name="WindowsRsyslog") { + action(type="mmjsonparse") + if $parsesuccess == "OK" then { + if $!id == 4634 then + set $!usr!type = "logoff"; + else if $!id == 4624 then + set $!usr!type = "logon"; + set $!usr!rcvdfrom = $!source; + set $!usr!rcvdat = $timereported; + set $!usr!user = $!TargetDomainName & "\\" & $!TargetUserName; + call outwriter + } +} + +/* This handles clumsy snare format. Note that "#011" are + * the escape sequences for tab chars used by snare. + */ +ruleset(name="WindowsSnare") { + set $!usr!type = field($rawmsg, "#011", 6); + if $!usr!type == 4634 then { + set $!usr!type = "logoff"; + set $!doProces = 1; + } else if $!usr!type == 4624 then { + set $!usr!type = "logon"; + set $!doProces = 1; + } else + set $!doProces = 0; + if $!doProces == 1 then { + set $!usr!rcvdfrom = field($rawmsg, 32, 4); + set $!usr!rcvdat = field($rawmsg, "#011", 5); + /* we need to fix up the snare date */ + set $!usr!rcvdat = field($!usr!rcvdat, 32, 2) & " " & + field($!usr!rcvdat, 32, 3) & " " & + field($!usr!rcvdat, 32, 4); + set $!usr!user = field($rawmsg, "#011", 8); + call outwriter + } +} + +/* plain Linux log messages (here: ssh and sudo) need to be + * parsed - we use mmnormalize for fast and efficient parsing + * here. + */ +ruleset(name="LinuxPlainText") { + action(type="mmnormalize" + rulebase="/home/rger/proj/rsyslog/linux.rb" userawmsg="on") + if $parsesuccess == "OK" and $!user != "" then { + if $!type == "opened" then + set $!usr!type = "logon"; + else if $!type == "closed" then + set $!usr!type = "logoff"; + set $!usr!rcvdfrom = $!rcvdfrom; + set $!usr!rcvdat = $!rcvdat; + set $!usr!user = $!user; + call outwriter + } +} + +/* with CSV, we the reader must receive information on the + * field names via some other method (e.g. tool configuration, + * prepending of a header to the written CSV-file). All of + * this is highly dependant on the actual CSV dialect needed. + * Below, we cover the basics. + */ +template(name="csv" type="list") { + property(name="$!usr!rcvdat" format="csv") + constant(value=",") + property(name="$!usr!rcvdfrom" format="csv") + constant(value=",") + property(name="$!usr!user" format="csv") + constant(value=",") + property(name="$!usr!type" format="csv") + constant(value="\n") +} + +/* template for Lumberjack-style logging. Note that the extra + * LF at the end is just for wrinting it to file - it MUST NOT + * be included for messages intended to be sent to a remote system. + * For the latter use case, the syslog header must also be prepended, + * something we have also not done for simplicity (as we write to files). + * Note that we use a JSON-shortcut: If a tree name is specified, JSON + * for its whole subtree is generated. Thus, we only need to specify the + * $!usr top node to get everytihing we need. + */ +template(name="cee" type="string" string="@cee: %$!usr%\n") + + +/* this ruleset simulates forwarding to the final destination */ +ruleset(name="outwriter"){ + action(type="omfile" + file="/home/rger/proj/rsyslog/logfile.csv" template="csv") + action(type="omfile" + file="/home/rger/proj/rsyslog/logfile.cee" template="cee") +} + + +/* below is just the usual "uninteresting" stuff... + * Note that this goes into the default rule set. So + * local logging is handled "as usual" without the need + * for any extra effort. + */ + + +#### GLOBAL DIRECTIVES #### + +# Use default timestamp format +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# Include all config files in /etc/rsyslog.d/ +# commented out not to interfere with the system rsyslogd +# (just for this test configuration!) +#$IncludeConfig /etc/rsyslog.d/*.conf + + +#### RULES #### + +# Log all kernel messages to the console. +# Logging much else clutters up the screen. +#kern.* /dev/console + +# Log anything (except mail) of level info or higher. +# Don't log private authentication messages! +*.info;mail.none;authpriv.none;cron.none /var/log/messages + +# The authpriv file has restricted access. +authpriv.* /var/log/secure + +# Log all the mail messages in one place. +mail.* /var/log/maillog + + +# Log cron stuff +cron.* /var/log/cron + +# Everybody gets emergency messages +*.emerg :omusrmsg:* + +# Save news errors of level crit and higher in a special file. +uucp,news.crit /var/log/spooler + +# Save boot messages also to boot.log +local7.* /var/log/boot.log diff --git a/doc/contributors.html b/doc/contributors.html new file mode 100644 index 00000000..713c3299 --- /dev/null +++ b/doc/contributors.html @@ -0,0 +1,60 @@ +<html> +<head> +<title>Contributor Hall of Fame</title> +</head> +<body> +<h2>Contributor Hall of Fame</h2> +<p><b>This page is dedicated to all the people who helped make +<a href="http://www.rsyslog.com/">rsyslog</a> become a reality.</b> +Unfortunately, I have begun this page in July of 2007, long after the project +started. I try to extract all past contributor information from CVS, readme's, +code etc - but I may fail. If you contributed and do not find yourself listed +below, please accept my sincere apologies and drop me a line.</p> +<p>Please also note that I will do the checks for past contributors once the +current very busy development phase is over, so it may take a few weeks to fully +populate this file.</p> +<p>Contributors are listed in alphabetical order. If I know an Alias only, that +alias is used as heading. Else the real name is used. If I know first and last +name, they are listed in that order ("Rainer Gerhards" and not "Gerhards, +Rainer"). I tend to be sparse with information on contributors, at least without +their permission. If you contribute, let me know if I may include your email +and/or web page address.</p> +<p>Thanks to all past, present and future contributors!</p> +<p><a href="http://www.gerhards.net/rainer">Rainer Gerhards</a><br> +Project Initiator and Maintainer</p> +<h2>Bartosz Kuzma</h2> +<ul> + <li>provided many contributions before I started this list, so there are + probably some missing</li> + <li>suggested the use of autotools in parallel to Peter Vrabec and helped me + get it going</li> + <li>sent a number of patches (see cvs log for details)</li> +</ul> +<h2>Michel Samia</h2> +<ul> + <li>provided patch with regex functionality for filters on 2007-07-14, first + seen in 1.16.1</li> +</ul> +<h2>mildew@gmail.com</h2> +<ul> + <li>provided a large patch to enhance $AllowedSender directive for IPv6 as + well as DNS names</li> +</ul> +<h2>Peter Vrabec</h2> +<ul> + <li>provided many contributions before I started this list, so there are + probably some missing</li> + <li>provided basic IPv6 support</li> + <li>convinced me to use autotools and provided the first working config for + it</li> + <li>provided Rainer with ongoing support, inspiration and motivation</li> +</ul> +<h2>varmojfekoj@gmail.com</h2> +<ul> + <li>provided contributions before I started this list, so there are probably + some missing</li> + <li>provided patches for several memory leaks</li> +</ul> +<p><font size="2">Last Updated: 2007-07-19</font></p> +</body> +</html> diff --git a/doc/cryprov_gcry.html b/doc/cryprov_gcry.html new file mode 100644 index 00000000..2568add9 --- /dev/null +++ b/doc/cryprov_gcry.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<meta http-equiv="Content-Language" content="en"> +<title>libgcryt Log Crypto Provider (gcry)</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back to rsyslog module overview</a> + +<h1>libgcrypt Log Crypto Provider (gcry)</h1> +<p><b>Signature Provider Name: gt</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Supported Since: </b>since 7.3.10 +<p><b>Description</b>:</p> +<p>Provides encryption support to rsyslog. +</p> + +<p><b>Configuration Parameters</b>:</p> +<p>Crypto providers are loaded by omfile, when the +provider is selected in its "cry.providerName" parameter. +Parameters for the provider are given in the omfile action instance +line. +<p>This provider creates an encryption information file with the same base name but +the extension ".encinfo" for each log file (both for fixed-name files +as well as dynafiles). Both files together form a set. So you need to +archive both in order to prove integrity. +<ul> +<li><b>cry.algo</b> <Encryption Algorithm><br> +The algorithm (cipher) to be used for encryption. +The default algorithm is "AES128". +<br>Currently, the following Algorithms are supported: + <ul> + <li>3DES + <li>CAST5 + <li>BLOWFISH + <li>AES128 + <li>AES192 + <li>AES256 + <li>TWOFISH + <li>TWOFISH128 + <li>ARCFOUR + <li>DES + <li>SERPENT128 + <li>SERPENT192 + <li>SERPENT256 + <li>RFC2268_40 + <li>SEED + <li>CAMELLIA128 + <li>CAMELLIA192 + <li>CAMELLIA256 + </ul> + <br> + The actual availability of an algorithms depends on which ones + are compiled into libgcrypt. Note that some versions of libgcrypt + simply abort the process (rsyslogd in this case!) if a supported + algorithm is select but not available due to libgcrypt build + settings. There is nothing rsyslog can do against this. So in + order to avoid production downtime, always check carefully when + you change the algorithm. +</li> +<li><b>cry.mode</b> <Algorithm Mode><br> +The encryption mode to be used. Default ist Cipher Block Chaining (CBC). +Note that not all encryption modes can be used together with all +algorithms. +<br>Currently, the following modes are supported: + <ul> + <li>ECB + <li>CFB + <li>CBC + <li>STREAM + <li>OFB + <li>CTR + <li>AESWRAP + </ul> +<li><b>cry.key</b> <encryption key><br> + TESTING AID, NOT FOR PRODUCTION USE. This uses the KEY specified + inside rsyslog.conf. This is the actual key, and as such this mode + is highly insecure. However, it can be useful for intial testing + steps. This option may be removed in the future. +</li> +<li><b>cry.keyfile</b> <filename><br> + Reads the key from the specified file. The file must contain the key, only, + no headers or other meta information. Keyfiles can be generated via the + rscrytool utility. +</li> +<li><b>cry.keyprogram</b> <path to program><br> + If given, the key is provided by a so-called "key program". This program + is executed and must return the key to (as well as some meta information) + via stdout. The core idea of key programs is that using this interface the + user can implement as complex (and secure) method to obtain keys as + desired, all without the need to make modifications to rsyslog. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>currently none known +</li> +</ul> +<p><b>Samples:</b></p> +<p>This encrypts a log file. Default parameters are used, they key is +provided via a keyfile. +</p> +<textarea rows="3" cols="60"> +action(type="omfile" file="/var/log/somelog" + cry.provider="gcry" keyfile="/secured/path/to/keyfile") +</textarea> +Note that the keyfile can be generated via the rscrytool utility (see its +documentation for how to actually do that). + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2013 by +<a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/dataflow.png b/doc/dataflow.png Binary files differnew file mode 100644 index 00000000..fd614d8c --- /dev/null +++ b/doc/dataflow.png diff --git a/doc/debug.html b/doc/debug.html new file mode 100644 index 00000000..557ca6d3 --- /dev/null +++ b/doc/debug.html @@ -0,0 +1,171 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<meta http-equiv="Content-Language" content="en"> +<title>Rsyslog Debug Support</title></head> +<body> +<h1>Rsyslog Debug Support</h1> +<p> +Rsyslog provides a number of debug aides. Some of them are activated by +adding the --enable-rtinst ./configure option ("rtinst" means runtime +instrumentation). Turning debugging on obviously costs some performance +(in some cases considerable). +</p> +<p>This is document is just being created and thus terse.</p> +<h2>Signals supported</h2> +<p><b>SIGUSR1</b> - turns debug messages on and off. Note that for this +signal to work, rsyslogd must be running with debugging enabled, either +via the -d command line switch or the environment options specified below. +It is <b>not</b> required that rsyslog was compiled with debugging enabled +(but depending on the settings this may lead to better debug info). +<p><b>SIGUSR2</b> - outputs debug information (including active threads +and a call stack) for the state when SIGUSR2 was received. This is a +one-time output. Can be sent as often as the user likes. +<p><b>Note:</b> this signal <b>may go away</b> in later releases and may +be replaced by something else.</p> +<h2>Environment Variables</h2> +<p>There are two environment variables that set several debug settings: +<ul> +<li>The "RSYSLOG_DEBUGLOG" (sample: RSYSLOG_DEBUGLOG="/path/to/debuglog/") +writes (allmost) +all debug message to the specified log file in addition to stdout. Some +system messages (e.g. segfault or abort message) are not written to the +file as we can not capture them. +<li>Runtime debug support is controlled by "RSYSLOG_DEBUG". +<p>The "RSYSLOG_DEBUG" environment variable contains an option string with the following +options possible (all are case insensitive):</p> +<ul> +<li><b>LogFuncFlow</b> - print out the logical flow of functions (entering and exiting them)</li> +<li><b>FileTrace</b> - specifies which files to trace LogFuncFlow. If <b>not</b> +set (the default), a LogFuncFlow trace is provided for all files. Set +to limit it to the files specified. FileTrace may be specified multiple +times, one file each (e.g. export RSYSLOG_DEBUG="LogFuncFlow +FileTrace=vm.c FileTrace=expr.c"</li> +<li><b>PrintFuncDB</b> - print the content of the debug function database whenever debug information is printed (e.g. abort case)!</li> +<li><b>PrintAllDebugInfoOnExit</b> - print all debug information immediately before rsyslogd exits (<span style="font-weight: bold; font-style: italic;">currently not implemented!</span>)</li> +<li><b>PrintMutexAction</b> - print mutex action as it happens. Useful for finding deadlocks and such.</li> +<li><b>NoLogTimeStamp</b> - do not prefix log lines with a timestamp (default is to do that).</li> +<li><b>NoStdOut</b> - do not emit debug messages to stdout. If RSYSLOG_DEBUGLOG is not set, this means no messages will be displayed at all.</li> +<li><b>Debug</b> - if present, turns on the debug system and enables debug output +<li><b>DebugOnDemand</b> - if present, turns on the debug system but does not enable +debug output itself. You need to send SIGUSR1 to turn it on when desired. +<li><b>OutputTidToStderr</b> - if present, makes rsyslog output information about +the thread id (tid) of newly create processesto stderr. Note that not necessarily +all new threads are reported (depends on the code, e.g. of plugins). This is +only available under Linux. This usually does NOT work when privileges have +been dropped (that's not a bug, but the way it is). +<li><b>help</b> - display a very short list of commands - hopefully a life saver if you can't access the documentation...</li> +</ul> +<p>Individual options are separated by spaces.</p> +</ul> +<h3>Why Environment Variables?</h3> +<p>You may ask why we use environment variables for debug-system parameters and not +the usual rsyslog.conf configuration commands. After all, environment variables force one +to change distro-specific configuration files, whereas regular configuration directives +would fit nicely into the one central rsyslog.conf. +<p>The problem here is that many settings of the debug system must be initialized +before the full rsyslog engine starts up. At that point, there is no such thing like +rsyslog.conf or the objects needed to process it present in an running instance. +And even if we would enable to change settings some time later, that would mean that +we can not correctly monitor (and debug) the initial startup phase of rsyslogd. What +makes matters worse is that during this startup phase (and never again later!) some +of the base debug structure needs to be created, at least if the build is +configured for that (many of these things only happen in --enable-rtinst mode). So +if we do not initialize the debug system <b>before</b> actually startig up the +rsyslog core, we get a number of data structures wrong. +<p>For these reasons, we utilize environment variables to initialize and configure +the debugging system. We understand this may be somewhat painful, but now you know +there are at least some good reasons for doing so. +<p>HOWEVER, if you have a too hard time to set debug instructions using the environment +variables, there is a cure, described in the next paragraph. + +<h2>Enabling Debug via rsyslog.conf</h2> +<p>As described in the previous paragraph, enabling debug via rsyslog.conf +may not be perfect for some debugging needs, but basic debug output will work - and +that is what most often is requried. There are limited options available, but these +cover the most important use cases. +<p>Debug processing is done via legacy config statements. There currently +is no plan to move these over to the v6+ config system. Availabe settings are +<ul> +<li>$DebugFile <filename> - sets the debug file name +<li>$DebugLevel <0|1|2> - sets the respective debug level, where +0 means debug off, 1 is debug on demand activated (but debug mode off) +and 2 is full debug mode. +</ul> +<p>Note that in theory it is forbidden to specify these parameters more +than once. However, we do not enforce that and if it happens results +are undefined. + +<h2>Getting debug information from a running Instance</h2> +<p>It is possible to obtain debugging information from a running instance, but this requires +some setup. We assume that the instance runs in the background, so debug output to +stdout is not desired. As such, all debug information needs to go into a log file. +<p>To create this setup, you need to +<ul> +<li>point the RSYSLOG_DEBUGLOG environment variable to a file that is accessible +during the while runtime (we strongly suggest a file in the local file system!) +<li>set RSYSLOG_DEBUG at least to "DebugOnDeman NoStdOut" +<li>make sure these environment variables are set in the correct (distro-specifc) +startup script if you do not run rsyslogd interactively +</ul> +<p>These settings enable the capability to react to SIGUSR1. The signal will toggle +debug status when received. So send it one to turn debug loggin on, and send it again +to turn debug logging off again. The third time it will be turned on again ... and so on. +<p>On a typical system, you can signal rsyslogd as follows: +<pre> +kill -USR1 `cat /var/run/rsyslogd.pid` +</pre> +Important: there are backticks around the "cat"-command. If you use the regular +quote it won't work. The debug log will show whether debug logging has been turned +on or off. There is no other indication of the status. +<p>Note: running with DebugOnDemand by itself does in practice not have any performance +toll. However, switching debug logging on has a severe performance toll. Also, debug +logging synchronizes much of the code, removing a lot of concurrency and thus +potential race conditions. As such, the very same running instance may behave +very differently with debug logging turned on vs. off. The on-demand debug log +functionality is considered to be very valuable to analyze hard-to-find bugs that +only manifest after a long runtime. Turning debug logging on a failing instance +may reveal the cause of the failure. However, depending on the failure, debug logging +may not even be successfully be turned on. Also note that with this rsyslog version we cannot +obtain any debug information on events that happened <i>before</i> debug logging was +turned on. +<p>If an instance hangs, it is possible to obtain some useful information about the current +threads and their calling stack by sending SIGUSR2. However, the usefulness of that +information is very much depending on rsyslog compile-time settings, must importantly +the --enable-rtinst configure flag. Note that activating this option causes additional overhead +and slows down rsyslgod considerable. So if you do that, you need to check if it is +capable to handle the workload. Also, threading behavior is modified by the +runtime instrumentation. +<p>Sending SIGUSR2 writes new process state information to the log file each time +it is sent. So it may be useful to do that from time to time. It probably is most +useful if the process seems to hang, in which case it may (may!) be able to output +some diagnostic information on the current processing state. In that case, turning +on the mutex debugging options (see above) is probably useful. +<h2>Interpreting the Logs</h2> +<p>Debug logs are primarily meant for rsyslog developers. But they may still provide valuable +information to users. Just be warned that logs sometimes contains informaton the looks like +an error, but actually is none. We put a lot of extra information into the logs, and there +are some cases where it is OK for an error to happen, we just wanted to record it inside +the log. The code handles many cases automatically. So, in short, the log may not make sense to +you, but it (hopefully) makes sense to a developer. Note that we developers often need +many lines of the log file, it is relatively rare that a problem can be diagnosed by +looking at just a couple of (hundered) log records. +<h2>Security Risks</h2> +<p>The debug log will reveal potentially sensible information, including user accounts and +passwords, to anyone able to read the log file. As such, it is recommended to properly +guard access to the log file. Also, an instance running with debug log enabled runs much +slower than one without. An attacker may use this to place carry out a denial-of-service +attack or try to hide some information from the log file. As such, it is suggested to +enable DebugOnDemand mode only for a reason. Note that when no debug mode is enabled, +SIGUSR1 and SIGUSR2 are completely ignored. +<p>When running in any of the debug modes (including on demand mode), an interactive +instance of rsyslogd can be aborted by pressing ctl-c. +<p> +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/design.tex b/doc/design.tex new file mode 100644 index 00000000..1def3fb7 --- /dev/null +++ b/doc/design.tex @@ -0,0 +1,957 @@ +\documentclass[a4paper,10pt]{article} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage{amssymb} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{algorithm,algorithmic} +\usepackage{float} + +\pagestyle{headings} + +\newcommand{\IN}{\mathbb{N}} +\newcommand{\MM}{\mathcal{M}} +\newcommand{\QQ}{\mathcal{Q}} +\newcommand{\AAA}{\mathcal{A}} +\title{Rsyslog Design and Internals} +\author{Rainer Gerhards\\ +rgerhards@adiscon.com} + +\begin{document} + +\maketitle + +\begin{abstract} +This paper describes rsyslog design and internals. It is created to facilitate a discussion about the implementation of "batched queue processing". As such, it does not describe the full design of rsyslog but rather those elements that are relevant to queues. However, the document may be expanded in the future. This is work in progress and should be considered with care. It is NOT updated during all phases of development. +\end{abstract} + +\tableofcontents + +\section{Preliminaries} +\subsection{On the Use of English} +\begin{quotation} +\begin{flushright} +I ventured to write this book in English because ... \\ +it will be more easily read in poor English, \\ +than in good German by 90\% of my intended readers. \\ +--- HANS J. STETTER, Analysis of Discretization Methods for \\ +Ordinary Differential Equations (1973) +\end{flushright} +\end{quotation} + +There is not much I could add to Mr. Stetter's thought, except, maybe, that the number to quote probably tends more to 99\% in this case than to the 90\% Mr. Stetter notes. So please pardon those errors in language use that I have not yet been able to fix or even see. Suggestions for corrections and improvements are always welcome. +\subsection{Notational Conventions} +In general, in rsyslog there exists single objects $o$, which are used to build larger sets $O$, which form a superset $\mathcal{O}$ of all those objects that exist at a given time inside a running instance of rsyslog. As seen above, single objects are always described by lower case letters ($o$), larger sets by upper case letters ($O$) and the ``all-sets'' in caligraphic letters ($\mathcal{O}$). Often, objects $O_i, i \in \IN, i \le |\mathcal{O}|$ partition $\mathcal{O}$, but this is not necessarily the case. + +\subsection{Definitions} +\subsubsection{Sudden Fatal Failure} +As sudden fatal failure is one that occurs at some instant and causes Complete loss of processing capabilities. The two major cases are a sudden power loss or a ``kill -9'' of the process. There are more exotic cases, too, like disasters. + +One may argue that it is possible to protect against many sudden fatal failure cases. For example, using an uninterruptable power supply (UPS) will prevent a sudden power loss. While this is true in most cases, it does not hold if looked very closely: in the case of the UPS, for example, a failure in the UPS itself may cause a sudden power loss, which can not be mitigated. Well, actually there can be several layers of mitigation, but always one more potential failure scenario remains. So it is not possible to totally solve the issue. + +The concept of ``sudden fatal failure'' now covers all these rest risk that result in termiantion of rsyslogd without the ability execute any code before this happens. This is a very important concept in regard to audit-gradeness. + +\subsubsection{Audit Grade} +In the context of this document, ``audit grade'' means that a subsystem never loses a message that it has taken responsibility for, not even in cases of sudden fatal failures. The only limit in this restriction is that a subsystem does not guarantee message survival if the subsytem at large is being destroyed (e.g. during a disaster) or some of its components are not of audit-grade. This draws a fine limitation on the audit-grade of a subsystem. + +For example, the rsyslog queue subsystem receives messages and acknowledges them to the submitter (e.g. an input), when they have been enqueued in the storage system. If the queue system is configured to provide audit-grade operation\footnote{Audit-grade queue operation is considerably slower than regular operations, as such this mode is not enabled by default. Most installations will never need a completely audit-grade queue}, the queue relies on the storage subsystem to work properly. If, for example, a disk read error occurs, the message may no longer be readable from the disk and as such is lost. The root cause here is that the disk subsystem was not of audit grade, because it otherwise would not have lost the message. So in this case the queue code is of audit grade, but the one of its components, the disk subsytem, was not. So the overall system is not of audit grade. + +To simplify talking about the audit-gradness of several subsytems, we assume that all of their subsystems are also of audit grade. In an actual deployment, however, this means the the system designer must carefully select audit-grade subsystems. Overlooking a single non-audit-grade component will make the whole system of not audit grade quality. + +Please note that it can be rather tricky to ensure a complete system is of audit grade. A border case is main memory integrity. Even with error-correcting memory, there may situations arise where a memory error occurs (probably due to a very unlikely series of well-hitting cosmic rays) that is unrecoverable. At this point, system integrity is at risk. The only real solution is to immediately shut down the system and restart it (without giving any process a chance to execute). Note, however, that in an extreme view, an operating system routine that does so can also be considered dangerous, as memory in use by this routine might be affected by the malfunction. We could extend this scenario and further complicate it, but that goes beyond the scope of this paper. The example was primarily meant to show how subtle audit-grade reliability is. + +In rsyslog, we currently use a slightly \marginpar{duplication\\permitted}relaxed consistency condition for message integrity inside an audit-grade subsystem. While we do not accept message loss, we permit slight message \emph{duplication}, but only in exceptional cases. This is permitted because, with proper message generation, the dulication problem can be easily fixed at the end-to-end layer. For example, the original sender can include a UUID, which can be used to sort out duplicates at the final destination. Insisting on not allowing duplication complicates matters and is often impossible with today's logging protocols. So, for the time being, we aim at this relaxed criteria, which is hard enough to achive. After we have achieved that goal, we may further try to solve the duplicaton problem. Some hooks already exist. But we do not guarantee such an effort will be made any time soon. + +\section{Overall Design} +From a high-level prespective, rsyslogd is ``just'' a high-performance message router. It accepts messages from various sources, applies user-configured filters to them, and routes potentially transformed messages to destinations based on these filters. +\section{Objects} +\subsection{Plugins} +Plugins provide code potentially written by a third party to extend rsyslog. + +Conceptually, a plugin is a tuple of callable functions $(\phi_1, \phi_2, \ldots)$ which implement an interface. There are three different types of plugins: input, output and library. The plugin type denotes the primary interface implemented by the plugin. Additional interfaces may be implemented\footnote{This is not yet done in plugins, but is possible and assumed to be done at a later point in time}. + +In the context of this paper, the output plugin interface is most important. It implements three entry points: + +\paragraph{doAction()} +is used to submit messages to the output plugin. The entry point may or may not commit the messages to their ultimate destination. + +\paragraph{beginTransaction()} +is used to inform the plugin that a new transaction begins. It must prepare for processing. + +\paragraph{endTransaction()} +is indicated that the upper layer \emph{needs} to close the transaction. If there is any uncommited data left, it must be commited or rolled back. + +Every instance of an output plugin is guaranteed \emph{not} to be called concurrently by multiple threads. Further, no context switch will happen between calls to $doAction()$ and $endTransaction()$. + +\subsection{State Sets} +Several object have associated state based on a specific state set. These state sets are described together with the objects. + +As a general rule, individual state is associated with all instances $o$ of a class of objects. This state is called the object's \marginpar{state component} \emph{state component} $s$. If we want to obtain an object's state, we write $S(o)$. Please note that $S(o)$ is only defined for those objects that have a state component. + +\subsection{Messages} +A message $m$ represents a a single syslog message inside the system. It is a tuple of attributes. Some of these attributes directly orginate from the message content, some others are meta-information taken from the context. For example, there is an meta-attribute ``time of reception'' which conveys when the message was received by rsyslog's input subsystem. We do not list attributes here, as there are many and it is not of importance which exactly they are. + +The set $\MM$ is composed of all messages that exist at a given time inside rsyslog. + +\subsection{Queue} +A queue +$$Q = (C, \Phi, M)$$ +is a triplet of a set of configuration parameters $C$, a set of callbacks $\Phi$ and a set of messages $M \subseteq \MM$. + +If we need to obtain the set of message from a queue, we write $M(Q)$. The elements of the set of configuration parameters are written as $C_{param}$ where $param$ is an abbreviation of the parameter's meaning. To obtain a specific parameter from a queue, we write $C_{param}(Q)$. The most important elements of $C$ are: + +\paragraph{$C_{type}$} which denotes the queue implementation type. Most importantly, this selects from a set of queue drivers (for example disk-only or in-memory driver), which affects the basic operation of the queue instance. + +\paragraph{$C_{mMsg}$} which denotes the upper bound on the cardinality of $M$. + +\paragraph{$C_{mBatch}$} which denotes the upper bound of the cardinality of message batches created for this queue. + +Be $\QQ = \{Q_m, Q_1, Q_2, \ldots, Q_{|\AAA|}\}$ the set of all queues that exist inside rsyslog after the configuration file has been processed, with $|\QQ| = |\AAA| + 1$. + +Then +$$M_0 = \MM \setminus \bigcup_{i=1}^{|\QQ|} Q_i(M)$$ +\marginpar{at-risk-set}is the set of non-queued messages. The messages have either never been enqueued or have been dequeued but not finally been processed. This set represents the messages that may potentially be lost during an unclean shutdown of rsyslogd. This is why I call this set the ``\emph{at-risk-set}''. + + +\subsection{Batches} +A batch represents multiple processable messages. It is a unit of processing inside rsyslog's output system. Batches are used to dequeue a number of messages from a queue and then submit them to the lower action layer. Batches are natural \emph{transaction boundaries}, in the sense that multiple output transactions may be done on the messages inside a batch, but each transaction must end at the end of the batch. A batch is always associated to a specific queue $Q$. + +A batch +$$B = (b_1, b_2, \ldots, b_n )$$ +is a $n$-tuple of \marginpar{processable\\message}processable messages +$$b = (m, s)$$ +which are an ordered pair of a message $m$ and an associated processing state $s$. To denote the $n$-th message inside the batch, we write $m(b_n)$, to denote the status component of the $n$-th message, we write $S(b_n)$. + +\begin{figure} +\begin{center} +\includegraphics[scale=0.4]{batch_state.jpeg} +\end{center} +\caption{batch message processing states} +\label{fig_batchmsg_states} +\end{figure} + +The state set for the processing states is defined as follows: +$$ +S_B = \{ rdy, bad, sub, disc \} +$$ + +With the semantics of the various states being the following: + +\begin{center} +\begin{tabular}{|l|l|} \hline + State & Semantics \\\hline + rdy & ready for processing\\ + bad & this message triggered an unrecoverable failure in action\\ + & processing and must not be resubmitted to this action\\ + sub & message submitted for processsing, result yet unknown \\ + disc & action sucessfully processed, but must not be submitted \\ + & to any further action in action unit \\\hline +\end{tabular} +\end{center} +The associated state diagram is shown in figure \ref{fig_batchmsg_states} on page \pageref{fig_batchmsg_states}. + +Batch sizes vary. The actual cardinality is a function of the cardinality of $M(Q)$ at the time of batch creation and the queue configuration: + +$$1 \leq |B| \leq \max(C_{mBatch}(Q), |M(Q)|)$$ + +\subsection{Action Unit} +An action unit +$$u = (f, a_1, \ldots, a_n), a_i \in \AAA \text{ for } i \in \IN, i \le n$$ +is a tuple consisting of a filter function $f$ and $n \in \IN$ actions. \emph{Does rsyslog still support nonsense action units with $n=0$? - check!} + +\subsection{Action} +An action +$$a = (a_C, a_\psi)$$ +is an ordered pair of a tuple of configuration attributes $a_C$, and a tuple of processing functions $a_\psi$. Be the set $\AAA$ composed of all actions that exist in rsyslog after the configuration file has been processed. + + +\section{Processing} +\subsection{Object States} +Various objects keep state. Some of these objects, like messages, batches and actions seem to share state. However, thinking about shared state leads to very complex setup. As such, state is modelled for each object $o$ individually. Instead, the state function $S_O(o)$ can be used to obtain an obtain an individual objects state. That state can be used to modify the state diagrams of the other objects with which relationships exist. + +\subsubsection{Actions} +Actions are provided by output plugins. An action enables the engine to write messages to some destination. It is important to note that ``destination'' is a very broad abstraction. A destination may be a file inside a local or remote file system, a database table or a remote syslog server in another network. + +Actions are transactional in the following sense: more than one message can be submitted to an action. The action does not necessarily process the submitted messages unless the caller ends the transaction. However, the action itself may also end the transaction and notify the caller. This is \emph{not} considered an error condition and \emph{must} be handled gracefully by the caller. If a transaction aborts, the caller \emph{must} assume that none of the elements submitted since the begin of transaction have been processed. The action will try to backout anything that was already processed at the time the transaction failed. However, not all outputs work on actually transactional destination. As such, an action is permitted not to backout incomplete interim results. As such, after a transaction abort, some message duplication may occur. We call this the \emph{relaxed integrity condition} for actions. + +An output transaction is started by calling \emph{beginTransaction()} either explicitely or implicitely by a call to \emph{doAction()} without calling \emph{beginTransaction()} before. Then, one or more calls to \emph{doAction()} follow. When the caller intends to finish the transaction, it calls \emph{endTransaction()}. However, the transaction may also be terminated from the action itself in response to a \emph{doAction()} call. + +Mathematically, an action transaction builds a totally ordered set of uncommitted messages $M_u$. The order relation is defined over the sequence in which messages are being provided to \emph{doAction()}. At any time a commit is attempted, the full set $M_u$ is committed and may either succeeed completely or not at all (in the sense of the relaxed integrity condition described above). + +A commit is attempted when +\begin{enumerate} +\item the caller decides to call \emph{endTransaction()} +\item or earlier if the action decides it needs to commit now (e.g. because of buffers filling up). +\end{enumerate} + +In the seconds case, the action may decide to commit all message but the current one or all (this is depending on action logic). So if the action decideds to commit a transaction before the caller calls \emph{endTransaction()}, a set of commited messages $M_c$ is build and $M_u$ is modified. Be $n$ the $n$-th iterated \emph{doAction()} call and $m_n$ the current message of this call, then the sets are build as follows: + +\begin{algorithm} +%\caption{} +\begin{algorithmic} +\IF{action commits $m_n$} + \STATE $M_c = M_u \cup m_n$ + \STATE $M_u = \emptyset$ +\ELSE + \STATE $M_c = M_u$ + \STATE $M_u = \{ m_n\}$ +\ENDIF +\end{algorithmic} +\end{algorithm} + +In other words, if anything is committed early, it is always the full set $M_u$, with or without the current message. The caller needs to know which messages are already commited. As \emph{doAction()} finishes one transaction and starts a new one in a single call, we can not use action state the let the caller know this happened. So we use our above finding and just convey back if the transacton is still continuing or the current message or all others before it were committed. The caller must then act accordingly. Please note that when an error happens, the whole transaction must still be considered failed. As such, ``partial commit'' states need not to be mixed with failure states. + +Please note that the above method leaves a small potential issue unaddressed: if the action does an early commit of $M_u \setminus m_n$, an error happens when adding $m_n$ to the new $M_u$ (like running out of resources), the action would need to convey both the successful transaction as well as the failure state. This is not possible with the current interface. We could use callbacks to provide such notification, but this complicates the code. So, if that situaton arises, the action must temporarily buffer the error condition and convey it as part of either the next \emph{doAction()} call or during \emph{endTransation()} processing. This can be done, for example, by advancing its internal state accordingly. + +The state set for a actions is defined as follows: +$$ +S_A = \{ rdy, itx, comm, rtry, susp, died \} +$$ + +With the semantics of the various states being the following: + +\begin{center} +\begin{tabular}{|l|l|} \hline + State & Semantics \\\hline + rdy & ready, waiting for transaction begin\\ + itx & in transaction, accept more data \\ + comm & transaction finished \\ + rtry & action failed but may be able to recover \\ + susp & action currently defunctional until timeout expires \\ + died & unrecoverable error condition occured, no longer usable \\\hline +\end{tabular} +\end{center} + +In the associated state diagram in figure \ref{fig_action_states}, we do not include the \emph{died} state, because it is entered whenever a totally unrecoverable error state may occur. This is a very exceptional incident (which most output plugins do not even support), so we have kept the diagram simple. + +\begin{figure} +\begin{center} +\includegraphics[scale=0.5]{action_state.jpeg} +\end{center} +\caption{Action State Diagram} +\label{fig_action_states} +\end{figure} + +\emph{Note well} that the state diagram describes the action state. It does \emph{not} describe the transaction state. While action- and transaction state are closely related to each other, they are different entities. + +The return code of \emph{doAction()} and \emph{endTransaction()} is used to convey the transaction state. As such, it is a function of the actions's current state after processing the request. The mapping is as shown below: + +\begin{center} +\begin{tabular}{|l|l|} \hline + State & Return Code (RS\_RET\_\ldots)\\\hline + rdy & OK \\ + itx & COMMITTED (if there was an auto-commit without $m_n$)\\ + & DEFER\_COMMIT (if there was no auto-commit)\\ + comm & internal state, not to be exposed to upper layer \\ + rtry & SUSPENDED \emph{(new code needed)} \\ + susp & SUSPENDED \\ + died & DISABLED \\\hline +\end{tabular} +\end{center} + +For the rest of this document, let's assume there is a function \emph{getReturnCode()} that implements this mapping. + +It is important to think about how retries are handled. There is a user-configured per-action upper number of retries $C_r$ and retry interval $C_i$. In \emph{rsyslog v3}, there is no concept of output transactions. As such, only single messages are processed. When a temporary action failure occurs, the action is re-tried $C_r$ times, where the action processing thread is waiting in a \emph{sleep()} $C_i$ operating system API call\footnote{a suitable API is used, not \emph{sleep()} itself}. If the action succeeds during the retry processing, everything continues as usual. If it does not succeed, two things happen: +\begin{itemize} +\item the message is flagged as ``action permanent failure'' (what may trigger backup processing) +\item the action is actually suspended for $C_i$ seconds +\end{itemize} +If then a new message is sent to the action, and $C_i$ seconds have not yet elapsed, the action is flagged as having failed without being re-tried again\footnote{During the analysis for this paper, it was seen that actually $C_r$ retries are attempted in v3, but each of them will never actually re-try the action. This is a software bug, which does not cause any harm and thus will not be fixed in v3. The new implementation in v4 will obviously not inherit this problem}. This is done in an effort to reduce resource utilization and prevent the system from slowing down e.g. by too-many retries to a remote server that went offline. + +With transactional output mode in \emph{rsyslog v4}, the logic above can no longer work. First of all, retrying single actions does not help, because all of the current transaction needs to be resubmitted. As such, the upper layers need to be notified of failure. Then, they need to resubmit the batch. In that design, the lower layer needs to return immediately after detecting the failure. Recovery handling is now to be done when the next transaction is started. However, we must make sure that we do not do excessive retries. So retry processing is only to be carried out if it was not tried less than $C_i$ seconds ago. + +The required functionality can be implemeted by a \emph{prepareAction} function that readies the action for processing if there is need to do so. That function is then called in all entry points before anything else is done. Then, actual processing is carried out and the resulting action state be used to generate the return code for the upper-layer caller. Find below a rough pseudocode to do so: + +\lstset{language=python} +\begin{lstlisting} +def prepareAction(): + if state == rtry: + try recovery (adjust state accordingly) + if state == rdy: + beginTransaction() [output plugin] + +def processMessage(message): + prepareAction() + if state == itx + doAction(message) [output plugin] + return getReturnCode() + +def doEndTransaction(): + prepareAction() + if state == itx + endTransaction(); [output plugin] + return getReturnCode() +\end{lstlisting} + +\subsection{Output Subsystem Layers} +The rsyslog engine is organized in layers, where each layer is represented by the dominating object: + +\begin{figure} +\includegraphics[scale=0.75]{rsyslog_output_layers.jpeg} +\label{rsyslog output layers} +\end{figure} + +If looking at the data flow, a queue dequeues batches of messages, which are than run through a generic action system and put into output plugins. Note that on the batch layer, only batches are supported as units of work, whereas the action layer is message-oriented but supports transactions of multiple messages. This is done by indicating when a transaction necessarily needs to end (that point being the end of batch from the batch layer). + +The plugins can be written by third parties and are roughly comparable to minidrivers. The generic action system provides all complexity of action processing wheras the output plugin provides a limited set of callbacks that enable the generic framework to talk to the actual destination system. As such, writing outputs is a very simple task. However, rsyslog does not limit the creation of very complex outputs, which may be able to offer superior performance for some destinations. + +\subsection{Output Failure} +\subsubsection{Cases} +When an output action is called, it may encounter a failure condition. In general, there are two different cases: +\begin{enumerate} +\item action caused failures +\item message-content caused failures +\end{enumerate}. + +Failures rooted in the action are things like broken network connections, file systems run out of space or database servers that are down. Most importantly, the failure is not related to message content. As such, it is appropriate to retry the action with the same message until it finally succeeds (assuming that someone restores the system in question to proper operation). We can not expect that the problem is cleared just by discarding the current message and re-trying with the next one. + +In my view, action caused failures are the far majority of all failures. For rsyslog versions 3 and below, all rsyslog-provided plugins consider failures to be action-caused and thus potentially recoverable by simple retry. With the only exception being fatal error conditions that render the whole action unusable. + +David Lang pointed out, that there may also exist error conditions that are not caused by the action (or the subsystem it talks to) itself, but rather by message data. He provided the following samples where message content can cause permanent issues with action execution: + +\begin{itemize} +\item unicode text causing grief +\item dynafile hits a read-only file +\item basicly data-driven things that trigger bugs in the message delivery +mechanism in some form. +\end{itemize} + +As David Lang said ``In an ideal world these would never happen, but for most output types I can think of some form of corrupt input that could cause that message to fail.''. +So this class of failure conditions actually exists. No matter how often the action retry mechanism is called, it will never succeeds (one may argue that the read-only dynafile is fixable, but we could replace that sample with an invalidly generated filename). The proper cure for these actions is to find the offending one and discard it. + +In conclusion, actions need to return different error states for these two different types of failures. Traditionally, RS\_RET\_SUSPENDED is returned when an action specific failure is hit. Most existing plugins also do this if a message-related failure occured, simply because they did not yet know that this situation exists. However, plugins also return different error codes, and at least these can be treated to mean message-permanent failures. To support this, a change to plugins is still required, because many simple return SUSPENDED state if anything went wrong (replacing the real error condition with SUSPENDED). A dedicated PROBABLE\_INVALID\_MSG return state is probably useful so that an output plugin can convey back that it consideres the message to be bad. On the other hand, this implies that the plugin must try to detect those, what means that the developer must think about all potential message-causes problems. That approach can be considered unreliable and as such it may be better not to provide such a dedicted state. + +\subsubsection{Handling of Failures} +In spite of the two different failure cases, different handling is needed for them. The action-based failure cases can and must be handled on the action level. As transactions abort when a failure occurs, support from the upper ``batch layer'' is necessary in order to handle resending batches of messages. + +For message-caused failure cases, the offending message must be found and then be discarded. A complexity here is that while a failure-causing message is being searched for, an action-based failure might occur. In that case, first the action-based failure condition must be solved, before the search for the problem message can continue. + +One approach might be that when the action-layer conveys back an action-caused failure (SUSPENDED), the batch layer knows that it simply needs to restart the full transaction (but not start an ``invalid message search''). If a message-based error condition is conveyed back, the batch system can not restart the full batch. Instead, it needs to enter search mode, where it creates partitions of the original batch, and calls itself recursively (at least in theory) on each of the subsets. + +Then, the same handling applies until either a failing message has been found or all messages have been successfully processed. Note that in the recursive step, action-based failures are recovered by full batch resubmits. This solves the above-mentioned complexity in a consistent way. + +If a binary-search-like method is used to detect failing records\footnote{This was originally suggested by David Lang.}, recursion may not really be an issue, as the recursion depth is limited to $\log_2 |B|$ where $B$ is the message batch. + +A message-caused failure can be rooted in one or more messages. One important question is if it is expected that the failure is caused by a single or multiple messages. Both is possible, so it is a question of probability. If we assume that it is more probable that a single messages causes the problems, it is useful to immediately return back to full batch submission of transactions once a problem-causing message has been identified. But then, if there are multiple problem-causing messages inside the batch, we may need many more iterations. + +If, on the other hand, we assume that it is more probable that multiple messages cause problems, it may make sense to keep resubmitting only subsets of the batch. However, then the performance is suboptimal if actually only one message was problematic. A solution might be to pick a compromise, e.g. first assume that a single message is problematic, but assume the opposite as soon as a second message with problems has been found. + +A potential algorithm for processing $n \le |B|$ messages from batch $B$ is described below. In the pseudocode, a ``processable'' message is one that neither is already committed nor had a permanent failure with this action. The term ``mpf'' means ``message permanent failure'' for this action (this will later be described in a batch state set). + +\begin{small} +\lstset{language=python} +\begin{lstlisting} +def submitBatch(B, n): + foreach processable message in + (first [at most] n messages of batch): + call processMessage + if action-caused failure: + retry full batch + if action-caused permanent failure: + mark all n messages as mpf + return + if auto-commit: + mark commited messages in batch as committed + if message-caused failure: + if n == 1: + mark message as mpf + return + else: + call submitBatch(B, n/2) + call submitBatch(B, n/2) +\end{lstlisting} +\end{small} + +After submitBatch() has completed, all messages are either committed or in mpf state. + +Note that an action-caused permanent failure occurs if an action-caused failure can not be resolved with the operator-configured number of retries. It will never occur if the user configured infinite retries. While an action is suspended, all calls will result in an action-caused permanent failure. Please keep in mind that these will be resubmitted to any backup actions inside the action unit, so the action's ability to cause permanent failure states is vital for a number of use cases (backup syslog server, to name just one). + +Batch processing inside an action unit thus can follow these strucuture: + +\begin{algorithm} +\caption{processBatch(B)} +\begin{algorithmic} +\FORALL{action $a$ in action unit} + \IF{execute action only on messages that failed before} + \STATE $n = |\text{messages in batch in mpf state}|$ + \STATE change mpf state back to ready + \ELSE + \STATE $n = |B \setminus \text{msgs with state discard}|$ + \STATE change all message states $\ne$ discard to ready + \ENDIF + \IF{$n >0$ } + \STATE call submitBatch(B, n) for action $a$ + \ENDIF +\ENDFOR +\end{algorithmic} +\end{algorithm} + +\paragraph{Why is it Important to differentiate the failure cases?} +This text originates from the mailing list and must be merged in. I provide it in the form it is, so it will not be forgotten (plus, it conveys the information). + +One may think that it is not necessary to differentiate between action-caused and message-caused failures. However, not doing so introduces subtle issues, because +then you either + +A) do not need the batch logic at all (because the action is configured for +infinite retries) + +Or + +B) you loose many messages if the action is not configured for infinite +retries and you have a longer-duration outage e.g. on a database server. +Let's say it is offline for a couple of hours, then you lose almost +everything in that period + +To prevent this, you need two different retry methods. + +One may argue that it is hard to differentiate between the two failure cases. This is correct. Buit I think it mostly depends on the quality of the output module. + +First of all, ``mostly'' implies that there may be some other cases, where it +really is impossible to differentiate between the two. In that case, I would +treat the issue as an action-caused failure. There are two reasons for this: + +1) rsyslog v3 currently does this always and not even a single person +complained about that so far. This is an empiric argument, and it does not +mean it caused problems. But it carries the co-notation that this seems not +to be too bad. + +2) If we would treat it as message-caused failure, we would no longer be able +to handle extended outages of destination systems, which I consider a vitally +important feature. + +When weighing the two, I know of lots of people who rely on 2), in sharp +contrast to knowig noone having problems with 1). So my conclusion is that it is +less problematic to define an otherwise undefinable failure reason to be +action-caused. Even more so as I assume this problem only exists in the +minority of cases. + +Now back to the quality of the output module: thinking about databases, their +API is usually very good at conveying back if there was a SQL error or a +connection abort. So while a SQL error may also be an indication of a +configuration problem, I would strongly tend to treat it is a being +message-caused. This is under the assumption that any reasonable responsive +admin will hopefully test his configuration at least once before turning it +into production. And config SQL errors should manifest immediately, so I +expect these to be fixed before a configuration runs in production. So it is +the duty of the output module to interpret the return code it received from +the API call and decide whether the failure is more likely action-caused or +message-caused. For database outputs, I would assume that it is always easy +to classify failures that must be action-caused, especially in the +dominating cases of failed network connections or failed servers. + +For other outputs it may not be as easy. But, for example, all stream network +outputs can detect a broken connection, so this also is a sure fit. + +For dynafiles, it really depends on how hard the output module is tries to differentiate +between the two failure cases. But I think you can go great length here, too. +Especially if you do not only look at the create() return code, but, iff a +failure occurs, you do more API calls to find out the cause. + +So I think the remaining problem is small enough to cause not too much issues +(and if so, they are unavoidable in any case). In conclusion, the two failure states are not only necessary, but can sufficiently sure enough be detected. + +\subsection{Random Topics} +I have begun to gather material from the mailing list in this section, because I feel it may be useful for others as well. Right now, the information is well hidden in the mailing list archives and there may be value in combining it all in one place. + +Due to the nature of this material, there is no specific organization between the subchapters and also formatting and language doesn't deny its rooting in the mailing list. + +\subsection{Reliability of Message Dequeueing} +A batch is actually dequeued when it is taken off a queue. So if at that point we +have a system power failure (for whatever reason), the messages are lost. +While the rsyslog engine intends to be very reliable, it is not a complete +transactional system. A slight risk remains. For this, you need to understand +what happens when the batch is processed. I assume that we have no sudden, +untrappable process termination. Then, if a batch cannot be processed, it is +returned back to the top of queue. This is not yet implemented, but is how +single messages (which you can think of an abstraction of a batch in the +current code) are handled. If, for example, the engine shuts down, but an +action takes longer than the configured shutdown timeout, the action is +cancelled and the queue engine reclaims the unprocessed messages. They go +into a special area inside the .qi file and are placed on top of the queue +once the engine restarts. + +The only case where this not work is sudden process termination. I see two +cases: + +a) a fatal software bug +We cannot really address this. Even if the messages were remaining in the +queue until finally processed, a software bug (maybe an invalid pointer) may +affect the queue structures at large, possibly even at the risk of total loss +of all data inside that queue. So this is an inevitable risk. + +b) sudden power fail +... which can and should be mitigated at another level + +One may argue that there also is + +c) admin error +e.g, kill -9 rsyslogd +Here a fully transactional queue will probably help. + +However, I do not think that the risk involved justifies a far more complex +fully transactional implementation of the queue object. Some risk always +remains (what in the disaster case, even with a fully transactional queue?). + +And it is so complex to let the messages stay in queue because it is complex +to work with such messages and disk queues. It would also cost a lot of +performance, especially when done reliably (need to sync). We would then need +to touch each element at least four times, twice as much as currently. Also, +the hybrid disk/memory queues become very, very complex. There are more +complexities around this, I just wanted to tell the most obvious. + +So, all in all, the idea is that messages are dequeued, processed and put +back to the queue (think: ungetc()) when something goes wrong. Reasonable +(but not more) effort is made to prevent message loss while the messages are +in unprocessed state outside of the queue. + +\paragraph{More reliable can actually be less reliable} +On the rsyslog mailing list, we had a discussion about how reliable rsyslog should be. It circles about a small potential window of message loss in the case of sudden fatal failure. Rsyslog can be configured to put all messages into a disk queue (instead of main memory), so these messages survive such a powerfail condition. However, messages dequeued and scheduled for processing during the power outage may be lost. + +I now consider a case where we have bursty UDP traffic and rsyslog is configured to use a disk-only queue (which obviously is much slower than an in-memory queue). Looking at processing speeds, the max burst rate is limited by using an ultra-reliable queue. To avoid using UDP messages, a second instance could be run that uses an in-memory queue and forwards received messages to the one in ultra-reliable mode (that is with the disk-only queue). So that second instance queues in memory until the (slower) reliable rsyslogd can now accept the message and put it into the reliable queue. Let's say that you have a burst of $r$ messages and that from these burst only $r/2$ can be enqueued (because the ultra reliable queue is so slow). So you lose $r/2$ messages. + +Now consider the case that you run rsyslog with just a reliable queue, one that is kept in memory but not able to cover the power failure scenario. Obviously, all messages in that queue are lost when power fails (or almost all to be precise). However, that system has a much broader bandwidth. So with it, there would never have been r messages inside the queue, because that system has a much higher sustained message rate (and thus the burst causes much less of trouble). Let's say the system is just twice as fast in this setup (I guess it usually would be *much* faster). Than, it would be able to process all r records. + +In that scenario, the ultra-reliable system loses $r/2$ messages, whereas the somewhat more "unreliable" system loses none - by virtue of being able to process messages as they arrive. + +Now extend that picture to messages residing inside the OS buffers or even those that are still queued in their sources because a stream transport blocked sending them. + +I know that each detail of this picture can be argued at length about. + +However, my opinion is that there is no "ultra-reliable" system in life, only various probabilities in losing messages. These probabilities often depend on each other, what makes calculating them very hard to impossible. Still, the probability of message loss in the system at large is just the product of the probabilities in each of its components. And reliability is just the inverse of that probability. + +This is where *I* conclude that it can make sense to permit a system to lose some messages under certain circumstances, if that influences the overall probability calculation towards the desired end result. In that sense, I tend to think that a fast, memory-queuing rsyslogd instance can be much more reliable compared to one that is configured as being ultra-reliable, where the rest of the system at large is badly influenced by this (the scenario above). + +However, I also know that for regulatory requirements, you often seem to need to prove that a system may not lose messages once it has received them, even at the cost of an overall increased probability of message loss. + +My view of reliability is much the same as my view of security: there is no such thing as "being totally secure", you can just reduce the probability that something bad happens. The worst thing in security is someone who thinks he is "totally secure" and as such is no longer actively looking at potential issues. + +The same I see for reliability. There is no thing like "being totally reliable" and it is a really bad idea to think you could ever be. Knowing this, one may begin to think about how to decrease the overall probability of message loss AND think about what rate is acceptable (and what to do with these cases, e.g. "how can they hurt"). + +\paragraph{Different Use Cases} +As David Lang pointed out, there exist different use cases for different levels of reliability. Most importantly, there exist use cases that do not demand very high throughput but rather ultra-realiability of the queue system. Here, ultra-reliability is just another word for the queue being of ``audit-grade''. Even if the queue provides audit-grade, the overall system is only then of audit-grade when all other components - most notably the transport protocols spoken by the inputs and outputs - are also of audit-grade. Most importantly, this means that an audit-grade system purely based on the IETF syslog protocol series can not be build. + +Used together with truly reliable protocols \emph{and} senders that block processing until a final acknowledgement has been received, an audit-grade system can potentially build based on rsyslog. To do so, an audit-grade queue subsystem is required, which is not present in releases less than 4.1.? (most importantly, v2 and v3 do not provide this capability). + +\subsection{Audit-Grade Queue Operations} +\subsubsection{Perquisites} +Audit-grade queue operations certain perquisites: +\begin{itemize} +\item rsyslog engine is of version 4.1.? or greater +\item disk-only queue type +\item checkpoint interval set to 1 +\item queue is configured to not permit losing any messages\footnote{The queue has several settings that can be used to fine-tune situations in which it may discard messages intentionally. All of these must be turned off. Most importantly, that means the producer is blocked for an infinite time if the queue is full.} +\item queue consumer must also be of audit-grade +\end{itemize} +Only when these prequisites are met, queue operation can be considered of being audit-grade. Note that when message loss in case of sudden fatal failure and similar incidents is acceptable, neither disk-only queues nore a checkpoint interval of 1 is necessary. Such a configuration can also be build with rsyslog v3, which is up to that level. + +Note that in the sections below we describe the implementation in broader terms. Most importantly, we do not restrict ourselves to disk-only queue storage drivers. This is important, because it simplifies design and opens the capability to introduce new, possibly faster-performing, queue storage drivers in the future. + +But it is important to keep in mind that a concrete queue is only of audit-grade if it matches all the perquisites given here, most importantly with the right configuration. + +\subsubsection{Implementation Alternatives} +Messages, or more precisely objects\footnote{While rsyslog deals with messages, the queue is designed to handle any type of thing that is represented as an rsyslog object. This is considered useful as queues may at some time contain other things than just messages, so we keep it generic.}, are enqueued by the queue producer (either an input module or the main message queue's consumer). The enqueue operation is completed only when the message has been successfully accepted by the queue storage driver. Then and only then the producer is permitted to remove the object from its own storage system. A rough sketch is given in algorithm \ref{alg_q_enq}. + +\begin{algorithm} +\caption{enqueueObject($o$)} +\begin{algorithmic} +\label{alg_q_enq} +\STATE lock queue mutex +\WHILE{queue is not ready for enqueue} + \STATE wait on queue to become ready +\ENDWHILE +\STATE call queue store driver to add $o$ +\STATE unlock queue mutex +\end{algorithmic} +\end{algorithm} + +The dequeue-operation is more complex. We must ensure that each object stays in the queue until it is finally processed. Hereby, an object is finally processed, when processing of it has been completed. Remember that to enhance performance, objects are dequeued in batches of many. So at any given time, multiple messages may be processed, but not necessarily have finally completed doing so. If another worker thread then tries to obtain a new batch for processing, those ``in-process'' message must not be handed out a second time. Also, if a sudden fatal failure occurs during processing, queue operation must restart at the point of last commit. This means that all ``in-process'' messages need to be changed back to ``no processed'' state and be restarted again. In those cases the (acceptable) slight message duplication can occur. + +In our design, we differentiate between ``logical'' and ``physical'' dequeuing of batches. If a batch is generated for processing, it is logically dequeued --- in the sense that no other batch generating request will be able to receive another copy of these messages. If no exceptional situation happens, those messages will be processed and thus can be considered consumed under normal circumstances. + +However, actual deletion from the physical queue storage happens only after the batch is fully processed. At this point, all objects have been acknowledged by their destinations, which now have the responsibility for the object's survival. Consequently, we can delete them from the queue store. This process is considered the ``physical'' dequeue of the object. + +In order to find some simpler terms, we will call the logical dequeue operation just ``dequeue'' and the physical dequeue operation ``delete''. This is consistent with all previous work on rsyslog and thus probably leads to the least surprise when reading older source code and documentation. + +A first idea for a deletion is given in algorithm \ref{alg_pdeq_batch_1} (remember that $O(b)$ contains all objects within the given batch $b$, this is \emph{not} $O$-notation and should probably in the future be replaced by something else). + +\begin{algorithm} +\caption{deleteBatch($b$), first approach} +\begin{algorithmic} +\label{alg_pdeq_batch_1} +\STATE lock queue mutex +\FORALL{$o \in O(b)$} + \STATE find $o$ in queue storage + \STATE remove $o$ and keep queue structures intact +\ENDFOR +\STATE unlock queue mutex +\end{algorithmic} +\end{algorithm} + +This algorithm is simple, but requires searching the queue store for the object to be deleted -- a potentially lengthy operation. However, we can improve the searching process if we know more about the inner structure of batch objects. It seems appropriate to dequeue objects in queue-sequential order. A drawback of doing so is that we must prevent other worker threads from trying to dequeue concurrently. This is not really a drawback. We need to guard dequeue operations by a mutex in any case, because otherwise internal structures can not be kept consistent. Practical experience and testing have shown that many small dequeue operations cause a lot of locking contention and as such badly affect performance. So it actually is a welcome enhancement to aquire the queue lock only once for the whole batch dequeue operation. As dequeing is a comperatively fast operation, the lock is not held for extended periods of time. + +A first approach to this functionality is shown in algorithm \ref{alg_ldeq_batch_1}. Note that $C_{mBatch}$ is the configured maximum number of elements inside a batch, $i$ is an index to address the objects inside the batch. + +\begin{figure}[h] +\begin{center} +\includegraphics[scale=0.6]{rsyslog_queue_pointers.jpeg} +\end{center} +\caption{\textbf{Queue Store Pointers}: boxes represent queue entries, colored boxes entries with objects. Objects in green are unprocessed, in blue are dequeued but not deleted and those in gray have already been deleted. White indicates not yet used entries. Gray objects may be overwritten at any time. Their entries are actually free, we have used the gray color primarily to indicate there once existed objects. Each queue pointer points to the next entry to process.} +\label{fig_queue_ptr} +\end{figure} + +\begin{algorithm} +\caption{dequeueBatch($b$)} +\begin{algorithmic} +\label{alg_ldeq_batch_1} +\STATE lock queue mutex +\STATE $0 \to i$ +\WHILE{queue non-empty and $i < C_{mBatch}$} + \STATE obtain next obj $o$ from queue store + \STATE advance logical dequeue position + \STATE put $o$ into batch +\ENDWHILE +\STATE unlock queue mutex +\end{algorithmic} +\end{algorithm} + +A key concept is somewhat hidden in \marginpar{queue pointers} \emph{advance logical dequeue position}. Each queue store is purely sequential, with objects being enqueued at one ``end'' of the store and dequeued at the other. Of course, each queue store has only finite capacity, but we ignore this to explain the overall picture. A queue can be implemented by two pointers: one that points to the tail of the queue, where new messages are enqueued and one that points to the head of it, where new messages are dequeued. The idea is now to duplicate the dequeue pointer and split it into one for (logical) dequeue and one for deletion. Figure \ref{fig_queue_ptr} shows this three-pointer approach. Now, we can simple advance either the dequeue or deletion pointer, depending on operation, and do not need to find the first dequeue position inside the queue store. The dequeue pointer always points at it. This mode can be implemented with all currently existing queue storage drivers (but the sequential disk driver may need to use a second file handle or stream object instead of two pointers). + +This makes an efficient implementation of algorithm \ref{alg_ldeq_batch_1} possible: when it logically dequeues, it just needs to advance the dequeue pointer. So the algorithm executes in $O(n)$ time where $n$ specifies the number of elements to dequeue with an upper bound of $C_{mBatch}$. + +\begin{figure}[h] +\begin{center} +\includegraphics[scale=0.6]{rsyslog_queue_pointers2.jpeg} +\end{center} +\caption{\textbf{Physically Dequeueing Messages}: In this sample, we have two batches. With multiple workers, they may be deleted in any order.} +\label{fig_queue_ptr_deq} +\end{figure} + +Furthermore, we can also improve algorithm \ref{alg_pdeq_batch_1}: Consider that each batch is logically dequeued as an atomic operation. That means all batch objects form a sequential subset of the queue. Figure \ref{fig_queue_ptr_deq} shows the situation when two batches have been dequeued. So the costly ``find'' operation now needs to be carried out only once at the beginning of the batch. As all other objects are sequential, once we have found the batch begin inside the queue, we can simply delete the $|b|$ elements in queue-sequential order after it. So the cost of the find operation can be reduced from $O(|b|)$ to $O(1)$. + +We can even reduce the remaining cost of the find operation. If the batch to be deleted is right at the queue's head (as is ``B1'' in the figure), the ``find'' immediately terminates with the first element and incurs no cost at all. The situation is different if the batch is not at the queue head, ``B2'' is an example for that (assuming that ``B1'' has not yet been dequeued). We would now still need to search over the objects that are not part of the batch and can then finally get to the object at the head of the batch in question. For queue storage drivers that support random access to queue elements, storing a simple pointer to the batches' queue head element further improves the situation and enables $O(1)$ access to the queue element. This is indicated by the dotted lines in figure \ref{fig_queue_ptr_deq}. Once the head of the queue has been found, two things can happen (depending on the capabilities of the queue storage driver): + +\begin{enumerate} +\item the head element can be flagged as ``this and next $n$ elements are deleted'' +\item all elements are actually deleted +\end{enumerate} + +Note that a mixed form is also possible (and probably useful for our \emph{singly} linked list storage driver: there, some $n'$ elements be actually deleted and the head element is flagged as ``this and next $n - n'$ elements are deleted''. Note that in the linked-list case, all but the first elements can be deleted with ease\footnote{It can be considered to change from a singly-linked list to a doubly-linked list, if the benefit outweighs the extra effort required.}, so probably just the head would stay inside the queue. Note that removing elements off the queue, where possible, is useful because it frees resources. On a busy system, freeing messages as soon as possible can prevent message loss (in non-audit-grade setup) or system slowdown. So it should be done when possible. + +If we have a purely sequential queue storage driver (currently the sequential disk driver), finding and updating the head element is not an option. Even in this case, we can observe that the batch at the actual deletion pointer will eventually be submitted for deletion. So a route to take is to create a list of elements that can be deleted as soon as the physical dequeue pointer reaches any of these elements. We call this the \marginpar{to-delete list}``to-delete list''. To facilitate processing, this list must be ordered in sequence of dequeing. This information may not be available from the storage subsystem itself, but it can easily be generated. To do so, a strictly monotonically increasing counter is kept with each logical dequeue operation and stored as part of the batch\footnote{As this must be done via the usual computer-implemented modular arithmetic, we must be careful that we do not see repetion of values because of overflows. Each day has $60 \cdot 60 \cot 24 = 86,400$ seconds (ignoring the subleties of UTC). Now let's assume that we have a moderately-busy system with 1,000 messages per second. We further assume, to be on the save side, that each message is processed inside its own batch. So we have $86,400,000$ batches per day. If we now use a typical $32$-bit integer for generating the batch IDs, we the unique range will be used up after +$$\frac{2^{32}}{8640000} \approx 497 \text{ days}$$ +days of uninterrupted rsyslog operation. While this sounds somewhat save, it goes down to approximately 10 days of messages are submitted at rate of 50,000 messages per second (which is high, but not unheared of). So it is strongly advised to use 64 bits, which we consider to be save, because for our 1,000 messages per second the range would be exhausted only after +$$\frac{2^{64}}{8640000} \approx 2.135 \cdot 10^{11} \text{ days}$$ +which equals approximately $584,500,000$ \emph{years}. So even at a rate of one million messages per second, the range would be sufficient for over 500,000 years of continuos operations -- that should be far sufficient.} +An example: let us assume that ``B2'' was submitted for deletion first. Then, the head of ``B2'' is not at the queue's delete pointer. As such, no action can be carried out immediately. So the batch head pointer is stored into a ``to be deleted'' list. Processing continues. Some time later, batch ``B1'' is submitted for deletion. Now, the head pointer is at the head of the delete list, as such all batch elements are dequeued. Then, the ``to be deleted'' list is checked, and ``B2'' is found in it. Now, ``B2'' is at the head of the (new) deletion pointer and can also be removed. So, ultimately, all messages are physically dequeued. This is more formally describe in algorithm \ref{alg_phys_deq_seq_store}. In that pseudocode, we made a simplification by always putting the to be deleted batch in the ``to-delete'' list, which then enables us to use somewhat more generic code to carry out the work. + +Note that there is a price to pay for deletions via the ``to-delete'' list: if a sudden fatal failure happens during processing, the set of duplicate messages is increased. For example, if a fatal failure happens after ``B2'' has been fully processed and scheduled for deletion, but \emph{before ``B1'' is also submitted for deletion}, ``B2'' will be reprocessed after recovery. This would not happen if ``B2'' would have been removed from the queue. + +\begin{algorithm} +\caption{deleteBatch($b$)} +\begin{algorithmic} +\label{alg_phys_deq_seq_store} +\REQUIRE queue mutex is locked by caller +\STATE enqueue $b.head, |b|$ in ``to-delete'' list $D$ +\COMMENT ``to-delete'' list must be in order of logical dequeue +\WHILE{$D.head = Q.deletePtr$} + \FOR{$|b|$ elements} + \STATE delete element at queue head + \STATE move $q.deletePtr$ + \ENDFOR + \STATE remove head of ``to-delete'' list +\ENDWHILE +\end{algorithmic} +\end{algorithm} + +\paragraph{Warp-Up of Queue Delete Operations} +When evaluating which route to take, the ``to-delete'' list approach looks elegant for all cases. The negative side effect of potentially increased message duplication currently does not even exist: today, the sequential disk queue storage driver permits only a single worker thread and thus there always will be only one thread at a time. Even if we remove that limitation, message duplication could not be avoided, as stated in the algorithm description above. What remains are the other queue storage drivers. However, they operate in-memory, so message duplication will not happen simply because all messages will be lost on sudden fatal failure. The advantage of limited message duplication only exists in the so-far hypothetical case of a random-access, audit-grade disk queue storage driver. Thus, the decision could be postponed unless that happens (if it ever does). + +From a code complexity point of view, the ``to-delete'' list approch is definitely advantagous. Not only because of the reduced number of algorithms required. We also do not need to maintain unique batch IDs and all the logic associated with them. + +The other aspect to look at is memory consumption. Assuming that we delete the actual objects, just not their containers inside the queue, extra memory consumption is not really that worse. More importantly, currently only the linked-list queue storage driver can benefit at all, because it is the only driver capable of deleting queue entries in mid-queue. All others, including the array memory driver, do not have this capability. + +From a performance point of view, the ``to delete'' list approach looks approximately as good as the others, with some mild better performance for some storage drivers for a non-``to delete'' list approach. This can be mitigated, especially if the potentially somewhat-costly maintenance of the ``to-delete'' list is slightly optimized and the algorithm actually checks if the to be deleted batch is right at the queue's delete pointer position. The improved code simplicity, together with current CPU's code caching, may even result in an otherwise not expected speedup. + +In conclusion, we will implement the ``to-delete'' list approach on the queue layer (above the queue storage drivers). However, we will leave the window open to permit overwriting it with queue storage driver specific functionality. How to do this will not be specified now, as there is currently no need and we do not even know if there ever will be. However, we retain the discussion on the various modes as well as the relevant algorithmic discussions and data structurs inside this paper so that it is readily available should need arise. We also think this is important so that everybody later knows that the decision was made based on good argument and not by accident (we consider this useful in another design enhancement attempt). + +\paragraph{Processing Sequence} Looking at the processing sequence, we notice that always objects are dequeued, then processed and then deleted. Then, the whole process starts again. In particular, this meanss that after the previous batch has been deleted, the next batch will be dequeued. Now consider that we need to have exclusive access to the queue for both of these operations. As such it seems natural to combine this into a single step, further reducing potential locking contention. + +Note that a side-effect of this approach is that messages can be deleted only when a new batch is dequeued. With current design, this means that at least one message must reside inside the queue. Otherwise, the last batch will not be deleted. However, this something that can (and must!) be solved on the queue worker layer, in that it deletes a batch when the queue is empty. + +This leads us to the implementation of dequeueBatch() and deleteBatch() shown in algorithms \ref{alg_deq_batch_final} and \ref{alg_del_batch_final}. Note that $l$ is a flag variable that indicates if the queue is already locked. + +\begin{algorithm} +\caption{dequeueBatch($b$): final version} +\begin{algorithmic} +\label{alg_deq_batch_final} +\STATE lock queue mutex +\STATE call deleteBatch(b, 1) +\STATE $0 \to i$ +\WHILE{queue non-empty and $i < C_{mBatch}$} + \STATE obtain next obj $o$ from queue store + \STATE advance dequeue position + \STATE put $o$ into batch +\ENDWHILE +\STATE commit queue changes to storage system (if needed, e.g. fsync()) +\STATE unlock queue mutex +\end{algorithmic} +\end{algorithm} + + +\begin{algorithm} +\caption{deleteBatch($b, l$): final version} +\begin{algorithmic} +\label{alg_del_batch_final} +\IF{queue not yet locked (test via $l$)} + \STATE lock queue mutex +\ENDIF +\FORALL{objects $o$ in $b$} + \STATE destruct $o$ +\ENDFOR +\STATE enqueue $b.head, |b|$ in ``to-delete'' list $D$ +\COMMENT ``to-delete'' list must be in order of logical dequeue +\WHILE{$D.head = Q.deletePtr$} + \FOR{$|b|$ elements} + \STATE delete element at queue head + \STATE move $q.deletePtr$ + \ENDFOR + \STATE remove head of ``to-delete'' list +\ENDWHILE +\STATE commit queue changes to storage system (if needed, e.g. fsync()) +\IF{queue not yet locked (test via $l$)} + \STATE unlock queue mutex +\ENDIF +\end{algorithmic} +\end{algorithm} + +\subsubsection{Queue Stores} +Currently, rsyslog supports three different types of queue store drivers: + +\begin{itemize} +\item memory array +\item memory linked list +\item disk sequential file +\end{itemize} + +They all provide an abstracted sequential queue store as shown in figure \ref{fig_queue_ptr} on page \pageref{fig_queue_ptr}. + +Obviously, some differences exist. Most importantly, the disk sequential file driver does \emph{not} support more than one queue worker thread (in order to prevent excessive disk activity and the subtle issues with rewriting parts of sequential files). So if this driver is used, the queue automatically limits itself to a maximum of one worker thread (even if user configuration settings + +Different queue store drivers have different properties: + +\begin{tabular}{|l||l|l|l|}\hline + & array & linked list & seqential file \\ \hline +pointer type & integer index & memory address & file number and \\ + & & & offset within file \\ \hline +physical access & random & random & sequential \\ \hline +remove middle & no & yes & no \\ +elements & & & \\ \hline +access to $n$-th& $O(1)$, index:& $O(n)$, follow & not supported \\ +element & $n \mod C_{mMsg}$ & pointer links & \\ \hline +speed & fastest & fast & slow \\\hline +mem overhead & large & some & almost none \\\hline +reliability & reliable & reliable & audit-grade\footnote{if configured correctly}\\ +\hline +\end{tabular} + +\subsubsection{Implementation} +The actual implementation will be based on algorithms \ref{alg_deq_batch_final} and \ref{alg_del_batch_final}. The rsyslog v3 queue storage driver will be extended one additional method, which permits non-destructive dequeueing of elements. As such, the driver now has the $qAdd()$, $qDeq()$, and $qDel()$ entry points (together with the usual construction and destruction entry points). The queue drivers must support the three pointers for enqueue, dequeue and delete. The ``to-delete'' list will be maintained on the upper queue layer (and not the queue driver layer). This functionality will be optimized so that if a batch to delete is right at the queue's delete pointer, it will immediatly be deleted and not be sent to the ``to-delete'' list. This is especially important with the sequential disk driver, as the condition here always is true (and thus the driver can pretend this in the relevant API without even comparing any pointers -- what would otherwise quite complicated in this driver. + +The full list of the queue store driver interface is: + +\paragraph{qConstruct} Initializes the queue store. + +\paragraph{qDestruct} Destructs the queue store, including all messages that may still be present in it. + +\paragraph{qAdd} Enqueue a new object into the queue. Note that this entry point must only be called when the queue is non-full. + +\paragraph{qDeq} Non-destructive dequeue of the object at queue head. Dequeue pointer is advanced. + +\paragraph{qDel} Delete the object at queue head. Delete pointer is advanced. + +Disk queue store drivers may support additional internal functions. However, they should not be exposed to the rest of the queue subsystem. + +\begin{figure} +\begin{center} +\includegraphics[scale=0.4]{queue_msg_state.jpeg} +\end{center} +\caption{Logical Message States during Queue Processing} +\label{fig_queue_msg_state} +\end{figure} + +Figure \ref{fig_queue_msg_state} shows a logical message state diagram during queue processing. There is no actual state variable, but rather the processing flow demands these state. Note that the state transition from ``dequeued'' to ``queued'' only happens after a fatal failure and a successful system recovery. So this is a rather exceptional case. + +Another subtle issue is that we now need two different queue size counters: one for seeing when the queue is physically full and one for detecting when there are no more messages to be dequeued. + +As a simplification, support for ungetting objects can be removed (as objects never leave the queue), what also means that cancel-processing is probably less complex. + +\paragraph{Sequential Disk Queue Store Driver} +The enequeue, deqeueue and delete pointers must be implemented via three stream objects. Most importantly, the dequeue stream must be configured not to delete files when it closes them. A side-effect of this implementation is that data is actually read twice, once to actually obtain it and a second time to delete it. This could only be avoided by an overall redesign on how the disk queue works. + +\subsubsection{Checkmarks} +The following things need to be verified in the actual implementation. + +\paragraph{Queue Full} +Is it possible to set an infinte timeout on queue full condition during enqueue? If not, we must provide it. + +\paragraph{Termination the Queue} +If we cancel a worker, we need to start from the physical dequeue pointer and pull everything that is not scheduled for deletion - NOT from the logical dequeue pointer. + +\paragraph{Failed Messages} +If a message fails on a detached action queue, no backup processing is available (because we detect the failure at a point where the message is already considered processed from the main queue's point of view. We need address this and have two options: + + +I see two approaches at handling this: + +a) we enable an action to configure a backup file that shall receive all +message permanent failures. This is simple (not only to implement but to +configure and understand) + +b) we push the failed message back to the main queue, but with an indication +that it failed in an action. This is harder to implement and most importantly +harder to understand/configure, but more flexible + +\section{Configuration System} +The configration system found in all versions up to v5 is based on sysklogd's +legacy. It does not have any clear distinction between config load and +activation. Starting with v6, a new config system is build. That new system +offers the necessary distinction. In the long term, the configuration language +will be enhanced towards the more flexible and easy to use RainerScript idea. + +\section{Plugin Interface} +This section describes some aspects of the plugin interface. +\subsection{Configuration Related} +To support the new v2 config system, plugins need to publish a number of entry +points that will be called by the rsyslog configuration section at various +stages of the configration load, activation and deactivation process. This list +may be extended as the configuration interface evolves. + +Plugins must not necessarily implement support for the v2 config system. If +they do, the ``beginCnfLoad'' entry point serves as a flag telling that support +is available. In that case, all other entry points need to be defined as well. +If a module does not support the v2 config system, it can still be run, but be +configured only via the legacy config system. Note that with the old system +there are also problems with droping privileges. So a legacy module may not +work correctly if privileges are dropped. + +The following entry points are available: +\begin{enumerate} + \item \emph{beginCnfLoad} -- called when a new config load begins. Only one +config load can be active at one time (no concurrent loads). + \item \emph{endCnfLoad} -- called when config load ends. This gives the module +a chance to do final changes and some cleanup. + \item \emph{checkCnf} -- called by the framework to verify a configuration. + \item \emph{activateCnfPrePrivDrop} -- called by the framework to activate a +configuration before privileges are dropped. This is an optional entry point +that shall only be implemented by plugins that need the do some processing +before rsyslog drops privileges. Processing inside this entry point should be +limited to what is absolutely necessary. The main activation work should be +done in activateCnf() as usual. + \item \emph{activateCnf} -- called by the framework to activate a +configuration. +\item \emph{freeCnf} -- called by the framework to free +(deallocate) a configuration. +\end{enumerate} + +In the current implementation, entry points are sequentially called as given +above. However, this will change. It is guaranteed that +\begin{itemize} + \item beginCnfLoad() will be followed by a matching endCnfLoad() and there +will be no new call to beginCnfLoad() before endCnfLoad() has been called. This +means no nested config load needs to be supported, + \item checkCnf() may be called at any time, even during a config load phase. +However, the config to check is a fully loaded one. + \item activateCnfPrePrivDrop(), if provided, will always be called before +activateCnf() is called. No other config-related calls will be made in between. +\end{itemize} + +\subsubsection{Output Modules} +The v1 config load system for output modules seems to provide all functionality +necessary to support the v2 system as well. As such, we currently do not +require output modules to implement the new calls to be fully supported by the +v2 system. + +\section{Network Stream Subsystem} +The idea of network streams was introduced when we implemented RFC5425 (syslog +over TLS) in 2008. The core idea is to encapsulate all stream-oriented network +data transfer into a single transport layer and make the upper layers +independent of actual transport being used. This is in line with the traditional +layer approaches in communication systems. + +Under this system, the upper layer provides plugins to send and receive streams +of syslog data. Framing is provided by the upper layer. The upper layer itself +is integrated in input and output plugins, which then are used to provide +application-level syslog message objects to and from the rsyslog core. To these +upper layers, the netstream layer provides reliable and sequenced message +delivery with much of the same semantics as a usual TCP stream. + + +\begin{figure} +\begin{center} +\includegraphics[scale=0.4]{tls.jpeg} +\end{center} +\caption{Objects at the Network Stream Layer} +\label{fig_netstream_objects} +\end{figure} + +At the netstream layer, we have a small set of generic classes, which are used for setup of the drivers and driver parameters. This is a very thin layer, mostly a wrapper. Once an actual lower-level netstream driver has been loaded, all parameters are passed through to it. + +Please note that both in theory and practice netstream drivers may call back into different netstream drivers. For example, the GnuTLS RFC5425 driver loads and calls back into the plain tcp driver, simply because that driver provides part of the required functionality and there is no point in re-implementing it for GnuTLS. + +The netstream driver layer does not only provide read and write calls but supports i/o multiplexing. To do so, it offers an interface that follows select() semantics. That permits an upper-layer comonent to request being blocked unless some data arrives. Note that due to the subleties in TLS processing, the upper layer may be awoken while there is no upper-layer work to do. This will properly be indicated by the netstream subsystem, is not an error and must be accepted and poperly handled by the upper layer. + +Using the nestream layer, we do not need to modify the input and output plugins while at the same time we can add additional transport providers. One weak spot in this design is the current configuration process. With the current system, we need to provide one configuration statement per driver property and we need to hardcode this. So if a new driver would require new properties, we still would need to modify the upper layers. This is unfortunate, but the current config system does not provide for any better way to handle the situation. Once we are able to create a new config system, we will address this by providing the ability to pass a string of parameters onto the driver, which will then have the ability to parse its content. So once we do this, we need to modify the driver interface, but the end result would be a simlification. + +So far, only drivers for GnuTLS and plain tcp are provided. However, during the design of the layer we also looked at openssl and Mozilla Network Security Services as well as kept an eye on the needs of Kerberos. In theory, it should not be a major problem to write drivers for these systems (but it most probably still is a lot of work to do). + +A final note on Kerberos: in order to keep compatible with previous protocol handling and due to constraints in testing environment and knowledge, we still support Kerberos not via the netstream layer but via special extension into the input and output modules. That, too, is unfortunate, but given the current resources at hand, there is no alternative to handling in that way. We would be very interested in moving over Kerberos to a netstream driver and any volunteer would be very welcome. + +\section{Future Development} +This section covers topics that can not currently be developed, but where important thoughts came up in discussions. For obvious reasons, the section has brainstorming character. + +\subsection{Lock-Free Queuing} +On a very busy system, lock contention can limit performance. We should investigate ways to apply lock-free algorithms inside rsyslog. It is believed that at least for some scenarios, lock-free algorigthms can be applied with great benefit. To do so, we should introduce new queue modes, which will use very different semantics from what is described so far for the queue engine. Most importantly, in lock-free mode we will have limits on the number of producers and we will most probably not be able to guarantee audit-grade processing. The later is not a problem, because there are ample use cases that do not require audit-gradeness. + +\subsection{Audit-Grade High Performance Queue Storage Driver} +An audit grade driver must ensure that no message is lost, but should also be able to handle large workloads. The sequential disk driver does not support the later. + +An additional disk driver is envisioned with the properties like the linked list driver, but a reliable on-disk store. In particular, random access to queue elements is desired, which requires an addressing capability. + +A potential implementation requires a pre-formatted file. That file is organized in pages of $n$ bytes (e.g. 1K). The page index is used to address a queue item. If an item fits into 1K, it uses one page. If it is larger than 1K, consequtive pages are used to store the element. A page header must be present to indicate how many pages a single element is made up of. + +It may be noted that we could even improve performance by keeping part of the data in-memory. For audit-gradeness, it is required that upon enqueue the message is written to disk and only after final processing it needs to be removed. However, it is not forbidden to keep the same message in main memory. That way, the logical dequeue operation could be done one the in-memory representation. Only the physical dequeue would need to write to disk again. As such, we save one disk read out of three writes and one read otherwise required (so one can roughly say that we save one third of disk operations. + +Note that due to potential multi-pages messages we can not directly address individual elements, but we can reliably and quikly address elements whom's address we know (learned, for example, during logical dequeue). This is similar to the organization of the in-memory linked list. Actally, such a store \emph{is} a linked list implementation, just that memory is allocated on disk instead of in main memory. + +To further improve speed, object representation could be zipped before being written to a page. + +File Layout +Page 0: control structures (most importantyle queue pointers) (can make sense to store in a separate file, which could be moved to a dedicated disk subsystem - can potentially greatly reduce disk seek times). +Page 1 to n: actual object storage + +Algorithms \ref{alg_AuditGradeStoreEnqueue} and \ref{alg_AuditGradeStoreDelete} show how records are enqueued and deleted. Note that the delete part does not even need to read back the record. If we keep at last some records in-memory, the performance cost of ultra-reliable mode can actually comparatively low. Note that we may not even really need to commit data to the storage system in ``AuditGradeStoreDelete()'', because if a fatal failure occurs at this point, at worst message duplication may happen, what we have considered to be acceptable. + +\begin{algorithm} +\caption{AuditGradeStoreEnqueue($o$)} +\begin{algorithmic} +\label{alg_AuditGradeStoreEnqueue} +\REQUIRE queue mutex is locked by caller +\STATE write $o$ to current enqueue location +\STATE update \& write queue structures [page 0] +\STATE sync all files touched +\STATE store $o$ in an in-memory structure (or a cache) +\end{algorithmic} +\end{algorithm} + +\begin{algorithm} +\caption{AuditGradeStoreDelete($o$)} +\begin{algorithmic} +\label{alg_AuditGradeStoreDelete} +\REQUIRE queue mutex is locked by caller +\STATE update queue dequeue pointer \& write queue structures [page 0] +\STATE sync all files touched +\end{algorithmic} +\end{algorithm} + + +\end{document} diff --git a/doc/dev_oplugins.html b/doc/dev_oplugins.html new file mode 100644 index 00000000..b33b67f9 --- /dev/null +++ b/doc/dev_oplugins.html @@ -0,0 +1,336 @@ +<html> +<head> +<title>writing rsyslog output plugins (developer's guide)</title> +</head> +<body> +<h1>Writing Rsyslog Output Plugins</h1> +<p>This page is the begin of some developer documentation for writing output +plugins. Doing so is quite easy (and that was a design goal), but there currently +is only sparse documentation on the process available. I was tempted NOT to +write this guide here because I know I will most probably not be able to +write a complete guide. +<p>However, I finally concluded that it may be better to have same information +and pointers than to have nothing. +<h2>Getting Started and Samples</h2> +<p>The best to get started with rsyslog plugin development is by looking at +existing plugins. All that start with "om" are <b>o</b>utput <b>m</b>odules. That +means they are primarily thought of being message sinks. In theory, however, output +plugins may aggergate other functionality, too. Nobody has taken this route so far +so if you would like to do that, it is highly suggested to post your plan on the +rsyslog mailing list, first (so that we can offer advise). +<p>The rsyslog distribution tarball contains two plugins that are extremely well +targeted for getting started: +<ul> +<li>omtemplate +<li>omstdout +</ul> +Plugin omtemplate was specifically created to provide a copy template for new output +plugins. It is bare of real functionality but has ample comments. Even if you decide +to start from another plugin (or even from scratch), be sure to read omtemplate source +and comments first. The omstdout is primarily a testing aide, but offers support for +the two different parameter-passing conventions plugins can use (plus the way to +differentiate between the two). It also is not bare of functionaly, only mostly +bare of it ;). But you can actually execute it and play with it. +<p>In any case, you should also read the comments in ./runtime/module-template.h. +Output plugins are build based on a large set of code-generating macros. These +macros handle most of the plumbing needed by the interface. As long as no +special callback to rsyslog is needed (it typically is not), an output plugin does +not really need to be aware that it is executed by rsyslog. As a plug-in programmer, +you can (in most cases) "code as usual". However, all macros and entry points need to be +provided and thus reading the code comments in the files mentioned is highly suggested. +<p>In short, the best idea is to start with a template. Let's assume you start by +copying omtemplate. Then, the basic steps you need to do are: +<ul> +<li>cp ./plugins/omtemplate ./plugins/your-plugin +<li>mv cd ./plugins/your-plugin +<li>vi Makefile.am, adjust to your-plugin +<li>mv omtemplate.c your-plugin.c +<li>cd ../.. +<li>vi Makefile.am configure.ac +<br>search for omtemplate, copy and modify (follow comments) +</ul> +<p>Basically, this is all you need to do ... Well, except, of course, coding +your plugin ;). For testing, you need rsyslog's debugging support. Some useful +information is given in "<a href="troubleshoot.html">troubleshooting rsyslog</a> +from the doc set. +<h2>Special Topics</h2> +<h3>Threading</h3> +<p>Rsyslog uses massive parallel processing and multithreading. However, a plugin's entry +points are guaranteed to be never called concurrently <b>for the same action</b>. +That means your plugin must be able to be called concurrently by two or more +threads, but you can be sure that for the same instance no concurrent calls +happen. This is guaranteed by the interface specification and the rsyslog core +guards against multiple concurrent calls. An instance, in simple words, is one +that shares a single instanceData structure. +<p>So as long as you do not mess around with global data, you do not need +to think about multithreading (and can apply a purely sequential programming +methodology). +<p>Please note that duringt the configuraton parsing stage of execution, access to +global variables for the configuration system is safe. In that stage, the core will +only call sequentially into the plugin. +<h3>Getting Message Data</h3> +<p>The doAction() entry point of your plugin is provided with messages to be processed. +It will only be activated after filtering and all other conditions, so you do not need +to apply any other conditional but can simply process the message. +<p>Note that you do NOT receive the full internal representation of the message +object. There are various (including historical) reasons for this and, among +others, this is a design decision based on security. +<p>Your plugin will only receive what the end user has configured in a $template +statement. However, starting with 4.1.6, there are two ways of receiving the +template content. The default mode, and in most cases sufficient and optimal, +is to receive a single string with the expanded template. As I said, this is usually +optimal, think about writing things to files, emailing content or forwarding it. +<p>The important philosophy is that a plugin should <b>never</b> reformat any +of such strings - that would either remove the user's ability to fully control +message formats or it would lead to duplicating code that is already present in the +core. If you need some formatting that is not yet present in the core, suggest it +to the rsyslog project, best done by sending a patch ;), and we will try hard to +get it into the core (so far, we could accept all such suggestions - no promise, though). +<p>If a single string seems not suitable for your application, the plugin can also +request access to the template components. The typical use case seems to be databases, where +you would like to access properties via specific fields. With that mode, you receive a +char ** array, where each array element points to one field from the template (from +left to right). Fields start at arrray index 0 and a NULL pointer means you have +reached the end of the array (the typical Unix "poor man's linked list in an array" +design). Note, however, that each of the individual components is a string. It is +not a date stamp, number or whatever, but a string. This is because rsyslog processes +strings (from a high-level design look at it) and so this is the natural data type. +Feel free to convert to whatever you need, but keep in mind that malformed packets +may have lead to field contents you'd never expected... +<p>If you like to use the array-based parameter passing method, think that it +is only available in rsyslog 4.1.6 and above. If you can accept that your plugin +will not be working with previous versions, you do not need to handle pre 4.1.6 cases. +However, it would be "nice" if you shut down yourself in these cases - otherwise the +older rsyslog core engine will pass you a string where you expect the array of pointers, +what most probably results in a segfault. To check whether or not the core supports the +functionality, you can use this code sequence: +<pre> +<code> +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bArrayPassingSupported; /* does core support template passing as an array? */ +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* check if the rsyslog core supports parameter passing code */ + bArrayPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports array passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_ARRAY) + bArrayPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, what is not acceptable */ + } + DBGPRINTF("omstdout: array-passing is %ssupported by rsyslog core.\n", bArrayPassingSupported ? "" : "not "); + + if(!bArrayPassingSupported) { + DBGPRINTF("rsyslog core too old, shutting down this plug-in\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + +</code> +</pre> +<p>The code first checks if the core supports the OMSRgetSupportedTplOpts() API (which is +also not present in all versions!) and, if so, queries the core if the OMSR_TPL_AS_ARRAY mode +is supported. If either does not exits, the core is too old for this functionality. The sample +snippet above then shuts down, but a plugin may instead just do things different. In +omstdout, you can see how a plugin may deal with the situation. +<p><b>In any case, it is recommended that at least a graceful shutdown is made and the +array-passing capability not blindly be used.</b> In such cases, we can not guard the +plugin from segfaulting and if the plugin (as currently always) is run within +rsyslog's process space, that results in a segfault for rsyslog. So do not do this. +<p>Another possible mode is OMSR_TPL_AS_JSON, where instead of the template +a json-c memory object tree is passed to the module. The module can extract data +via json-c API calls. It MUST NOT modify the provided structure. This mode is +primarily aimed at plugins that need to process tree-like data, as found +for example in MongoDB or ElasticSearch. +<h3>Batching of Messages</h3> +<p>Starting with rsyslog 4.3.x, batching of output messages is supported. Previously, only +a single-message interface was supported. +<p>With the <b>single message</b> plugin interface, each message is passed via a separate call to the plugin. +Most importantly, the rsyslog engine assumes that each call to the plugin is a complete transaction +and as such assumes that messages be properly commited after the plugin returns to the engine. +<p>With the <b>batching</b> interface, rsyslog employs something along the line of +"transactions". Obviously, the rsyslog core can not make non-transactional outputs +to be fully transactional. But what it can is support that the output tells the core which +messages have been commited by the output and which not yet. The core can than take care +of those uncommited messages when problems occur. For example, if a plugin has received +50 messages but not yet told the core that it commited them, and then returns an error state, the +core assumes that all these 50 messages were <b>not</b> written to the output. The core then +requeues all 50 messages and does the usual retry processing. Once the output plugin tells the +core that it is ready again to accept messages, the rsyslog core will provide it with these 50 +not yet commited messages again (actually, at this point, the rsyslog core no longer knows that +it is re-submiting the messages). If, in contrary, the plugin had told rsyslog that 40 of these 50 +messages were commited (before it failed), then only 10 would have been requeued and resubmitted. +<p>In order to provide an efficient implementation, there are some (mild) constraints in that +transactional model: first of all, rsyslog itself specifies the ultimate transaction boundaries. +That is, it tells the plugin when a transaction begins and when it must finish. The plugin +is free to commit messages in between, but it <b>must</b> commit all work done when the core +tells it that the transaction ends. All messages passed in between a begin and end transaction +notification are called a batch of messages. They are passed in one by one, just as without +transaction support. Note that batch sizes are variable within the range of 1 to a user configured +maximum limit. Most importantly, that means that plugins may receive batches of single messages, +so they are required to commit each message individually. If the plugin tries to be "smarter" +than the rsyslog engine and does not commit messages in those cases (for example), the plugin +puts message stream integrity at risk: once rsyslog has notified the plugin of transacton end, +it discards all messages as it considers them committed and save. If now something goes wrong, +the rsyslog core does not try to recover lost messages (and keep in mind that "goes wrong" +includes such uncontrollable things like connection loss to a database server). So it is +highly recommended to fully abide to the plugin interface details, even though you may +think you can do it better. The second reason for that is that the core engine will +have configuration settings that enable the user to tune commit rate to their use-case +specific needs. And, as a relief: why would rsyslog ever decide to use batches of one? +There is a trivial case and that is when we have very low activity so that no queue of +messages builds up, in which case it makes sense to commit work as it arrives. +(As a side-note, there are some valid cases where a timeout-based commit feature makes sense. +This is also under evaluation and, once decided, the core will offer an interface plus a way +to preserve message stream integrity for properly-crafted plugins). +<p>The second restriction is that if a plugin makes commits in between (what is perfectly +legal) those commits must be in-order. So if a commit is made for message ten out of 50, +this means that messages one to nine are also commited. It would be possible to remove +this restriction, but we have decided to deliberately introduce it to simpify things. +<h3>Output Plugin Transaction Interface</h3> +<p>In order to keep compatible with existing output plugins (and because it introduces +no complexity), the transactional plugin interface is build on the traditional +non-transactional one. Well... actually the traditional interface was transactional +since its introduction, in the sense that each message was processed in its own +transaction. +<p>So the current <code>doAction()</b> entry point can be considered to have this +structure (from the transactional interface point of view): +<p><pre><code> +doAction() + { + beginTransaction() + ProcessMessage() + endTransaction() + } + </code></pre> +<p>For the <b>transactional interface</b>, we now move these implicit <code>beginTransaction()</code> +and <code>endTransaction(()</code> call out of the message processing body, resulting is such +a structure: +<p><pre><code> +beginTransaction() + { + /* prepare for transaction */ + } + +doAction() + { + ProcessMessage() + /* maybe do partial commits */ + } + +endTransaction() + { + /* commit (rest of) batch */ + } +</code></pre> +<p>And this calling structure actually is the transactional interface! It is as simple as this. +For the new interface, the core calls a <code>beginTransaction()</code> entry point inside the +plugin at the start of the batch. Similarly, the core call <code>endTransaction()</code> at the +end of the batch. The plugin must implement these entry points according to its needs. +<p>But how does the core know when to use the old or the new calling interface? This is rather +easy: when loading a plugin, the core queries the plugin for the <code>beginTransaction()</code> +and <code>endTransaction()</code> entry points. If the plugin supports these, the new interface is +used. If the plugin does not support them, the old interface is used and rsyslog implies that +a commit is done after each message. Note that there is no special "downlevel" handling +necessary to support this. In the case of the non-transactional interface, rsyslog considers +each completed call to <code>doAction</code> as partial commit up to the current message. +So implementation inside the core is very straightforward. +<p>Actually, <b>we recommend that the transactional entry points only be defined by those +plugins that actually need them</b>. All others should not define them in which case +the default commit behaviour inside rsyslog will apply (thus removing complexity from the +plugin). +<p>In order to support partial commits, special return codes must be defined for +<code>doAction</code>. All those return codes mean that processing completed successfully. +But they convey additional information about the commit status as follows: +<p> +<table border="0"> +<tr> +<td valign="top"><i>RS_RET_OK</i></td> +<td>The record and all previous inside the batch has been commited. +<i>Note:</i> this definition is what makes integrating plugins without the +transaction being/end calls so easy - this is the traditional "success" return +state and if every call returns it, there is no need for actually calling +<code>endTransaction()</code>, because there is no transaction open).</td> +</tr> +<tr> +<td valign="top"><i>RS_RET_DEFER_COMMIT</i></td> +<td>The record has been processed, but is not yet commited. This is the +expected state for transactional-aware plugins.</td> +</tr> +<tr> +<td valign="top"><i>RS_RET_PREVIOUS_COMMITTED</i></td> +<td>The <b>previous</b> record inside the batch has been committed, but the +current one not yet. This state is introduced to support sources that fill up +buffers and commit once a buffer is completely filled. That may occur halfway +in the next record, so it may be important to be able to tell the +engine the everything up to the previouos record is commited</td> +</tr> +</table> +<p>Note that the typical <b>calling cycle</b> is <code>beginTransaction()</code>, +followed by <i>n</i> times +<code>doAction()</code></n> followed by <code>endTransaction()</code>. However, if either +<code>beginTransaction()</code> or <code>doAction()</code> return back an error state +(including RS_RET_SUSPENDED), then the transaction is considered aborted. In result, the +remaining calls in this cycle (e.g. <code>endTransaction()</code>) are never made and a +new cycle (starting with <code>beginTransaction()</code> is begun when processing resumes. +So an output plugin must expect and handle those partial cycles gracefully. +<p><b>The question remains how can a plugin know if the core supports batching?</b> +First of all, even if the engine would not know it, the plugin would return with RS_RET_DEFER_COMMIT, +what then would be treated as an error by the engine. This would effectively disable the +output, but cause no further harm (but may be harm enough in itself). +<p>The real solution is to enable the plugin to query the rsyslog core if this feature is +supported or not. At the time of the introduction of batching, no such query-interface +exists. So we introduce it with that release. What the means is if a rsyslog core can +not provide this query interface, it is a core that was build before batching support +was available. So the absence of a query interface indicates that the transactional +interface is not available. One might now be tempted the think there is no need to do +the actual check, but is is recommended to ask the rsyslog engine explicitely if +the transactional interface is present and will be honored. This enables us to +create versions in the future which have, for whatever reason we do not yet know, no +support for this interface. +<p>The logic to do these checks is contained in the <code>INITChkCoreFeature</code> macro, +which can be used as follows: +<p><pre><code> +INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); +</code></pre> +<p>Here, bCoreSupportsBatching is a plugin-defined integer which after execution is +1 if batches (and thus the transational interface) is supported and 0 otherwise. +CORE_FEATURE_BATCHING is the feature we are interested in. Future versions of rsyslog +may contain additional feature-test-macros (you can see all of them in +./runtime/rsyslog.h). +<p>Note that the ompsql output plugin supports transactional mode in a hybrid way and +thus can be considered good example code. + +<h2>Open Issues</h2> +<ul> +<li>Processing errors handling +<li>reliable re-queue during error handling and queue termination +</ul> + + + +<h3>Licensing</h3> +<p>From the rsyslog point of view, plugins constitute separate projects. As such, +we think plugins are not required to be compatible with GPLv3. However, this is +no legal advise. If you intend to release something under a non-GPLV3 compatible license +it is probably best to consult with your lawyer. +<p>Most importantly, and this is definite, the rsyslog team does not expect +or require you to contribute your plugin to the rsyslog project (but of course +we are happy if you do). +<h2>Copyright</h2> +<p>Copyright (c) 2009 <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p>Permission is granted to copy, distribute and/or modify this document under +the terms of the GNU Free Documentation License, Version 1.2 or any later +version published by the Free Software Foundation; with no Invariant Sections, +no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be +viewed at <a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body> +</html> diff --git a/doc/dev_queue.html b/doc/dev_queue.html new file mode 100644 index 00000000..bf2af7f0 --- /dev/null +++ b/doc/dev_queue.html @@ -0,0 +1,250 @@ +<html> +<head> +<title>rsyslog queue object</title> +</head> +<body> +<h1>The rsyslog queue object</h1> +<p>This page reflects the status as of 2008-01-17. The documentation is still incomplete. +Target audience is developers and users who would like to get an in-depth understanding of +queues as used in <a href="http://www.rsyslog.com/">rsyslog</a>.</p> +<p><b>Please note that this document is outdated and does not longer reflect the +specifics of the queue object. However, I have decided to leave it in the doc +set, as the overall picture provided still is quite OK. I intend to update this +document somewhat later when I have reached the "store-and-forward" milestone.</b></p> +<h1>Some definitions</h1> +<p>A queue is DA-enabled if it is configured to use disk-assisted mode when +there is need to. A queue is in DA mode (or DA run mode), when it actually runs +disk assisted.</p> +<h1>Implementation Details</h1> +<h2>Disk-Assisted Mode</h2> +<p>Memory-Type queues may utilize disk-assisted (DA) mode. DA mode is enabled +whenever a queue file name prefix is provided. This is called DA-enabled mode. +If DA-enabled, the queue operates as a regular memory queue until a high water +mark is reached. If that happens, the queue activates disk assistance (called +"runs disk assisted" or "runs DA" - you can find that often in source file +comments). To do so, it creates a helper queue instance (the DA queue). At that +point, there are two queues running - the primary queue's consumer changes to a +shuffle-to-DA-queue consumer and the original primary consumer is assigned to +the DA queue. Existing and new messages are spooled to the disk queue, where the +DA worker takes them from and passes them for execution to the actual consumer. +In essence, the primary queue has now become a memory buffer for the DA queue. +The primary queue will be drained until a low water mark is reached. At that +point, processing is held. New messages enqueued to the primary queue will not +be processed but kept in memory. Processing resumes when either the high water +mark is reached again or the DA queue indicates it is empty. If the DA queue is +empty, it is shut down and processing of the primary queue continues as a +regular in-memory queue (aka "DA mode is shut down"). The whole thing iterates +once the high water mark is hit again.</p> +<p>There is one special case: if the primary queue is shut down and could not +finish processing all messages within the configured timeout periods, the DA +queue is instantiated to take up the remaining messages. These will be preserved +and be processed during the next run. During that period, the DA queue runs in +"enqueue-only" mode and does not execute any consumer. Draining the primary +queue is typically very fast. If that behaviour is not desired, it can be turned +of via parameters. In that case, any remaining in-memory messages are lost.</p> +<p>Due to the fact that when running DA two queues work closely together and +worker threads (including the DA worker) may shut down at any time (due to +timeout), processing synchronization and startup and shutdown is somewhat +complex. I'll outline the exact conditions and steps down here. I also do this +so that I know clearly what to develop to, so please be patient if the +information is a bit too in-depth ;)</p> +<h2>DA Run Mode Initialization</h2> +<p>Three cases:</p> +<ol> + <li>any time during queueEnqObj() when the high water mark is hit</li> + <li>at queue startup if there is an on-disk queue present (presence of QI + file indicates presence of queue data)</li> + <li>at queue shutdown if remaining in-memory data needs to be persisted to + disk</li> +</ol> +<p>In <b>case 1</b>, the worker pool is running. When switching to DA mode, all +regular workers are sent termination commands. The DA worker is initiated. +Regular workers may run in parallel to the DA worker until they terminate. +Regular workers shall terminate as soon as their current consumer has completed. +They shall not execute the DA consumer.</p> +<p>In <b>case 2</b>, the worker pool is not yet running and is NOT started. The +DA worker is initiated.</p> +<p>In <b>case 3</b>, the worker pool is already shut down. The DA worker is +initiated. The DA queue runs in enqueue-only mode.</p> +<p>In all cases, the DA worker starts up and checks if DA mode is already fully +initialized. If not, it initializes it, what most importantly means construction +of the queue.</p> +<p>Then, regular worker processing is carried out. That is, the queue worker +will wait on empty queue and terminate after an timeout. However, If any message +is received, the DA consumer is executed. That consumer checks the low water +mark. If the low water mark is reached, it stops processing until either the +high water mark is reached again or the DA queue indicates it is empty (there is +a pthread_cond_t for this synchronization).</p> +<p>In theory, a <b>case-2</b> startup could lead to the worker becoming inactive +and terminating while waiting on the primary queue to fill. In practice, this is +highly unlikely (but only for the main message queue) because rsyslog issues a +startup message. HOWEVER, we can not rely on that, it would introduce a race. If +the primary rsyslog thread (the one that issues the message) is scheduled very +late and there is a low inactivty timeout for queue workers, the queue worker +may terminate before the startup message is issued. And if the on-disk queue +holds only a few messages, it may become empty before the DA worker is +re-initiated again. So it is possible that the DA run mode termination criteria +occurs while no DA worker is running on the primary queue.</p> +<p>In cases 1 and 3, the DA worker can never become inactive without hitting the +DA shutdown criteria. In <b>case 1</b>, it either shuffles messages from the +primary to the DA queue or it waits because it has the hit low water mark. </p> +<p>In <b>case 3</b>, it always shuffles messages between the queues (because, +that's the sole purpose of that run). In order for this to happen, the high +water mark has been set to the value of 1 when DA run mode has been initialized. +This ensures that the regular logic can be applied to drain the primary queue. +To prevent a hold due to reaching the low water mark, that mark must be changed +to 0 before the DA worker starts.</p> +<h2>DA Run Mode Shutdown</h2> +<p>In essence, DA run mode is terminated when the DA queue is empty and the +primary worker queue size is below the high water mark. It is also terminated +when the primary queue is shut down. The decision to switch back to regular +(non-DA) run mode is typically made by the DA worker. If it switches, the DA +queue is destructed and the regular worker pool is restarted. In some cases, the +queue shutdown process may initiate the "switch" (in this case more or less a +clean shutdown of the DA queue).</p> +<p>One might think that it would be more natural for the DA queue to detect +being idle and shut down itself. However, there are some issues associated with +that. Most importantly, all queue worker threads need to be shut down during +queue destruction. Only after that has happend, final destruction steps can +happen (else we would have a myriad of races). However, it is the DA queues +worker thread that detects it is empty (empty queue detection always happens at +the consumer side and must so). That would lead to the DA queue worker thread to +initiate DA queue destruction which in turn would lead to that very same thread +being canceled (because workers must shut down before the queue can be +destructed). Obviously, this does not work out (and I didn't even mention the +other issues - so let's forget about it). As such, the thread that enqueues +messages must destruct the queue - and that is the primary queue's DA worker +thread.</p> +<p>There are some subleties due to thread synchronization and the fact that the +DA consumer may not be running (in a <b>case-2 startup</b>). So it is not +trivial to reliably change the queue back from DA run mode to regular run mode. +The priority is a clean switch. We accept the fact that there may be situations +where we cleanly shut down DA run mode, just to re-enable it with the very next +message being enqueued. While unlikely, this will happen from time to time and +is considered perfectly legal. We can't predict the future and it would +introduce too great complexity to try to do something against that (that would +most probably even lead to worse performance under regular conditions).</p> +<p>The primary queue's DA worker thread may wait at two different places:</p> +<ol> + <li>after reaching the low water mark and waiting for either high water or + DA queue empty</li> + <li>at the regular pthread_cond_wait() on an empty primary queue</li> +</ol> +<p>Case 2 is unlikely, but may happen (see info above on a case 2 startup).</p> +<p><b>The DA worker may also not wait at all,</b> because it is actively +executing and shuffeling messages between the queues. In that case, however, the +program flow passes both of the two wait conditions but simply does not wait.</p> +<p><b>Finally, the DA worker may be inactive </b>(again, with a case-2 startup). +In that case no work(er) at all is executed. Most importantly, without the DA +worker being active, nobody will ever detect the need to change back to regular +mode. If we have this situation, the very next message enqueued will cause the +switch, because then the DA run mode shutdown criteria is met. However, it may +take close to eternal for this message to arrive. During that time, disk and +memory resources for the DA queue remain allocated. This also leaves processing +in a sub-optimal state and it may take longer than necessary to switch back to +regular queue mode when a message burst happens. In extreme cases, this could +even lead to shutdown of DA run mode, which takes so long that the high water +mark is passed and DA run mode is immediately re-initialized - while with an +immediate switch, the message burst may have been able to be processed by the +in-memory queue without DA support.</p> +<p>So in short, it is desirable switch to regular run mode as soon as possible. +To do this, we need an active DA worker. The easy solution is to initiate DA +worker startup from the DA queue's worker once it detects empty condition. To do +so, the DA queue's worker must call into a "<i>DA worker startup initiation</i>" +routine inside the main queue. As a reminder, the DA worker will most probably +not receive the "DA queue empty" signal in that case, because it will be long +sent (in most cases) before the DA worker even waits for it. So <b>it is vital +that DA run mode termination checks be done in the DA worker before it goes into +any wait condition</b>.</p> +<p>Please note that the "<i>DA worker startup initiation</i>" routine may be +called concurrently from multiple initiators. <b>To prevent a race, it must be +guarded by the queue mutex </b>and return without any action (and no error +code!) if the DA worker is already initiated.</p> +<p>All other cases can be handled by checking the termination criteria +immediately at the start of the worker and then once again for each run. The +logic follows this simplified flow diagram:</p> +<p align="center"><a href="queueWorkerLogic.jpg"> +<img border="0" src="queueWorkerLogic_small.jpg" width="431" height="605"></a></p> +<p>Some of the more subtle aspects of worker processing (e.g. enqueue thread +signaling and other fine things) have been left out in order to get the big +picture. What is called "check DA mode switchback..." right after "worker init" +is actually a check for the worker's termination criteria. Typically, <b>the +worker termination criteria is a shutdown request</b>. However, <b>for a DA +worker, termination is also requested if the queue size is below the high water +mark AND the DA queue is empty</b>. There is also a third termination criteria +and it is not even on the chart: that is the inactivity timeout, which exists in +all modes. Note that while the inactivity timeout shuts down a thread, it +logically does not terminate the worker pool (or DA worker): workers are +restarted on an as-needed basis. However, inactivity timeouts are very important +because they require us to restart workers in some situations where we may +expect a running one. So always keep them on your mind.</p> +<h2>Queue Destruction</h2> +<p>Now let's consider <b>the case of destruction of the primary queue. </b>During +destruction, our focus is on loosing as few messages as possible. If the +queue is not DA-enabled, there is nothing but the configured timeouts to handle +that situation. However, with a DA-enabled queue there are more options.</p> +<p>If the queue is DA-enabled, it may be <i>configured to persist messages to +disk before it is terminated</i>. In that case, loss of messages never occurs +(at the price of a potentially lengthy shutdown). Even if that setting is not +applied, the queue should drain as many messages as possible to the disk. For +that reason, it makes no sense to wait on a low water mark. Also, if the queue +is already in DA run mode, it does not make any sense to switch back to regular +run mode during termination and then try to process some messages via the +regular consumer. It is much more appropriate the try completely drain the queue +during the remaining timeout period. For the same reason, it is preferred that +no new consumers be activated (via the DA queue's worker), as they only cost +valuable CPU cycles and, more importantly, would potentially be long(er)-running +and possibly be needed to be cancelled. To prevent all of that, <b>queue +parameters are changed for DA-enabled queues:</b> the high water mark is to 1 +and the low water mark to 0 on the primary queue. The DA queue is commanded to +run in enqueue-only mode. If the primary queue is <i>configured to persist +messages to disk before it is terminated</i>, its SHUTDOWN timeout is changed to +to eternal. These parameters will cause the queue to drain as much as possible +to disk (and they may cause a case 3 DA run mode initiation). Please note that +once the primary queue has been drained, the DA queue's worker will +automatically switch back to regular (non-DA) run mode. <b>It must be ensured +that no worker cancellation occurs during that switchback</b>. Please note that +the queue may not switch back to regular run mode if it is not <i>configured to +persist messages to disk before it is terminated</i>. In order to apply the new +parameters, <b>worker threads must be awakened.</b> Remember we may not be in DA +run mode at this stage. In that case, the regular workers must be awakened, which +then will switch to DA run mode. No worker may be active, in that case one must +be initiated. If in DA run mode and the DA worker is inactive, the "<i>DA +worker startup initiation</i>" must be called to activate it. That routine +ensures only one DA worker is started even with multiple concurrent callers - +this may be the case here. The DA queue's worker may have requested DA worker +startup in order to terminate on empty queue (which will probably not be honored +as we have changed the low water mark).</p> +<p>After all this is done, the queue destructor requests termination of the +queue's worker threads. It will use the normal timeouts and potentially cancel +too-long running worker threads. <b>The shutdown process must ensure that all +workers reach running state before they are commanded to terminate</b>. +Otherwise it may run into a race condition that could lead to a false shutdown +with workers running asynchronously. As a few workers may have just been started +to initialize (to apply new parameter settings), the probability for this race +condition is extremely high, especially on single-CPU systems.</p> +<p>After all workers have been shut down (or cancelled), the queue may still be +in DA run mode. If so, this must be terminated, which now can simply be done by +destructing the DA queue object. This is not a real switchback to regular run +mode, but that doesn't matter because the queue object will soon be gone away.</p> +<p>Finally, the queue is mostly shut down and ready to be actually destructed. +As a last try, the queuePersists() entry point is called. It is used to persists +a non-DA-enabled queue in whatever way is possible for that queue. There may be +no implementation for the specific queue type. Please note that this is not just +a theoretical construct. This is an extremely important code path when the DA +queue itself is destructed. Remember that it is a queue object in its own right. +The DA queue is obviously not DA-enabled, so it calls into queuePersists() +during its destruction - this is what enables us to persist the disk queue!</p> +<p>After that point, left over queue resources (mutexes, dynamic memory, ...) +are freed and the queue object is actually destructed.</p> +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p>Permission is granted to copy, distribute and/or modify this document under +the terms of the GNU Free Documentation License, Version 1.2 or any later +version published by the Free Software Foundation; with no Invariant Sections, +no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be +viewed at <a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body> +</html>
\ No newline at end of file diff --git a/doc/direct_queue0.png b/doc/direct_queue0.png Binary files differnew file mode 100644 index 00000000..6d1b373f --- /dev/null +++ b/doc/direct_queue0.png diff --git a/doc/direct_queue1.png b/doc/direct_queue1.png Binary files differnew file mode 100644 index 00000000..503f8151 --- /dev/null +++ b/doc/direct_queue1.png diff --git a/doc/direct_queue2.png b/doc/direct_queue2.png Binary files differnew file mode 100644 index 00000000..cbb99af8 --- /dev/null +++ b/doc/direct_queue2.png diff --git a/doc/direct_queue3.png b/doc/direct_queue3.png Binary files differnew file mode 100644 index 00000000..cc49299f --- /dev/null +++ b/doc/direct_queue3.png diff --git a/doc/direct_queue_directq.png b/doc/direct_queue_directq.png Binary files differnew file mode 100644 index 00000000..c5d8769d --- /dev/null +++ b/doc/direct_queue_directq.png diff --git a/doc/direct_queue_rsyslog.png b/doc/direct_queue_rsyslog.png Binary files differnew file mode 100644 index 00000000..6150222d --- /dev/null +++ b/doc/direct_queue_rsyslog.png diff --git a/doc/direct_queue_rsyslog2.png b/doc/direct_queue_rsyslog2.png Binary files differnew file mode 100644 index 00000000..807b064d --- /dev/null +++ b/doc/direct_queue_rsyslog2.png diff --git a/doc/droppriv.html b/doc/droppriv.html new file mode 100644 index 00000000..7293e872 --- /dev/null +++ b/doc/droppriv.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>dropping privileges in rsyslog</title> +</head> +<body> +<h1>Dropping privileges in rsyslog</h1> +<p><b>Available since: </b> 4.1.1</p> +<p><b>Description</b>:</p> +<p> +Rsyslogd provides the ability to drop privileges by +impersonating as another user and/or group after startup. + +<p>Please note that due to POSIX standards, rsyslogd always needs to start +up as root if there is a listener who must bind to a network port below 1024. +For example, the UDP listener usually needs to listen to 514 and as such +rsyslogd needs to start up as root. + +<p>If you do not need this functionality, you can start rsyslog directly as an ordinary +user. That is probably the safest way of operations. However, if a startup as +root is required, you can use the $PrivDropToGroup and $PrivDropToUser config +directives to specify a group and/or user that rsyslogd should drop to after initialization. +Once this happend, the daemon runs without high privileges (depending, of +course, on the permissions of the user account you specified). +<p>There is some additional information available in the +<a href="http://wiki.rsyslog.com/index.php/Security#Dropping_Privileges">rsyslog wiki</a>. +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$PrivDropToUser</b><br> +Name of the user rsyslog should run under after startup. Please note that +this user is looked up in the system tables. If the lookup fails, privileges are +NOT dropped. Thus it is advisable to use the less convenient $PrivDropToUserID directive. +If the user id can be looked up, but can not be set, rsyslog aborts. +<br> +</li> +<li><b>$PrivDropToUserID</b><br> +Much the same as $PrivDropToUser, except that a numerical user id instead of a name +is specified.Thus, privilege drop will always happen. +rsyslogd aborts. +<li><b>$PrivDropToGroup</b><br> +Name of the group rsyslog should run under after startup. Please note that +this user is looked up in the system tables. If the lookup fails, privileges are +NOT dropped. Thus it is advisable to use the less convenient $PrivDropToGroupID directive. +Note that all supplementary groups are removed from the process if $PrivDropToGroup is +specified. +If the group id can be looked up, but can not be set, rsyslog aborts. +<br> +</li> +<li><b>$PrivDropToGroupID</b><br> +Much the same as $PrivDropToGroup, except that a numerical group id instead of a name +is specified. Thus, privilege drop will always happen. +</ul> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> + +</body></html> diff --git a/doc/expression.html b/doc/expression.html new file mode 100644 index 00000000..c401d9ab --- /dev/null +++ b/doc/expression.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Expressions in rsyslog</title></head> +<body> +<a href="rsyslog_conf_filter.html">back to rsyslog filter conditions</a> +<h1>Expressions in rsyslog</h1> +<p>Rsyslog supports expressions at a growing number of places. So +far, they are supported for filtering messages.</p> +<p>Expression support is provided by RainerScript. Please see the +<a href="rainerscript.html">RainerScript documentation</a> for more details.</p> +<p>C-like comments (/* some comment */) are supported <b>inside</b> the expression, +but not yet in the rest of the configuration file.</p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008, 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/features.html b/doc/features.html new file mode 100644 index 00000000..626ff65d --- /dev/null +++ b/doc/features.html @@ -0,0 +1,158 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog features</title> +</head> +<body> +<a href="rsyslog_conf.html">back</a> +<h1>RSyslog - Features</h1> +<p><b>This page lists both current features as well as +those being considered for future versions of rsyslog.</b> If you +think a feature is missing, drop +<a href="mailto:rgerhards@adiscon.com">Rainer</a> a +note. Rsyslog is a vital project. Features are added each few days. If +you would like to keep up of what is going on, you can also subscribe +to the <a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">rsyslog +mailing list</a>.</p> +<p><span style="font-weight: bold;">A better +structured feature list is now contained in our </span><a style="font-weight: bold;" href="rsyslog_ng_comparison.html">rsyslog +vs. syslog-ng comparison</a><span style="font-weight: bold;">. +</span>Probably that page will replace this one in the +future. +</p> +<h2>Current Features</h2> +<ul> +<li>native support for <a href="rsyslog_mysql.html">writing +to MySQL databases</a></li> +<li> native support for writing to Postgres databases</li> +<li>direct support for Firebird/Interbase, +OpenTDS (MS SQL, Sybase), SQLLite, Ingres, Oracle, and mSQL via libdbi, +a database abstraction layer (almost as good as native)</li> +<li>native support for <a href="ommail.html">sending +mail messages</a> (first seen in 3.17.0)</li> +<li>support for (plain) tcp based syslog - much better +reliability</li> +<li>support for sending and receiving compressed syslog messages</li> +<li>support for on-demand on-disk spooling of messages that can +not be processed fast enough (a great feature for <a href="rsyslog_high_database_rate.html">writing massive +amounts of syslog messages to a database</a>)</li> +<li>support for selectively <a href="http://wiki.rsyslog.com/index.php/OffPeakHours">processing +messages only during specific timeframes</a> and spooling them to +disk otherwise</li> +<li>ability to monitor text files and convert their contents +into syslog messages (one per line)</li> +<li>ability to configure backup syslog/database servers - if +the primary fails, control is switched to a prioritized list of backups</li> +<li>support for receiving messages via reliable <a href="http://www.monitorware.com/Common/en/glossary/rfc3195.php"> +RFC 3195</a> delivery (a bit clumpsy to build right now...)</li> +<li>ability to generate file names and directories (log +targets) dynamically, based on many different properties</li> +<li>control of log output format, including ability to present +channel and priority as visible log data</li> +<li>good timestamp format control; at a minimum, ISO 8601/RFC +3339 second-resolution UTC zone</li> +<li>ability to reformat message contents and work with +substrings</li> +<li>support for log files larger than 2gb</li> +<li>support for file size limitation and automatic rollover +command execution</li> +<li>support for running multiple rsyslogd instances on a single +machine</li> +<li>support for <a href="rsyslog_tls.html">TLS-protected +syslog</a> (both <a href="rsyslog_tls.html">natively</a> +and via <a href="rsyslog_stunnel.html">stunnel</a>)</li> +<li>ability to filter on any part of the message, not just +facility and severity</li> +<li>ability to use regular expressions in filters</li> +<li>support for discarding messages based on filters</li> +<li>ability to execute shell scripts on received messages</li> +<li>control of whether the local hostname or the hostname of +the origin of the data is shown as the hostname in the output</li> +<li>ability to preserve the original hostname in NAT +environments and relay chains </li> +<li>ability to limit the allowed network senders</li> +<li>powerful BSD-style hostname and program name blocks for +easy multi-host support</li> +<li> massively multi-threaded with dynamic work thread pools +that start up and shut themselves down on an as-needed basis (great for +high log volume on multicore machines)</li> +<li>very experimental and volatile support for <a href="syslog_protocol.html">syslog-protocol</a> +compliant messages (it is volatile because standardization is currently +underway and this is a proof-of-concept implementation to aid this +effort)</li> +<li> world's first implementation of syslog-transport-tls</li> +<li> the sysklogd's klogd functionality is implemented as the <i>imklog</i> +input plug-in. So rsyslog is a full replacement for the sysklogd package</li> +<li> support for IPv6</li> +<li> ability to control repeated line reduction ("last message +repeated n times") on a per selector-line basis</li> +<li> supports sub-configuration files, which can be +automatically read from directories. Includes are specified in the main +configuration file</li> +<li> supports multiple actions per selector/filter condition</li> +<li> MySQL and Postgres SQL functionality as a dynamically +loadable plug-in</li> +<li> modular design for inputs and outputs - easily extensible +via custom plugins</li> +<li> an easy-to-write to plugin interface</li> +<li> ability to send SNMP trap messages</li> +<li> ability to filter out messages based on sequence of arrival</li> +<li>support for comma-seperated-values (CSV) output generation +(via the "csv" property replace option). The +CSV format supported is that from RFC 4180.</li> +<li>support for arbitrary complex boolean, string and +arithmetic expressions in message filters</li> +</ul> +<h2>World's first</h2> +Rsyslog has an interesting number of "world's firsts" - things that +were implemented for the first time ever in rsyslog. Some of them are still features not available elsewhere.<br><ul> +<li>world's first implementation of IETF I-D syslog-protocol (February 2006, version 1.12.2 and above), now RFC5424</li><li>world's first implementation of dynamic syslog on-the-wire compression (December 2006, version 1.13.0 and above)</li><li>world's first open-source implementation of a disk-queueing syslogd (January 2008, version 3.11.0 and above)</li> +<li>world's first implementation of IETF I-D +syslog-transport-tls (May 2008, version 3.19.0 and above)</li> +</ul> +<h2>Upcoming Features</h2> +<p>The list below is something like a repository of ideas we'd +like to implement. Features on this list are typically NOT scheduled +for immediate inclusion. We maintain a +<a href="http://bugzilla.adiscon.com/rsyslog-feature.html">feature +request tracker at our bugzilla</a>. This tracker has things +typically within reach of implementation. Users are encouraged to +submit feature requests there (or via our forums). If we like them but +they look quite long-lived (aka "not soon to be implemented"), they +will possibly be migrated to this list here and at some time moved back +to the bugzilla tracker.</p> +<p><b>Note that we also maintain a +<a href="http://www.rsyslog.com/sponsor_feature">list of features that are looking for sponsors</a>. +If you are interested in any of these features, or any other feature, you may consider sponsoring +the implementation. This is also a great way to show your commitment to the open source +community. Plus, it can be financially attractive: just think about how much less it may +be to sponsor a feature instead of purchasing a commercial implementation. Also, the benefit +of being recognised as a sponsor may even drive new customers to your business!</b> +<ul> +<li>port it to more *nix variants (eg AIX and HP UX) - this +needs volunteers with access to those machines and knowledge </li> +<li>pcre filtering - maybe (depending on feedback) - +simple regex already partly added. So far, this seems sufficient so +that there is no urgent need to do pcre. If done, it will be a loadable RainerScript function.</li> +<li>support for <a href="http://www.monitorware.com/Common/en/glossary/rfc3195.php">RFC +3195</a> as a sender - this is currently unlikely to happen, +because there is no real demand for it. Any work on RFC 3195 has been +suspend until we see some real interest in it. It is probably +much better to use TCP-based syslog, which is interoperable with a +large number of applications. You may also read my blog post on the +future of liblogging, which contains interesting information about the <a href="http://rgerhards.blogspot.com/2007/09/where-is-liblogging-heading-to.html"> +future of RFC 3195 in rsyslog</a>.</li> +</ul> +<p>To see when each feature was added, see the +<a href="http://www.rsyslog.com/Topic4.phtml">rsyslog +change log</a> (online only).</p> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body></html> + diff --git a/doc/free_support.html b/doc/free_support.html new file mode 100644 index 00000000..182a82cd --- /dev/null +++ b/doc/free_support.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Free Support for Rsyslog</title> + +</head> +<body> +<h1>Free Services for Rsyslog</h1> +<p><i>A personal word from Rainer, the lead developer of rsyslog:</i> +<p><b>The rsyslog community provides ample free support resources. Please see our +<a href="troubleshoot.html">troubleshooting guide</a> to get started.</b></p> +<p>Every now and then I receive private mail with support questions. I appreciate +any feedback, but I must limit my resources so that I can help driver a great logging +system forward. +<p>To do so, I have decided not to reply to unsolicited support emails, at least not +with a solution (but rather a link to this page ;)). I hope this does not offend you. The +reason is quite simple: If I do personal support, you gain some advantage without +contributing something back. Think about it: if you ask your question on the public +forum or mailing list, other with the same problem can you and, most importantly, even +years later find your post (and the answer) and get the problem solved. So by +solving your issue in public, you help create a great community ressource and also +help your fellow users finding solutions quicker. In the long term, this +also contributes to improved code because the more questions users can find +solutions to themselves, the fewer I need to look at. +<p>But it comes even better: the rsyslog community is much broader than Rainer ;) - there +are helpful other members hanging around at the public places. They often answer +questions, so that I do not need to look at them (btw, once again a big "thank you", folks!). +And, more important, those folks have different background than me. So they often +either know better how to solve your problem (e.g. because it is distro-specific) +or they know how to better phrase it (after all, I like abstract terms and concepts ;)). +So you do yourself a favor if you use the public places. +<p>An excellent place to go to is the +<a href="http://kb.monitorware.com/rsyslog-f40.html">rsyslog forum</a> inside the +knowledge base (which in itself is a great place to visit!). For those used to +mailing lists, the +<a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">rsyslog mailing list</a> +also offers excellent advise. +<p><b>Don't like to post your question in a public place?</b> Well, then you should +consider purchasing <a href="professional_support.html">rsyslog professional support</a>. +The fees are very low and help fund the project. If you use rsyslog seriously inside +a corporate environment, there is no excuse for not getting one of the support +packages ;) +<p>Of course, things are different when I ask you to mail me privately. I'll usually do +that when I think it makes sense, for example when we exchange debug logs. +<p>I hope you now understand the free support options and the reasoning for them. +I hope I haven't offended you with my words - this is not my intension. I just needed to +make clear why there are some limits on my responsiveness. Happy logging! +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/generic_design.html b/doc/generic_design.html new file mode 100644 index 00000000..74dbd807 --- /dev/null +++ b/doc/generic_design.html @@ -0,0 +1,149 @@ +<html>
+<head>
+<title>syslogd generic design</title>
+</head>
+<body>
+<h2>Generic design of a syslogd</h2>
+<p>Written 2007-04-10 by
+<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a></p>
+<p>The text below describes a generic approach on how a syslogd can be
+implemented. I created this description for some other project, where it was not
+used. Instead of throwing it away, I thought it would be a good addition to the
+rsyslog documentation. While rsyslog differs in details from the description
+below, it is sufficiently close to it. Further development of rsyslog will
+probably match it even closer to the description.</p>
+<p>If you intend to read the rsyslog source code, I recommend reading this
+document here first. You will not find the same names and not all of the
+concepts inside rsyslog. However, I think your understanding will benefit from
+knowing the generic architecture.</p>
+<font size="3"><pre>
+
+ +-----------------+
+ | "remote" PLOrig |
+ +-----------------+
+ |
+ I +--------+-----+-----+ +-----+-------+------+-----+
+ P | PLOrig | GWI | ... | | GWO | Store | Disc | ... |
+ C +--------+-----+-----+ +-----+-------+------+-----+
+ | | ^
+ v v |
+ +--------------+ +------------+ +--------------+
+ | PLGenerator | | RelayEng | | CollectorEng |
+ +--------------+ +------------+ +--------------+
+ | ^ ^
+ | | |
+ v v |
+ +-------------+ +------------+ +--------------+
+ | PLG Ext | | RelEng Ext | | CollcEng Ext |
+ +-------------+ +------------+ +--------------+
+ | ^ ^
+ | | |
+ v v |
+ +--------------------------------------------------------------+
+ | Message Router |
+ +--------------------------------------------------------------+
+ | ^
+ v |
+ +--------------------------------------------------------------+
+ | Message CoDec (e.g. RFC 3164, RFCYYYY) |
+ +--------------------------------------------------------------+
+ | ^
+ v |
+ +---------------------+-----------------------+----------------+
+ | transport UDP | transport TLS | ... |
+ +---------------------+-----------------------+----------------+
+
+ Generic Syslog Application Architecture
+</pre></font>
+<ul>
+ <li>A "syslog application" is an application whose purpose is the
+processing of syslog messages. It may be part of a larger
+application with a broader purpose. An example: a database
+application might come with its own syslog send subsystem and not
+go through a central syslog application. In the sense of this
+document, that application is called a "syslog application" even
+though a casual observer might correctly call it a database
+application and may not even know that it supports sending of
+syslog messages.</li>
+ <li>Payload is the information that is to be conveyed. Payload by
+itself may have any format and is totally independent from to
+format specified in this document. The "Message CoDec" of the
+syslog application will bring it into the required format.</li>
+ <li>Payload Originators ("PLOrig") are the original creators of payload.
+Typically, these are application programs.</li>
+ <li>A "Remote PLOrig" is a payload originator residing in a different
+application than the syslog application itself. That application
+may reside on a different machine and may talk to the syslog
+application via RPC.</li>
+ <li>A "PLOrig" is a payload originator residing within the syslog
+application itself. Typically, this PLOrig emits syslog
+application startup, shutdown, error and status log messages.</li>
+ <li>A "GWI" is a inbound gateway. For example, a SNMP-to-syslog
+gateway may receive SNMP messages and translate them into syslog.</li>
+ <li>The ellipsis after "GWI" indicates that there are potentially a
+variety of different other ways to originally generate payload.</li>
+ <li>A "PLGenerator" is a payload generator. It takes the information
+from the payload-generating source and integrates it into the
+syslog subsystem of the application. This is a highly theoretical
+concept. In practice, there may not actually be any such
+component. Instead, the payload generators (or other parts like
+the GWI) may talk directly to the syslog subsystem. Conceptually,
+the "PLGenerator" is the first component where the information is
+actually syslog content.</li>
+ <li>A "PLG Ext" is a payload generator extension. It is used to
+modify the syslog information. An example of a "PLG Ext" might be
+the addition of cryptographic signatures to the syslog
+information.</li>
+ <li>A "Message Router" is a component that accepts in- and outbound
+syslog information and routes it to the proper next destination
+inside the syslog application. The routing information itself is
+expected to be learnt by operator configuration.</li>
+ <li>A "Message CoDec" is the message encoder/decoder. The encoder
+takes syslog information and encodes them into the required format<br>for a syslog message. The decoder takes a syslog message and
+decodes it into syslog information. Codecs for multiple syslog
+formats may be present inside a single syslog application.</li>
+ <li>A transport (UDP, TLS, yet-to-be-defined ones) sends and receives
+syslog messages. Multiple transports may be used by a single<br>syslog application at the same time. A single transport instance
+may be used for both sending and receiving. Alternatively, a
+single instance might be used for sending and receiving
+exclusively. Multiple instances may be used for different
+listener ports and receivers.</li>
+ <li>A "RelayEng" is the relaying engine. It provides functionality
+necessary for receiving syslog information and sending it to
+another syslog application.</li>
+ <li>A "RelEng Ext" is an extension that processes syslog information
+as it enters or exits a RelayEng. An example of such a component
+might be a relay cryptographically signing received syslog
+messages. Such a function might be useful to guarantee authenticity
+starting from a given point inside a relay chain.</li>
+ <li>A "CollectorEng" is a collector engine. At this component, syslog
+information leaves the syslog system and is translated into some
+other form. After the CollectorEng, the information is no longer
+defined to be of native syslog type.</li>
+ <li>A "CollcEng Ext" is a collector engine extension. It modifies
+syslog information before it is passed on to the CollectorEng. An
+example for this might be the verification of cryptographically
+signed syslog message information. Please note that another
+implementation approach would be to do the verification outside of
+the syslog application or in a stage after "CollectorEng".</li>
+ <li>A "GWO" is an outbound gateway. An example of this might be the
+forwarding of syslog information via SNMP or SMTP. Please note
+that when a GWO directly connects to a GWI on a different syslog
+application, no native exchange of syslog information takes place.
+Instead, the native protocol of these gateways (e.g. SNMP) is
+used. The syslog information is embedded inside that protocol.
+Depending on protocol and gateway implementation, some of the
+native syslog information might be lost.</li>
+ <li>A "Store" is any way to persistently store the extracted syslog
+information, e.g. to the file system or to a data base.</li>
+ <li>"Disc" means the discarding of messages. Operators often find it
+useful to discard noise messages and so most syslog applications<br>contain a way to do that.</li>
+ <li>The ellipsis after "Disc" indicates that there are potentially a variety of different other ways to consume syslog information.</li>
+ <li>There may be multiple instances of each of the described
+components in a single syslog application.</li>
+ <li>A syslog application is made up of all or some of the above
+mentioned components.</li>
+</ul>
+</p>
+</body>
+</html>
diff --git a/doc/gssapi.html b/doc/gssapi.html new file mode 100644 index 00000000..3ad7d07b --- /dev/null +++ b/doc/gssapi.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>GSSAPI module support in rsyslog v3</title> + +</head> +<body> +<h1>GSSAPI module support in rsyslog v3</h1> +<p style="font-weight: bold;">What is it good for.</p> +<ul style="margin-left: 1.25cm;"> +<li> +client-serverauthentication </li> +<li> +Log +messages encryption </li> +</ul> +<p class="P5"> </p> +<p class="P3"><span style="font-weight: bold;">Requirements.</span> +</p> +<ul> +<li>Kerberos infrastructure</li> +<li>rsyslog, rsyslog-gssapi</li> +</ul> +<p> </p> +<p><span style="font-weight: bold;">Configuration.</span> +</p> +<p>Let's assume there are 3 machines in kerberos Realm: </p> +<ul> +<li>the +first is running KDC (Kerberos Authentication Service and Key +Distribution Center),</li> +<li>the second is a client sending its logs to the server,</li> +<li>the third is receiver, gathering all logs.</li> +</ul> +<p class="P7"> </p> +<p class="P10"><span style="font-style: italic;">1. +KDC:</span> </p> +<ul> +<li>Kerberos +database must be properly set-up on KDC machine first. Use +kadmin/kadmin.local to do that. Two principals need to be add in our +case:</li> +</ul> +<ol style="margin-left: 1.25cm; list-style-type: decimal;"> +<li> +<p>sender@REALM.ORG +</p> +</li> +</ol> +<ul> +<li>client must have ticket for pricipal sender</li> +<li>REALM.ORG is kerberos Realm</li> +</ul> +<ol style="margin-left: 1.25cm; list-style-type: decimal;"> +<li>host/receiver.mydomain.com@REALM.ORG - service principal</li> +</ol> +<ul> +<li>Use ktadd to export service principal and transfer it to +/etc/krb5.keytab +on receiver </li> +</ul> +<p><span style="font-style: italic;">2. CLIENT:</span> +</p> +<ul> +<li>set-up rsyslog, in /etc/rsyslog.conf</li> +<li>$ModLoad omgssapi - load output gss module </li> +<li>$GSSForwardServiceName +otherThanHost - set the name of service principal, "host" is the +default one</li> +<li>*.* :omgssapi:receiver.mydomain.com - action line, forward +logs to receiver</li> +<li>kinit root - get the TGT ticket</li> +<li>service rsyslog start +<p class="P14" style="margin-left: 0.25cm;"> </p> +</li> +</ul> +<p><span style="font-style: italic;">3. SERVER:</span> +</p> +<ul> +<li class="P14" style="margin-left: 0cm;"> +<p class="P14" style="margin-left: 0.25cm;">set-up +rsyslog, in /etc/rsyslog.conf </p> +</li> +<li class="P16"> +<p class="P16" style="margin-left: 0.25cm;">$ModLoad +<a href="imgssapi.html">imgssapi</a> - load input gss module </p> +</li> +<li class="P16"> +<p class="P16" style="margin-left: 0.25cm;">$InputGSSServerServiceName +otherThanHost - set the name of service principal, "host" is the +default one </p> +</li> +<li class="P16"> +<p class="P16" style="margin-left: 0.25cm;">$InputGSSServerPermitPlainTCP +on - accept GSS and TCP connections (not authenticated senders), off by +default </p> +</li> +<li class="P16"> +<p class="P16" style="margin-left: 0.25cm;">$InputGSSServerRun +514 - run server on port </p> +</li> +<li class="P14" style="margin-left: 0cm;"> +<p class="P14" style="margin-left: 0.25cm;">service +rsyslog start </p> +</li> +</ul> +<span style="font-weight: bold;">The picture demonstrate +how things work.</span> +<p class="P18"> </p> +<img src="gssapi.png" alt="rsyslog gssapi support"> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/gssapi.png b/doc/gssapi.png Binary files differnew file mode 100644 index 00000000..c82baa52 --- /dev/null +++ b/doc/gssapi.png diff --git a/doc/highperf.txt b/doc/highperf.txt new file mode 100644 index 00000000..5f9481e1 --- /dev/null +++ b/doc/highperf.txt @@ -0,0 +1,4 @@ +links to high performance papers: + +- http://www.kegel.com/c10k.html +- http://pl.atyp.us/content/tech/servers.html (**) diff --git a/doc/history.html b/doc/history.html new file mode 100644 index 00000000..57b64004 --- /dev/null +++ b/doc/history.html @@ -0,0 +1,147 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>rsyslog history</title></head> +<body> +<h1>RSyslog - History</h1> + +<b>Rsyslog is a GPL-ed, enhanced syslogd. Among others, it offers support for +reliable syslog over TCP, writing to +MySQL databases and fully configurable output formats (including great timestamps).</b> +Rsyslog was initiated by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a>. +If you are interested to learn why Rainer initiated the project, you +may want to read his blog posting on "<a href="http://rgerhards.blogspot.com/2007/08/why-does-world-need-another-syslogd.html">why +the world needs another syslogd</a>".<p>Rsyslog has +been forked in <b>2004</b> from the <a href="http://www.infodrom.org/projects/sysklogd/">sysklogd standard package</a>. +The goal of the +rsyslog project is to provide a feature-richer and reliable +syslog daemon while retaining drop-in replacement capabilities to stock +syslogd. By "reliable", we mean support for reliable transmission +modes like TCP or <a href="http://www.monitorware.com/Common/en/glossary/rfc3195.php">RFC 3195</a> +(syslog-reliable). We do NOT imply that the sysklogd package is unreliable.</p> +<p>The name "rsyslog" stems back to the +planned support for syslog-reliable. Ironically, the initial release +of rsyslog did NEITHER support syslog-reliable NOR tcp based syslog. +Instead, it contained enhanced configurability and other enhancements +(like database support). The reason for this is that full support for +RFC 3195 would require even more changes and especially fundamental +architectural +changes. Also, questions asked on the loganalysis list and at other +places indicated that RFC3195 is NOT a prime priority for users, but +rather better control over the output format. So there we were, with +a rsyslogd that covers a lot of enhancements, but not a single one +of these that made its name ;) Since version 0.9.2, receiving syslog +messages via plain tcp is finally supported, a bit later sending via +TCP, too. Starting with 1.11.0, RFC 3195 is finally supported at the +receiving side (a.k.a. "listener"). Support for sending via RFC 3195 is +still due. Anyhow, rsyslog has come much closer to what it name +promises.</p> +<p> +The database support was initially included so that our web-based syslog +interface could be used. This is another open source project which can be found +under <a href="http://www.phplogcon.org">http://www.phplogcon.org</a>. We highly recommend having a look at +it. It might not work for you if you expect thousands of messages per +second (because your database won't be able to provide adequate performance), +but in many cases it is a very handy analysis and troubleshooting tool. + +In the mean time, of course, lots of people have found many applications for +writing to databases, so the prime focus is no longer on phpLogcon. + +</p> +<p>Rsyslogd supports an enhanced syslog.conf file format, and also works +with the standard syslog.conf. In theory, it should be possible to simply replace +the syslogd binary with the one that comes with rsyslog. Of course, in order +to use any of the new features, you must re-write your syslog.conf. To learn +how to do this, please review our commented <a href="sample.conf.php">sample.conf</a> +file. It outlines the enhancements over stock syslogd. Discussion has often +arisen of whether having an "old syslogd" logfile format is good or evil. So +far, this has not been solved (but Rainer likes the idea of a new format), so we +need to live with it for the time being. It is planned to be reconsidered in the +3.x release time frame. +</p><p>If you are interested in the <a href="http://en.wikipedia.org/wiki/IHE">IHE</a> +environment, you might be interested to hear that rsyslog supports message with +sizes of 32k and more. This feature has been tested, but by default is turned off +(as it has some memory footprint that we didn't want to put on users not +actually requiring it). Search the file syslogd.c and search for "IHE" - you +will find easy and precise instructions on what you need to change (it's just +one line of code!). Please note that RFC 3195/COOKED supports 1K message sizes +only. It'll probably support longer messages in the future, but it is our +believe that using larger messages with current RFC 3195 is a violation of the +standard.</p><p>In <b>February 2007</b>, 1.13.1 was released and served for quite a +while as a stable reference. Unfortunately, it was not later released as stable, +so the stable build became quite outdated.</p><p>In <b>June 2007</b>, Peter Vrabec from Red Hat helped us to create +RPM files for Fedora as well as supporting IPv6. There also seemed to be some +interest from the Red Hat community. This interest and new ideas resulted in a +very busy time with many great additions.</p><p>In <b>July 2007</b>, Andrew +Pantyukhin added BSD ports files for rsyslog and liblogging. We were strongly +encouraged by this too. It looks like rsyslog is getting more and more momentum. +Let's see what comes next...</p><p>Also in <b>July 2007</b> (and beginning of +August), Rainer remodeled the output part of rsyslog. It got a clean object model +and is now prepared for a plug-in architecture. During that time, some base +ideas for the overall new object model appeared.</p><p>In <b>August 2007</b> +community involvement grew more and more. Also, more packages appeared. We were +quite happy about that. To facilitate user contributions, we set up a +<a href="http://wiki.rsyslog.com/">wiki</a> on August 10th, 2007. Also in August +2007, rsyslog 1.18.2 appeared, which is deemed to be quite close to the final +2.0.0 release. With its appearance, the pace of changes was deliberately reduced, +in order to allow it to mature (see Rainers's +<a href="http://rgerhards.blogspot.com/2007/07/pace-of-changes-in-rsyslog.html"> +blog post</a> on this topic, written a bit early, but covering the essence).</p><p> +In <b>November 2007</b>, rsyslog became the default syslogd in Fedora 8. +Obviously, that was something we *really* liked. Community involvement also is +still growing. There is one sad thing to note: ever since summer, there is an +extremely hard to find segfault bug. It happens on very rare occasions only and +never in lab. We are hunting this bug for month now, but still could not get +hold of it. Unfortunately, this also affects the new features schedule. It makes +limited sense to implement new features if problems with existing ones are not +really understood.</p><p><b>December 2007</b> showed the appearance of a postgres +output module, contributed by sur5r. With 1.20.0, December is also the first +time since the bug hunt that we introduce other new features. It has been decided +that we carefully will add features in order to not affect the overall project +by these rare bugs. Still, the bug hunt is top priority, but we need to have more +data to analyze. At then end of December, it looked like the bug was found (a +race condition), but further confirmation from the field is required before +declaring victory. December also brings the initial development on <b>rsyslog v3</b>, +resulting in loadable input modules, now running on a separate thread each.</p><p>On +<b>January, 2nd 2008</b>, rsyslog 1.21.2 is re-released as rsyslog v2.0.0 +stable. This is a major milestone as far as the stable build is concerned. v3 is +not yet officially announced. Other than the stable v2 build, v3 will not be +backwards compatibile (including missing compatibility to stock sysklogd) for +quite a while. Config file changes are required and some command line options do +no longer work due to the new design.</p><p>On <span style="font-weight: bold;">January, 31st 2008</span> +the new massively-multithreaded queue engine was released for the first +time. It was a major milestone, implementing a feature I dreamed of for +more than a year.</p><p>End of <span style="font-weight: bold;">February 2008</span> +saw the first note about RainerScript, a way to configure rsyslogd via +a script-language like configuration format. This effort evolved out of +the need to have complex expression support, which was also the first +use case. On February, 28th rsyslog 3.12.0 was released, the first +version to contain expression support. This also meant that rsyslog +from that date on supported all syslog-ng major features, but had a +number of major features exlusive to it. With 3.12.0, I consider +rsyslog fully superior to syslog-ng (except for platform support).</p> + +<p>Following the Fedora Developer's conference in Brno <b>2012</b>, rsyslog +got very serious on implementing <b>structured logging</b> in +project Lumberjack (CEE) style. Project Lumberjack was a much broader +effort and brought closer collaboration with the syslog-ng folks, which +helped to maintain and improve interoperability. In the +<b>late winter/spring/summer 2012</b> timeframe numerous engine enhancements +were made and plugins written (among them the first "official" interfaces +to the Linux audit subsystem). At the end of the year, this culminated in the +rsyslog 7, which not only implemented Lumberjack but also was the first one +to support full condition nesting in rsyslog.conf (and a ton of other features as +well). + +<p>In <b>spring 2013</b> major new security features were engineered, +namely anonymization support, as well as log file signing and +encryption capabilities. + +<p>Be sure to visit Rainer's <a href="http://rgerhards.blogspot.com/">syslog blog</a> +to get some more insight into the development and futures of rsyslog and syslog in general. +Don't be shy to post to either the blog or the +<a href="http://www.rsyslog.com/PNphpBB2.phtml">rsyslog forums</a>.</p> +<h2>Some useful links</h2> +<ul> + <li><a href="http://www.rsyslog.com/Topic4.phtml">the rsyslog change log</a></li> +</ul> +</body></html> diff --git a/doc/how2help.html b/doc/how2help.html new file mode 100644 index 00000000..7fda6949 --- /dev/null +++ b/doc/how2help.html @@ -0,0 +1,60 @@ +<html> +<head> +<title>How you can Help</title> +</head> +<body> +<h2>How you can Help</h2> +<p><b>You like rsyslog and would like to lend us a helping hand?</b> This page +tells you how easy it is to help a little bit. You can contribute to the project +even with a single mouse click! If you could pick a single item from the +wish list, that would be awfully helpful!</p> +<p>This is our wish list:</p> +<ul> + <li>let others know how great rsyslog is<ul> + <li>spread word about rsyslog in forums and newsgroups</li> + <li>place a link to <a href="http://www.rsyslog.com">www.rsyslog.com</a> + from your home page</li> + <li>you may also want to tell others about the + <a href="http://loganalyzer.adiscon.com">log analyzer tool + created by the same folks as rsyslog</a> - at least, if you like it ;) + </ul> + </li> + <li>let us know about rsyslog - we are eager for feedback<ul> + <li>tell us what you like and what you not like - so that we can include + that into development</li> + <li>tell us what you use rsyslog for - especially if you have high + traffic volume or an otherwise "uncommon" deployment. We are looking for + case studies and experience how rsyslog performs in unusual scenarios.</li> + <li>allow us to post your thoughts and experiences as a "user story" on + the web site (so far, none are there ;))</li> + </ul> + </li> + <li>if you know how to create packages (rpm, deb, ...)<ul> + <li>we would very much appreciate your help with package creation. We know + that it is important to have good binary packages for a product to + spread widely. Yet, we do not have the knowledge to do it all ourselves. + <a href="mailto:rgerhards@adiscon.com">Drop Rainer a note </a>if you + could help us out.</li> + </ul> + </li> + <li>if you have configured a device for sending syslog data, and that device + is not in our + <a href="http://www.monitorware.com/en/syslog-enabled-products/">syslog + configuration database</a>, you might want to tell us how to configure it.</li> + <li>if you are a corporate user<ul> + <li>you might consider <a href="http://www.adiscon.com">Adiscon</a>'s + commercial <a href="http://www.monitorware.com/">MonitorWare products</a> + for Windows, e.g. to deliver Windows Event Log data to rsyslogd (sales + of the commercial products funds the open source development - and they + also work very well).</li> + <li>you might be interested in + <a href="http://www.adiscon.com/Common/en/Products/techsup.php"> + purchasing professional support or add-on development</a> for rsyslog</li> + </ul> + </li> +</ul> +<p><b>We appreciate your help very much.</b> A big thank you for anything you +might do!</p> + +</body> +</html> diff --git a/doc/im3195.html b/doc/im3195.html new file mode 100644 index 00000000..aad9f3d1 --- /dev/null +++ b/doc/im3195.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>RFC3195 Input Module (im3195)</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>RFC3195 Input Module</h1> +<p><b>Module Name: im3195</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>Receives syslog messages via RFC 3195. The RAW profile is fully implemented and the +COOKED profile is provided in an experimental state. This module uses +<a href="http://www.liblogging.org">liblogging</a> for the actual protocol handling.</p> +<p><b>Configuration Directives</b>:</p> +<ul> +<li><strong>$Input3195ListenPort <port></strong><br> +The port on which imklog listens for RFC 3195 messages. The default port is 601 +(the IANA-assigned port)</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>Due to no demand at all for RFC3195, we have converted rfc3195d +to this input module, but we have NOT conducted any testing. Also, +the module does not yet properly handle the recovery case. If someone +intends to put this module into production, good testing should be +cunducted. It also is a good idea to notify the rsyslog project that you intend to use +it in production. In this case, we'll probably give the module another +cleanup. We don't do this now because so far it looks just like a big +waste of time. +<p>Currently only a single listener can be defined. That one binds to all interfaces.</p> +<p><b>Sample:</b></p> +<p>The following sample accepts syslog messages via RFC 3195 on port 1601. +<br> +</p> +<textarea rows="15" cols="60">$ModLoad im3195 +$Input3195ListenPort 1601 +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imfile.html b/doc/imfile.html new file mode 100644 index 00000000..274d6e60 --- /dev/null +++ b/doc/imfile.html @@ -0,0 +1,240 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>Text File Input Monitor</title></head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Text File Input Module</h1> +<p><b>Module Name: imfile</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>Provides the ability to convert any standard text file into +a syslog message. A standard +text file is a file consisting of printable characters with lines +being delimited by LF.</p> +<p>The file is read line-by-line and any line read is passed to +rsyslog's rule engine. The rule engine applies filter conditons and +selects which actions needs to be carried out. Empty lines are <b>not</b> +processed, as they would result in empty syslog records. They are simply +ignored.</p> +<p>As new lines are written they are taken from the file and +processed. Please note that this happens based on a polling interval +and not immediately. The file monitor support file rotation. To fully +work, rsyslogd must run while the file is rotated. Then, any remaining +lines from the old file are read and processed and when done with that, +the new file is being processed from the beginning. If rsyslogd is +stopped during rotation, the new file is read, but any not-yet-reported +lines from the previous file can no longer be obtained.</p> +<p>When rsyslogd is stopped while monitoring a text file, it +records the last processed location and continues to work from there +upon restart. So no data is lost during a restart (except, as noted +above, if the file is rotated just in this very moment).</p> +<p>Currently, the file must have a fixed name and location +(directory). It is planned to add support for dynamically generating +file names in the future.</p> +<p>Multiple files may be monitored by specifying +$InputRunFileMonitor multiple times. +</p> + +<p><b>Configuration Directives</b>:</p> +<p><b>Module Directives</b></p> +<ul> +<li><span style="font-weight: bold;">PollingInterval +seconds</span><br> +This is a global setting. It specifies how often files are to be polled +for new data. The time specified is in seconds. The <span style="font-weight: bold;">default value</span> is 10 +seconds. Please note that future +releases of imfile may support per-file polling intervals, but +currently this is not the case. If multiple PollingInterval +statements are present in rsyslog.conf, only the last one is used.<br> +A short poll interval provides more rapid message forwarding, but +requires more system ressources. While it is possible, we stongly +recommend not to set the polling interval to 0 seconds. That will make +rsyslogd become a CPU hog, taking up considerable ressources. It is +supported, however, for the few very unusual situations where this +level may be needed. Even if you need quick response, 1 seconds should +be well enough. Please note that imfile keeps reading files as long as +there is any data in them. So a "polling sleep" will only happen when +nothing is left to be processed.</li> +</ul> + +<p><b>Action Directives</b></p> +<ul> +<li><strong>File /path/to/file</strong><br> +The file being monitored. So far, this must be an absolute name (no +macros or templates)</li> +<li><span style="font-weight: bold;">Tag +tag:</span><br> +The tag to be used for messages that originate from this file. If you +would like to see the colon after the tag, you need to specify it here +(as shown above).</li> +<li><span style="font-weight: bold;">StateFile +<name-of-state-file></span><br> +Rsyslog must keep track of which parts of the to be monitored file it +already processed. This is done in the state file. This file always is +created in the rsyslog working directory (configurable via +$WorkDirectory). Be careful to use unique names for different files +being monitored. If there are duplicates, all sorts of "interesting" +things may happen. Rsyslog currently does not check if a name is +specified multiple times. +Note that when $WorkDirectory is not set or set to a non-writable +location, the state file will not be generated.</li> +<li><span style="font-weight: bold;">Facility +facility</span><br> +The syslog facility to be assigned to lines read. Can be specified in +textual form (e.g. "local0", "local1", ...) or as numbers (e.g. 128 for +"local0"). Textual form is suggested. <span style="font-weight: bold;">Default</span> is +"local0".<span style="font-weight: bold;"></span></li> +<li><span style="font-weight: bold;">Severity</span><br> +The +syslog severity to be assigned to lines read. Can be specified in +textual form (e.g. "info", "warning", ...) or as numbers (e.g. 4 for +"info"). Textual form is suggested. <span style="font-weight: bold;">Default</span> +is "notice".</li> +<li><b>PersistStateInterval</b> [lines]</b><br> +Specifies how often the state file shall be written when processing the input +file. The default value is 0, which means a new state file is only written when +the monitored files is being closed (end of rsyslogd execution). Any other +value n means that the state file is written every time n file lines have +been processed. This setting can be used to guard against message duplication due +to fatal errors (like power fail). Note that this setting affects imfile +performance, especially when set to a low value. Frequently writing the state +file is very time consuming. +<li><b>ReadMode</b> [mode]</b><br> +This mode should defined when having multiline messages. The value can range from 0-2 and determines the multiline detection method. +<br>0 (default) - line based (Each line is a new message) +<br>1 - paragraph (There is a blank line between log messages) +<br>2 - indented (New log messages start at the beginning of a line. If a line starts with a space it is part of the log message before it) +<li><b>MaxLinesAtOnce</b> [number]</b> +<br> +This is useful if multiple files need to be monitored. If set to 0, each file +will be fully processed and then processing switches to the next file +(this was the default in previous versions). If it is set, a maximum of +[number] lines is processed in sequence for each file, and then the file is +switched. This provides a kind of mutiplexing the load of multiple files and +probably leads to a more natural distribution of events when multiple busy files +are monitored. The default is 1024. +<li><b>MaxSubmitAtOnce</b> [number]</b> +<br> +This is an expert option. It can be used to set the maximum input batch size that +imfile can generate. The default is 1024, which is suitable for a wide range of +applications. Be sure to understand rsyslog message batch processing before you +modify this option. If you do not know what this doc here talks about, this is a +good indication that you should NOT modify the default. +<li><b>Ruleset</b> <ruleset> +Binds the listener to a specific <a href="multi_ruleset.html">ruleset</a>.</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>So far, only 100 files can be monitored. If more are needed, +the source needs to be patched. See define MAX_INPUT_FILES in imfile.c</p><p>Powertop +users may want to notice that imfile utilizes polling. Thus, it is no +good citizen when it comes to conserving system power consumption. We +are currently evaluating to move to inotify(). However, there are a +number of subtle issues, which needs to be worked out first. We will +make the change as soon as we can. If you can afford it, we recommend +using a long polling interval in the mean time. +</p> +<p><b>Sample:</b></p> +<p>The following sample monitors two files. If you need just one, +remove the second one. If you need more, add them according to the +sample ;). This code must be placed in /etc/rsyslog.conf (or wherever +your distro puts rsyslog's config files). Note that only commands +actually needed need to be specified. The second file uses less +commands and uses defaults instead.<br> +</p> +<textarea rows="15" cols="60">module(load="imfile" PollingInterval="10") #needs to be done just once +# File 1 +input(type="imfile" File="/path/to/file1" + Tag="tag1" + StateFile="/var/spool/rsyslog/statefile1" + Severity="error" + Facility="local7") +# File 2 +input(type="imfile" File="/path/to/file2" + Tag="tag2" + StateFile="/var/spool/rsyslog/statefile2") +# ... and so on ... +# +</textarea> + + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li><strong>$InputFileName /path/to/file</strong><br> +equivalent to: File </li> +<li><span style="font-weight: bold;">$InputFileTag +tag:</span><br> +equivalent to: Tag </li> +<li><span style="font-weight: bold;">$InputFileStateFile +<name-of-state-file></span><br> +equivalent to: StateFile </li> +<li><span style="font-weight: bold;">$InputFileFacility +facility</span><br> +equivalent to: Facility </span></li> +<li><span style="font-weight: bold;">$InputFileSeverity</span><br> +equivalent to: Severity</li> +<li><span style="font-weight: bold;">$InputRunFileMonitor</span><br> +This <span style="font-weight: bold;">activates</span> +the current monitor. It has no parameters. If you forget this +directive, no file monitoring will take place.</li> +<li><span style="font-weight: bold;">$InputFilePollInterval +seconds</span><br> +equivalent to: PollingInterva</li> +<li><b>$InputFilePersistStateInterval</b> [lines]</b><br> +Available in 4.7.3+, 5.6.2+<br> +equivalent to: PersistStateInterval +<li><b>$InputFileReadMode</b> [mode]</b><br> +Available in 5.7.5+<br> +equivalent to: ReadMode +<li><b>$InputFileMaxLinesAtOnce</b> [number]</b><br> +Available in 5.9.0+<br> +equivalent to: MaxLinesAtOnce +<li>$InputFileBindRuleset <ruleset><br> +Available in 5.7.5+, 6.1.5+<br> +equivalent to: Ruleset </li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>So far, only 100 files can be monitored. If more are needed, +the source needs to be patched. See define MAX_INPUT_FILES in imfile.c</p><p>Powertop +users may want to notice that imfile utilizes polling. Thus, it is no +good citizen when it comes to conserving system power consumption. We +are currently evaluating to move to inotify(). However, there are a +number of subtle issues, which needs to be worked out first. We will +make the change as soon as we can. If you can afford it, we recommend +using a long polling interval in the mean time. +</p> +<p><b>Sample:</b></p> +<p>The following sample monitors two files. If you need just one, +remove the second one. If you need more, add them according to the +sample ;). This code must be placed in /etc/rsyslog.conf (or wherever +your distro puts rsyslog's config files). Note that only commands +actually needed need to be specified. The second file uses less +commands and uses defaults instead.<br> +</p> +<textarea rows="15" cols="60">$ModLoad imfile # needs to be done just once +# File 1 +$InputFileName /path/to/file1 +$InputFileTag tag1: +$InputFileStateFile stat-file1 +$InputFileSeverity error +$InputFileFacility local7 +$InputRunFileMonitor +# File 2 +$InputFileName /path/to/file2 +$InputFileTag tag2: +$InputFileStateFile stat-file2 +$InputRunFileMonitor +# ... and so on ... +# +# check for new lines every 10 seconds +$InputFilePollingInterval 10 +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and <a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imgssapi.html b/doc/imgssapi.html new file mode 100644 index 00000000..dd90fec7 --- /dev/null +++ b/doc/imgssapi.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>GSSAPI Syslog Input Module</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>GSSAPI Syslog Input Module</h1> +<p><b>Module Name: imgssapi</b></p> +<p><b>Author: </b>varmojfekoj</p> +<p><b>Description</b>:</p> +<p>Provides the ability to receive syslog messages from the +network protected via Kerberos 5 encryption and authentication. This +module also accept plain tcp syslog messages on the same port if configured to do so. If you need just plain tcp, use <a href="imtcp.html">imtcp</a> instead.</p> +<p>There is also an <a href="gssapi.html">overview of gssapi support in rsyslog</a> available. We recommend reading +it before digging into the configuration parameters.</p> +<p><b>Configuration Directives</b>:</p> +<ul> +<li>InputGSSServerRun <port><br> +Starts a GSSAPI server on selected port - note that this runs +independently from the TCP server.</li> +<li>InputGSSServerServiceName <name><br> +The service name to use for the GSS server.</li> +<li>$InputGSSServerPermitPlainTCP on|<span style="font-weight: bold;">off</span><br> +Permits the server to receive plain tcp syslog (without GSS) on the +same port</li> +<li>$InputGSSServerMaxSessions <number><br> +Sets the maximum number of sessions supported</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>module always binds to all interfaces</li> +<li>only a single listener can be bound</li> + +</ul> +<p><b>Sample:</b></p> +<p>This sets up a GSS server on port 1514 that also permits to +receive plain tcp syslog messages (on the same port):<br> +</p> +<textarea rows="15" cols="60">$ModLoad imgssapi # needs to be done just once +$InputGSSServerRun 1514 +$InputGSSServerPermitPlainTCP on +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2011 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imjournal.html b/doc/imjournal.html new file mode 100644 index 00000000..28b47014 --- /dev/null +++ b/doc/imjournal.html @@ -0,0 +1,111 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>Systemd Journal Input Module</title></head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Systemd Journal Input Module</h1> +<p><b>Module Name: imjournal</b></p> +<p><b>Author: </b>Milan Bartos +<mbartos@redhat.com></p> +<p><b>Description</b>:</p> +<p>Provides the ability to import structured log messages from systemd journal +to syslog.</p> +<p>Note that this module reads the journal database, what is considered a +relativly performance-intense operation. As such, the performance of a +configuration utilizing this +module may be notably slower then when using +<a href="imuxsock.html">imuxsock</a>. The journal provides imuxsock with a +copy of all "classical" syslog messages, however, it does not provide +structured data. If the latter is needed, imjournal must be used. Otherwise, +imjournal may be simply replaced by imuxsock. +<p>We suggest to check out our short presentation on +<a href="http://youtu.be/GTS7EuSdFKE">rsyslog journal integration</a> to +learn more details of anticipated use cases. + +<p><b>Warning:</b> Some versions of systemd journal have problems with database +corruption, which leads to the journal to return the same data endlessly +in a thight loop. This results in massive message duplication inside rsyslog +probably resulting in a denial-of-service when the system ressouces get +exhausted. This can be somewhat mitigated by using proper rate-limiters, but +even then there are spikes of old data which are endlessly repeated. By default, +ratelimiting is activated and permits to process 20,000 messages within 10 +seconds, what should be well enough for most use cases. If insufficient, use +the parameters described below to adjust the permitted volume. +<b>It is strongly recommended to use this plugin only if there +is hard need to do so.</b> + +<p><b>Configuration Directives</b>:</p> +<p><b>Module Directives</b></p> +<ul> +<li><b>PersistStateInterval</b> number-of-messages<br> +This is a global setting. It specifies how often should the journal state be persisted. +The persists happens after each <i>number-of-messages</i>. +This option is useful for rsyslog to start reding from the last journal message it read. + +<li><b>StateFile</b> /path/to/file<br> +This is a global setting. It specifies where the state file for persisting +journal state is located. + +<li><b>ratelimit.interval</b> seconds (default: 600)<br> +Specifies the interval in seconds onto which rate-limiting is to be applied. +If more than ratelimit.burst messages are read during that interval, further +messages up to the end of the interval are discarded. The number of messages +discarded is emitted at the end of the interval (if there were any discards). +<br>Setting this to value zero turns off ratelimiting. Note that it is +<b>not recommended to turn of ratelimiting</b>, except that you know for +sure journal database entries will never be corrupted. Without ratelimiting, +a corrupted systemd journal database may cause a kind of denial of service (we +are stressing this point as multiple users have reported us such problems +with the journal database - information current as of June 2013). + +<li><b>ratelimit.burst</b> messages (default: 20000)<br> +Specifies the maximum number of messages that can be emitted within the +ratelimit.interval interval. For futher information, see description there. + +<li><b>IgnorePreviousMessages</b> [<b>off</b>/on]<br> +This option specifies whether imjournal should ignore messages currently in +journal and read only new messages. This option is only used when there is +no StateFile to avoid message loss. +</ul> + +<b>Caveats/Known Bugs:</b> +<p> +<ul> +<li>As stated above, a corrupted systemd journal database can cause major +problems, depending on what the corruption results in. This is beyond the +control of the rsyslog team. +</ul> +</p> +<p><b>Sample:</b></p> +<p> +The following example shows pulling structured imjournal messages and saving them into /var/log/ceelog. +</p> +<textarea rows="11" cols="80"> +module(load="imjournal" PersistStateInterval="100" StateFile="/path/to/file") #load imjournal module +module(load="mmjsonparse") #load mmjsonparse module for structured logs + +template(name="CEETemplate" type="string" + string="%TIMESTAMP% %HOSTNAME% %syslogtag% @cee: %$!all-json%\n" + ) #template for messages + +action(type="mmjsonparse") +action(type="omfile" file="/var/log/ceelog" template="CEETemplate") +</textarea> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li><b>$imjournalPersistStateInterval</b><br> +Equivalent to: PersistStateInterval</li> +<li><b>$imjournalStateFile</b><br> +Equivalent to: StateFile</li> +<li><b>$imjournalRatelimitInterval</b><br> +Equivalent to: ratelimit.interval</li> +<li><b>$imjournalRatelimitBurst</b><br> +Equivalent to: ratelimit.burst</li> +<li><strong>$ImjournalIgnorePreviousMessages</strong><br> +Equivalent to: ignorePreviousMessages</li> +</ul> + +</body> +</html> diff --git a/doc/imklog.html b/doc/imklog.html new file mode 100644 index 00000000..1f195b16 --- /dev/null +++ b/doc/imklog.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>Kernel Log Input Module (imklog)</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Kernel Log Input Module</h1> +<p><b>Module Name: imklog</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>Reads messages from the kernel log and submits them to the +syslog engine.</p> +<p><b>Configuration Directives</b>:</p> +<ul> +<li><strong>LogPath</strong><br> +The path to the Kernel log. This value should only be changed if you really know what +you are doing.</li> +<li><strong>InternalMsgFacility +<facility></strong><br> +The facility which messages internally generated by imklog will have. +imklog generates some messages of itself (e.g. on problems, startup and +shutdown) and these do not stem from the kernel. Historically, under +Linux, these too have "kern" facility. Thus, on Linux platforms the +default is "kern" while on others it is "syslogd". You usually do not +need to specify this configuratin directive - it is included primarily +for few limited cases where it is needed for good reason. Bottom line: +if you don't have a good idea why you should use this setting, do not +touch it.</li> +<li><b>PermitNonKernelFacility [on/<i>off</i>]</b><br> +At least under BSD the kernel log may contain entries +with non-kernel facilities. This setting controls how those are +handled. The default is "off", in which case these messages are +ignored. Switch it to on to submit non-kernel messages to rsyslog +processing.</li> +<li><b>ParseKernelTimeStamp</b> [on/<b>off</b>]<br> +If enabled and the kernel creates a timestamp for its log messages, this timestamp will be +parsed and converted into regular message time instead to use the receive time of the kernel +message (as in 5.8.x and before). Default is to not parse the kernel timestamp, because the +clock used by the kernel to create the timestamps is not supposed to be as accurate as the +monotonic clock required to convert it. Depending on the hardware and kernel, it can result +in message time differences between kernel and system messages which occurred at same time. +<li><b>KeepKernelTimeStamp</b> [on/<b>off</b>]<br> +If enabled, this option causes to keep the [timestamp] provided by the kernel at the begin +of in each message rather than to remove it, when it could be parsed and converted into +local time for use as regular message time. Only used when <b>ParseKernelTimestamp</b> is on. +<li><b>ConsoleLogLevel</b> [<i>number</i>] +(former klogd -c option) -- sets the console log level. If specified, only messages with +up to the specified level are printed to the console. The default is -1, which means that +the current settings are not modified. To get this behavior, do not specify +ConsoleLogLevel in the configuration file. Note that this is a global parameter. Each time +it is changed, the previous definition is re-set. The one activate will be that one that is +active when imklog actually starts processing. In short words: do not specify this +directive more than once! +</ul> +<b>Caveats/Known Bugs:</b> +<p>This is obviously platform specific and requires platform +drivers. +Currently, imklog functionality is available on Linux and BSD.</p> +<p>This module is <b>not supported on Solaris</b> and not needed there. +For Solaris kernel input, use <a href="imsolaris.html">imsolaris</a>.</p> +<p><b>Sample:</b></p> +<p>The following sample pulls messages from the kernel log. All +parameters are left by default, which is usually a good idea. Please +note that loading the plugin is sufficient to activate it. No directive +is needed to start pulling kernel messages.<br> +</p> +<textarea rows="4" cols="60">module(load="imklog") +</textarea> +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li><strong>$KLogInternalMsgFacility +<facility></strong><br> +equivalent to: InternalMsgFacility</li> +<li><span style="font-weight: bold;">$KLogPermitNonKernelFacility +[on/<span style="font-style: italic;">off</span>]<br> +equivalent to: PermitNonKernelFacility</li> +<li><span style="font-weight: bold;"></span>$DebugPrintKernelSymbols +[on/<b>off</b>]<br> +Linux only, ignored on other platforms (but may be specified)</li> +<li><b>$klogLocalIPIF</b> [interface name] - (available since 5.9.6) - if provided, the IP of the specified +interface (e.g. "eth0") shall be used as fromhost-ip for imklog-originating messages. +If this directive is not given OR the interface cannot be found (or has no IP address), +the default of "127.0.0.1" is used. +</li> +<li>$klogSymbolLookup [on/<b>off</b>] -- +disables imklog kernel symbol translation (former klogd -x option). NOTE that +this option is counter-productive on recent kernels (>= 2.6) because the +kernel already does the symbol translation and this option breaks the information.<br> +<b>This option is scheduled for removal, probably with version 4.x.</b> Do not use +it except if you have a very good reason. If you have one, let us know +because otherwise new versions will no longer support it.<br> +Linux only, ignored on other platforms (but may be specified)</li> +<li><b>$klogConsoleLogLevel</b> [<i>number</i>] +<br>equivalent to: ConsoleLogLevel</li> +<li><b>$klogUseSyscallInterface</b> [on/<b>off</b>] +-- former klogd -s option<br> +Linux only, ignored on other platforms (but may be specified)</li> +<li>$klogSymbolsTwice [on/<b>off</b>] -- +former klogd -2 option<br> +Linux only, ignored on other platforms (but may be specified)<br style="font-weight: bold;"> +</li> +<li><b>$klogParseKernelTimeStamp</b> [on/<b>off</b>]<br> +equivalent to: ParseKernelTimeStamp</li> +<li><b>$klogKeepKernelTimeStamp</b> [on/<b>off</b>]<br> +equivalent to: KeepKernelTimeStamp</li> +</ul> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2012 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imkmsg.html b/doc/imkmsg.html new file mode 100644 index 00000000..23b96147 --- /dev/null +++ b/doc/imkmsg.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>/dev/kmsg Log Input Module (imkmsg)</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>/dev/kmsg Log Input Module</h1> +<p><b>Module Name: imkmsg</b></p> +<p><b>Authors: </b>Rainer Gerhards +<rgerhards@adiscon.com><br /> +Milan Bartos +<mbartos@redhat.com></p> +<p><b>Description</b>:</p> +<p>Reads messages from the /dev/kmsg structured kernel log and submits them to the +syslog engine.</p> +<p> +The printk log buffer constains log records. These records are exported by /dev/kmsg +device as structured data in the following format:<br /> + "level,sequnum,timestamp;<message text>\n"<br /> +There could be continuation lines starting with space that contains key/value pairs.<br /> +<br /> +Log messages are parsed as necessary into rsyslog msg_t structure. Continuation lines are parsed +as json key/value pairs and added into rsyslog's message json representation. +</p> +<p><b>Configuration Directives</b>:</p> +<p>This module has no configuration directives.</p> +<b>Caveats/Known Bugs:</b> +<p>This module can't be used together with imklog module. When using one of them, make sure the other +one is not enabled.</p> +<p>This is Linux specific module and requires /dev/kmsg device with structured kernel logs.</p> +<p><b>Sample:</b></p> +<p>The following sample pulls messages from the /dev/kmsg log device. All +parameters are left by default, which is usually a good idea. Please +note that loading the plugin is sufficient to activate it. No directive +is needed to start pulling messages.<br> +</p> +<textarea rows="15" cols="60">$ModLoad imkmsg +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2009 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/impstats.html b/doc/impstats.html new file mode 100644 index 00000000..8db9c6f6 --- /dev/null +++ b/doc/impstats.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>Periodic Statistics of Internal Counters (impstats)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Input Module to Generate Periodic Statistics of Internal Counters</h1> +<p><b>Module Name: impstats</b></p> +<p><b>Available since: </b>5.7.0+, 6.1.1+ +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module provides periodic output of rsyslog internal counters. +Note that the whole statistics system is currently under development. So +availabilty and format of counters may change and is not yet stable (so be +prepared to change your trending scripts when you upgrade to a newer rsyslog version). +<p>The set of available counters will be output as a set of syslog messages. This +output is periodic, with the interval being configurable (default is 5 minutes). +Be sure that your configuration records the counter messages (default is syslog.=info). +Besides logging to the regular syslog stream, the module can also be configured to +write statistics data into a (local) file. +<p>Note that loading this module has impact on rsyslog performance. Depending on +settings, this impact may be noticable (for high-load environments). +<p>The rsyslog website has an updated overview of available +<a href="http://rsyslog.com/rsyslog-statistic-counter/">rsyslog statistic counters</a>. +</p> +<p><b>Module Confguration Parameters</b>:</p> +<p>This module supports module parameters, only. +<ul> + <li><strong>interval </strong>[seconds] (default 300 [5minutes])<br> + Sets the interval, in <b>seconds</b> at which messages are generated. Please note that the + actual interval may be a bit longer. We do not try to be precise and so the interval is + actually a sleep period which is entered after generating all messages. So the actual + interval is what is configured here plus the actual time required to generate messages. + In general, the difference should not really matter. + <br></li> + <li><strong>facility </strong>[templateName]<br> + The numerical syslog facility code to be used for generated messages. Default + is 5 (syslog). This is useful for filtering messages. + <br></li> + <li><strong>severity </strong>[templateName]<br> + The numerical syslog severity code to be used for generated messages. Default + is 6 (info).This is useful for filtering messages. + <br></li> + <li><strong>format </strong>[json/cee/<b>legacy</b>](rsyslog v6.3.8+ only)<br> + Specifies the format of emitted stats messages. The default of "legacy" is + compatible with pre v6-rsyslog. The other options provide support for + structured formats (note the "cee" is actually "project lumberack" logging). + <br></li> + <li><strong>log.syslog </strong>[<b>on</b>/off] - available since 7.3.6<br> + This is a boolean setting specifying if data should be sent + to the usual syslog stream. This is useful if custom formatting + or more elaborate processing is desired. However, output is placed + under the same restrictions as regular syslog data, especially in + regard to the queue position (stats data may sit for an extended + period of time in queues if they are full).<br></li> + <li><strong>log.file </strong>[file name] - available since 7.3.6<br> + If specified, statistics data is written the specified file. For + robustness, this should be a local file. The file format cannot be + customized, it consists of a date header, followed by a colon, + followed by the actual statistics record, all on one line. Only + very limited error handling is done, so if things go wrong stats + records will probably be lost. Logging to file an be a useful + alternative if for some reasons (e.g. full queues) the regular + syslog stream method shall not be used solely. Note that turning + on file logging does NOT turn of syslog logging. If that is desired + log.syslog="off" must be explicitely set. + <br></li> + +</ul> +<p><b>Legacx Configuration Directives</b>:</p> +A limited set of parameters can also be set via the legacy configuration +syntax. Note that this is intended as an upward compatibilit layer, so +newer features are intentionally <b>not</b> available via legacy directives. +<ul> +<li>$PStatInterval <Seconds> - same as the "interval" parameter. +<li>$PStatFacility <numerical facility> - same as the "facility" parameter. +<li>$PStatSeverity <numerical severity> - same as the "severity" parameter. +<li>$PStatJSON <on/<b>off</b>> (rsyslog v6.3.8+ only)<br> +If set to on, stats messages are emitted as structured cee-enhanced syslog. If +set to off, legacy format is used (which is compatible with pre v6-rsyslog). +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>This module MUST be loaded right at the top of rsyslog.conf, otherwise +stats may not get turned on in all places.</li> +</ul> +<p><b>Samples:</b></p> +<p>This activates the module and records messages to /var/log/rsyslog-stats in 10 minute intervals:<br> +</p> +<textarea rows="5" cols="60">module(load="impstats" interval="600" severity="7") + +# to actually gather the data: +syslog.=debug /var/log/rsyslog-stats +</textarea> +<p><b>Legacy Sample:</b></p> +<p>This activates the module and records messages to /var/log/rsyslog-stats in 10 minute intervals:</p> +<textarea rows="6" cols="60">$ModLoad impstats +$PStatInterval 600 +$PStatSeverity 7 + +syslog.=debug /var/log/rsyslog-stats +</textarea> +<p>In the next sample, the default interval of 5 minutes is used. However, this time +stats data is NOT emitted to the syslog stream but to a local file instead. +<p> +<textarea rows="3" cols="70">module(load="impstats" interval="600" severity="7" + log.syslog="off" /* need to turn log stream logging off! */ + log.file="/path/to/local/stats.log") +</textarea> +<p>And finally, we log to both the regular syslog log stream as well as a file. +Within the log stream, we forward the data records to another server: +<p> +<textarea rows="4" cols="70">module(load="impstats" interval="600" severity="7" + log.file="/path/to/local/stats.log") + +syslog.=debug @central.example.net +</textarea> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2013 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imptcp.html b/doc/imptcp.html new file mode 100644 index 00000000..3efcce27 --- /dev/null +++ b/doc/imptcp.html @@ -0,0 +1,171 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Plain TCP Syslog Input Module (imptcp)</title></head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Plain TCP Syslog Input Module</h1> +<p><b>Module Name: imptcp</b></p> +<p><b>Available since: </b>4.7.3+, 5.5.8+ +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>Provides the ability to receive syslog messages via plain TCP syslog. +This is a specialised input plugin tailored for high performance on Linux. It will +probably not run on any other platform. Also, it does not provide TLS services. +Encryption can be provided by using <a href="rsyslog_stunnel.html">stunnel</a>. +<p>This module has no limit on the number of listeners and sessions that can be used. +</p> + +<p><b>Configuration Directives</b>:</p> +<p>This plugin has config directives similar named as imtcp, but they all have <b>P</b>TCP in +their name instead of just TCP. Note that only a subset of the parameters are supported. +<ul> + +<p><b>Module Parameters</b>:</p> +<p>These paramters can be used with the "module()" statement. They apply +globaly to all inputs defined by the module. +<ul> +<li>Threads <number><br> +Number of helper worker threads to process incoming messages. These +threads are utilized to pull data off the network. On a busy system, additional +helper threads (but not more than there are CPUs/Cores) can help improving +performance. The default value is two. +</ul> +<p><b>Input Parameters</b>:</p> +<p>These parameters can be used with the "input()" statement. They apply to the +input they are specified with. +<ul> +<li><b>AddtlFrameDelimiter</b> <Delimiter><br> +This directive permits to specify an additional frame delimiter for plain tcp syslog. +The industry-standard specifies using the LF character as frame delimiter. Some vendors, +notable Juniper in their NetScreen products, use an invalid frame delimiter, in Juniper's +case the NUL character. This directive permits to specify the ASCII value of the delimiter +in question. Please note that this does not guarantee that all wrong implementations can +be cured with this directive. It is not even a sure fix with all versions of NetScreen, +as I suggest the NUL character is the effect of a (common) coding error and thus will +probably go away at some time in the future. But for the time being, the value 0 can +probably be used to make rsyslog handle NetScreen's invalid syslog/tcp framing. +For additional information, see this +<a href="http://kb.monitorware.com/problem-with-netscreen-log-t1652.html">forum thread</a>. +<br><b>If this doesn't work for you, please do not blame the rsyslog team. Instead file +a bug report with Juniper!</b> +<br>Note that a similar, but worse, issue exists with Cisco's IOS implementation. They do +not use any framing at all. This is confirmed from Cisco's side, but there seems to be +very limited interest in fixing this issue. This directive <b>can not</b> fix the Cisco bug. +That would require much more code changes, which I was unable to do so far. Full details +can be found at the <a href="http://www.rsyslog.com/Article321.phtml">Cisco tcp syslog anomaly</a> +page. +<li><b>SupportOctetCountedFraming</b> <<b>on</b>|off><br> +If set to "on", the legacy octed-counted framing (similar to RFC5425 framing) is +activated. This is the default and should be left unchanged until you know +very well what you do. It may be useful to turn it off, if you know this framing +is not used and some senders emit multi-line messages into the message stream. +</li> +<li><b>ServerNotifyOnConnectionClose</b> [on/<b>off</b>]<br> +instructs imptcp to emit a message if the remote peer closes a connection.<br> +<li><b>KeepAlive</b> <on/<b>off</b>><br> +enable of disable keep-alive packets at the tcp socket layer. The default is +to disable them.</li> +<li><b>KeepAlive.Probes</b> <number><br> +The number of unacknowledged probes to send before considering the connection dead and notifying the application layer. +The default, 0, means that the operating system defaults are used. This has only +effect if keep-alive is enabled. The functionality may not be available on +all platforms. +<li><b>KeepAlive.Interval</b> <number><br> +The interval between subsequential keepalive probes, regardless of what the connection has exchanged in the meantime. +The default, 0, means that the operating system defaults are used. This has only +effect if keep-alive is enabled. The functionality may not be available on +all platforms. +<li><b>KeepAlive.Time</b> <number><br> +The interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe; after the connection is marked to need keepalive, this counter is not used any further. +The default, 0, means that the operating system defaults are used. This has only +effect if keep-alive is enabled. The functionality may not be available on +all platforms. +<li><b>Port</b> <number><br> +Select a port to listen on</li> +<li><b>Name</b> <name><br> +Sets a name for the inputname property. If no name is set "imptcp" is used by default. Setting a +name is not strictly necessary, but can be useful to apply filtering based on which input +the message was received from. +<li><b>Ruleset</b> <name><br> +Binds specified ruleset to next server defined. +<li><b>Address</b> <name><br> +On multi-homed machines, specifies to which local address the listerner should be bound. +<li><b>RateLimit.Interval</b> [number] - (available since 7.3.1) specifies the rate-limiting +interval in seconds. Default value is 0, which turns off rate limiting. Set it to a number +of seconds (5 recommended) to activate rate-limiting. +</li> +<li><b>RateLimit.Burst</b> [number] - (available since 7.3.1) specifies the rate-limiting +burst in number of messages. Default is 10,000. +<li><b>compression.mode</b><i>mode</i><br> +<i>mode</i> is one of "none" or "stream:always". +It is the counterpart to the compression modes set in +<a href="omfile.html">omfile</a>. +Please see it's documentation for details. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>module always binds to all interfaces</li> +</ul> +<p><b>Sample:</b></p> +<p>This sets up a TCP server on port 514:<br> +</p> +<textarea rows="4" cols="60">module(load="/folder/to/rsyslog/plugins/imptcp/.libs/imptcp") # needs to be done just once +input(type="imptcp" port="514") +</textarea> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li>$InputPTCPServerAddtlFrameDelimiter <Delimiter><br> +Equivalent to: AddTLFrameDelimiter</li> +<li><b>$InputPTCPSupportOctetCountedFraming</b> <<b>on</b>|off><br> +Equivalent to: SupportOctetCountedFraming +</li> +<li>$InputPTCPServerNotifyOnConnectionClose [on/<b>off</b>]<br> +Equivalent to: ServerNotifyOnConnectionClose.<br></li> +<li><b>$InputPTCPServerKeepAlive</b> <on/<b>off</b>><br> +Equivalent to: KeepAlive </li> +<li><b>$InputPTCPServerKeepAlive_probes</b> <number><br> +Equivalent to: KeepAlive.Probes</li> +<li><b>$InputPTCPServerKeepAlive_intvl</b> <number><br> +Equivalent to: KeepAlive.Interval </li> +<li><b>$InputPTCPServerKeepAlive_time</b> <number><br> +Equivalent to: KeepAlive.Time</li> +<li><b>$InputPTCPServerRun</b> <port><br> +Equivalent to: Port </li> +<li>$InputPTCPServerInputName <name><br> +Equivalent to: Name </li> +<li>$InputPTCPServerBindRuleset <name><br> +Equivalent to: Ruleset </li> +<li>$InputPTCPHelperThreads <number><br> +Number of helper worker threads to process incoming messages. These +threads are utilized to pull data off the network. On a busy system, additional +helper threads (but not more than there are CPUs/Cores) can help improving +performance. The default value is two. +<li>$InputPTCPServerListenIP <name><br> +Equivalent to: Address </li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>module always binds to all interfaces</li> +</ul> +<p><b>Sample:</b></p> +<p>This sets up a TCP server on port 514:<br> +</p> +<textarea rows="3" cols="60">$ModLoad imptcp # +needs to be done just once +$InputPTCPServerRun 514 +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2010-2012 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imrelp.html b/doc/imrelp.html new file mode 100644 index 00000000..73af2659 --- /dev/null +++ b/doc/imrelp.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>RELP Input Module</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>RELP Input Module</h1> +<p><b>Module Name: imrelp</b></p> +<p><b>Author: Rainer Gerhards</b></p> +<p><b>Description</b>:</p> +<p>Provides the ability to receive syslog messages via the +reliable RELP protocol. This module requires <a href="http://www.librelp.com">librelp</a> to be +present on the system. From the user's point of view, imrelp works much +like imtcp or imgssapi, except that no message loss can occur. Please +note that with the currently supported relp protocol version, a minor +message duplication may occur if a network connection between the relp +client and relp server breaks after the client could successfully send +some messages but the server could not acknowledge them. The window of +opportunity is very slim, but in theory this is possible. Future +versions of RELP will prevent this. Please also note that rsyslogd may +lose a few messages if rsyslog is shutdown while a network conneciton +to the server is broken and could not yet be recovered. Future version +of RELP support in rsyslog will prevent that. Please note that both +scenarios also exists with plain tcp syslog. RELP, even with the small +nits outlined above, is a much more reliable solution than plain tcp +syslog and so it is highly suggested to use RELP instead of plain tcp. +Clients send messages to the RELP server via omrelp.</p> + +<p><b>Module Parameters</b>:</p> +<ul> + <li><b>Ruleset</b> <name></br> + Binds the specified ruleset to <b>all</b> RELP listeners. +</ul> +<p><b>Input Parameters</b>:</p> +<ul> +<li><b>Port</b> <port><br> +Starts a RELP server on selected port</li> +<li><b>tls</b> (not mandatory, values "on","off", default "off")<br> +If set to "on", the RELP connection will be encrypted by TLS, +so that the data is protected against observers. Please note +that both the client and the server must have set TLS to +either "on" or "off". Other combinations lead to unpredictable +results. +</li> +<li><b>tls.compression</b> (not mandatory, values "on","off", default "off")<br> +The controls if the TLS stream should be compressed (zipped). While this +increases CPU use, the network bandwidth should be reduced. Note that +typical text-based log records usually compress rather well. +</li> +<li><b>tls.dhbits</b> (not mandatory, integer)<br> +This setting controls how many bits are used for Diffie-Hellman key +generation. If not set, the librelp default is used. For secrity +reasons, at least 1024 bits should be used. Please note that the number +of bits must be supported by GnuTLS. If an invalid number is given, rsyslog +will report an error when the listener is started. We do this to be transparent +to changes/upgrades in GnuTLS (to check at config processing time, we would need +to hardcode the supported bits and keep them in sync with GnuTLS - this is +even impossible when custom GnuTLS changes are made...). +</li> +<li><b>tls.prioritystring</b> (not mandatory, string)<br> +This parameter permits to specify the so-called "priority string" to +GnuTLS. This string gives complete control over all crypto parameters, +including compression setting. For this reason, when the prioritystring +is specified, the "tls.compression" parameter has no effect and is +ignored. +<br>Full information about how to construct a priority string can be +found in the GnuTLS manual. At the time of this writing, this +information was contained in +<a href="http://gnutls.org/manual/html_node/Priority-Strings.html">section 6.10 of the GnuTLS manual</a>. +<br><b>Note: this is an expert parameter.</b> Do not use if you do +not exactly know what you are doing. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>see description</li> +<li>To obtain the remote system's IP address, you need to have at least +librelp 1.0.0 installed. Versions below it return the hostname instead +of the IP address.</li> +<li>Contrary to other inputs, the ruleset can only be bound to all listeners, +not specific ones. This is due to a currently existing limitation in librelp. +</ul> +<p><b>Sample:</b></p> +<p>This sets up a RELP server on port 20514.<br> +</p> +<textarea rows="5" cols="60">module(load="imrelp") # needs to be done just once +input(type="imrelp" port="20514") +</textarea> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li>InputRELPServerBindRuleset <name> (available in 6.3.6+)</br> +equivalent to: RuleSet +<li>InputRELPServerRun <port><br> +equivalent to: Port</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>To obtain the remote system's IP address, you need to have at least +librelp 1.0.0 installed. Versions below it return the hostname instead +of the IP address.</li> +<li>Contrary to other inputs, the ruleset can only be bound to all listeners, +not specific ones. This is due to a currently existing limitation in librelp. +</ul> +<p><b>Sample:</b></p> +<p>This sets up a RELP server on port 20514.<br> +</p> +<textarea rows="5" cols="60">$ModLoad imrelp # needs to be done just once +$InputRELPServerRun 20514 +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imsolaris.html b/doc/imsolaris.html new file mode 100644 index 00000000..ce0e7e84 --- /dev/null +++ b/doc/imsolaris.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Solaris Input Module (imsolaris)</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Solaris Input Module</h1> +<p><b>Module Name: imsolaris</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>Reads local Solaris log messages including the kernel log.</p> +<p>This module is specifically tailored for Solaris. Under Solaris, there +is no special kernel input device. Instead, both kernel messages as well as +messages emitted via syslog() are received from a single source. +<p>This module obeys the Solaris door() mechanism to detect a running syslogd +instance. As such, only one can be active at one time. If it detects another +active intance at startup, the module disables itself, but rsyslog will +continue to run. +<p><b>Configuration Directives</b>:</p> +<ul> +<li><strong>$IMSolarisLogSocketName <name></strong><br> +This is the name of the log socket (stream) to read. If not given, /dev/log +is read. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>None currently known. For obvious reasons, works on Solaris, only (and compilation +will most probably fail on any other platform). +<p><b>Sample:</b></p> +<p>The following sample pulls messages from the default log source +<br> +</p> +<textarea rows="15" cols="60">$ModLoad imsolaris +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imtcp.html b/doc/imtcp.html new file mode 100644 index 00000000..b9f0b056 --- /dev/null +++ b/doc/imtcp.html @@ -0,0 +1,180 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<meta http-equiv="Content-Language" content="en"> +<title>TCP Syslog Input Module</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back to rsyslog module overview</a> + +<h1>TCP Syslog Input Module</h1> +<p><b>Module Name: imtcp</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Multi-Ruleset Support: </b>since 4.5.0 and 5.1.1 +<p><b>Description</b>:</p> +<p>Provides the ability to receive syslog messages via TCP. +Encryption is natively provided by selecting the approprioate network stream driver and +can also be provided by using <a href="rsyslog_stunnel.html">stunnel</a> +(an alternative is the use the <a href="imgssapi.html">imgssapi</a> module).</p> + +<p><b>Configuration Directives</b>:</p> +<p><b>Global Directives</b>:</p> +<ul> +<li><b>AddtlFrameDelimiter</b> <Delimiter><br> +This directive permits to specify an additional frame delimiter for plain tcp syslog. +The industry-standard specifies using the LF character as frame delimiter. Some vendors, +notable Juniper in their NetScreen products, use an invalid frame delimiter, in Juniper's +case the NUL character. This directive permits to specify the ASCII value of the delimiter +in question. Please note that this does not guarantee that all wrong implementations can +be cured with this directive. It is not even a sure fix with all versions of NetScreen, +as I suggest the NUL character is the effect of a (common) coding error and thus will +probably go away at some time in the future. But for the time being, the value 0 can +probably be used to make rsyslog handle NetScreen's invalid syslog/tcp framing. +For additional information, see this +<a href="http://kb.monitorware.com/problem-with-netscreen-log-t1652.html">forum thread</a>. +<br><b>If this doesn't work for you, please do not blame the rsyslog team. Instead file +a bug report with Juniper!</b> +<br>Note that a similar, but worse, issue exists with Cisco's IOS implementation. They do +not use any framing at all. This is confirmed from Cisco's side, but there seems to be +very limited interest in fixing this issue. This directive <b>can not</b> fix the Cisco bug. +That would require much more code changes, which I was unable to do so far. Full details +can be found at the <a href="http://www.rsyslog.com/Article321.phtml">Cisco tcp syslog anomaly</a> +page. +<li><b>DisableLFDelimiter</b> <on/<b>off</b>><br> +Industry-strandard plain text tcp syslog uses the LF to delimit syslog frames. However, +some users brought up the case that it may be useful to define a different delimiter and +totally disable LF as a delimiter (the use case named were multi-line messages). This mode +is non-standard and will probably come with a lot of problems. However, as there is need +for it and it is relatively easy to support, we do so. Be sure to turn this setting to +"on" only if you exactly know what you are doing. You may run into all sorts of troubles, +so be prepared to wrangle with that! +<li><b>NotifyOnConnectionClose</b> [on/<b>off</b>]<br> +instructs imtcp to emit a message if the remote peer closes a connection.<br> +<b>Important:</b> This directive is global to all listeners and must be given right +after loading imtcp, otherwise it may have no effect.</li> +<li><b>KeepAlive</b> <on/<b>off</b>><br> +enable of disable keep-alive packets at the tcp socket layer. The default is +to disable them.</li> +<li><b>FlowControl</b> <<b>on</b>/off><br> +This setting specifies whether some message flow control shall be exercised on the +related TCP input. If set to on, messages are handled as "light delayable", which means +the sender is throttled a bit when the queue becomes near-full. This is done in order +to preserve some queue space for inputs that can not throttle (like UDP), but it +may have some undesired effect in some configurations. Still, we consider this as +a useful setting and thus it is the default. To turn the handling off, simply +configure that explicitely. +</li> +<li><b>MaxListeners</b> <number><br> +Sets the maximum number of listeners (server ports) supported. Default is 20. This must be set before the first $InputTCPServerRun directive.</li> +<li><b>MaxSessions</b> <number><br> Sets the maximum number of sessions supported. Default is 200. This must be set before the first $InputTCPServerRun directive</li> +<li><b>StreamDriver.Mode</b> <number><br> +Sets the driver mode for the currently selected <a href="netstream.html">network stream driver</a>. <number> is driver specifc.</li> +<li><b>StreamDriver.AuthMode</b> <mode-string><br> +Sets the authentication mode for the currently selected <a href="netstream.html">network stream driver</a>. <mode-string> is driver specifc.</li> +<li><b>PermittedPeer</b> <id-string><br> +Sets permitted peer IDs. Only these peers are able to connect to the +listener. <id-string> semantics depend on the currently selected +AuthMode and <a href="netstream.html">network stream driver</a>. PermittedPeer may not be set in anonymous modes. +<br>PermittedPeer may be set either to a single peer or an array of peers either of type IP or name, depending on the tls certificate. +<br>Single peer: PermittedPeer="127.0.0.1" +<br>Array of peers: PermittedPeer=["test1.example.net","10.1.2.3","test2.example.net","..."]</li> +</ul> +<p><b>Action Directives</b>:</p> +<ul> +<li><b>Port</b> <port><br> +Starts a TCP server on selected port</li> +<li><b>Name</b> <name><br> +Sets a name for the inputname property. If no name is set "imtcp" is used by default. Setting a +name is not strictly necessary, but can be useful to apply filtering based on which input +the message was received from. +<li><b>Ruleset</b> <ruleset><br> +Binds the listener to a specific <a href="multi_ruleset.html">ruleset</a>.</li> +<li><b>SupportOctetCountedFraming</b> <<b>on</b>|off><br> +If set to "on", the legacy octed-counted framing (similar to RFC5425 framing) is +activated. This is the default and should be left unchanged until you know +very well what you do. It may be useful to turn it off, if you know this framing +is not used and some senders emit multi-line messages into the message stream. +</li> +<li><b>RateLimit.Interval</b> [number] - (available since 7.3.1) specifies the rate-limiting +interval in seconds. Default value is 0, which turns off rate limiting. Set it to a number +of seconds (5 recommended) to activate rate-limiting. +</li> +<li><b>RateLimit.Burst</b> [number] - (available since 7.3.1) specifies the rate-limiting +burst in number of messages. Default is 10,000. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>module always binds to all interfaces</li> +<li>can not be loaded together with <a href="imgssapi.html">imgssapi</a> +(which includes the functionality of imtcp)</li> +</ul> +<p><b>Example:</b></p> +<p>This sets up a TCP server on port 514 and permits it to accept up to 500 connections:<br> +</p> +<textarea rows="15" cols="60">module(load="imtcp" MaxSessions="500") +input(type="imtcp" port="514") +</textarea> +<p>Note that the global parameters (here: max sessions) need to be set when the module is loaded. Otherwise, the parameters will not apply. +</p> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li><b>$InputTCPServerAddtlFrameDelimiter <Delimiter></b><br> +equivalent to: AddtlFrameDelimiter +<li><b>$InputTCPServerDisableLFDelimiter</b> <on/<b>off</b>> (available since 5.5.3)<br> +equivalent to: DisableLFDelimiter +<li><b>$InputTCPServerNotifyOnConnectionClose</b> [on/<b>off</b>] (available since 4.5.5)<br> +equivalent to: NotifyOnConnectionClose<br> +</li> +<li><b>$InputTCPServerKeepAlive</b> <on/<b>off</b>><br> +equivalent to: KeepAlive</li> +<li><b>$InputTCPServerRun</b> <port><br> +equivalent to: Port</li> +<li><b>$InputTCPFlowControl</b> <<b>on</b>/off><br> +equivalent to: FlowControl +</li> +<li><b>$InputTCPMaxListeners</b> <number><br> +equivalent to: MaxListeners</li> +<li><b>$InputTCPMaxSessions</b> <number><br> +equivalent to: MaxSessions</li> +<li><b>$InputTCPServerStreamDriverMode</b> <number><br> +equivalent to: StreamDriver.Mode</li> +<li><b>$InputTCPServerInputName</b> <name><br> +equivalent to: Name +<li><b>$InputTCPServerStreamDriverAuthMode</b> <mode-string><br> +equivalent to: StreamDriver.AuthMode</li> +<li><b>$InputTCPServerStreamDriverPermittedPeer</b> <id-string><br> +equivalent to: PermittedPeer.</li> +<li><b>$InputTCPServerBindRuleset</b> <ruleset><br> +equivalent to: Ruleset</a>.</li> +<li><b>$InputTCPSupportOctetCountedFraming</b> <<b>on</b>|off><br> +equivalent to: SupportOctetCountedFraming +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>module always binds to all interfaces</li> +<li>can not be loaded together with <a href="imgssapi.html">imgssapi</a> +(which includes the functionality of imtcp)</li> +</ul> +<p><b>Example:</b></p> +<p>This sets up a TCP server on port 514 and permits it to accept up to 500 connections:<br> +</p> +<textarea rows="15" cols="60">$ModLoad imtcp # needs to be done just once +$InputTCPMaxSessions 500 +$InputTCPServerRun 514 +</textarea> +<p>Note that the parameters (here: max sessions) need to be set <b>before</b> the listener +is activated. Otherwise, the parameters will not apply. +</p> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008,2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/imudp.html b/doc/imudp.html new file mode 100644 index 00000000..6c949536 --- /dev/null +++ b/doc/imudp.html @@ -0,0 +1,148 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<meta http-equiv="Content-Language" content="en"> +<title>UDP Syslog Input Module (imudp)</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back to rsyslog module overview</a> + +<h1>UDP Syslog Input Module</h1> +<p><b>Module Name: imudp</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Multi-Ruleset Support: </b>since 5.3.2 +<p><b>Description</b>:</p> +<p>Provides the ability to receive syslog messages via UDP. +<p>Multiple receivers may be configured by specifying +multiple input actions. +</p> + +<p><b>Configuration Parameters</b>:</p> +<p><b>Module Parameters</b>:</p> +<ul> +<li><b>TimeRequery</b> <nbr-of-times><br> +this is a performance +optimization. Getting the system time is very costly. With this setting, imudp can +be instructed to obtain the precise time only once every n-times. This logic is +only activated if messages come in at a very fast rate, so doing less frequent +time calls should usually be acceptable. The default value is two, because we have +seen that even without optimization the kernel often returns twice the identical time. +You can set this value as high as you like, but do so at your own risk. The higher +the value, the less precise the timestamp. +<li><b>SchedulingPolicy</b> <rr/fifo/other><br> +Can be used the set the scheduler priority, if the necessary functionality +is provided by the platform. Most useful to select "fifo" for real-time +processing under Linux (and thus reduce chance of packet loss). +<li><b>SchedulingPriority</b> <number><br> +Scheduling priority to use. +</ul> +<p><b>Input Parameters</b>:</p> +<ul> +<li><b>Address</b> <IP><br> +local IP address (or name) the UDP listens should bind to</li> +<li><b>Port</b> <port><br> +default 514, start UDP server on this port. Either a single port can be specified or an array of ports. If multiple ports are specified, a listener will be automatically started for each port. Thus, no additional inputs need to be configured. +<br>Single port: Port="514" +<br>Array of ports: Port=["514","515","10514","..."]</li> +<li><b>Ruleset</b> <ruleset><br> +Binds the listener to a specific <a href="multi_ruleset.html">ruleset</a>.</li> +<li><b>RateLimit.Interval</b> [number] - (available since 7.3.1) specifies the rate-limiting +interval in seconds. Default value is 0, which turns off rate limiting. Set it to a number +of seconds (5 recommended) to activate rate-limiting. +</li> +<li><b>RateLimit.Burst</b> [number] - (available since 7.3.1) specifies the rate-limiting +burst in number of messages. Default is 10,000. +</li> +<li><b>InputName</b> [name] - (available since 7.3.9) specifies the value of +the inputname. In older versions, this was always "imudp" for all listeners, +which still i the default. +Starting with 7.3.9 it can be set to different values for each listener. +Note that when a single input statement defines multipe listner ports, the +inputname will be the same for all of them. If you want to differentiate in that +case, use "InputName.AppendPort" to make them unique. +Note that the "InputName" parameter can be an empty string. In that case, the +corresponding inputname property will obviously also be the empty string. This +is primarily meant to be used togehter with "InputName.AppendPort" to set the +inputname equal to the port. +</li> +<li><b>InputName.AppendPort</b> [on/<b>off</b>] - (available since 7.3.9) +appends the port the the inputname. Note that when no inputname is specified, +the default of "imudp" is used and the port is appended to that default. So, +for example, a listner port of 514 in that case will lead to an inputname +of "imudp514". The ability to append a port is most useful when multiple ports +are defined for a single input and each of the inputnames shall be unique. +Note that there currently is no differentiation between IPv4/v6 listners on +the same port. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>Scheduling parameters are set <b>after</b> privileges have been dropped. +In most cases, this means that setting them will not be possible after +privilege drop. This may be worked around by using a sufficiently-privileged +user account. +</li> +</ul> +<p><b>Samples:</b></p> +<p>This sets up an UPD server on port 514:<br> +</p> +<textarea rows="3" cols="60">module(load="imudp") # needs to be done just once +input(type="imudp" port="514") +</textarea> + +<p>In the next example, we set up three listners at ports 10514, 10515 and 10516 +and assign a listner name of "udp" to it, followed by the port number: +</p> +<textarea rows="4" cols="60">module(load="imudp") +input(type="imudp" port=["10514","10515","10516"] + inputname="udp" inputname.appendPort="on") +</textarea> + +<p>The next example is almost equal to the previous one, but +now the inputname property will just be set to the port number. +So if a message was received on port 10515, the input name will be +"10515" in this example whereas it was "udp10515" in the previous one. +Note that to do that we set the inputname to the empty string. +</p> +<textarea rows="4" cols="60">module(load="imudp") +input(type="imudp" port=["10514","10515","10516"] + inputname="" inputname.appendPort="on") +</textarea> + + +<p><b>Legacy Configuration Directives</b>:</p> +<p>Multiple receivers may be configured by specifying +$UDPServerRun multiple times. +</p> +<ul> +<li>$UDPServerAddress <IP><br> +equivalent to: Address </li> +<li>$UDPServerRun <port><br> +equivalent to: Port </li> +<li>$UDPServerTimeRequery <nbr-of-times><br> +equivalent to: TimeRequery +<li>$InputUDPServerBindRuleset <ruleset><br> +equivalent to: Ruleset </li> +<li>$IMUDPSchedulingPolicy <rr/fifo/other> Available since 4.7.4+, 5.7.3+, 6.1.3+.<br> +equivalent to: SchedulingPolicy +<li>$IMUDPSchedulingPriority <number> Available since 4.7.4+, 5.7.3+, 6.1.3+.<br> +equivalent to: SchedulingPriority +</ul> +<p><b>Sample:</b></p> +<p>This sets up an UPD server on port 514:<br> +</p> +<textarea rows="3" cols="60">$ModLoad imudp # needs to be done just once +$UDPServerRun 514 +</textarea> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2009-2013 by +<a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/imuxsock.html b/doc/imuxsock.html new file mode 100644 index 00000000..0affe8c3 --- /dev/null +++ b/doc/imuxsock.html @@ -0,0 +1,330 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Unix Socket Input</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Unix Socket Input</h1> +<p><b>Module Name: imuxsock</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p><b>Provides the ability to accept syslog messages via local Unix +sockets. Most importantly, this is the mechanism by which the syslog(3) +call delivers syslog messages to rsyslogd.</b> So you need to have this +module loaded to read the system log socket and be able to process log +messages from applications running on the local system.</p> +<p><b>Application-provided +timestamps are ignored by default.</b> This is needed, as some programs +(e.g. sshd) log with inconsistent timezone information, what +messes up the local logs (which by default don't even contain time zone +information). This seems to be consistent with what sysklogd did for +the past four years. Alternate behaviour may be desirable if +gateway-like processes send messages via the local log slot - in this +case, it can be enabled via the +IgnoreTimestamp and SysSock.IgnoreTimestamp config directives</p> +<p><b>There is input rate limiting available,</b> (since 5.7.1) to guard you against +the problems of a wild running logging process. +If more than SysSock.RateLimit.Interval * SysSock.RateLimit.Burst log messages are emitted +from the same process, those messages with SysSock.RateLimit.Severity or lower will be +dropped. It is not possible to recover anything about these messages, but imuxsock will +tell you how many it has dropped one the interval has expired AND the next message +is logged. Rate-limiting depends on SCM_CREDENTIALS. If the platform does not support +this socket option, rate limiting is turned off. If multiple sockets are configured, +rate limiting works independently on each of them (that should be what you usually expect). +The same functionality is available for additional log sockets, in which case the +config statements just use +the prefix RateLimit... but otherwise works exactly the same. +When working with severities, please keep in mind that higher severity numbers mean lower +severity and configure things accordingly. +To turn off rate limiting, set the interval to zero. +<p><b>Unix log sockets can be flow-controlled.</b> That is, if processing queues fill up, +the unix socket reader is blocked for a short while. This may be useful to prevent overruning +the queues (which may cause exessive disk-io where it actually would not be needed). However, +flow-controlling a log socket (and especially the system log socket) can lead to a very +unresponsive system. As such, flow control is disabled by default. That means any log records +are places as quickly as possible into the processing queues. If you would like to have +flow control, you need to enable it via the SysSock.FlowControl and +FlowControl config directives. Just make sure you thought about +the implications. Note that for many systems, turning on flow control does not hurt. +<p>Starting with rsyslog 5.9.4, +<b><a href="http://www.rsyslog.com/what-are-trusted-properties/">trusted syslog properties</a> +are available</b>. These require a recent enough Linux Kernel and access to the /proc file +system. In other words, this may not work on all platforms and may not work fully when +privileges are dropped (depending on how they are dropped). Note that trusted properties +can be very useful, but also typically cause the message to grow rather large. Also, the +format of log messages is obviously changed by adding the trusted properties at the end. +For these reasons, the feature is <b>not enabled by default</b>. If you want to use it, +you must turn it on (via SysSock.Annotate and Annotate). + +<p><b>Configuration Directives</b>:</p> +<p><b>Global Parameters</b></p> +<ul> +<li><b>SysSock.IgnoreTimestamp</b> [<b>on</b>/off]<br> +Ignore timestamps included in the messages, applies to messages received via the system log socket. +</li> +<li><b>SysSock.IgnoreOwnMessages</b> [<b>on</b>/off] (available since 7.3.7)<br> +Ignores messages that originated from the same instance of rsyslogd. There usually +is no reason to receive messages from ourselfs. This setting is vital +when writing messages to the Linux journal. See <a href="omjournal.html">omjournal</a> +module documentation for a more in-depth description. +</li> +<li><b>SysSock.Use</b> (imuxsock) [on/<b>off</b>] +do NOT listen for the local log socket. This is most useful if you run multiple +instances of rsyslogd where only one shall handle the system log socket. +</li> +<li><b>SysSock.Name</b> <name-of-socket> +</li> +<li><b>SysSock.FlowControl</b> [on/<b>off</b>] - specifies if flow control should be applied +to the system log socket. +</li> +<li><b>SysSock.UsePIDFromSystem</b> [on/<b>off</b>] - specifies if the pid being logged shall +be obtained from the log socket itself. If so, the TAG part of the message is rewritten. +It is recommended to turn this option on, but the default is "off" to keep compatible +with earlier versions of rsyslog. +</li> +<li><b>SysSock.RateLimit.Interval</b> [number] - specifies the rate-limiting +interval in seconds. Default value is 5 seconds. Set it to 0 to turn rate limiting off. +</li> +<li><b>SysSock.RateLimit.Burst</b> [number] - specifies the rate-limiting +burst in number of messages. Default is 200. +</li> +<li><b>SysSock.RateLimit.Severity</b> [numerical severity] - specifies the severity of +messages that shall be rate-limited. +</li> +<li><b>SysSock.UseSysTimeStamp</b> [<b>on</b>/off] the same as $InputUnixListenSocketUseSysTimeStamp, but for the system log socket. +</li> +<li><b>SysSock.Annotate</b> <on/<b>off</b>> turn on annotation/trusted +properties for the system log socket.</li> +<li><b>SysSock.ParseTrusted</b> <on/<b>off</b>> if Annotation is turned on, create +JSON/lumberjack properties out of the trusted properties (which can be accessed +via RainerScript JSON Variables, e.g. "$!pid") instead of adding them to the message. +</li> +<li><b>SysSock.Unlink</b> <<b>on</b>/off> (available since 7.3.9)<br> +if turned on (default), the system socket is unlinked and re-created when +opened and also unlinked when finally closed. Note that this setting has +no effect when running under systemd control (because systemd handles +the socket). +</li> +</ul> + +<p><b>Input Instance Parameters</b></p> +<ul> +<li><b>IgnoreTimestamp</b> [<b>on</b>/off] +<br>Ignore timestamps included in the message. Applies to the next socket being added.</li> +<li><b>IgnoreOwnMessages</b> [<b>on</b>/off] (available since 7.3.7)<br> +Ignore messages that originated from the same instance of rsyslogd. There usually +is no reason to receive messages from ourselfs. This setting is vital +when writing messages to the Linux journal. See <a href="omjournal.html">omjournal</a> +module documentation for a more in-depth description. +</li> +<li><b>FlowControl</b> [on/<b>off</b>] - specifies if flow control should be applied +to the next socket.</li> +<li><b>RateLimit.Interval</b> [number] - specifies the rate-limiting +interval in seconds. Default value is 0, which turns off rate limiting. Set it to a number +of seconds (5 recommended) to activate rate-limiting. The default of 0 has been choosen +as people experienced problems with this feature activated by default. Now it needs an +explicit opt-in by setting this parameter. +</li> +<li><b>RateLimit.Burst</b> [number] - specifies the rate-limiting +burst in number of messages. Default is 200. +</li> +<li><b>RateLimit.Severity</b> [numerical severity] - specifies the severity of +messages that shall be rate-limited. +</li> +<!--<li><b>LocalIPIF</b> [interface name] - if provided, the IP of the specified +interface (e.g. "eth0") shall be used as fromhost-ip for imuxsock-originating messages. +If this directive is not given OR the interface cannot be found (or has no IP address), +the default of "127.0.0.1" is used. +</li>--> +<li><b>UsePIDFromSystem</b> [on/<b>off</b>] - specifies if the pid being logged shall +be obtained from the log socket itself. If so, the TAG part of the message is rewritten. +It is recommended to turn this option on, but the default is "off" to keep compatible +with earlier versions of rsyslog. </li> +<li><b>UseSysTimeStamp</b> [<b>on</b>/off] instructs imuxsock +to obtain message time from the system (via control messages) insted of using time +recorded inside the message. This may be most useful in combination with systemd. Note: +this option was introduced with version 5.9.1. Due to the usefulness of it, we +decided to enable it by default. As such, 5.9.1 and above behave slightly different +than previous versions. However, we do not see how this could negatively affect +existing environments.<br> +<li><b>CreatePath</b> [on/<b>off</b>] - create directories in the socket path +if they do not already exist. They are created with 0755 permissions with the owner being the process under +which rsyslogd runs. The default is not to create directories. Keep in mind, though, that rsyslogd always +creates the socket itself if it does not exist (just not the directories by default). +<br>Note that this statement affects the +next Socket directive that follows in sequence in the configuration file. It never works +on the system log socket (where it is deemed unnecessary). Also note that it is automatically +being reset to "off" after the Socket directive, so if you would have it active +for two additional listen sockets, you need to specify it in front of each one. This option is primarily considered +useful for defining additional sockets that reside on non-permanent file systems. As rsyslogd probably starts +up before the daemons that create these sockets, it is a vehicle to enable rsyslogd to listen to those +sockets even though their directories do not yet exist.</li> +<li><b>Socket</b> <name-of-socket> adds additional unix socket, default none -- former -a option</li> +<li><b>HostName</b> <hostname> permits to override the hostname that +shall be used inside messages taken from the <b>next</b> Socket socket. Note that +the hostname must be specified before the $AddUnixListenSocket configuration directive, and it +will only affect the next one and then automatically be reset. This functionality is provided so +that the local hostname can be overridden in cases where that is desired.</li> +<li><b>Annotate</b> <on/<b>off</b>> turn on annotation/trusted +properties for the non-system log socket in question.</li> +<li><b>ParseTrusted</b> <on/<b>off</b>> equivalent to the SysSock.ParseTrusted module +parameter, but applies to the input that is being defined. +<li><b>Unlink</b> <<b>on</b>/off> (available since 7.3.9)<br> +if turned on (default), the socket is unlinked and re-created when +opened and also unlinked when finally closed. Set it to off if you +handle socket creation yourself. Note that handling socket creation +oneself has the advantage that a limited amount of messages may be +queued by the OS if rsyslog is not running. +</li> +</ul> + +<b>Caveats/Known Bugs:</b><br> +<ul> +<li>There is a compile-time limit of 50 concurrent sockets. If you need more, you need to +change the array size in imuxsock.c. +<li>This documentation is sparse and incomplete. +</ul> +<p><b>Sample:</b></p> +<p>The following sample is the minimum setup required to accept syslog messages from applications running +on the local system.<br> +</p> +<textarea rows="2" cols="70">module(load="imuxsock" # needs to be done just once +SysSock.FlowControl="on") # enable flow control (use if needed) +</textarea> + +<p>The following sample is similiar to the first one, but enables trusted +properties, which are put into JSON/lumberjack variables. +<br> +</p> +<textarea rows="2" cols="70">module(load="imuxsock" SysSock.Annotate="on" SysSock.ParseTrusted="on") +</textarea> + +<p>The following sample is a configuration where rsyslogd pulls logs from two +jails, and assigns different hostnames to each of the jails: </p> +<textarea rows="6" cols="70">module(load="imuxsock") # needs to be done just once + +input(type="imuxsock" HostName="jail1.example.net" Socket="/jail/1/dev/log") +input(type="imuxsock" HostName="jail2.example.net" Socket="/jail/2/dev/log") +</textarea> +<p>The following sample is a configuration where rsyslogd reads the openssh log +messages via a separate socket, but this socket is created on a temporary file +system. As rsyslogd starts up before the sshd, it needs to create the socket +directories, because it otherwise can not open the socket and thus not listen +to openssh messages. Note that it is vital not to place any other socket between +the CreatePath and the Socket.</p> +<textarea rows="6" cols="70">module(load="imuxsock") # needs to be done just once + +input(type="imuxsock" Socket="/var/run/sshd/dev/log" CreatePath="on") +</textarea> +<p>The following sample is used to turn off input rate limiting on the system log +socket. +<textarea rows="4" cols="70">module(load="imuxsock" # needs to be done just once +SysSock.RateLimit.Interval="0") # turn off rate limiting +</textarea> +<p>The following sample is used activate message annotation and thus trusted properties +on the system log socket. +<textarea rows="4" cols="70">module(load="imuxsock" # needs to be done just once +SysSock.Annotate="on") +</textarea> + + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li><b>$InputUnixListenSocketIgnoreMsgTimestamp</b> [<b>on</b>/off] +<br>equivalent to: IgnoreTimestamp.</li> +<li><b>$InputUnixListenSocketFlowControl</b> [on/<b>off</b>] - equivalent to: FlowControl .</li> +<li><b>$IMUXSockRateLimitInterval</b> [number] - equivalent to: RateLimit.Interval +</li> +<li><b>$IMUXSockRateLimitBurst</b> [number] - equivalent to: RateLimit.Burst +</li> +<li><b>$IMUXSockRateLimitSeverity</b> [numerical severity] - equivalent to: RateLimit.Severity +</li> +<li><b>$IMUXSockLocalIPIF</b> [interface name] - (available since 5.9.6) - if provided, the IP of the specified +interface (e.g. "eth0") shall be used as fromhost-ip for imuxsock-originating messages. +If this directive is not given OR the interface cannot be found (or has no IP address), +the default of "127.0.0.1" is used. +</li> +<li><b>$InputUnixListenSocketUsePIDFromSystem</b> [on/<b>off</b>] - equivalent to: UsePIDFromSystem. +<br>This option was introduced in 5.7.0.</li> +<li><b>$InputUnixListenSocketUseSysTimeStamp</b> [<b>on</b>/off] equivalent to: UseSysTimeStamp .<br> +<li><b>$SystemLogSocketIgnoreMsgTimestamp</b> [<b>on</b>/off]<br> +equivalent to: SysSock.IgnoreTimestamp.</li> +<li><b>$OmitLocalLogging</b> (imuxsock) [on/<b>off</b>] equivalent to: SysSock.Use</li> +<li><b>$SystemLogSocketName</b> <name-of-socket> equivalent to: SysSock.Name</li> +<li><b>$SystemLogFlowControl</b> [on/<b>off</b>] - equivalent to: SysSock.FlowControl.</li> +<li><b>$SystemLogUsePIDFromSystem</b> [on/<b>off</b>] - equivalent to: SysSock.UsePIDFromSystem. +<br>This option was introduced in 5.7.0.</li> +<li><b>$SystemLogRateLimitInterval</b> [number] - equivalent to: SysSock.RateLimit.Interval. +</li> +<li><b>$SystemLogRateLimitBurst</b> [number] - equivalent to: SysSock.RateLimit.Burst +</li> +<li><b>$SystemLogRateLimitSeverity</b> [numerical severity] - equivalent to: SysSock.RateLimit.Severity +</li> +<li><b>$SystemLogUseSysTimeStamp</b> [<b>on</b>/off] equivalent to: SysSock.UseSysTimeStamp. +<li><b>$InputUnixListenSocketCreatePath</b> [on/<b>off</b>] - equivalent to: CreatePath +<br>[available since 4.7.0 and 5.3.0]</li> +<li><b>$AddUnixListenSocket</b> <name-of-socket> equivalent to: Socket </li> +<li><b>$InputUnixListenSocketHostName</b> <hostname> equivalent to: HostName.</li> +<li><b>$InputUnixListenSocketAnnotate</b> <on/<b>off</b>> equivalent to: Annotate.</li> +<li><b>$SystemLogSocketAnnotate</b> <on/<b>off</b>> equivalent to: SysSock.Annotate.</li> +<li><b>$SystemLogSocketParseTrusted</b> <on/<b>off</b>> equivalent to: SysSock.ParseTrusted.</li> +</ul> + +<b>Caveats/Known Bugs:</b><br> +<ul> +<li>There is a compile-time limit of 50 concurrent sockets. If you need more, you need to +change the array size in imuxsock.c. +<li>This documentation is sparse and incomplete. +</ul> +<p><b>Sample:</b></p> +<p>The following sample is the minimum setup required to accept syslog messages from applications running +on the local system.<br> +</p> +<textarea rows="2" cols="70">$ModLoad imuxsock # needs to be done just once +$SystemLogSocketFlowControl on # enable flow control (use if needed) +</textarea> +<p>The following sample is a configuration where rsyslogd pulls logs from two +jails, and assigns different hostnames to each of the jails: </p> +<textarea rows="6" cols="70">$ModLoad imuxsock # needs to be done just once + +$InputUnixListenSocketHostName jail1.example.net +$AddUnixListenSocket /jail/1/dev/log +$InputUnixListenSocketHostName jail2.example.net +$AddUnixListenSocket /jail/2/dev/log +</textarea> +<p>The following sample is a configuration where rsyslogd reads the openssh log +messages via a separate socket, but this socket is created on a temporary file +system. As rsyslogd starts up before the sshd, it needs to create the socket +directories, because it otherwise can not open the socket and thus not listen +to openssh messages. Note that it is vital not to place any other socket between +the $InputUnixListenSocketCreatePath and the $InputUnixListenSocketHostName.</p> +<textarea rows="6" cols="70">$ModLoad imuxsock # needs to be done just once + +$InputUnixListenSocketCreatePath on # turn on for *next* socket +$InputUnixListenSocket /var/run/sshd/dev/log +</textarea> +<p>The following sample is used to turn off input rate limiting on the system log +socket. +<textarea rows="4" cols="70">$ModLoad imuxsock # needs to be done just once + +$SystemLogRateLimitInterval 0 # turn off rate limiting +</textarea> +<p>The following sample is used activate message annotation and thus trusted properties +on the system log socket. +<textarea rows="4" cols="70">$ModLoad imuxsock # needs to be done just once + +$SystemLogSocketAnnotate on +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 00000000..d753e2ed --- /dev/null +++ b/doc/index.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Welcome to rsyslog</title></head> +<body> +<h1>Welcome to rsyslog</h1> +<p><b><a href="http://www.rsyslog.com/">Rsyslog</a> +is an enhanced syslogd suitable both for small systems as +well as large enterprises.</b> +<p>This page provide a few quick pointers which hopefully make your +experience with rsyslog a pleasant one. These are +<ul> +<li><b>Most importantly, the <a href="manual.html">rsyslog manual</a></b> - this points to locally +installed documentation which exactly matches the version you have installed. +It is highly suggested to at least briefly look over these files. +<li>The <a href="http://www.rsyslog.com">rsyslog web site</a> which offers +probably every information you'll ever need (ok, just kidding...). +<li>The <a href="http://www.rsyslog.com/status">project status page</a> provides +information on current releases +<li>and the <a href="troubleshoot.html">troubleshooting guide</a> hopefully helps if +things do not immediately work out +</ul> +<p>In general, rsyslog supports plain old syslog.conf format, except that the +config file is now called rsyslog.conf. This should help you get started +quickly. +To do the really cool things, though, +you need to learn a bit about its new features. +The man pages offer a bare minimum of information (and are still quite long). Read the +<a href="manual.html">html documentation</a> instead. +When you change the configuration, remember to restart rsyslogd, because otherwise +it will not use your new settings (and you'll end up totally puzzled why this great +config of yours does not even work a bit...;)) +</body></html> diff --git a/doc/install.html b/doc/install.html new file mode 100644 index 00000000..48b7f649 --- /dev/null +++ b/doc/install.html @@ -0,0 +1,180 @@ +<html><head> +<title>A guide on HOWTO install rsyslog</title> +<meta name="KEYWORDS" content="syslog encryption, rsyslog, stunnel, secure syslog, tcp, reliable, howto, ssl"> +</head> +<body> +<h1>HOWTO install rsyslog</h1> + <P><small><i>Written by + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</a></i></small></P> +<h2>Abstract</h2> +<p><i><b>In this paper, I describe how to install +<a href="http://www.rsyslog.com/">rsyslog</a>.</b> It is intentionally a brief +step-by-step guide, targeted to those who want to quickly get it up and running. +For more elaborate information, please consult the rest of the +<a href="manual.html">manual set</a>.</i></p> +<h2>How to make your life easier...</h2> +<p>Some folks have thankfully created <a href="rsyslog_packages.html"> +RPMs/packages for rsyslog</a>. If you use them, you can spare yourself many of +the steps below. This is highly recommended if there is a package for your +distribution available.</p> +<h2>Steps To Do</h2> +<p>Rsyslog does currently only have very limited availability as a package (if +you volunteer to create one, <a href="mailto:rgerhards@adiscon.com">drop me a +line</a>). Thus, this guide focuses on installing from the source, which +thankfully is <b>quite easy</b>.</p> +<h3>Step 1 - Download Software</h3> +<p>For obvious reasons, you need to download rsyslog. Here, I assume that you +use a distribution tarball. If you would like to use a version directly from +the repository, see <a href="build_from_repo.html">build rsyslog from repository</a> +instead. +<p>Load the most recent build +from <a href="http://www.rsyslog.com/downloads">http://www.rsyslog.com/downloads</a>. +Extract the software with "tar xzf -nameOfDownloadSet-". This will create a new +subdirectory rsyslog-version in the current working directory. CD into that. </p> +<p>Depending on your system configuration, you also need to install some build +tools, most importantly make, the gcc compiler and the MySQL development system +(if you intend to use MySQL - the package is often named "mysql-dev"). On many systems, these things should already be +present. If you don't know exactly, simply skip this step for now and see if +nice error messages pop up during the compile process. If they do, you can still +install the missing build environment tools. So this is nothing that you need to +look at very carefully.</p> +<h3>Step 2 - Run ./configure</h3> +<p>Run ./configure to adopt rsyslog to your environment. While doing so, you can +also enable options. Configure will display selected options when it is +finished. For example, to enable MySQL support, run</p> +<p>./configure --enable-mysql</p> +<p>Please note that MySQL support by default is NOT disabled.</p> +<h3>Step 3 - Compile</h3> +<p>That is easy. Just type "make" and let the compiler work. On any recent +system, that should be a very quick task, on many systems just a matter of a few +seconds. If an error message comes up, most probably a part of your build +environment is not installed. Check with step 1 in those cases. </p> +<h3>Step 4 - Install</h3> +<p>Again, that is quite easy. All it takes is a "make install". That will copy +the rsyslogd and the man pages to the relevant directories.</p> +<h3>Step 5 - Configure rsyslogd</h3> +<p>In this step, you tell rsyslogd what to do with received messages. If you are +upgrading from stock syslogd, /etc/syslog.conf is probably a good starting +point. Rsyslogd understands stock syslogd syntax, so you can simply copy over +/etc/syslog.conf to /etc/rsyslog.conf. Note since version 3 rsyslog requires +to load plug-in modules to perform useful work (more about +<a href="v3compatibility.html">compatibilty notes v3</a>). To load the most common plug-ins, +add the following to the top of rsyslog.conf:</p> +<p> +$ModLoad immark # provides --MARK-- message capability <br /> +$ModLoad imudp # provides UDP syslog reception <br /> +$ModLoad imtcp # provides TCP syslog reception and GSS-API (if compiled to support it) <br /> +$ModLoad imuxsock # provides support for local system logging (e.g. via logger command) <br /> +$ModLoad imklog # provides kernel logging support (previously done by rklogd) <br /> +</p> +Change rsyslog.conf for any further +enhancements you would like to see. For example, you can add database writing as +outlined in the paper "<a href="rsyslog_mysql.html">Writing syslog Data to MySQL</a>" +(remember you need to enable MySQL support during step 2 if you want to do +that!).</p> +<h3>Step 6 - Disable stock syslogd</h3> +<p>In almost all cases, there already is stock syslogd installed. Because both +it and rsyslogd listen to the same sockets, they can NOT be run concurrently. So +you need to disable the stock syslogd. To do this, you typically must change +your rc.d startup scripts.</p> +<p>For example, under <a href="http://www.debian.org/">Debian</a> this must be +done as follows: The default runlevel is 2. We modify the init scripts for +runlevel 2 - in practice, you need to do this for all run levels you will ever +use (which probably means all). Under /etc/rc2.d there is a S10sysklogd script (actually +a symlink). Change the name to _S10sysklogd (this keeps the symlink in place, +but will prevent further execution - effectively disabling it).</p> +<h3>Step 7 - Enable rsyslogd Autostart</h3> +<p>This step is very close to step 3. Now, we want to enable rsyslogd to start +automatically. The rsyslog package contains a (currently small) number of +startup scripts. They are inside the distro-specific directory (e.g. debian). If +there is nothing for your operating system, you can simply copy the stock +syslogd startup script and make the minor modifications to run rsyslogd (the +samples should be of help if you intend to do this).</p> +<p>In our Debian example, the actual scripts are stored in /etc/init.d. Copy the +standard script to that location. Then, you need to add a symlink to it in the +respective rc.d directory. In our sample, we modify rc2.d, and can do this via +the command "ln -s ../init.d/rsyslogd S10rsyslogd". Please note that the S10 +prefix tells the system to start rsyslogd at the same time stock sysklogd was +started.</p> +<p><b>Important:</b> if you use the database functionality, you should make sure +that MySQL starts before rsyslogd. If it starts later, you will receive an error +message during each restart (this might be acceptable to you). To do so, either +move MySQL's start order before rsyslogd or rsyslogd's after MySQL.</p> +<h3>Step 8 - Check daily cron scripts</h3> +<p>Most distributions come pre-configured with some daily scripts for log +rotation. As long as you use the same log file names, the log rotation scripts +will probably work quite well. There is one caveat, though. The scripts need to +tell syslogd that the files have been rotated. To do this, they typically have a +part using syslogd's init script to do that. Obviously, the default scripts do +not know about rsyslogd, so they manipulate syslogd. If that happens, in most +cases an additional instance of stock syslogd is started (in almost all cases, +this was not functional, but it is at least distracting). It also means that +rsyslogd is not properly told about the log rotation, which will lead it to +continue to write to the now-rotated files.</p> +<p>So you need to fix these scripts. See your distro-specific documentation how +they are located. Under most Linuxes, the primary script to modify is /etc/cron.daily/sysklogd. +Watch for a comment "Restart syslogd" (usually at the very end of the file). The +restart command must be changed to use rsyslogd's rc script.</p> +<p>Also, if you use klogd together with rsyslogd (under most Linuxes you will do +that), you need to make sure that klogd is restarted after rsyslogd is restarted. +So it might be a good idea to put a klogd reload-or-restart command right after +the rsyslogd command in your daily script. This can save you lots of troubles.</p> +<h3>Done</h3> +<p>This concludes the steps necessary to install rsyslogd. Of course, it is +always a good idea to test everything thoroughly. At a minimalist level, you +should do a reboot and after that check if everything has come up correctly. Pay +attention not only to running processes, but also check if the log files (or the +database) are correctly being populated.</p> +<p>If rsyslogd encounters any serious errors during startup, you should be able +to see them at least on the system console. They might not be in log file, as +errors might occur before the log file rules are in place. So it is always a +good idea to check system console output when things don't go smooth. In some +rare cases, enabling debug logging (-d option) in rsyslogd can be helpful. If +all fails, go to <a href="http://www.rsyslog.com">www.rsyslog.com</a> and check +the forum or mailing list for help with your issue.</p> +<h2>Housekeeping stuff</h2> +<p>This section and its subsections contain all these nice things that you +usually need to read only if you are really curios ;)</p> +<h3>Feedback requested</h3> +<P>I would appreciate feedback on this tutorial. It is still in its infancy, so additional ideas, +comments or bug sighting reports are very welcome. Please +<a href="mailto:rgerhards@adiscon.com">let me know</a> about them.</P> +<h3>Revision History</h3> +<ul> + <li>2005-08-08 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> * Initial + version created</li> + <li>2005-08-09 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * updated to include distro-specific directories, which are now mandatory</li> + <li>2005-09-06 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * added information on log rotation scripts</li> + <li>2007-07-13 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * updated to new autotools-based build system</li> + <li>2008-10-01 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * added info on building from source repository</li> +</ul> +<h3>Copyright</h3> +<p>Copyright © 2005-2008 +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 1.2 or higher.</font></p> +</body> +</html> diff --git a/doc/ipv6.html b/doc/ipv6.html new file mode 100644 index 00000000..67c8e1fc --- /dev/null +++ b/doc/ipv6.html @@ -0,0 +1,48 @@ +<html> +<head> +<title>Notes on IPv6 Handling in Rsyslog</title> +</head> +<body> +<h1>Notes on IPv6 Handling in Rsyslog</h1> +<p><b>Rsyslog fully* supports sending and receiving syslog messages via both +IPv4 and IPv6.</b> IPv6 is natively supported for both UDP and TCP. However, +there are some options that control handling of IPv6 operations. I thought it is +is a good idea to elaborate a little about them, so that you can probably find +your way somewhat easier.</p> +<p>First of all, you can restrict rsyslog to using IPv4 or IPv6 addresses only +by specifying the -4 or -6 command line option (now guess which one does +what...). If you do not provide any command line option, rsyslog uses IPv4 and +IPv6 addresses concurrently. In practice, that means the listener binds to both +addresses (provided they are configured). When sending syslog messages, rsyslog +uses IPv4 addresses when the receiver can be reached via IPv4 and IPv6 addresses +if it can be reached via IPv6. If it can be reached on either IPv4 and v6, +rsyslog leaves the choice to the socket layer. The important point to know is +that it uses whatever connectivity is available to reach the destination.</p> +<p><b>There is one subtle difference between UDP and TCP.</b> With the new +IPv4/v6 ignorant code, rsyslog has potentially different ways to reach +destinations. The socket layer returns all of these paths in a sorted array. +For TCP, rsyslog loops through this array until a successful TCP connect can be +made. If that happens, the other addresses are ignored and messages are sent via +the successfully-connected socket.</p> +<p>For UDP, there is no such definite success indicator. Sure, the socket layer +may detect some errors, but it may not notice other errors (due to the +unreliable nature of UDP). By default, the UDP sender also tries one entry after +the other in the sorted array of destination addresses. When a send fails, the +next address is tried. When the send function finally succeeds, rsyslogd assumes +the UDP packet has reached its final destination. However, if rsyslogd is +started with the "-A" (capital A!) was given on the command line, rsyslogd will +continue to send messages until the end of the destination address array is +reached. This may result in duplicate messages, but it also provides some +additional reliability in case a message could not be received. You need to be +sure about the implications before applying this option. In general, it is NOT +recommended to use the -A option.</p> +<p><i><b>*</b>rsyslog does not support RFC 3195 over IPv6. The reason is that +the RFC 3195 library, <a href="http://www.liblogging.org/">liblogging</a>, +supports IPv4, only. Currently, there are no plans to update either rsyslog to +another RFC 3195 stack or update liblogging. There is simply no demand for 3195 +solutions.</i></p> +<p><font size="2">Last Updated: 2007-07-02<br> +Copyright © 2007 by Rainer Gerhards, released under the GNU GPL V2 or later.</font></p> + +</body> +</html> diff --git a/doc/licensing.html b/doc/licensing.html new file mode 100644 index 00000000..93a50930 --- /dev/null +++ b/doc/licensing.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>rsyslog licensing</title> + +</head> +<body> +<h1>rsyslog licensing</h1> +<p><b>Most important things first: if you intend to use rsyslog inside a GPLv3 compatible project, you are free to do so.</b> You don't even need to continue reading. +If you intend to use rsyslog inside a non-GPLv3 +compatible project, rsyslog offers you some liberties to do that, too. However, you then need +to study the licensing details in depth. +<p>The project hopes this is a good compromise, which also gives a boost to fellow free +software developers who release under GPLv3. +<p>And now on to the dirty and boring license details, still on a executive summary level. For the +real details, check source files and the files COPYING and COPYING.LESSER inside the distribution. +<p>The rsyslog package contains several components: +<ul> +<li>the rsyslog core programs (like rsyslogd) +<li>plugins (like imklog, omrelp, ...) +<li>the rsyslog runtime library +</ul> +<p>Each of these components can be thought of as individual projects. In fact, some of the +plugins have different main authors than the rest of the rsyslog package. All of these +components are currently put together into a single "rsyslog" package (tarball) for +convinience: this makes it easier to distribute a consistent version where everything +is included (and in the right versions) to build a full system. Platform package +maintainers in general take the overall package and split off the individual components, so that +users can install only what they need. In source installations, this can be done via the +proper ./configure switches. +<p>However, while it is convenient to package all parts in a single tarball, it does not +imply all of them are necessarily covered by the same license. Traditionally, GPL licenses +are used for rsyslog, because the project would like to provide free software. GPLv3 has been +used since around 2008 to help fight for our freedom. All rsyslog core programs are +released under GPLv3. But, from the beginning on, plugins were separate projects and we did not +impose and license restrictions on them. So even though all plugins that currently ship with +the rsyslog package are also placed under GPLv3, this can not taken for granted. You need +to check each plugins license terms if in question - this is especially important for +plugins that do NOT ship as part of the rsyslog tarball. +<p>In order to make rsyslog technology available to a broader range of applications, +the rsyslog runtime is, at least partly, licensed under LGPL. If in doubt, check the source file +licensing comments. As of now, the following files are licensed under LGPL: +<ul> +<li>queue.c/.h +<li>wti.c/.h +<li>wtp.c/.h +<li>vm.c/.h +<li>vmop.c/.h +<li>vmprg.c/.h +<li>vmstk.c/.h +<li>expr.c/.h +<li>sysvar.c/.h +<li>ctok.c/.h +<li>ctok_token.c/.h +<li>regexp.c/.h +<li>sync.c/.h +<li>stream.c/.h +<li>var.c/.h +</ul> +This list will change as time of the runtime modularization. At some point in the future, there will +be a well-designed set of files inside a runtime library branch and all of these will be LGPL. Some +select extras will probably still be covered by GPL. We are following a similar licensing +model in GnuTLS, which makes effort to reserve some functionality exclusively to open source +projects. +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Last Update: 2008-04-15. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/log_rotation_fix_size.html b/doc/log_rotation_fix_size.html new file mode 100644 index 00000000..51edf033 --- /dev/null +++ b/doc/log_rotation_fix_size.html @@ -0,0 +1,69 @@ +<html><head> +<title>Keep the log file size accurate with log rotation</title> +<meta name="KEYWORDS" content="log rotation, howto, guide, fixed-size log"> +</head> +<body> +<a href="rsyslog_conf_output.html">back</a> + +<h1>Log rotation with rsyslog</h1> + <P><small><i>Written by + Michael Meckelein</i></small></P> +<h2>Situation</h2> + +<p>Your environment does not allow you to store tons of logs? +You have limited disc space available for logging, for example +you want to log to a 124 MB RAM usb stick? Or you do not want to +keep all the logs for months, logs from the last days is sufficient? +Think about log rotation.</p> + +<h2>Log rotation based on a fixed log size</h2> + +<p>This small but hopefully useful article will show you the way +to keep your logs at a given size. The following sample is based on +rsyslog illustrating a simple but effective log rotation with a +maximum size condition.</p> + +<h2>Use Output Channels for fixed-length syslog files</h2> + +<p>Lets assume you do not want to spend more than 100 MB hard +disc space for you logs. With rsyslog you can configure Output +Channels to achieve this. Putting the following directive</p> + +<p><pre> +# start log rotation via outchannel +# outchannel definiation +$outchannel log_rotation,/var/log/log_rotation.log, 52428800,/home/me/./log_rotation_script +# activate the channel and log everything to it +*.* :omfile:$log_rotation +# end log rotation via outchannel +</pre></p> + +<p>to ryslog.conf instruct rsyslog to log everything to the destination file +'/var/log/log_rotation.log' until the give file size of 50 MB is reached. If +the max file size is reached it will perform an action. In our case it executes +the script /home/me/log_rotation_script which contains a single command:</p> + +<p><pre> +mv -f /var/log/log_rotation.log /var/log/log_rotation.log.1 +</p></pre> + +<p>This moves the original log to a kind of backup log file. +After the action was successfully performed rsyslog creates a new /var/log/log_rotation.log +file and fill it up with new logs. So the latest logs are always in log_roatation.log.</p> + +<h2>Conclusion</h2> + +<p>With this approach two files for logging are used, each with a maximum size of 50 MB. So +we can say we have successfully configured a log rotation which satisfies our requirement. +We keep the logs at a fixed-size level of100 MB.</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body> +</html> diff --git a/doc/lookup_tables.html b/doc/lookup_tables.html new file mode 100644 index 00000000..d72810f1 --- /dev/null +++ b/doc/lookup_tables.html @@ -0,0 +1,205 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>Lookup Tables</title> +</head> + +<body> +<h1>Lookup Tables</h1> + +<p><b><font color="red">NOTE: this is</font> proposed functionality, which is +<font color="red">NOT YET IMPLEMENTED</font>!</b> + +<p><b>Lookup tables</a> are a powerful construct +to obtain "class" information based on message content (e.g. to build +log file names for different server types, departments or remote +offices).</b> +<p>The base idea is to use a message variable as an index into a table which then +returns another value. For example, $fromhost-ip could be used as an index, with +the table value representing the type of server or the department or remote office +it is located in. A main point with lookup tables is that the lookup is very fast. +So while lookup tables can be emulated with if-elseif constructs, they are generally +much faster. Also, it is possible to reload lookup tables during rsyslog runtime without +the need for a full restart. +<p>The lookup tables itself exists in a separate configuration file (one per table). This +file is loaded on rsyslog startup and when a reload is requested. +<p>There are different types of lookup tables: +<ul> +<li><b>string</b> - the value to be looked up is an arbitrary string. Only exact +some strings match. +<li><b>array</b> - the value to be looked up is an integer number from a consequtive set. +The set does not need to start at zero or one, but there must be no number missing. So, for example +5,6,7,8,9 would be a valid set of index values, while 1,2,4,5 would not be (due to missing +2). +A match happens if the requested number is present. +<li><b>sparseArray</b> - the value to be looked up is an integer value, but there may +be gaps inside the set of values (usually there are large gaps). A typical use case would +be the matching of IPv4 address information. A match happens on the first value that is +less than or equal to the requested value. +</ul> +<p>Note that index integer numbers are represented by unsigned 32 bits. +<p>Lookup tables can be access via the lookup() built-in function. The core idea is to +set a local variable to the lookup result and later on use that local variable in templates. +<p>More details on usage now follow. +<h2>Lookup Table File Format</h2> +<p>Lookup table files contain a single JSON object. This object contains of a header and a +table part. +<h3>Header</h3> +<p>The header is the top-level json. It has paramters "version", "nomatch", and "type". +The version parameter +must be given and must always be one for this version of rsyslog. The nomatch +parameter is optional. If specified, it contains the value to be used if lookup() +is provided an index value for which no entry exists. The default for +"nomatch" is the empty string. Type specifies the type of lookup to be done. +<h3>Table</h3> +This must be an array of elements, even if only a single value exists (for obvious +reasons, we do not expect this to occur often). Each array element must contain two +fields "index" and "value". +<h3>Example</h3> +<p>This is a sample of how an ip-to-office mapping may look like: +<pre> +{ "version":1, "nomatch":"unk", "type":"string", + "table":[ {"index":"10.0.1.1", "value":"A" }, + {"index":"10.0.1.2", "value":"A" }, + {"index":"10.0.1.3", "value":"A" }, + {"index":"10.0.2.1", "value":"B" }, + {"index":"10.0.2.2", "value":"B" }, + {"index":"10.0.2.3", "value":"B" } + ] +} +</pre> +Note: if a different IP comes in, the value "unk" +is returend thanks to the nomatch parameter in +the first line. +<p> +<h2>RainerScript Statements</h2> +<h3>lookup_table() Object</h3> +<p>This statement defines and intially loads a lookup table. Its format is +as follows: +<pre> +lookup_table(name="name" file="/path/to/file" reloadOnHUP="on|off") +</pre> +<h4>Parameters</h4> +<ul> + <li><b>name</b> (mandatory)<br> + Defines the name of lookup table for further reference + inside the configuration. Names must be unique. Note that + it is possible, though not advisible, to have different + names for the same file. + <li><b>file</b> (mandatory)<br> + Specifies the full path for the lookup table file. This file + must be readable for the user rsyslog is run under (important + when dropping privileges). It must point to a valid lookup + table file as described above. + <li><b>reloadOnHUP</b> (optional, default "on")<br> + Specifies if the table shall automatically be reloaded + as part of HUP processing. For static tables, the + default is "off" and specifying "on" triggers an + error message. Note that the default of "on" may be + somewhat suboptimal performance-wise, but probably + is what the user intuitively expects. Turn it off + if you know that you do not need the automatic + reload capability. +</ul> + +<h3>lookup() Function</h3> +<p>This function is used to actually do the table lookup. Format: +<pre> +lookup_table("name", indexvalue) +</pre> +<h4>Parameters</h4> +<ul> + <li><b>return value</b><br> + The function returns the string that is associated with the + given indexvalue. If the indexvalue is not present inside the + lookup table, the "nomatch" string is returned (or an empty string + if it is not defined). + <li><b>name</b> (constant string)<br> + The lookup table to be used. Note that this must be specificed as a + constant. In theory, variable table names could be made possible, but + their runtime behaviour is not as good as for static names, and we do + not (yet) see good use cases where dynamic table names could be useful. + <li><b>indexvalue</b> (expression)<br> + The value to be looked up. While this is an arbitrary RainerScript expression, + it's final value is always converted to a string in order to conduct + the lookup. For example, "lookup(table, 3+4)" would be exactly the same + as "lookup(table, "7")". In most cases, indexvalue will probably be + a single variable, but it could also be the result of all RainerScript-supported + expression types (like string concatenation or substring extraction). + Valid samples are "lookup(name, $fromhost-ip & $hostname)" or + "lookup(name, substr($fromhost-ip, 0, 5))" as well as of course the + usual "lookup(table, $fromhost-ip)". +</ul> + + +<h3>load_lookup_table Statement</h3> + +<p><b>Note: in the final implementation, this MAY be implemented as an action. +This is a low-level decesion that must be made during the detail development +process. Parameters and semantics will remain the same of this happens.</b> + +<p>This statement is used to reload a lookup table. It will fail if +the table is static. While this statement is executed, lookups to this table +are temporarily blocked. So for large tables, there may be a slight performance +hit during the load phase. It is assume that always a triggering condition +is used to load the table. +<pre> +load_lookup_table(name="name" errOnFail="on|off" valueOnFail="value") +</pre> +<h4>Parameters</h4> +<ul> + <li><b>name</b> (string)<br> + The lookup table to be used. + <li><b>errOnFail</b> (boolean, default "on")<br> + Specifies whether or not an error message is to be emitted if + there are any problems reloading the lookup table. + <li><b>valueOnFail</b> (optional, string)<br> + This parameter affects processing if the lookup table cannot + be loaded for some reason: If the parameter is not present, + the previous table will be kept in use. If the parameter is + given, the previous table will no longer be used, and instead + an empty table be with nomath=valueOnFail be generated. In short, + that means when the parameter is set and the reload fails, + all matches will always return what is specified in valueOnFail. +</ul> + +<h3>Usage example</h3> +<p>For clarity, we show only those parts of rsyslog.conf that affect +lookup tables. We use the remote office example that an example lookup +table file is given above for. +<pre> +lookup_table(name="ip2office" file="/path/to/ipoffice.lu" + reloadOnHUP="off") + + +template(name="depfile" type="string" + string="/var/log/%$usr.dep%/messages") + +set $usr.dep = lookup("ip2office", $fromhost-ip); +action(type="omfile" dynfile="depfile") + +# support for reload "commands" +if $fromhost-ip == "10.0.1.123" + and $msg contains "reload office lookup table" + then + load_lookup_table(name="ip2office" errOnFail="on") +</pre> + +<p>Note: for performance reasons, it makes sense to put the reload command into +a dedicated ruleset, bound to a specific listener - which than should also +be sufficiently secured, e.g. via TLS mutual auth. + +<h2>Implementation Details</h2> +<p>The lookup table functionality is implemented via highly efficient algorithms. +The string lookup is based on a parse tree and has O(1) time complexity. The array +lookup is also O(1). In case of sparseArray, we have O(log n). +<p>To preserve space and, more important, increase cache hit performance, equal +data values are only stored once, no matter how often a lookup index points to them. +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/manual.html b/doc/manual.html new file mode 100644 index 00000000..8cb519a5 --- /dev/null +++ b/doc/manual.html @@ -0,0 +1,123 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog documentation</title></head> +<body> +<h1>RSyslog - Documentation</h1> +<p><b><a href="http://www.rsyslog.com/">Rsyslog</a> +is an enhanced syslogd +supporting, among others, <a href="rsyslog_mysql.html">MySQL</a>, +PostgreSQL, <a href="http://wiki.rsyslog.com/index.php/FailoverSyslogServer">failover +log destinations</a>, syslog/tcp, fine grain output format +control, high precision timestamps, queued operations and the ability to filter on any message +part.</b> +It is quite compatible to stock sysklogd and can be used as a drop-in +replacement. Its <a href="features.html"> +advanced features</a> make it suitable for enterprise-class, <a href="rsyslog_tls.html">encryption protected syslog</a> +relay chains while at the same time being very easy to setup for the +novice user. And as we know what enterprise users really need, there are +also <a href="http://www.rsyslog.com/professional-services"> rsyslog +professional services</a> available directly from the source!</p> +<p><b>Please visit the <a href="http://www.rsyslog.com/sponsors">rsyslog sponsor's page</a> +to honor the project sponsors or become one yourself!</b> We are very grateful for any help towards the +project goals.</p> +<p><b>This documentation is for version 7.5.0 (devel branch) of rsyslog.</b> +Visit the <i><a href="http://www.rsyslog.com/status">rsyslog status page</a></i></b> +to obtain current version information and project status. +</p><p><b>If you like rsyslog, you might +want to lend us a helping hand. </b>It doesn't require a lot of +time - even a single mouse click helps. Learn <a href="how2help.html">how to help the rsyslog project</a>. +Due to popular demand, there is now a <a href="rsyslog_ng_comparison.html">side-by-side comparison +between rsyslog and syslog-ng</a>.</p> +<p>If you are upgrading from rsyslog v2 or stock sysklogd, +<a href="v3compatibility.html">be sure to read the rsyslog v3 compatibility notes</a>, +and if you are upgrading from v3, read the +<a href="v4compatibility.html">rsyslog v4 compatibility notes</a>, +if you upgrade from v4, read the +<a href="v5compatibility.html">rsyslog v5 compatibility notes</a>, and +if you upgrade from v5, read the +<a href="v6compatibility.html">rsyslog v6 compatibility notes</a>. +if you upgrade from v6, read the +<a href="v7compatibility.html">rsyslog v7 compatibility notes</a>. +<p>Rsyslog will work even +if you do not read the doc, but doing so will definitely improve your experience.</p> +<p><b>Follow the links below for the</b></p> +<ul> +<li><a href="troubleshoot.html">troubleshooting rsyslog problems</a></li> +<li><a href="rsyslog_conf.html">configuration file format (rsyslog.conf)</a></li> +<li><a href="http://www.rsyslog.com/tool-regex">a regular expression checker/generator tool for rsyslog</a></li> +<li> <a href="property_replacer.html">property replacer, an important core component</a></li> +<li><a href="bugs.html">rsyslog bug list</a></li> +<li><a href="messageparser.html">understanding rsyslog message parsers</a></li> +<li><a href="generic_design.html">backgrounder on generic syslog application design</a></li> +<li><a href="modules.html">description of rsyslog modules</a></li> +<li><a href="rsyslog_packages.html">rsyslog packages</a></li> +</ul> +<p><b>To keep current on rsyslog development, follow +<a href="http://twitter.com/rgerhards">Rainer's twitter feed</a>.</b></p> +<p><b>We have some in-depth papers on</b></p> +<ul> +<li><a href="install.html">installing rsyslog</a></li> +<li><a href="build_from_repo.html">obtaining rsyslog from the source repository</a></li> +<li><a href="ipv6.html">rsyslog and IPv6</a> (which is fully supported)</li> +<li><a href="rsyslog_secure_tls.html">native TLS encryption for syslog</a></li> +<li><a href="multi_ruleset.html">using multiple rule sets in rsyslog</a></li> +<li><a href="rsyslog_stunnel.html">ssl-encrypting syslog with stunnel</a></li> +<li><a href="rsyslog_mysql.html">writing syslog messages to MySQL (and other databases as well)</a></li> +<li><a href="rsyslog_pgsql.html">writing syslog messages to PostgreSQL (and other databases as well)</a></li> +<li><a href="rsyslog_high_database_rate.html">writing massive amounts of syslog messages to a database</a></li> +<li><a href="rsyslog_reliable_forwarding.html">reliable forwarding to a remote server</a></li> +<li><a href="rsyslog_php_syslog_ng.html">using +php-syslog-ng with rsyslog</a></li> +<li><a href="rsyslog_recording_pri.html">recording +the syslog priority (severity and facility) to the log file</a></li> +<li><a href="http://www.rsyslog.com/Article19.phtml">preserving +syslog sender over NAT</a> (online only)</li> +<li><a href="gssapi.html">an overview and howto of rsyslog gssapi support</a></li> +<li><a href="debug.html">debug support in rsyslog</a></li> +<li>Developer Documentation + <ul> + <li><a href="build_from_repo.html">building rsyslog from the source repository</a></li> + <li><a href="dev_oplugins.html">writing rsyslog output plugins</a></li> + <li><a href="dev_queue.html">the rsyslog message queue object (developer's view)</a></li> + </ul></li> +</ul> +<p>Our <a href="history.html">rsyslog history</a> +page is for you if you would like to learn a little more +on why there is an rsyslog at all. If you are interested why you should +care about rsyslog at all, you may want to read Rainer's essay on "<a href="http://rgerhards.blogspot.com/2007/08/why-does-world-need-another-syslogd.html">why +the world needs another syslogd</a>".</p> +<p>Documentation is added continuously. Please note that the +documentation here +matches only the current version of rsyslog. If you use an older +version, be sure to use the doc that came with it.</p> +<p><b>You can also browse the following online resources:</b></p> +<ul> +<li>the <a href="http://wiki.rsyslog.com/">rsyslog +wiki</a>, a community resource which includes <a href="http://wiki.rsyslog.com/index.php/Configuration_Samples">rsyslog configuration examples</a></li> +<li><a href="http://www.rsyslog.com/module-Static_Docs-view-f-manual.html.phtml">rsyslog +online documentation (most current version only)</a></li> + +<li><a href="http://kb.monitorware.com/rsyslog-f40.html">rsyslog discussion forum - use this for technical support</a></li> +<li><a href="http://www.rsyslog.com/Topic8.phtml">rsyslog video tutorials</a></li> +<li><a href="http://www.rsyslog.com/Topic4.phtml">rsyslog change log</a></li> +<li><a href="http://www.rsyslog.com/Topic3.phtml">rsyslog FAQ</a></li> +<li><a href="http://www.monitorware.com/en/syslog-enabled-products/">syslog device configuration guide</a> (off-site)</li> +<li><a href="http://www.rsyslog.com/PNphpBB2.phtml">rsyslog discussion forum - use this for technical support</a></li> +<li><a href="http://kb.monitorware.com/rsyslog-f49.html">deutsches rsyslog forum</a> (forum in German language)</li> +</ul> +<p>And don't forget about the <a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">rsyslog +mailing list</a>. If you are interested in the "backstage", you +may find +<a href="http://www.gerhards.net/rainer">Rainer</a>'s +<a href="http://blog.gerhards.net/">blog</a> an +interesting read (filter on syslog and rsyslog tags). +Or meet <a href="http://www.facebook.com/people/Rainer-Gerhards/1349393098">Rainer Gerhards at Facebook</a> +or <a href="https://plus.google.com/112402185904751517878/posts">Google+</a>. +If you would like to use rsyslog source code inside your open source project, you can do that without +any restriction as long as your license is GPLv3 compatible. If your license is incompatible to GPLv3, +you may even be still permitted to use rsyslog source code. However, then you need to look at the way +<a href="licensing.html">rsyslog is licensed</a>.</p> +<p>Feedback is always welcome, but if you have a support question, please do not +mail Rainer directly (<a href="free_support.html">why not?</a>) - use the +<a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">rsyslog mailing list</a> +or <a href="http://kb.monitorware.com/rsyslog-f40.html">rsyslog formum</a> instead. +</body></html> diff --git a/doc/messageparser.html b/doc/messageparser.html new file mode 100644 index 00000000..370db59f --- /dev/null +++ b/doc/messageparser.html @@ -0,0 +1,222 @@ +<html> +<head> +<title>Message parsers in rsyslog</title> +</head> +<body> +<a href="manual.html">rsyslog documentation</a> + +<h1>Message parsers in rsyslog</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2009-11-06)</i></small></p> +<h2>Intro</h2> +<p>Message parsers are a feature of rsyslog 5.3.4 and above. In this article, I describe what +message parsers are, what they can do and how they relate to the relevant standards. I will +also describe what you can not do with time. Finally, I give some advice on implementing your +own custom parser. + +<h2>What are message parsers?</h2> +<p>Well, the quick answer is that message parsers are the component of rsyslog that +parses the syslog message after it is being received. Prior to rsyslog 5.3.4, message parsers +where built in into the rsyslog core itself and could not be modified (other than by modifying +the rsyslog code). +<p>In 5.3.4, we changed that: message parsers are now loadable modules (just +like input and output modules). That means that new message parsers can be added without +modifying the rsyslog core, even without contributing something back to the +project. +<p>But that doesn't answer what a message parser really is. What does ist mean to "parse a +message" and, maybe more importantly, what is a message? To answer these questions correctly, +we need to dig down into the relevant standards. +<a href="http://tools.ietf.org/html/rfc5424">RFC5424</a> specifies a layered architecture +for the syslog protocol: +<p align="center"><img src="rfc5424layers.png" alt="RFC5424 syslog protocol layers"> +<p>For us important is the distinction between the syslog transport and the upper layers. +The transport layer specifies how a stream of messages is assembled at the sender side and +how this stream of messages is disassembled into the individual messages at the receiver +side. In networking terminology, this is called "framing". The core idea is that +each message is put into a so-called "frame", which then is transmitted over the communications +link. +<p>The framing used is depending on the protocol. For example, in UDP the "frame"-equivalent is +a packet that is being sent (this also means that no two messages can travel within a single +UDP packet). In "plain tcp syslog", the industry standard, LF is used as a frame delimiter +(which also means that no multi-line message can properly be transmitted, a "design" flaw +in plain tcp syslog). In <a href="http://tools.ietf.org/html/rfc5425">RFC5425</a> there is +a header in front of each frame that contains the size of the message. With this framing, +any message content can properly be transferred. +<p>And now comes the important part: <b>message parsers do NOT operate at the transport +layer</b>, they operate, as their name implies, on messages. So we can not use message +parsers to change the underlying framing. For example, if a sender splits (for whatever +reason) a single message into two and encapsulates these into two frames, there is no way +a message parser could undo that. +<p>A typical example may be a multi-line message: let's assume some originator has generated +a message for the format "A\nB" (where \n means LF). If that message is being transmitted +via plain tcp syslog, the frame delimiter is LF. So the sender will delimite the frame with +LF, but otherwise send the message unmodified onto the wire (because that is how things are +-unfortunately- done in plain tcp syslog...). So wire will see "A\nB\n". When this +arrives at the receiver, the transport layer will undo the framing. When it sees the LF +after A, it thinks it finds a valid frame delimiter (in fact, this is the correct view!). So +the receive will extract one complete message A and one complete message B, not knowing +that they once were both part of a large multi-line message. These two messages are then +passed to the upper layers, where the message parsers receive them and extract information. +However, the message parsers never know (or even have a chance to see) that A and B +belonged together. Even further, in rsyslog there is no guarnatee that A will be parsed +before B - concurrent operations may cause the reverse order (and do so very validly). +<p>The important lesson is: <b>message parsers can not be used to fix a broken framing</b>. +You need a full protocol implementation to do that, what is the domain of input and +output modules. +<p>I have now told you what you can not do with message parsers. But what they are good for? +Thankfully, broken framing is not the primary problem of the syslog world. A wealth of different +formats is. Unfortunately, many real-world implementations violate the relevant standards +in one way or another. That makes it often very hard to extract meaningful information from +a message or to process messages from different sources by the same rules. In my article +<a href="syslog_parsing.html">syslog parsing in rsyslog</a> I have elaborated on all +the real-world evil that you can usually see. So I won't repeat that here. But in short, the +real problem is not the framing, but how to make malformed messages well-looking. +<p><b>This is what message parsers permit you to do: take a (well-known) malformed message, parse +it according to its semantics and generate perfectly valid internal message representations +from it.</b> So as long as messages are consistenly in the same wrong format (and they usually +are!), a message parser can look at that format, parse it, and make the message processable just +like it were wellformed in the first place. Plus, one can abuse the interface to do some other +"intersting" tricks, but that would take us to far. +<p>While this functionality may not sound exciting, it actually solves a very big issue (that you +only really understand if you have managed a system with various different syslog sources). +Note that we were often able to process malformed messages in the past with the help of the +property replacer and regular expressions. While this is nice, it has a performance hit. A +message parser is a C code, compiled to native language, and thus typically much faster than +any regular expression based method (depending, of course, on the quality of the implementation...). + +<h2>How are message parsers used?</h2> +<p>In a simlified view, rsyslog +<ol> +<li>first receives messages (via the input module), +<li><i>then parses them (at the message level!)</i> and +<li>then processes them (operating on the internal message representation). +</ol> +Message parsers are utilized in the second step (written in italics). +Thus, they take the raw message (NOT frame!) received from the remote system and create +the internal structure out of it that the other parts of rsyslog need in order to perform +their processing. Parsing is vital, because an unparsed message can not be processed in the +third stage, the actual application-level processing (like forwarding or writing to files). +<h3>Parser Chains and how they Operate</h3> +Rsyslog chains parsers together to provide flexibility. +A <b>parser chain</b> +contains all parsers that can potentially be used to parse a message. +It is assumed that there is some +way a parser can detect if the message it is being presented is supported by it. If so, the parser +will tell the rsyslog engine and parse the message. The rsyslog engine now calls each parser +inside the chain (in sequence!) until the first parser is able to parse the message. After one +parser has been found, the message is considered parsed and no others parsers are called on that +message. +<p>Side-note: this method implies there are some "not-so-dirty" tricks available to modify +the message by a parser module that declares itself as "unable to parse" but still does +some message modification. This was not a primary design goal, but may be utilized, and the +interface probably extended, to support generic filter modules. These would need to go +to the root of the parser chain. As mentioned, the current system already supports this. +<p>The position inside the parser chain can be thought of as a priority: parser sitting +earlier in the chain take precedence over those sitting later in it. So more specific +parser should go ealier in the chain. A good example of how this works is the default parser +set provided by rsyslog: rsyslog.rfc5424 and rsyslog.rfc3164, each one parses according to the +rfc that has named it. RFC5424 was designed to be distinguishable from RFC3164 message by the +sequence "1 " immediately after the so-called PRI-part (don't worry about these words, it is +sufficient if you understand there is a well-defined sequence used to indentify RFC5424 +messages). In contrary, RFC3164 actually permits everything as a valid message. Thus the +RFC3164 parser will always parse a message, sometimes with quite unexpected outcome (there is +a lot of guesswork involved in that parser, which unfortunately is unavoidable due to +existing techology limits). So the default parser chain is to try the RFC5424 parser first +and after it the RFC3164 parser. If we have a 5424-formatted message, that parser will +identify and parse it and the rsyslog engine will stop processing. But if we receive a +legacy syslog message, the RFC5424 will detect that it can not parse it, return this status +to the engine which then calls the next parser inside the chain. That usually happens to be +the RFC3164 parser, which will always process the message. But there could also be any other +parser inside the chain, and then each one would be called unless one that is able to parse +can be found. +<p>If we reversed the parser order, RFC5424 messages would incorrectly parsed. Why? Because the +RFC3164 parser will always parse every message, so if it were asked first, it would parse +(and misinterpret) the 5424-formatted message, return it did so and the rsyslog engine would +never call the 5424 parser. So oder of sequence is very important. +<p>What happens if no parser in the chain could parse a message? Well, then we could not +obtain the in-memory representation that is needed to further process the message. In that +case, rsyslog has no other choice than to discard the message. If it does so, it will emit +a warning message, but only in the first 1,000 incidents. This limit is a safety measure +against message-loops, which otherwise could quickly result from a parser chain +misconfiguration. <b>If you do not tolerate loss of unparsable messages, you must ensure +that each message can be parsed.</b> You can easily achive this by always using the +"rsyslog-rfc3164" parser as the <i>last</i> parser inside parser chains. That may result +in invalid parsing, but you will have a chance to see the invalid message (in debug mode, +a warning message will be written to the debug log each time a message is dropped due to +inability to parse it). +<h3>Where are parser chains used?</h3> +<p>We now know what parser chains are and how they operate. The question is now how many +parser chains can be active and how it is decicded which parser chain is used on which message. +This is controlled via <a href="multi_ruleset.html">rsyslog's rulesets</a>. In short, multiple +rulesets can be defined and there always exist at least one ruleset (for specifcs, follow +the <a href="multi_ruleset.html">link</a>). A parser chain is bound to a specific ruleset. +This is done by virtue of defining parsers via the +<a href="rsconf1_rulesetparser.html">$RulesetParser</a> configuration directive (for specifics, +see there). If no such directive is specified, the default parser chain is used. As of this +writing, the default parser chain always consists of "rsyslog.rfc5424", "rsyslog.rfc3164", in +that order. As soon as a parser is configured, the default list is cleared and the new parser +is added to the end of the (initially empty) ruleset's parser chain. +<p>The important point to know is that parser chains are defined on a per-ruleset basis. +<h3>Can I use different parser chains for different devices?</h3> +<p>The correct answer is: generally yes, but it depends. First of all, remember that input +modules (and specific listeners) may be bound to specific rulesets. As parser chains "reside" +in rulesets, binding to a ruleset also binds to the parser chain that is bound to that ruleset. +As a number one prequisite, the input module must support binding to different rulesets. Not +all do, but their number is growing. For example, the important +<a href="imudp.html">imudp</a> and <a href="imtcp.html">imtcp</a> input modules support +that functionality. Those that do not (for example <a href="im3195">im3195</a>) can only +utilize the default ruleset and thus the parser chain defined in that ruleset. +<p>If you do not know if the input module in question supports ruleset binding, check +its documentation page. Those that support it have the requiered directives. +<p>Note that it is currently under evaluation if rsyslog will support binding parser chains +to specific inputs directly, without depending on the ruleset. There are some concerns that +this may not be necessary but adds considerable complexity to the configuration. So this may +or may not be possible in the future. In any case, if we decide to add it, input modules +need to support it, so this functionality would require some time to implement. +<p>The coockbook recipe for using different parsers for different devices is given +as an actual in-depth example in the <a href="rscon1_rulesetsparser.html">$RulesetParser</a> +configuration directive doc page. In short, it is acomplished by defining specific rulesets +for the required parser chains, definining different listener ports for each of the devices +with different format and binding these listeners to the correct ruleset (and thus parser +chains). Using that approach, a variety of different message formats can be supported +via a single rsyslog instance. + +<h2>Which message parsers are available</h2> +<p>As of this writing, there exist only two message parsers, one for RFC5424 format and one for +legacy syslog (loosely described in +<a href="http://tools.ietf.org/html/rfc3164">RFC3164</a>). These parsers are built-in and +must not be explicitely loaded. However, message parsers can be added with relative ease +by anyone knowing to code in C. Then, they can be loaded via $ModLoad just like any +other loadable module. It is expected that the rsyslog project will be contributed additional +message parsers over time, so that at some point there hopefully is a rich choice of them +(I intend to add a browsable repository as soon as new parsers pop up). +<h3>How to write a message parser?</h3> +<p>As a prequisite, you need to know the exact format that the device is sending. Then, you need +moderate C coding skills, and a little bit of rsyslog internals. I guess the rsyslog specific part +should not be that hard, as almost all information can be gained from the existing parsers. They +are rather simple in structure and can be found under the "./tools" directory. They are named +pmrfc3164.c and pmrfc5424.c. You need to follow the usual loadable module guidelines. +It is my expectation that writing a parser should typically not take longer than a single +day, with maybe a day more to get aquainted with rsyslog. Of course, I am not sure if the number +is actually right. +<p>If you can not program or have no time to do it, Adiscon can also write a message parser +for you as +part of the <a href="http://www.rsyslog/professional-services">rsyslog professional services +offering</a>. +<h2>Conclusion</h2> +<p>Malformed syslog messages are a pain and unfortunately often seen in practice. Message parsers +provide a fast and efficient solution for this problem. Different parsers can be defined for +different devices, and they all convert message information into rsyslog's well-defined +internal format. Message parsers were first introduced in rsyslog 5.3.4 and also offer +some interesting ideas that may be explored in the future - up to full message normalization +capabilities. It is strongly recommended that anyone with a heterogenous environment take +a look at message parser capabilities. + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/mmanon.html b/doc/mmanon.html new file mode 100644 index 00000000..16065a1f --- /dev/null +++ b/doc/mmanon.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>IP Address Anonimization Module (mmanon)</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>IP Address Anonimization Module (mmanon)</h1> +<p><b>Module Name: mmanon</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Available since</b>: 7.3.7</p> +<p><b>Description</b>:</p> +<p>The mmanon module permits to anonymize IP addresses. It is a message +modification module that actually changes the IP address inside the message, +so after calling mmanon, the original message can no longer be obtained. +Note that anonymization will break digital signatures on the message, if +they exist. +<p><i>How are IP-Addresses defined?</i> +<p>We assume that an IP address consists of four octets in dotted notation, +where each of the octets has a value between 0 and 255, inclusively. After +the last octet, there must be either a space or a colon. So, for example, +"1.2.3.4 Test" and "1.2.3.4:514 Test" are detected as containing valid IP +addresses, whereas this is not the case for "1.2.300.4 Test" or +"1.2.3.4-Test". The message text may contain multiple addresses. If so, +each of them is anonimized (according to the same rules). +<b>Important:</b> We may change the set of acceptable characters after +the last octet in the future, if there are good reasons to do so. +<p> </p> + +<p><b>Module Configuration Parameters</b>:</p> +<p>Currently none. +<p> </p> +<p><b>Action Confguration Parameters</b>:</p> +<ul> +<li><b>mode</b> - default "rewrite"<br> +There exists the "simple" and "rewrite" mode. In simple mode, only octets +as whole can be anonymized and the length of the message is never changed. +This means that when the last three octets of the address 10.1.12.123 are +anonymized, the result will be 10.0.00.000. This means that the length of the +original octets is still visible and may be used to draw some privacy-evasive +conclusions. This mode is slightly faster than "overwrite" mode, and this +may matter in high throughput environments.<br> +The default "rewrite" mode will do full anonymization of any number of bits +and it will also normlize the address, so that no information about the +original IP address is available. So in the above example, 10.1.12.123 would +be anonymized to 10.0.0.0. +<li><b>ipv4.bits</b> - default 16<br> +This set the number of bits that should be anonymized (bits are from the +right, so lower bits are anonymized first). This setting permits to save +network information while still anonymizing user-specific data. The more +bits you discard, the better the anonymization obviously is. The default +of 16 bits reflects what German data privacy rules consider as being +sufficinetly anonymized. We assume, this can also be used as a rough +but conservative guideline for other countries.<br> +Note: when in simple mode, only bits on a byte boundary can be specified. +As such, any value other than 8, 16, 24 or 32 is invalid. If an invalid +value is given, it is rounded to the next byte boundary (so we favor stronger +anonymization in that case). For example, a bit value of 12 will become 16 in +simple mode (an error message is also emitted). +<li><b>replacementChar</b> - default "x"<br> +In simple mode, this sets the character +that the to-be-anonymized part of the IP address is to be overwritten +with. In rewrite mode, this parameter is <b>not permitted</b>, as in +this case we need not necessarily rewrite full octets. As such, the anonymized +part is always zero-filled and replacementChar is of no use. If it is +specified, an error message is emitted and the parameter ignored. +</ul> + +<p><b>Caveats/Known Bugs:</b> +<ul> +<li><b>only IPv4</b> is supported +</ul> + +<p><b>Samples:</b></p> +<p>In this snippet, we write one file without anonymization and another one +with the message anonymized. Note that once mmanon has run, access to the +original message is no longer possible (execept if stored in user +variables before anonymization). +<p><textarea rows="5" cols="60">module(load="mmanon") +action(type="omfile" file="/path/to/non-anon.log") +action(type="mmanon") +action(type="omfile" file="/path/to/anon.log") +</textarea> + +<p>This next snippet is almost identical to the first one, but +here we anonymize the full IPv4 address. Note that by +modifying the number of bits, you can anonymize different parts +of the address. Keep in mind that in simple mode (used here), the bit values +must match IP address bytes, so for IPv4 only the values 8, 16, 24 and +32 are valid. Also, in this example the replacement is done +via asterisks instead of lower-case "x"-letters. Also keep in mind that +"replacementChar" can only be set in simple mode. +<p><textarea rows="5" cols="60">module(load="mmanon") +action(type="omfile" file="/path/to/non-anon.log") +action(type="mmanon" ipv4.bits="32" mode="simple" replacementChar="*") +action(type="omfile" file="/path/to/anon.log") +</textarea> + +<p>The next snippet is also based on the first one, but anonimzes an +"odd" number of bits, 12. The value of 12 is used by some folks as a +compromise between keeping privacy and still permiting to gain some +more in-depth insight from log files. Note that anonymizing 12 bits +may be insufficient to fulfill legal requirements (if such exist). +<p><textarea rows="5" cols="60">module(load="mmanon") +action(type="omfile" file="/path/to/non-anon.log") +action(type="mmanon" ipv4.bits="12") +action(type="omfile" file="/path/to/anon.log") +</textarea> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/mmcount.html b/doc/mmcount.html new file mode 100644 index 00000000..1d06340d --- /dev/null +++ b/doc/mmcount.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>mmcount</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>mmcount</h1> +<p><b>Module Name: mmcount</b></p> +<p><b>Author: </b>Bala.FA <barumuga@redhat.com></p> +<p><b>Status: </b>Non project-supported module - contact author +or rsyslog mailing list for questions +<p><b>Available since</b>: 7.5.0</p> +<p><b>Description</b>:</p> +<p> +<pre> + mmcount: message modification plugin which counts messages + + This module provides the capability to count log messages by severity + or json property of given app-name. The count value is added into the + log message as json property named 'mmcount' + + Example usage of the module in the configuration file + + module(load="mmcount") + + # count each severity of appname gluster + action(type="mmcount" appname="gluster") + + # count each value of gf_code of appname gluster + action(type="mmcount" appname="glusterd" key="!gf_code") + + # count value 9999 of gf_code of appname gluster + action(type="mmcount" appname="glusterfsd" key="!gf_code" value="9999") + + # send email for every 50th mmcount + if $app-name == 'glusterfsd' and $!mmcount <> 0 and $!mmcount % 50 == 0 then { + $ActionMailSMTPServer smtp.example.com + $ActionMailFrom rsyslog@example.com + $ActionMailTo glusteradmin@example.com + $template mailSubject,"50th message of gf_code=9999 on %hostname%" + $template mailBody,"RSYSLOG Alert\r\nmsg='%msg%'" + $ActionMailSubject mailSubject + $ActionExecOnlyOnceEveryInterval 30 + :ommail:;RSYSLOG_SyslogProtocol23Format + } +</pre> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/mmjsonparse.html b/doc/mmjsonparse.html new file mode 100644 index 00000000..c2c862d7 --- /dev/null +++ b/doc/mmjsonparse.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>CEE/lumberjack JSON support Module (mmjsonparse)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Log Message Normalization Module</h1> +<p><b>Module Name: mmjsonparse</b></p> +<p><b>Available since: </b>6.6.0+ +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module provides support for parsing structured log messages +that follow the CEE/lumberjack spec. The so-called "CEE cookie" is checked +and, if present, the JSON-encoded structured message content is parsed. +The properties are than available as original message properties. +</p> +<p><b>Action specific Configuration Directives</b>:</p> +<p>currently none +<ul> +<p><b>Legacy Configuration Directives</b>:</p> +<p>none +<b>Caveats/Known Bugs:</b> +<p>None known at this time. +</ul> +<p><b>Sample:</b></p> +<p>This activates the module and applies normalization to all messages:<br> +</p> +<textarea rows="2" cols="60">module(load="mmjsonparse") +action(type="mmjsonparse") +</textarea> +<p>The same in legacy format:</p> +<textarea rows="2" cols="60">$ModLoad mmjsonparse +*.* :mmjsonparse: +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2012 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/mmnormalize.html b/doc/mmnormalize.html new file mode 100644 index 00000000..787bd957 --- /dev/null +++ b/doc/mmnormalize.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>Log Message Normalization Module (mmnormalize)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Log Message Normalization Module</h1> +<p><b>Module Name: mmnormalize</b></p> +<p><b>Available since: </b>6.1.2+ +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module provides the capability to normalize log messages via +<a href="http://www.liblognorm.com">liblognorm</a>. Thanks to liblognorm, unstructured text, +like usually found in log messages, can very quickly be parsed and put into +a normal form. This is done so quickly, that it should be possible +to normalize events in realtime. +<p>This module is implemented via the output module interface. This means that +mmnormalize should be called just like an action. After it has been called, +the normalized message properties are avaialable and can be accessed. These properties +are called the "CEE/lumberjack" properties, because liblognorm creates a format that is +inspired by the CEE/lumberjack approach. +<p><b>Please note:</b> CEE/lumberjack properties are different from regular properties. +They have always "$!" prepended to the property name given in the rulebase. Such a +property needs to be called with <b>%$!propertyname%</b>. +<p>Note that mmnormalize should only be called once on each message. Behaviour is +undefined if multiple calls to mmnormalize happen for the same message. +</p> +<p><b>Action Parameters</b>:</p> +<ul> +<li><b>ruleBase</b> [word]<br> +Specifies which rulebase file is to use. If there are +multiple mmnormalize instances, each one can use a different file. However, +a single instance can use only a single file. This parameter MUST be given, +because normalization can only happen based on a rulebase. It is recommended +that an absolute path name is given. Information on how to create the rulebase +can be found in the <a href="http://www.liblognorm.com/files/manual/index.html">liblognorm manual</a>. +<li><b>useRawMsg</b> [boolean]<br> +Specifies if the raw message should be used for normalization (on) or just the +MSG part of the message (off). Default is "off". +</ul> +<p><b>Legacy Configuration Directives</b>:</p> +<ul> +<li>$mmnormalizeRuleBase <rulebase-file> - equivalent to the "ruleBase" +parameter. +<li>$mmnormalizeUseRawMsg <on/off> - equivalent to the "useRawMsg" +parameter. +</ul> +<b>Caveats/Known Bugs:</b> +<p>None known at this time. +</ul> +<p><b>Sample:</b></p> +<p>This activates the module and applies normalization to all messages:<br> +</p> +<textarea rows="2" cols="60">module(load="mmnormalize") +action(type="mmnormalize" ruleBase="/path/to/rulebase.rb") +</textarea> +<p>The same in legacy format:</p> +<textarea rows="3" cols="60">$ModLoad mmnormalize +$mmnormalizeRuleBase /path/to/rulebase.rb +*.* :mmnormalize: +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2010-2012 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/mmsnmptrapd.html b/doc/mmsnmptrapd.html new file mode 100644 index 00000000..699049d3 --- /dev/null +++ b/doc/mmsnmptrapd.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<meta http-equiv="Content-Language" content="en"> +<title>mmsnmptrapd message modification module</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back to rsyslog module overview</a> + +<h1>mmsnmptrapd message modification module</h1> +<p><b>Module Name: imtcp</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com> (custom-created)</p> +<p><b>Multi-Ruleset Support: </b>since 5.8.1 +<p><b>Description</b>:</p> +<p>This module uses a specific configuration of snmptrapd's tag values to +obtain information of the original source system and the severity present inside the +original SNMP trap. It then replaces these fields inside the syslog message. +<p>Let's look at an example. Essentially, SNMPTT will invoke something like this: +<pre>logger -t snmptrapd/warning/realhost Host 003c.abcd.ffff in vlan 17 is flapping between port Gi4/1 and port Gi3/2 +</pre> +<p> +This message modification module will change the tag (removing the additional information), +hostname and severity (not shown in example), so the log entry will look as follows: +<pre> +2011-04-21T16:43:09.101633+02:00 realhost snmptrapd: Host 003c.abcd.ffff in vlan 122 is flapping between port Gi4/1 and port Gi3/2 +</pre> +The following logic is applied to all message being processed: +<ol> +<li>The module checks incoming syslog entries. If their TAG field starts with "snmptrapd/" +(configurable), they are modified, otherwise not. If the are modified, this happens as follows: +<li>It will derive the hostname from the tag field which has format snmptrapd/severity/hostname +<li>It should derive the severity from the tag field which has format +snmptrapd/severity/hostname. A configurable mapping table will be used to drive a new +severity value from that severity string. If no mapping has been defined, the original +severity is not changed. +<li>It replaces the "FromHost" value with the derived value from step2 +<li>It replaces the "Severity" value with the derived value from step 3 +</ol> +<p>Note that the placement of this module inside the configuration is important. All actions +before this modules is called will work on the unmodified message. All messages after it's call +will work on the modified message. Please also note that there is some extra power in case it +is required: as this module is implemented via the output module interface, a filter +can be used (actually must be used) in order to tell when it is called. Usually, the catch-all +filter (*.*) is used, but more specific filters are fully supported. So it is possible to define +different parameters for this module depending on different filters. It is also possible to +just run messages from one remote system through this module, with the help of filters or +multiple rulesets and ruleset bindings. In short words, all capabilities rsyslog offers +to control output modules are also available to mmsnmptrapd. +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$mmsnmptrapdTag</b> [tagname]<br> +tells the module which start string inside the tag to look for. The default is +"snmptrapd". Note that a slash is automatically added to this tag when it comes to +matching incoming messages. It MUST not be given, except if two slashes are required +for whatever reasons (so "tag/" results in a check for "tag//" at the start of +the tag field). +<li><b>$mmsnmptrapdSeverityMapping</b> [severtiymap]<br> +This specifies the severity mapping table. It needs to be specified as a list. Note that +due to the current config system <b>no whitespace</b> is supported inside the list, so be +sure not to use any whitespace inside it.<br> +The list is constructed of Severtiy-Name/Severity-Value pairs, delimited by comma. +Severity-Name is a case-sensitive string, e.g. "warning" and an associated +numerical value (e.g. 4). +Possible values are in the rage 0..7 and are defined in RFC5424, table 2. The +given sample would be specified as "warning/4".<br> +If multiple instances of mmsnmptrapd are used, each instance uses the most recently +defined $mmsnmptrapdSeverityMapping before itself. +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>currently none known</li> +</ul> +<p><b>Example:</b></p> +<p>This enables to rewrite messages from snmptrapd and configures error and warning +severities. The default tag is used.<br> +</p> +<textarea rows="10" cols="80">$ModLoad mmsnmptrapd # needs to be done just once +# ... other module loads and listener setup ... +*.* /path/to/file/with/orignalMessage # this file receives *un*modified messages +$mmsnmptrapdSeverityMapping warning/4,error/3 +*.* :mmsnmptrapd: # *now* message is modified +*.* /path/to/file/with/modifiedMessage # this file receives modified messages +# ... rest of config ... +</textarea> +</p> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2011 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/module_workflow.png b/doc/module_workflow.png Binary files differnew file mode 100644 index 00000000..e1a72e96 --- /dev/null +++ b/doc/module_workflow.png diff --git a/doc/modules.html b/doc/modules.html new file mode 100644 index 00000000..4eae6db3 --- /dev/null +++ b/doc/modules.html @@ -0,0 +1,94 @@ +<html><head> +<title>Writing syslog Data to MySQL</title> +<meta name="KEYWORDS" content="syslog, mysql, syslog to mysql, howto"> +</head> +<body> +<h1>About rsyslog Modules</h1> +<P><small><i>Written by +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> (2007-07-28)</i></small></P> +<p><font color="#FF0000"><b>This document is incomplete. The module interface is +also quite incomplete and under development. Do not currently use it!</b></font> +You may want to visit <a href="http://rgerhards.blogspot.com/">Rainer's blog</a> +to learn what's going on.</p> +<h2>Overview</h2> +<p>In theory, modules provide input and output, among other functions, in +rsyslog. In practice, modules are only utilized for output in the current +release. The module interface is not yet completed and a moving target. We do +not recommend to write a module based on the current specification. If you do, +please be prepared that future released of rsyslog will probably break your +module. </p> +<p>A goal of modularization is to provide an easy to use plug-in interface. +However, this goal is not yet reached and all modules must be statically linked.</p> +<h2>Module "generation"</h2> +<p>There is a lot of plumbing that is always the same in all modules. For +example, the interface definitions, answering function pointer queries and such. +To get rid of these laborious things, I generate most of them automatically from +a single file. This file is named module-template.h. It also contains the +current best description of the interface "specification".</p> +<p>One thing that can also be achieved with it is the capability to cope with a +rapidly changing interface specification. The module interface is evolving. +Currently, it is far from being finished. As I moved the monolithic code to +modules, I needed (and still need) to make many "non-clean" code hacks, just to +get it working. These things are now gradually being removed. However, this +requires frequent changes to the interfaces, as things move in and out while +working towards a clean interface. All the interim is necessary to reach the +goal. This volatility of specifications is the number one reasons I currently +advise against implementing your own modules (hint: if you do, be sure to use +module-template.h and be prepared to fix newly appearing and disappearing data +elements).</p> +<h2>Naming Conventions</h2> +<h3>Source</h3> +<p>Output modules, and only output modules, should start with a file name of +"om" (e.g. "omfile.c", "omshell.c"). Similarly, input modules will use "im" and +filter modules "fm". The third character shall not be a hyphen.</p> +<h2>Module Security</h2> +<p>Modules are directly loaded into rsyslog's address space. As such, any module +is provided a big level of trust. Please note that further module interfaces +might provide a way to load a module into an isolated address space. This, +however, is far from being completed. So the number one rule about module +security is to run only code that you know you can trust.</p> +<p>To minimize the security risks associated with modules, rsyslog provides only +the most minimalistic access to data structures to its modules. For that reason, +the output modules do not receive any direct pointers to the selector_t +structure, the syslogd action structures and - most importantly - the msg +structure itself. Access to these structures would enable modules to access data +that is none of their business, creating a potential security weakness.</p> +<p>Not having access to these structures also simplifies further queueing and +error handling cases. As we do not need to provide e.g. full access to the msg +object itself, we do not need to serialize and cache it. Instead, strings needed +by the module are created by syslogd and then the final result is provided to +the module. That, for example, means that in a queued case $NOW is the actual +timestamp of when the message was processed, which may be even days before it +being dequeued. Think about it: If we wouldn't cache the resulting string, $NOW +would be the actual date if the action were suspended and messages queued for +some time. That could potentially result in big confusion.</p> +<p>It is thought that if an output module actually needs access to the while msg +object, we will (then) introduce a way to serialize it (e.g. to XML) in the +property replacer. Then, the output module can work with this serialized object. +The key point is that output modules never deal directly with msg objects (and +other internal structures). Besides security, this also greatly simplifies the +job of the output module developer.</p> +<h2>Action Selectors</h2> +<p>Modules (and rsyslog) need to know when they are called. For this, there must +a an action identification in selector lines. There are two syntaxes: the +single-character syntax, where a single characters identifies a module (e.g. "*" +for a wall message) and the modules designator syntax, where the module name is +given between colons (e.g. ":ommysql:"). The single character syntax is +depreciated and should not be used for new plugins.</p> +<p>An in-depth discussion of module designation in action selectors can be found +in this forum thread:</p> +<p> +<a href="http://www.rsyslog.com/index.php?name=PNphpBB2&file=viewtopic&p=678#678"> +http://www.rsyslog.com/index.php?name=PNphpBB2&file=viewtopic&p=678#678</a></p> +<h2>Copyright</h2> +<p>Copyright (c) 2007 +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> +and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p>Permission is granted to copy, distribute and/or modify this document under +the terms of the GNU Free Documentation License, Version 1.2 or any later +version published by the Free Software Foundation; with no Invariant Sections, +no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be +viewed at <a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body> +</html> diff --git a/doc/msgflow.txt b/doc/msgflow.txt new file mode 100644 index 00000000..ebee18f8 --- /dev/null +++ b/doc/msgflow.txt @@ -0,0 +1,56 @@ +flow of messages (in terms of functions) after they have +been pulled off the main queue. + +Functions are listed in the order they are (usually) called +if there are branches in processing flow, this is explicitely +stated. + +as of: 2010-06-08, master branch (v5) + +syslogd.c/msgConsumer +syslogd.c/msgConsumeOne + if ACLcheck needed: + net.cvthname, + net.isAllowedSinder2 + MsgSetRcvFromStr + MsgSetRcvFromIPStr + if NEEDS_PARSING: + parser.ParseMsg +ruleset.ProcessBatch (loops through ruleset) +ruleset.c/processMsgDoRules (for each rule in ruleset) +rule.c/processMsg +1:rule.c/shouldProcessThisMessage + (evaluates filters, optimize via ALL-Filter) +if to be processed, loop through associated actions -> +2:rule.c/processMsgsDoAction +action.c/actionCallAction (LOCKs action object!) +action.c/doActionCallAction (does duplicate message reduction) +action.c/actionWriteToAction + limits based on iExecEveryNthOccur + generates "message repeated..." string if necessary + limits based on iSecsExecOnceInterval +! **qqueueEnqObj** + This means, we are done processing the action at this + stage. The queue may run async, but usually does not + do so (in default settings). + + +Now looking at processing of the action queue. If the queue is +in direct mode, remember that the action object is still +be locked (this may also be a potential bug in non-direct mode, as +it looks like we need this prequisite!). + +action.c/processBatchMain (queue Consumer, LOOK mutActExec) +action.c/processAction + (calls finishBatch at the end, but not so important + for our analysis) +action.c/submitBatch (recursive submit/retry loop for messages) +action.c/tryDoAction (submits a [potentially partial] batch) +action.c/actionProcessMessage + (action.c/actionPrepare (utility to set status/TX mode)) +action.c/actionCallDoAction +1: action.c/prepareDoActionParams +1: template.c/tplToString-tplToArray + string buffer is cached in action object +2:<output Module>/doAction + diff --git a/doc/multi_ruleset.html b/doc/multi_ruleset.html new file mode 100644 index 00000000..37c54065 --- /dev/null +++ b/doc/multi_ruleset.html @@ -0,0 +1,238 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>Multiple Rulesets in rsyslog</title></head> +<body> +<h1>Multiple Rulesets in rsyslog</h1> +<p>Starting with version 4.5.0 and 5.1.1, <a href="http://www.rsyslog.com">rsyslog</a> supports +multiple rulesets within a single configuration. +This is especially useful for routing the recpetion of remote messages to a set of specific rules. +Note that the input module must support binding to non-standard rulesets, so the functionality +may not be available with all inputs. +<p>In this document, I am using <a href="imtcp.html">imtcp</a>, an input module +that supports binding to non-standard rulesets since rsyslog started to support them. +<h2>What is a Ruleset?</h2> +If you have worked with (r)syslog.conf, you know that it is made up of what I call rules (others +tend to call them selectors, a sysklogd term). Each rule consist of a filter and one or more +actions to be carried out when the filter evaluates to true. A filter may be as simple as a +traditional +syslog priority based filter (like "*.*" or "mail.info" or a as complex as a +script-like expression. Details on that are covered in the config file documentation. After the +filter come action specifiers, and an action is something that does something to a message, e.g. +write it to a file or forward it to a remote logging server. + +<p>A traditional configuration file is made up of one or more of these rules. When a new +message arrives, its processing starts with the first rule (in order of appearance in +rsyslog.conf) and continues for each rule until either all rules have been processed or +a so-called "discard" action happens, in which case processing stops and the +message is thrown away (what also happens after the last rule has been processed). + +<p>The <b>multi-ruleset</b> support now permits to specify more than one such rule sequence. +You can think of a traditional config file just as a single default rule set, which is +automatically bound to each of the inputs. This is even what actually happens. When +rsyslog.conf is processed, the config file parser looks for the directive + +<pre>ruleset(name="rulesetname"); +</pre> + +<p>Where name is any name the user likes (but must not start with "RSYSLOG_", which +is the name space reserved for rsyslog use). If it finds this directive, it begins a new +rule set (if the name was not yet know) or switches to an already-existing one (if the name +was known). All rules defined between this $RuleSet directive and the next one are appended +to the named ruleset. Note that the reserved name "RSYSLOG_DefaultRuleset" is used to +specify rsyslogd's default ruleset. You can use that name whereever you can use a ruleset name, +including when binding an input to it. + +<p>Inside a ruleset, messages are processed as described above: they start with the first rule +and rules are processed in the order of appearance of the configuration file until either +there are no more rules or the discard action is executed. Note that with multiple rulesets +no longer <b>all</b> rsyslog.conf rules are executed but <b>only</b> those that are +contained within the specific ruleset. + +<p>Inputs must explicitely bind to rulesets. If they don't do, the default ruleset is bound. + +<p>This brings up the next question: + +<h2>What does "To bind to a Ruleset" mean?</h2> +<p>This term is used in the same sense as "to bind an IP address to an interface": +it means that a specific input, or part of an input (like a tcp listener) will use a specific +ruleset to "pass its messages to". So when a new message arrives, it will be processed +via the bound ruleset. Rule from all other rulesets are irrelevant and will never be processed. +<p>This makes multiple rulesets very handy to process local and remote message via +seperate means: bind the respective receivers to different rule sets, and you do not need +to seperate the messages by any other method. + +<p>Binding to rulesets is input-specifc. For imtcp, this is done via the + +<pre>input(type="imptcp" port="514" ruleset="rulesetname"); +</pre> + +directive. Note that "name" must be the name of a ruleset that is already defined +at the time the bind directive is given. There are many ways to make sure this happens, but +I personally think that it is best to define all rule sets at the top of rsyslog.conf and +define the inputs at the bottom. This kind of reverses the traditional recommended ordering, but +seems to be a really useful and straightforward way of doing things. +<h2>Why are rulesets important for different parser configurations?</h2> +<p>Custom message parsers, used to handle differnet (and potentially otherwise-invalid) +message formats, can be bound to rulesets. So multiple rulesets can be a very useful +way to handle devices sending messages in different malformed formats in a consistent +way. Unfortunately, this is not uncommon in the syslog world. An in-depth explanation +with configuration sample can be found at the +<a href="rsconf1_rulesetparser.html">$RulesetParser</a> configuration directive. +<h2>Can I use a different Ruleset as the default?</h2> +<p>This is possible by using the + +<pre>$DefaultRuleset <name> +</pre> + +Directive. Please note, however, that this directive is actually global: that is, it does not +modify the ruleset to which the next input is bound but rather provides a system-wide +default rule set for those inputs that did not explicitly bind to one. As such, the directive +can not be used as a work-around to bind inputs to non-default rulesets that do not support +ruleset binding. +<h2>Examples</h2> +<h3>Split local and remote logging</h3> +<p>Let's say you have a pretty standard system that logs its local messages to the usual +bunch of files that are specified in the default rsyslog.conf. As an example, your rsyslog.conf +might look like this: + +<pre> +# ... module loading ... +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +</pre> + +<p>Now, you want to add receive messages from a remote system and log these to +a special file, but you do not want to have these messages written to the files +specified above. The traditional approach is to add a rule in front of all others that +filters on the message, processes it and then discards it: + +<pre> +# ... module loading ... +# process remote messages +if $fromhost-ip == '192.168.152.137' then { + action(type="omfile" file="/var/log/remotefile02") + stop + } + + +# only messages not from 192.0.21 make it past this point + +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +</pre> + +<p>Note that "stop" is the discard action!. Also note that we assume that +192.0.2.1 is the sole remote sender (to keep it simple). + +<p>With multiple rulesets, we can simply define a dedicated ruleset for the remote reception +case and bind it to the receiver. This may be written as follows: + +<pre> +# ... module loading ... +# process remote messages +# define new ruleset and add rules to it: +ruleset(name="remote"){ + action(type="omfile" file="/var/log/remotefile") +} +# only messages not from 192.0.21 make it past this point + +# bind ruleset to tcp listener and activate it: +input(type="imptcp" port="10514" ruleset="remote") +</pre> + +<h3>Split local and remote logging for three different ports</h3> +<p>This example is almost like the first one, but it extends it a little bit. While it is +very similar, I hope it is different enough to provide a useful example why you may want +to have more than two rulesets. + +<p>Again, we would like to use the "regular" log files for local logging, only. But +this time we set up three syslog/tcp listeners, each one listening to a different +port (in this example 10514, 10515, and 10516). Logs received from these receivers shall go into +different files. Also, logs received from 10516 (and only from that port!) with +"mail.*" priority, shall be written into a specif file and <b>not</b> be +written to 10516's general log file. + +<p>This is the config: + +<pre> +# ... module loading ... +# process remote messages + +ruleset(name="remote10514"){ + action(type="omfile" file="/var/log/remote10514") +} + +ruleset(name="remote10515"){ + action(type="omfile" file="/var/log/remote10515") +} + +ruleset(name="test1"){ + if prifilt("mail.*") then { + /var/log/mail10516 + stop + # note that the stop-command will prevent this message from + # being written to the remote10516 file - as usual... + } + /var/log/remote10516 +} + + +# and now define listners bound to the relevant ruleset +input(type="imptcp" port="10514" ruleset="remote10514") +input(type="imptcp" port="10515" ruleset="remote10515") +input(type="imptcp" port="10516" ruleset="remote10516") +</pre> + + + + +<h2>Performance</h2> +<h3>Fewer Filters</h3> +<p>No rule processing can be faster than not processing a rule at all. As such, it is useful +for a high performance system to identify disjunct actions and try to split these off to +different rule sets. In the example section, we had a case where three different tcp listeners +need to write to three different files. This is a perfect example of where multiple rule sets +are easier to use and offer more performance. The performance is better simply because there +is no need to check the reception service - instead messages are automatically pushed to the +right rule set and can be processed by very simple rules (maybe even with +"*.*"-filters, the fastest ones available). + +<h3>Partitioning of Input Data</h3> +<p>Starting with rsyslog 5.3.4, rulesets permit higher concurrency. They offer the ability +to run on their own "main" queue. What that means is that a own queue is associated +with a specific rule set. That means that inputs bound to that ruleset do no longer need +to compete with each other when they enqueue a data element into the queue. Instead, enqueue +operations can be completed in parallel. +<p>An example: let us assume we have three TCP listeners. Without rulesets, each of them +needs to insert messages into the main message queue. So if each of them wants to +submit a newly arrived message into the queue at the same time, only one can do so +while the others need to wait. With multiple rulesets, its own queue can be created for each +ruleset. If now each listener is bound to its own ruleset, concurrent message submission is +possible. On a machine with a sufficiently large number of corse, this can result in +dramatic performance improvement. +<p>It is highly advised that high-performance systems define a dedicated ruleset, with a +dedicated queue for each of the inputs. +<p>By default, rulesets do <b>not</b> have their own queue. It must be activated via the +<a href="rsconf1_rulesetcreatemainqueue.html">$RulesetCreateMainQueue</a> directive. + +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/multi_ruleset_legacy_format.html b/doc/multi_ruleset_legacy_format.html new file mode 100644 index 00000000..5a9e7a4a --- /dev/null +++ b/doc/multi_ruleset_legacy_format.html @@ -0,0 +1,192 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>Multiple Rulesets in legacy format</title></head> +<body> +<h1>Multiple Rulesets in rsyslog</h1> +<p>Starting with version 4.5.0 and 5.1.1, <a href="http://www.rsyslog.com">rsyslog</a> supports +multiple rulesets within a single configuration. +This is especially useful for routing the recpetion of remote messages to a set of specific rules. +Note that the input module must support binding to non-standard rulesets, so the functionality +may not be available with all inputs.<p> +<b>Attention: this guide is shortened and only contains the samples in legacy format.</b> +Please follow this link to the full guide in the new config format "list": <a href="http://www.rsyslog.com/doc/multi_ruleset.html">http://www.rsyslog.com/doc/multi_ruleset.html<a> + + +<h2>Examples</h2> +<h3>Split local and remote logging</h3> +<p>Let's say you have a pretty standard system that logs its local messages to the usual +bunch of files that are specified in the default rsyslog.conf. As an example, your rsyslog.conf +might look like this: + +<pre> +# ... module loading ... +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +</pre> + +<p>Now, you want to add receive messages from a remote system and log these to +a special file, but you do not want to have these messages written to the files +specified above. The traditional approach is to add a rule in front of all others that +filters on the message, processes it and then discards it: + +<pre> +# ... module loading ... +# process remote messages +:fromhost-ip, isequal, "192.0.2.1" /var/log/remotefile +& ~ +# only messages not from 192.0.21 make it past this point + +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +</pre> + +<p>Note the tilde character, which is the discard action!. Also note that we assume that +192.0.2.1 is the sole remote sender (to keep it simple). + +<p>With multiple rulesets, we can simply define a dedicated ruleset for the remote reception +case and bind it to the receiver. This may be written as follows: + +<pre> +# ... module loading ... +# process remote messages +# define new ruleset and add rules to it: +$RuleSet remote +*.* /var/log/remotefile +# only messages not from 192.0.21 make it past this point + +# bind ruleset to tcp listener +$InputTCPServerBindRuleset remote +# and activate it: +$InputTCPServerRun 10514 + +# switch back to the default ruleset: +$RuleSet RSYSLOG_DefaultRuleset +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +</pre> + +<p>Here, we need to switch back to the default ruleset after we have defined our custom +one. This is why I recommend a different ordering, which I find more intuitive. The sample +below has it, and it leads to the same results: + +<pre> +# ... module loading ... +# at first, this is a copy of the unmodified rsyslog.conf +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +# end of the "regular" rsyslog.conf. Now come the new definitions: + +# process remote messages +# define new ruleset and add rules to it: +$RuleSet remote +*.* /var/log/remotefile + +# bind ruleset to tcp listener +$InputTCPServerBindRuleset remote +# and activate it: +$InputTCPServerRun 10514 +</pre> + +<p>Here, we do not switch back to the default ruleset, because this is not needed as it is +completely defined when we begin the "remote" ruleset. + +<p>Now look at the examples and compare them to the single-ruleset solution. You will notice +that we do <b>not</b> need a real filter in the multi-ruleset case: we can simply use +"*.*" as all messages now means all messages that are being processed by this +rule set and all of them come in via the TCP receiver! This is what makes using multiple +rulesets so much easier. + +<h3>Split local and remote logging for three different ports</h3> +<p>This example is almost like the first one, but it extends it a little bit. While it is +very similar, I hope it is different enough to provide a useful example why you may want +to have more than two rulesets. + +<p>Again, we would like to use the "regular" log files for local logging, only. But +this time we set up three syslog/tcp listeners, each one listening to a different +port (in this example 10514, 10515, and 10516). Logs received from these receivers shall go into +different files. Also, logs received from 10516 (and only from that port!) with +"mail.*" priority, shall be written into a specif file and <b>not</b> be +written to 10516's general log file. + +<p>This is the config: + +<pre> +# ... module loading ... +# at first, this is a copy of the unmodified rsyslog.conf +# The authpriv file has restricted access. +authpriv.* /var/log/secure +# Log all the mail messages in one place. +mail.* /var/log/maillog +# Log cron stuff +cron.* /var/log/cron +# Everybody gets emergency messages +*.emerg * +... more ... +# end of the "regular" rsyslog.conf. Now come the new definitions: + +# process remote messages + +#define rulesets first +$RuleSet remote10514 +*.* /var/log/remote10514 + +$RuleSet remote10515 +*.* /var/log/remote10515 + +$RuleSet remote10516 +mail.* /var/log/mail10516 +& ~ +# note that the discard-action will prevent this messag from +# being written to the remote10516 file - as usual... +*.* /var/log/remote10516 + +# and now define listners bound to the relevant ruleset +$InputTCPServerBindRuleset remote10514 +$InputTCPServerRun 10514 + +$InputTCPServerBindRuleset remote10515 +$InputTCPServerRun 10515 + +$InputTCPServerBindRuleset remote10516 +$InputTCPServerRun 10516 +</pre> + +<p>Note that the "mail.*" rule inside the "remote10516" ruleset does +not affect processing inside any other rule set, including the default rule set. + + +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/netstream.html b/doc/netstream.html new file mode 100644 index 00000000..cbfa12ae --- /dev/null +++ b/doc/netstream.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Network Stream Drivers</title> + +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h1>Network Stream Drivers</h1><p>Network stream drivers are a layer +between various parts of rsyslogd (e.g. the imtcp module) and the +transport layer. They provide sequenced delivery, authentication and +confidentiality to the upper layers. Drivers implement different +capabilities.</p><p> Users need to know about netstream drivers because +they need to configure the proper driver, and proper driver properties, +to achieve desired results (e.g. a <a href="rsyslog_tls.html">TLS-protected syslog transmission</a>).</p><p>The following drivers exist:</p><ul><li><a href="ns_ptcp.html">ptcp</a> - the plain tcp network transport (no security)</li><li><a href="ns_gtls.html">gtls</a> - a secure TLS transport implemented via the GnuTLS library</li></ul>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>] +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/ns_gtls.html b/doc/ns_gtls.html new file mode 100644 index 00000000..0d02ad02 --- /dev/null +++ b/doc/ns_gtls.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>gtls Network Stream Driver</title> + +</head> +<body> +<h1>gtls Network Stream Driver</h1> +<p>This <a href="netstream.html">network stream +driver</a> implements a TLS protected transport via the <a href="http://www.gnu.org/software/gnutls/" target="_blank">GnuTLS +library</a>.</p> +<p><b>Available since:</b> 3.19.0 (suggested minimum 3.19.8 and above)</p> +<p style="font-weight: bold;">Supported Driver Modes</p> +<ul> +<li>0 - unencrypted trasmission (just like <a href="ns_ptcp.html">ptcp</a> driver)</li> +<li>1 - TLS-protected operation</li> +</ul> +Note: mode 0 does not provide any benefit over the ptcp driver. This +mode exists for technical reasons, but should not be used. It may be +removed in the future.<br> +<span style="font-weight: bold;">Supported Authentication +Modes</span><br> +<ul> +<li><span style="font-weight: bold;">anon</span> +- anonymous authentication as +described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft</li> +<li><span style="font-weight: bold;">x509/fingerprint</span> +- certificate fingerprint authentication as +described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft</li> +<li><span style="font-weight: bold;">x509/certvalid</span> +- certificate validation only</li> +<li><span style="font-weight: bold;">x509/name</span> +- certificate validation and subject name authentication as +described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft +</li> +</ul> +Note: "anon" does not permit to authenticate the remote peer. As such, +this mode is vulnerable to man in the middle attacks as well as +unauthorized access. It is recommended NOT to use this mode.</p> +<p>x509/certvalid is a nonstandard mode. It validates the remote +peers certificate, but does not check the subject name. This is +weak authentication that may be useful in scenarios where multiple +devices are deployed and it is sufficient proof of authenticy when +their certificates are signed by the CA the server trusts. This is +better than anon authentication, but still not recommended. +<b>Known Problems</b><br> +<p>Even in x509/fingerprint mode, both the client and sever +certificate currently must be signed by the same root CA. This is an +artifact of the underlying GnuTLS library and the way we use it. It is +expected that we can resolve this issue in the future.</p> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>] +</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/ns_ptcp.html b/doc/ns_ptcp.html new file mode 100644 index 00000000..c028d6c0 --- /dev/null +++ b/doc/ns_ptcp.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>ptcp Network Stream Driver</title> + +</head> +<body> +<h1>ptcp Network Stream Driver</h1> +<p>This <a href="netstream.html">network stream driver</a> implement a plain tcp transport without security properties.</p><p>Supported Driver Modes</p><ul><li>0 - unencrypted trasmission</li></ul>Supported Authentication Modes<br><ul><li>"anon" - no authentication</li></ul>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>] +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html>
\ No newline at end of file diff --git a/doc/omelasticsearch.html b/doc/omelasticsearch.html new file mode 100644 index 00000000..618b7065 --- /dev/null +++ b/doc/omelasticsearch.html @@ -0,0 +1,177 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + <meta content="en" http-equiv="Content-Language" /> + <title>Elasticsearch Output Module</title> + </head> + <body> + <p> + <a href="rsyslog_conf_modules.html">back</a></p> + <h1> + Elasticsearch Output Module</h1> + <p> + <b>Module Name: omelasticsearch</b></p> + <p> + <b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> + <p> + <b>Available since: </b>6.4.0+</p> + <p> + <b>Description</b>:</p> + <p> + This module provides native support for logging to <a href="http://www.elasticsearch.org/">Elasticsearch</a>.</p> + <p> + <b>Action Parameters</b>:</p> + <ul> + <li> + <b>server</b><br /> + Host name or IP address of the Elasticsearch server. Defaults to "localhost"</li> + <li> + <b>serverport</b><br /> + HTTP port to connect to Elasticsearch. Defaults to 9200</li> + <li> + <b>searchIndex</b><br /> + <a href="http://www.elasticsearch.org/guide/appendix/glossary.html#index">Elasticsearch index</a> to send your logs to. Defaults to "system"</li> + <li> + <b>dynSearchIndex </b><on/<b>off</b>><br /> + Whether the string provided for <strong>searchIndex</strong> should be taken as a <a href="http://www.rsyslog.com/doc/rsyslog_conf_templates.html">template</a>. Defaults to "off", which means the index name will be taken literally. Otherwise, it will look for a template with that name, and the resulting string will be the index name. For example, let's assume you define a template named "date-days" containing "%timereported:1:10:date-rfc3339%". Then, with dynSearchIndex="on", if you say searchIndex="date-days", each log will be sent to and index named after the first 10 characters of the timestamp, like "2013-03-22".</li> + <li> + <b>searchType</b><br /> + <a href="http://www.elasticsearch.org/guide/appendix/glossary.html#type">Elasticsearch type</a> to send your index to. Defaults to "events"</li> + <li> + <b>dynSearchType</b> <on/<strong>off</strong>><br /> + Like <strong>dynSearchIndex</strong>, it allows you to specify a <a href="http://www.rsyslog.com/doc/rsyslog_conf_templates.html">template</a> for <strong>searchType</strong>, instead of a static string.</li> + <li> + <strong>asyncrepl </strong><on/<strong>off</strong>><br /> + By default, an indexing operation returns after all <a href="http://www.elasticsearch.org/guide/appendix/glossary.html#replica_shard">replica shards</a> have indexed the document. With asyncrepl="on" it will return after it was indexed on the <a href="http://www.elasticsearch.org/guide/appendix/glossary.html#primary_shard">primary shard</a> only - thus trading some consistency for speed.</li> + <li> + <strong>timeout</strong><br /> + How long Elasticsearch will wait for a primary shard to be available for indexing your log before sending back an error. Defaults to "1m".</li> + <li> + <strong>template</strong><br /> + This is the JSON document that will be indexed in Elasticsearch. The resulting string needs to be a valid JSON, otherwise Elasticsearch will return an error. Defaults to:</li> + </ul> + <pre> +$template JSONDefault, "{\"message\":\"%msg:::json%\",\"fromhost\":\"%HOSTNAME:::json%\",\"facility\":\"%syslogfacility-text%\",\"priority\":\"%syslogpriority-text%\",\"timereported\":\"%timereported:::date-rfc3339%\",\"timegenerated\":\"%timegenerated:::date-rfc3339%\"}" +</pre> + <p> + Which will produce this sort of documents (pretty-printed here for readability):</p> + <ul> + </ul> + <pre> +{ + "message": " this is a test message", + "fromhost": "test-host", + "facility": "user", + "priority": "info", + "timereported": "2013-03-12T18:05:01.344864+02:00", + "timegenerated": "2013-03-12T18:05:01.344864+02:00" +}</pre> + <ul> + <li> + <strong>bulkmode </strong><on/<strong>off</strong>><br /> + The default "off" setting means logs are shipped one by one. Each in its own HTTP request, using the <a href="http://www.elasticsearch.org/guide/reference/api/index_.html">Index API</a>. Set it to "on" and it will use Elasticsearch's <a href="http://www.elasticsearch.org/guide/reference/api/bulk.html">Bulk API</a> to send multiple logs in the same request. The maximum number of logs sent in a single bulk request depends on your queue settings - usually limited by the <a href="http://www.rsyslog.com/doc/node35.html">dequeue batch size</a>. More information about queues can be found <a href="http://www.rsyslog.com/doc/node32.html">here</a>.</li> + <li> + <strong>parent</strong><br /> + Specifying a string here will index your logs with that string the parent ID of those logs. Please note that you need to define the <a href="http://www.elasticsearch.org/guide/reference/mapping/parent-field.html">parent field</a> in your <a href="http://www.elasticsearch.org/guide/reference/mapping/">mapping</a> for that to work. By default, logs are indexed without a parent.</li> + <li> + <strong>dynParent </strong><on/<strong>off</strong>><br /> + Using the same parent for all the logs sent in the same action is quite unlikely. So you'd probably want to turn this "on" and specify a <a href="http://www.rsyslog.com/doc/rsyslog_conf_templates.html">template</a> that will provide meaningful parent IDs for your logs.</li> + <li> + <strong>uid</strong><br /> + If you have basic HTTP authentication deployed (eg: through the <a href="https://github.com/Asquera/elasticsearch-http-basic">elasticsearch-basic plugin</a>), you can specify your user-name here.</li> + <li> + <strong>pwd</strong><br /> + Password for basic authentication.</li> + </ul> + <p> + <b>Samples:</b></p> + <p> + The following sample does the following:</p> + <ul> + <li> + loads the omelasticsearch module</li> + <li> + outputs all logs to Elasticsearch using the default settings</li> + </ul> + <pre> +module(load="omelasticsearch") +*.* action(type="omelasticsearch")</pre> + <p> + The following sample does the following:</p> + <ul> + <li> + loads the omelasticsearch module</li> + <li> + defines a template that will make the JSON contain the following properties (more info about what properties you can use <a href="http://www.rsyslog.com/doc/property_replacer.html">here</a>): + <ul> + <li> + RFC-3339 timestamp when the event was generated</li> + <li> + the message part of the event</li> + <li> + hostname of the system that generated the message</li> + <li> + severity of the event, as a string</li> + <li> + facility, as a string</li> + <li> + the tag of the event</li> + </ul> + </li> + <li> + outputs to Elasticsearch with the following settings + <ul> + <li> + host name of the server is myserver.local</li> + <li> + port is 9200</li> + <li> + JSON docs will look as defined in the template above</li> + <li> + index will be "test-index"</li> + <li> + type will be "test-type"</li> + <li> + activate bulk mode. For that to work effectively, we use an in-memory queue that can hold up to 5000 events. The maximum bulk size will be 300</li> + <li> + retry indefinitely if the HTTP request failed (eg: if the target server is down)</li> + </ul> + </li> + </ul> + <pre> +module(load="omelasticsearch") +template(name="testTemplate" + type="list" + option.json="on") { + constant(value="{") + constant(value="\"timestamp\":\"") property(name="timereported" dateFormat="rfc3339") + constant(value="\",\"message\":\"") property(name="msg") + constant(value="\",\"host\":\"") property(name="hostname") + constant(value="\",\"severity\":\"") property(name="syslogseverity-text") + constant(value="\",\"facility\":\"") property(name="syslogfacility-text") + constant(value="\",\"syslogtag\":\"") property(name="syslogtag") + constant(value="\"}") + } +*.* action(type="omelasticsearch" + server="myserver.local" + serverport="9200" + template="testTemplate" + searchIndex="test-index" + searchType="test-type" + bulkmode="on" + queue.type="linkedlist" + queue.size="5000" + queue.dequeuebatchsize="300" + action.resumeretrycount="-1")</pre> + <p> + </p> + <pre> +</pre> + <p> + [<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> + <p> + <font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> project.<br /> + Copyright © 2008-2012 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and <a href="http://www.adiscon.com/">Adiscon</a>. Released under the ASL 2.0.</font></p> + </body> +</html> + diff --git a/doc/omfile.html b/doc/omfile.html new file mode 100644 index 00000000..cd53fd1d --- /dev/null +++ b/doc/omfile.html @@ -0,0 +1,187 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>File Output Module</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>File Output Module</h1> +<p><b>Module Name: omfile</b></p> +<p><b>Author: </b>Rainer Gerhards <rgergards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>The omfile plug-in provides the core functionality of writing messages to files residing inside the local file system (which may actually be remote if methods like NFS are used). Both files named with static names as well files with names based on message content are supported by this module. It is a built-in module that does not need to be loaded. </p> +<p> </p> + +<p><b>Module Parameters</b>:</p> +<ul> + <li><strong>Template </strong>[templateName]<br> + sets a new default template for file actions.<br></li> + +</ul> +<p> </p> +<p><b>Action Parameters</b>:</p> +<ul> + <li><strong>DynaFileCacheSize </strong>(not mandatory, default will be used)<br> + Defines a template to be used for the output. <br></li><br> + + <li><strong>ZipLevel </strong>0..9 [default 0]<br> + if greater 0, turns on gzip compression of the output file. The higher the number, the better the compression, but also the more CPU is required for zipping.<br></li><br> + + <li><b>VeryRobustZip</b> [<b>on</b>/off] (v7.3.0+) - if ZipLevel is greater 0, + then this setting controls if extra headers are written to make the resulting file + extra hardened against malfunction. If set to off, data appended to previously unclean + closed files may not be accessible without extra tools. + Note that this risk is usually expected to be bearable, and thus "off" is the default mode. + The extra headers considerably + degrade compression, files with this option set to "on" may be four to five times as + large as files processed in "off" mode. + </li><br> + + <li><strong>FlushInterval </strong>(not mandatory, default will be used)<br> + Defines a template to be used for the output. <br></li><br> + + <li><strong>ASyncWriting </strong>on/off [default off]<br> + if turned on, the files will be written in asynchronous mode via a separate thread. In that case, double buffers will be used so that one buffer can be filled while the other buffer is being written. Note that in order to enable FlushInterval, AsyncWriting must be set to "on". Otherwise, the flush interval will be ignored. Also note that when FlushOnTXEnd is "on" but AsyncWriting is off, output will only be written when the buffer is full. This may take several hours, or even require a rsyslog shutdown. However, a buffer flush can be forced in that case by sending rsyslogd a HUP signal. <br></li><br> + + <li><strong>FlushOnTXEnd </strong>on/off [default on]<br> + Omfile has the capability to write output using a buffered writer. Disk writes are only done when the buffer is full. So if an error happens during that write, data is potentially lost. In cases where this is unacceptable, set FlushOnTXEnd to on. Then, data is written at the end of each transaction (for pre-v5 this means after each log message) and the usual error recovery thus can handle write errors without data loss. Note that this option severely reduces the effect of zip compression and should be switched to off for that use case. Note that the default -on- is primarily an aid to preserve the traditional syslogd behaviour.<br></li><br> + + <li><strong>IOBufferSize </strong><size_nbr>, default 4k<br> + size of the buffer used to writing output data. The larger the buffer, the potentially better performance is. The default of 4k is quite conservative, it is useful to go up to 64k, and 128K if you used gzip compression (then, even higher sizes may make sense)<br></li><br> + + <li><strong>DirOwner </strong><br> + Set the file owner for directories newly created. Please note that this setting does not affect the owner of directories already existing. The parameter is a user name, for which the userid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.<br></li><br> + + <li><strong>DirGroup </strong><br> + Set the group for directories newly created. Please note that this setting does not affect the group of directories already existing. The parameter is a group name, for which the groupid is obtained by rsyslogd on during startup processing. Interim changes to the user mapping are not detected.<br></li><br> + + <li><strong>FileOwner </strong><br> + Set the file owner for files newly created. Please note that this setting does not affect the owner of files already existing. The parameter is a user name, for which the userid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.<br></li><br> + + <li><strong>FileGroup </strong><br> + Set the group for files newly created. Please note that this setting does not affect the group of files already existing. The parameter is a group name, for which the groupid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.<br></li><br> + + <li><strong>DirCreateMode </strong>[defaul 0700]<br> + This is the same as $FileCreateMode, but for directories automatically generated.<br></li><br> + + <li><strong>FileCreateMode </strong>[default 0644]<br> + The FileCreateMode directive allows to specify the creation mode with which rsyslogd creates new files. If not specified, the value 0644 is used (which retains backward-compatibility with earlier releases). The value given must always be a 4-digit octal number, with the initial digit being zero. <br>Please note that the actual permission depend on rsyslogd's process umask. If in doubt, use "$umask 0000" right at the beginning of the configuration file to remove any restrictions. <br>FileCreateMode may be specified multiple times. If so, it specifies the creation mode for all selector lines that follow until the next $FileCreateMode directive. Order of lines is vitally important.<br></li><br> + + <li><strong>FailOnCHOwnFailure </strong>on/off [default on]<br> + This option modifies behaviour of file creation. If different owners or groups are specified for new files or directories and rsyslogd fails to set these new owners or groups, it will log an error and NOT write to the file in question if that option is set to "on". If it is set to "off", the error will be ignored and processing continues. Keep in mind, that the files in this case may be (in)accessible by people who should not have permission. The default is "on".<br></li><br> + + <li><strong>CreateDirs </strong>on/off [default on]<br> + create directories on an as-needed basis<br></li><br> + + <li><strong>Sync </strong>on/off [default off]<br> + enables file syncing capability of omfile.<br></li><br> + + <li><strong>File </strong><br> + If the file already exists, new data is appended to it. Existing data is not truncated. If the file does not already exist, it is created. Files are kept open as long as rsyslogd is active. This conflicts with external log file rotation. In order to close a file after rotation, send rsyslogd a HUP signal after the file has been rotated away. <br></li><br> + + <li><strong>DynaFile </strong><br> + For each message, the file name is generated based on the given template. Then, this file is opened. As with the ``file'' property, data is appended if the file already exists. If the file does not exist, a new file is created. A cache of recent files is kept. Note that this cache can consume quite some memory (especially if large buffer sizes are used). Files are kept open as long as they stay inside the cache. Currently, files are only evicted from the cache when there is need to do so (due to insufficient cache size). To force-close (and evict) a dynafile from cache, send a HUP signal to rsyslogd. <br></li><br> + + <li><b>Sig.Provider </b>[ProviderName]<br> + Selects a signature provider for log signing. Currently, + there only is one provider called + "<a href="sigprov_gt.html">gt</a>".<br></li><br> + + <li><b>Cry.Provider </b>[ProviderName]<br> + Selects a crypto provider for log encryption. Currently, + there only is one provider called + "<a href="cryprov_gcry.html">gcry</a>".<br></li><br> + + <li><strong>Template </strong>[templateName]<br> + sets a new default template for file actions.<br></li><br> + +</ul> +<p><b>Caveats/Known Bugs:</b></p><ul><li>None.</li></ul> +<p><b>Sample:</b></p> +<p>The following command writes all syslog messages into a file.</p> +<textarea rows="5" cols="60">Module (load="builtin:omfile") +*.* action(type="omfile" + DirCreateMode="0700" + FileCreateMode="0644" + File="/var/log/messages") +</textarea> + +<br><br> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> + <li><strong>$DynaFileCacheSize </strong><br> + equivalent to the "dynaFileCacheSize" parameter<br></li><br> + + <li><strong>$OMFileZipLevel </strong><br> + equivalent to the "zipLevel" parameter<br></li><br> + + <li><strong>$OMFileFlushInterval </strong><br> + equivalent to the "flushInterval" parameter<br></li><br> + + <li><strong>$OMFileASyncWriting </strong><br> + equivalent to the "asyncWriting" parameter<br></li><br> + + <li><strong>$OMFileFlushOnTXEnd </strong><br> + equivalent to the "flushOnTXEnd" parameter<br></li><br> + + <li><strong>$OMFileIOBufferSize </strong><br> + equivalent to the "IOBufferSize" parameter<br></li><br> + + <li><strong>$DirOwner </strong><br> + equivalent to the "dirOwner" parameter<br></li><br> + + <li><strong>$DirGroup </strong><br> + equivalent to the "dirGroup" parameter<br></li><br> + + <li><strong>$FileOwner </strong><br> + equivalent to the "fileOwner" parameter<br></li><br> + + <li><strong>$FileGroup </strong><br> + equivalent to the "fileGroup" parameter<br></li><br> + + <li><strong>$DirCreateMode </strong><br> + equivalent to the "dirCreateMode" parameter<br></li><br> + + <li><strong>$FileCreateMode </strong><br> + equivalent to the "fileCreateMode" parameter<br></li><br> + + <li><strong>$FailOnCHOwnFailure </strong><br> + equivalent to the "failOnChOwnFailure" parameter<br></li><br> + + <li><strong>$F$OMFileForceCHOwn </strong><br> + equivalent to the "ForceChOwn" parameter<br></li><br> + + <li><strong>$CreateDirs </strong><br> + equivalent to the "createDirs" parameter<br></li><br> + + <li><strong>$ActionFileEnableSync </strong><br> + equivalent to the "enableSync" parameter<br></li><br> + + <li><strong>$ActionFileDefaultTemplate </strong><br> + equivalent to the "template" module parameter<br></li><br> + + <li><strong>$ResetConfigVariables </strong><br> + Resets all configuration variables to their default value.<br></li><br> + +</ul> + +<p><b>Legacy Sample:</b></p> +<p>The following command writes all syslog messages into a file.</p> +<textarea rows="5" cols="60">$ModLoad omfile +$DirCreateMode 0700 +$FileCreateMode 0644 +*.* /var/log/messages +</textarea> + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omfwd.html b/doc/omfwd.html new file mode 100644 index 00000000..51aa58b5 --- /dev/null +++ b/doc/omfwd.html @@ -0,0 +1,149 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Forwarding Output Module</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Forwarding Output Module</h1> +<p><b>Module Name: omfwd</b></p> +<p><b>Author: </b>Rainer Gerhards <rgergards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>The omfwd plug-in provides the core functionality of traditional message forwarding via UDP and plain TCP. It is a built-in module that does not need to be loaded. </p> +<p> </p> + +<p><b>Global Configuration Directives</b>:</p> +<ul> + <li><strong>Template </strong>[templateName]<br> + sets a non-standard default template for this module.<br></li> + +</ul> +<p> </p> +<p><b>Action specific Configuration Directives</b>:</p> +<ul> + <li><strong>Target </strong>string<br> + Name or IP-Address of the system that shall receive messages. Any resolvable name is fine. <br></li><br> + + <li><strong>Port </strong>[Default 514]<br> + Name or numerical value of port to use when connecting to target. <br></li><br> + + <li><strong>Protocol </strong>udp/tcp [default udp]<br> + Type of protocol to use for forwarding. Note that ``tcp'' means both legacy plain tcp syslog as well as RFC5425-based TCL-encrypted syslog. Which one is selected depends on the protocol drivers set before the action commend. Note that as of 6.3.6, there is no way to specify this within the action itself. <br></li><br> + + <li><strong>TCP_Framing </strong>``traditional'' or ``octet-counted'' [default traditional]<br> + Framing-Mode to be for forwarding. This affects only TCP-based protocols. It is ignored for UDP. In protocol engineering, ``framing'' means how multiple messages over the same connection are separated. Usually, this is transparent to users. Unfortunately, the early syslog protocol evolved, and so there are cases where users need to specify the framing. The traditional framing is nontransparent. With it, messages are end when a LF (aka ``line break'', ``return'') is encountered, and the next message starts immediately after the LF. If multi-line messages are received, these are essentially broken up into multiple message, usually with all but the first message segment being incorrectly formatted. The octet-counting framing solves this issue. With it, each message is prefixed with the actual message length, so that a receivers knows exactly where the message ends. Multi-line messages cause no problem here. This mode is very close to the method described in RFC5425 for TLS-enabled syslog. Unfortunately, only few syslogd implementations support octet-counted framing. As such, the traditional framing is set as default, even though it has defects. If it is known that the receiver supports octet-counted framing, it is suggested to use that framing mode. <br></li><br> + + <li><strong>ZipLevel </strong>0..9 [default 0]<br> + Compression level for messages. + <br>Up until rsyslog 7.5.1, this was the only compression setting that + rsyslog understood. Starting with 7.5.1, we have different compression + modes. All of them are affected by the ziplevel. If, however, no mode + is explicitely set, setting ziplevel also turns on "single" + compression mode, so pre 7.5.1 configuration will continue to work + as expected. + <br>The compression level is specified via the usual factor of 0 to 9, with 9 being the strongest compression (taking up most processing time) and 0 being no compression at all (taking up no extra processing time). <br></li><br> + <li><b>compression.mode</b><i>mode</i><br> + <i>mode</i> is one of "none", "single", or "stream:always". The + default is "none", in which no compression happens at all. + <br>In "single" compression mode, Rsyslog implements a proprietary + capability to zip transmitted messages. That compression happens + on a message-per-message basis. As such, there is a performance gain + only for larger messages. Before compressing a message, rsyslog checks + if there is some gain by compression. If so, the message is sent + compressed. If not, it is sent uncompressed. As such, it is totally + valid that compressed and uncompressed messages are intermixed + within a conversation. + <br>In "stream:always" compression mode the full stream is being + compressed. This also uses non-standard protocol and is compatible + only with receives that have the same abilities. This mode offers + potentially very high compression ratios. With typical syslog + messages, it can be as high as 95+% compression (so only one twentieth + of data is actually transmitted!). Note that this mode introduces + extra latency, as data is only sent when the compressor emits new + compressed data. For typical syslog messages, this can mean that + some hundered messages may be held in local buffers before they are + actually sent. This mode has been introduced in 7.5.1. + <br><b>Note: currently only imptcp supports receiving stream-compressed + data.</b> + <br></li><br> + + <li><strong>RebindInterval </strong>integer<br> + Permits to specify an interval at which the current connection is broken and re-established. This setting is primarily an aid to load balancers. After the configured number of messages has been transmitted, the current connection is terminated and a new one started. Note that this setting applies to both TCP and UDP traffic. For UDP, the new ``connection'' uses a different source port (ports are cycled and not reused too frequently). This usually is perceived as a ``new connection'' by load balancers, which in turn forward messages to another physical target system. <br></li><br> + + <li><strong>StreamDriver </strong>string<br> + Set the file owner for directories newly created. Please note that this setting does not affect the owner of directories already existing. The parameter is a user name, for which the userid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.<br></li><br> + + <li><strong>StreamDriverMode </strong>integer [default 0]<br> + mode to use with the stream driver (driver-specific)<br></li><br> + + <li><strong>StreamDriverAuthMode </strong>string<br> + authentication mode to use with the stream driver. Note that this directive requires TLS netstream drivers. For all others, it will be ignored. (driver-specific).<br></li><br> + + <li><strong>StreamDriverPermittedPeers </strong>string<br> + accepted fingerprint (SHA1) or name of remote peer. Note that this directive requires TLS netstream drivers. For all others, it will be ignored. (driver-specific)<br></li><br> + + <li><strong>ResendLastMSGOnReconnect </strong>on/off<br> + Permits to resend the last message when a connection is reconnected. This setting affects TCP-based syslog, only. It is most useful for traditional, plain TCP syslog. Using this protocol, it is not always possible to know which messages were successfully transmitted to the receiver when a connection breaks. In many cases, the last message sent is lost. By switching this setting to "yes", rsyslog will always retransmit the last message when a connection is reestablished. This reduces potential message loss, but comes at the price that some messages may be duplicated (what usually is more acceptable). <br></li><br> + +</ul> +<p><b>Caveats/Known Bugs:</b></p><ul><li>None.</li></ul> +<p><b>Sample:</b></p> +<p>The following command sends all syslog messages to a remote server via TCP port 10514.</p> +<textarea rows="5" cols="60">Module (load="builtin:omfwd") +*.* action(type="omfwd" +Target="192.168.2.11" +Port="10514" +Protocol="tcp" +) +</textarea> + +<br><br> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> + <li><strong>$ActionForwardDefaultTemplateName </strong>string [templatename]<br> + sets a new default template for UDP and plain TCP forwarding action<br></li><br> + + <li><strong>$ActionSendTCPRebindInterval </strong>integer<br> + instructs the TCP send action to close and re-open the connection to the remote host every nbr of messages sent. Zero, the default, means that no such processing is done. This directive is useful for use with load-balancers. Note that there is some performance overhead associated with it, so it is advisable to not too often "rebind" the connection (what "too often" actually means depends on your configuration, a rule of thumb is that it should be not be much more often than once per second).<br></li><br> + + <li><strong>$ActionSendUDPRebindInterval </strong>integer<br> + instructs the UDP send action to rebind the send socket every nbr of messages sent. Zero, the default, means that no rebind is done. This directive is useful for use with load-balancers.<br></li><br> + + <li><strong>$ActionSendStreamDriver </strong><driver basename><br> + just like $DefaultNetstreamDriver, but for the specific action <br></li><br> + + <li><strong>$ActionSendStreamDriverMode </strong><mode> [default 0]<br> + mode to use with the stream driver (driver-specific)<br></li><br> + + <li><strong>$ActionSendStreamDriverAuthMode </strong><mode><br> + authentication mode to use with the stream driver. Note that this directive requires TLS netstream drivers. For all others, it will be ignored. (driver-specific))<br></li><br> + + <li><strong>$ActionSendStreamDriverPermittedPeers </strong><ID><br> + accepted fingerprint (SHA1) or name of remote peer. Note that this directive requires TLS netstream drivers. For all others, it will be ignored. (driver-specific) <br></li><br> + + <li><strong>$ActionSendResendLastMsgOnReconnect </strong>on/off [default off]<br> + specifies if the last message is to be resend when a connecition breaks and has been reconnected. May increase reliability, but comes at the risk of message duplication. <br></li><br> + + <li><strong>$ResetConfigVariables </strong><br> + Resets all configuration variables to their default value. Any settings made will not be applied to configuration lines following the $ResetConfigVariables. This is a good method to make sure no side-effects exists from previous directives. This directive has no parameters.<br></li><br> + +</ul> + +<p><b>Legacy Sample:</b></p> +<p>The following command sends all syslog messages to a remote server via TCP port 10514.</p> +<textarea rows="5" cols="60">$ModLoad omfwd +*.* @@192.168.2.11:10514 +</textarea> + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omhdfs.html b/doc/omhdfs.html new file mode 100644 index 00000000..ef7e60c5 --- /dev/null +++ b/doc/omhdfs.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog output module for HDFS (omhdfs)</title> +<a href="features.html">back</a> +</head> +<body> +<h1>Unix sockets Output Module (omhdfs)</h1> +<p><b>Module Name: omhdfs</b></p> +<p><b>Available since: </b> 5.7.1</p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module supports writing message into files on Hadoop's HDFS +file system. +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$OMHDFSFileName</b> [name]<br> +The name of the file to which the output data shall be written. +</li> +<li><b>$OMHDFSHost</b> [name]<br> +Name or IP address of the HDFS host to connect to. +</li> +<li><b>$OMHDFSPort</b> [name]<br> +Port on which to connect to the HDFS host. +</li> +<li><b>$OMHDFSDefaultTemplate</b> [name]<br> +Default template to be used when none is specified. This saves the work of +specifying the same template ever and ever again. Of course, the default +template can be overwritten via the usual method. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>Building omhdfs is a challenge because we could not yet find out how +to integrate Java properly into the autotools build process. The issue is +that HDFS is written in Java and libhdfs uses JNI to talk to it. That requires +that various system-specific environment options and pathes be set correctly. At +this point, we leave this to the user. If someone know how to do it better, +please drop us a line! +<ul> +<li>In order to build, you need to set these environment variables BEFORE running +./configure: +<ul> +<li>JAVA_INCLUDES - must have all include pathes that are needed to build +JNI C programms, including the -I options necessary for gcc. An example is<br> +# export JAVA_INCLUDES="-I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/linux" +<li>JAVA_LIBS - must have all library pathes that are needed to build +JNI C programms, including the -l/-L options necessary for gcc. An example is<br> +# export export JAVA_LIBS="-L/usr/java/jdk1.6.0_21/jre/lib/amd64 -L/usr/java/jdk1.6.0_21/jre/lib/amd64/server -ljava -ljvm -lverify" +</ul> + +<li>As of HDFS architecture, you must make sure that all relevant environment +variables (the usual Java stuff and HADOOP's home directory) are properly set. +<li>As it looks, libhdfs makes Java throw exceptions to stdout. There is no +known work-around for this (and it usually should not case any troubles. +</ul> +<p><b>Sample:</b></p> +<p> +</p> +<textarea rows="4" cols="80">$ModLoad omhdfs + +$OMHDFSFileName /var/log/logfile +*.* :omhdfs: +</textarea> +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omjournal.html b/doc/omjournal.html new file mode 100644 index 00000000..6124e40c --- /dev/null +++ b/doc/omjournal.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Linux Journal Output Module (omjournal)</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Linux Journal Output Module (omjournal)</h1> +<p><b>Module Name: omjournal</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Available since</b>: 7.3.7</p> +<p><b>Description</b>:</p> +<p>The omjournal output module provides an interface to the Linux journal. +It is meant to be used in those cases where the Linux journal is being used +as the sole system log database. With omjournal, messages from various +sources (e.g. files and remote devices) can also be written to the journal +and processed by its tools. +<p>A typical use case we had on our mind is a SOHO environment, where the +user wants to include syslog data obtained from the local router to be +part of the journal data. +<p>We suggest to check out our short presentation on +<a href="http://youtu.be/GTS7EuSdFKE">rsyslog journal integration</a> to +learn more details of anticipated use cases. +<p> </p> + +<p><b>Module Configuration Parameters</b>:</p> +<p>Currently none. +<p> </p> +<p><b>Action Confguration Parameters</b>:</p> +<p>Currently none. + +<p><b>Caveats/Known Bugs:</b> +<ul> +<li>One needs to be careful that no message routing loop is created. The +systemd journal forwards messages it receives to the traditional syslog +system (if present). That means rsyslog will receive the same message that +it just wrote as new input on imuxsock. If not handled specially and assuming +all messages be written to the journal, the message would be emitted to the +journal again and a deadly loop is started. +<p>To prevent that, imuxsock by default does not accept messages originating +from its own process ID, aka it ignores messages from the current instance of +rsyslogd. However, this setting can be changed, and if so the problem may occur. +</ul> + +<p><b>Sample:</b></p> +<p>We assume we have a DSL router inside the network and would like to +receive its syslog message into the journal. Note that this configuration can be +used without havoing any other syslog functionality at all (most importantly, there +is no need to write any file to /var/log!). We assume syslog over UDP, as this +is the most probable choice for the SOHO environment that this use case reflects. +To log to syslog data to the journal, add the following snippet to rsyslog.conf: +<textarea rows="20" cols="60">/* first, we make sure all necessary + * modules are present: + */ +module(load="imudp") # input module for UDP syslog +module(load="omjournal") # output module for journal + +/* then, define the actual server that listens to the + * router. Note that 514 is the default port for UDP + * syslog and that we use a dedicated ruleset to + * avoid mixing messages with the local log stream + * (if there is any). + */ +input(type="imudp" port="514" ruleset="writeToJournal") + +/* inside that ruleset, we just write data to the journal: */ +ruleset(name="writeToJournal") { + action(type="omjournal") +} +</textarea> +<p>Note that this can be your sole rsyslog.conf if you do not use rsyslog +for anything else than receving the router syslog messages. +<p>If you do not receive messages, <b>you probably need to enable inbound UDP +syslog traffic in your firewall</b>. + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omlibdbi.html b/doc/omlibdbi.html new file mode 100644 index 00000000..e47c7f57 --- /dev/null +++ b/doc/omlibdbi.html @@ -0,0 +1,150 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>Generic Database Output Module (omlibdbi)</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Generic Database Output Module (omlibdbi)</h1> +<p><b>Module Name: omlibdbi</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This modules supports a large number of database systems via <a href="http://libdbi.sourceforge.net/">libdbi</a>. +Libdbi abstracts the database layer and provides drivers for many +systems. Drivers are available via the <a href="http://libdbi-drivers.sourceforge.net/">libdbi-drivers</a> +project. As of this writing, the following drivers are available:</p> +<ul> +<li><a href="http://www.firebird.sourceforge.net/">Firebird/Interbase</a></li> +<li><a href="http://www.freetds.org/">FreeTDS</a> +(provides access to <a href="http://www.microsoft.com/sql">MS +SQL Server</a> and <a href="http://www.sybase.com/products/informationmanagement/adaptiveserverenterprise">Sybase</a>)</li> +<li><a href="http://www.mysql.com/">MySQL</a> +(also +supported via the native ommysql plugin in rsyslog)</li> +<li><a href="http://www.postgresql.org/">PostgreSQL</a>(also +supported via the native +ommysql plugin in rsyslog)</li> +<li><a href="http://www.sqlite.org/">SQLite/SQLite3</a></li> +</ul> +<p>The following drivers are in various stages of completion:</p> +<ul> +<li><a href="http://ingres.com/">Ingres</a></li> +<li><a href="http://www.hughes.com.au/">mSQL</a></li> +<li><a href="http://www.oracle.com/">Oracle</a></li> +</ul> +<p>These drivers seem to be quite usable, at +least from an rsyslog point of view.</p> +<p>Libdbi provides a slim layer between rsyslog and the actual +database engine. We have not yet done any performance testing (e.g. +omlibdbi vs. ommysql) but honestly believe that the performance impact +should be irrelevant, if at all measurable. Part of that assumption is +that rsyslog just does the "insert" and most of the time is spent +either in the database engine or rsyslog itself. It's hard to think of +any considerable time spent in the libdbi abstraction layer.</p> +<p><span style="font-weight: bold;">Setup</span></p> +<p>In order for this plugin to work, you need to have libdbi, the +libdbi driver for your database backend and the client software for +your database backend installed. There are libdbi packages for many +distributions. Please note that rsyslogd requires a quite recent +version (0.8.3) of libdbi. It may work with older versions, but these +need some special ./configure options to support being called from a +dlopen()ed plugin (as omlibdbi is). So in short, you probably save you +a lot of headache if you make sure you have at least libdbi version +0.8.3 on your system. +</p> +<p><b>Module Parameters</b></p> +<ul> +<li><b>template</b><br> +The default template to use. This template is used when no template is +explicitely specified in the action() statement. +<li><b>driverdirectory</b><br> +Path to the libdbi drivers. Usually, +you do not need to set it. If you installed libdbi-drivers at a +non-standard location, you may need to specify the directory here. If +you are unsure, do <b>not</b> use this configuration directive. +Usually, everything works just fine. +Note that this was an action() paramter in rsyslog versions below 7.3.0. +However, only the first action's driverdirectory parameter was actually used. +This has been cleaned up in 7.3.0, where this now is a module paramter. +</li> +</ul> +<p><b>Action Parameters</b></p> +<ul> +<li><b>server</b><br>Name or address of the MySQL server +<li><b>db</b><br>Database to use +<li><b>uid</b><br>logon userid used to connect to server. Must have proper permissions. +<li><b>pwd</b><br>the user's password +<li><b>template</b><br>Template to use when submitting messages. +<li><b>driver</b><br> +Name of the dbidriver to use, see libdbi-drivers documentation. As a +quick excerpt, at least those were available at the time of this +writiting "mysql" (suggest to use ommysql instead), "firebird" (Firbird +and InterBase), "ingres", "msql", "Oracle", "sqlite", "sqlite3", +"freetds" (for Microsoft SQL and Sybase) and "pgsql" (suggest to use +ompgsql instead).</li> +</ul> +<p><b>Legacy (pre-v6) Configuration Directives</b>:</p> +<p>It is strongly recommended NOT to use legacy format. +<ul> +<li><i>$ActionLibdbiDriverDirectory /path/to/dbd/drivers</i> +- like the driverdirectory action parameter. +<li><i>$ActionLibdbiDriver drivername</i> - like the drivername action parameter +<li><i>$ActionLibdbiHost hostname</i> - like the server action parameter +<li><i>$ActionLibdbiUserName user</i> - like the uid action parameter +<li><i>$ActionlibdbiPassword</i> - like the pwd action parameter +<li><i>$ActionlibdbiDBName db</i> - like the db action parameter +<li><i>selector line: :omlibdbi:<code>;template</code></i><br> +executes the recently configured omlibdbi action. The ;template part is +optional. If no template is provided, a default template is used (which +is currently optimized for MySQL - sorry, folks...)</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>You must make sure that any templates used for omlibdbi +properly escape strings. This is usually done by supplying the SQL (or +STDSQL) option to the template. Omlibdbi rejects templates without this +option for security reasons. However, omlibdbi does not detect if you +used the right option for your backend. Future versions of rsyslog +(with full expression support) will provide advanced +ways of handling this situation. So far, you must be careful. The +default template provided by rsyslog is suitable for MySQL, but not +necessarily for your database backend. Be careful!</p> +<p>If you receive the rsyslog error message "libdbi or libdbi +drivers not present on this system" you may either not have libdbi and +its drivers installed or (very probably) the version is earlier than +0.8.3. In this case, you need to make sure you have at least 0.8.3 and +the libdbi driver for your database backend present on your system.</p><p>I +do not have most of the database supported by omlibdbi in my lab. So it +received limited cross-platform tests. If you run into troubles, be +sure the let us know at <a href="http://www.rsyslog.com">http://www.rsyslog.com</a>.</p> +<p><b>Sample:</b></p> +<p>The following sample writes all syslog messages to the +database "syslog_db" on mysqlsever.example.com. The server is MySQL and +being accessed under the account of "user" with password "pwd" (if you +have empty passwords, just remove the $ActionLibdbiPassword line).<br> +</p> +<textarea rows="5" cols="60">module(load="omlibdbi") +*.* action(type="omlibdbi" driver="mysql" + server="mysqlserver.example.com" db="syslog_db" + uid="user" pwd="pwd" +</textarea> +<p><b>Legacy Sample:</b></p> +<p>The same as above, but in legacy config format (pre rsyslog-v6): +<textarea rows="8" cols="60">$ModLoad omlibdbi +$ActionLibdbiDriver mysql +$ActionLibdbiHost mysqlserver.example.com +$ActionLibdbiUserName user +$ActionLibdbiPassword pwd +$ActionLibdbiDBName syslog_db +*.* :omlibdbi: +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2012 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the ASL 2.0.</font></p> +</body></html> diff --git a/doc/ommail.html b/doc/ommail.html new file mode 100644 index 00000000..0841dc9f --- /dev/null +++ b/doc/ommail.html @@ -0,0 +1,146 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>mail output module - sending syslog messages via mail</title> +<a href="features.html">back</a> +</head> +<body> +<h1>Mail Output Module (ommail)</h1> +<p><b>Module Name: ommail</b></p> +<p><b>Available since: </b> 3.17.0</p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module supports sending syslog messages via mail. Each +syslog message is sent via its own mail. Obviously, you will want to +apply rigorous filtering, otherwise your mailbox (and mail server) will +be heavily spammed. The ommail plugin is primarily meant for alerting +users. As such, it is assume that mails will only be sent in an +extremely limited number of cases.</p> +<p>Please note that ommail is especially well-suited to work in +tandem with <a href="imfile.html">imfile</a> to +watch files for the occurence of specific things to be alerted on. So +its scope is far broader than forwarding syslog messages to mail +recipients.</p> +Ommail uses two templates, one for the mail body and one for the +subject line. If neither is provided, a quite meaningless subject line +is used and the mail body will be a syslog message just as if it were +written to a file. It is expected that the users customizes both +messages. In an effort to support cell phones (including SMS gateways), +there is an option to turn off the body part at all. This is considered +to be useful to send a short alert to a pager-like device.<br> +<br> +It is highly recommended to use the "<span style="font-weight: bold;">$ActionExecOnlyOnceEveryInterval +<seconds></span>" directive to limit the amount of +mails that potentially be generated. With it, mails are sent at most in +a <seconds> interval. This may be your life safer. And +remember that an hour has 3,600 seconds, so if you would like to +receive mails at most once every two hours, include a +"$ActionExecOnlyOnceEveryInterval 7200" immediately before the ommail +action. Messages sent more frequently are simpy discarded.<span style="font-weight: bold;"></span> +<p><b>Configuration Directives</b>:</p> +<ul> +<li><span style="font-weight: bold;">$ActionMailSMTPServer</span><br> +Name or IP address of the SMTP server to be used. Must currently be +set. The default is 127.0.0.1, the SMTP server on the local machine. +Obviously it is not good to expect one to be present on each machine, +so this value should be specified.<br> +</li> +<li><span style="font-weight: bold;">$ActionMailSMTPPort</span><br> +Port number or name of the SMTP port to be used. The default is 25, the +standard SMTP port.</li> +<li><span style="font-weight: bold;">$ActionMailFrom</span><br> +The email address used as the senders address. There is no default.</li> +<li><span style="font-weight: bold;">$ActionMailTo</span><br> +The recipient email addresses. There is no default. To specify multiple +recpients, repeat this directive as often as needed. Note: <b>This directive +must be specified for each new action and is automatically reset.</b> +[Multiple recipients are supported for 3.21.2 and above.]</li> +<li><span style="font-weight: bold;">$ActionMailSubject</span><br> +The name of the <span style="font-weight: bold;">template</span> +to be used as the mail subject. If this is not specified, a more or +less meaningless mail subject is generated (we don't tell you the exact +text because that can change - if you want to have something specific, +configure it!).</li> +<li><span style="font-weight: bold;">$ActionMailEnableBody</span><br> +Setting this to "off" permits to exclude the actual message body. This +may be useful for pager-like devices or cell phone SMS messages. The +default is "on", which is appropriate for allmost all cases. Turn it +off only if you know exactly what you do!</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>The current ommail implementation supports <span style="font-weight: bold;">SMTP-direct mode</span> +only. In that mode, the plugin talks to the mail server via SMTP +protocol. No other process is involved. This mode offers best +reliability as it is not depending on any external entity except the +mail server. Mail server downtime is acceptable if the action is put +onto its own action queue, so that it may wait for the SMTP server to +come back online. However, the module implements only the bare SMTP +essentials. Most importantly, it does not provide any authentication +capabilities. So your mail server must be configured to accept incoming +mail from ommail without any authentication needs (this may be change +in the future as need arises, but you may also be referred to +sendmail-mode).</p> +<p>In theory, ommail should also offer a mode where it uses the +sendmail utility to send its mail (<span style="font-weight: bold;">sendmail-mode</span>). +This is somewhat less reliable (because we depend on an entity we do +not have close control over - sendmail). It also requires dramatically +more system ressources, as we need to load the external process (but +that should be no problem given the expected infrequent number of calls +into this plugin). The big advantage of sendmail mode is that it +supports all the bells and whistles of a full-blown SMTP implementation +and may even work for local delivery without a SMTP server being +present. Sendmail mode will be implemented as need arises. So if you +need it, please drop us a line (I nobody does, sendmail mode will +probably never be implemented).</p> +<p><b>Sample:</b></p> +<p>The following sample alerts the operator if the string "hard +disk fatal failure" is present inside a syslog message. The mail server +at mail.example.net is used and the subject shall be "disk problem on +<hostname>". Note how \r\n is included inside the body +text +to create line breaks. A message is sent at most once every 6 hours, +any other messages are silently discarded (or, to be precise, not being +forwarded - they are still being processed by the rest of the +configuration file).<br> +</p> +<textarea rows="15" cols="80">$ModLoad ommail +$ActionMailSMTPServer mail.example.net +$ActionMailFrom rsyslog@example.net +$ActionMailTo operator@example.net +$template mailSubject,"disk problem on %hostname%" +$template mailBody,"RSYSLOG Alert\r\nmsg='%msg%'" +$ActionMailSubject mailSubject +# make sure we receive a mail only once in six +# hours (21,600 seconds ;)) +$ActionExecOnlyOnceEveryInterval 21600 +# the if ... then ... mailBody mus be on one line! +if $msg contains 'hard disk fatal failure' then :ommail:;mailBody +</textarea> +<p>The sample below is the same, but sends mail to two recipients:</p> +<textarea rows="15" cols="80">$ModLoad ommail +$ActionMailSMTPServer mail.example.net +$ActionMailFrom rsyslog@example.net +$ActionMailTo operator@example.net +$ActionMailTo admin@example.net +$template mailSubject,"disk problem on %hostname%" +$template mailBody,"RSYSLOG Alert\r\nmsg='%msg%'" +$ActionMailSubject mailSubject +# make sure we receive a mail only once in six +# hours (21,600 seconds ;)) +$ActionExecOnlyOnceEveryInterval 21600 +# the if ... then ... mailBody mus be on one line! +if $msg contains 'hard disk fatal failure' then :ommail:;mailBody +</textarea> +<p>A more advanced example plus a discussion on using the email feature +inside a reliable system can be found in Rainer's blogpost +"<a style="font-style: italic;" href="http://rgerhards.blogspot.com/2008/04/why-is-native-email-capability.html">Why +is native email capability an advantage for a syslogd?</a>" +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> + +</body></html> diff --git a/doc/ommongodb.html b/doc/ommongodb.html new file mode 100644 index 00000000..a6112642 --- /dev/null +++ b/doc/ommongodb.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>MongoDB Output Module</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>MongoDB Output Module</h1> +<p><b>Module Name: ommongodb</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module provides native support for logging to MongoDB. +</p> +<p><b>Action Parameters</b>:</p> +<ul> +<li><b>server</b><br>Name or address of the MongoDB server +<li><b>serverport</b><br>Permits to select +a non-standard port for the MongoDB server. The default is 0, which means the +system default port is used. There is no need to specify this parameter unless +you know the server is running on a non-standard listen port. +<li><b>db</b><br>Database to use +<li><b>collection</b><br>Collection to use +<li><b>uid</b><br>logon userid used to connect to server. Must have proper permissions. +<li><b>pwd</b><br>the user's password +<li><b>template</b><br>Template to use when submitting messages. +</ul> +<p>Note rsyslog contains a canned default template to write to the MongoDB. It +will be used automatically if no other template is specified to be used. This template is: +<p> +<textarea rows="5" cols="80">template(name="BSON" type="string" string="\"sys\" : \"%hostname%\", \"time\" : \"%timereported:::rfc3339%\", \"time_rcvd\" : \"%timegenerated:::rfc3339%\", \"msg\" : \"%msg%\", \"syslog_fac\" : \"%syslogfacility%\", \"syslog_sever\" : \"%syslogseverity%\", \"syslog_tag\" : \"%syslogtag%\", \"procid\" : \"%programname%\", \"pid\" : \"%procid%\", \"level\" : \"%syslogpriority-text%\"") +</textarea> +<p>This creates the BSON document needed for MongoDB if no template is specified. The default +schema is aligned to CEE and project lumberjack. As such, the field names are standard +lumberjack field names, and <b>not</b> +<a href="property_replacer.html">rsyslog property names</a>. When specifying templates, be sure +to use rsyslog property names as given in the table. If you would like to use lumberjack-based +field names inside MongoDB (which probably is useful depending on the use case), you need to +select fields names based on the lumberjack schema. +If you just want to use a subset of the fields, but with lumberjack names, you can look up the +mapping in the default template. For example, the lumberjack field "level" contains the rsyslog +property "syslogpriority-text". +<p><b>Sample:</b></p> +<p>The following sample writes all syslog messages to the +database "syslog" and into the collection "log" on mongosever.example.com. The server is +being accessed under the account of "user" with password "pwd". +</p> +<textarea rows="5" cols="80">module(load="ommongodb") +*.* action(type="ommongodb" server="mongoserver.example.com" db="syslog" collection="log" uid="user" pwd="pwd") +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2012 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the ASL 2.0.</font></p> +</body></html> diff --git a/doc/ommysql.html b/doc/ommysql.html new file mode 100644 index 00000000..7769fb86 --- /dev/null +++ b/doc/ommysql.html @@ -0,0 +1,85 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>MySQL Database Output Module</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>MySQL Database Output Module</h1> +<p><b>Module Name: ommysql</b></p> +<p><b>Author: </b>Michael Meckelein (Initial Author) / Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module provides native support for logging to MySQL databases. It offers +superior performance over the more generic <a href="omlibdbi.html">omlibdbi</a> module. +</p> +<p><b>Action Parameters</b>:</p> +<ul> +<li><b>server</b><br>Name or address of the MySQL server +<li><b>serverport</b><br>Permits to select +a non-standard port for the MySQL server. The default is 0, which means the +system default port is used. There is no need to specify this parameter unless +you know the server is running on a non-standard listen port. +<li><b>db</b><br>Database to use +<li><b>uid</b><br>logon userid used to connect to server. Must have proper permissions. +<li><b>pwd</b><br>the user's password +<li><b>template</b><br>Template to use when submitting messages. +<li><b>mysqlconfig.file</b><br>Permits the selection +of an optional MySQL Client Library configuration file (my.cnf) for extended +configuration functionality. The use of this configuration directive is necessary +only if you have a non-standard environment or if fine-grained control over the +database connection is desired.</li> +<li><b>mysqlconfig.section</b><br>Permits the selection of the +section within the configuration file specified by the <b>myselconfig.file</b> parameter. +<br>This will likely only be used where the database administrator provides a single +configuration file with multiple profiles. +<br>This configuration parameter is ignored unless <b>mysqlconfig.file</b> is also used. +<br>If omitted, the MySQL Client Library default of "client" will be used.</li> +</ul> +<p><b>Legacy (pre-v6) Configuration Directives</b>:</p> +<p>ommysql mostly uses the "very old style" (v0) configuration, with almost everything on the +action line itself. +<ul> +<li><b>$ActionOmmysqlServerPort <port></b> - like the "serverport" action parameter. +<li><b>$OmMySQLConfigFile <file name></b> - like the "mysqlconfig.file" action parameter. +<li><b>$OmMySQLConfigSection <string></b> - like the "mysqlconfig.file" action parameter. +<li>Action line: +<br><b>:ommysql:database-server,database-name,database-userid,database-password</b> +<br>All parameters should be filled in for a successful connect. +</ul> +<p>Note rsyslog contains a canned default template to write to the MySQL +database. It works on the MonitorWare schema. This template is: +<p> +<textarea rows="5" cols="80">$template tpl,"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')",SQL +</textarea> +<p>As you can see, the template is an actual SQL statement. Note the ",SQL" option: it tells the +template processor that the template is used for SQL processing, thus quote characters are quoted +to prevent security issues. You can not assign a template without ",SQL" to a MySQL output action. +<p>If you would like to change fields contents or add or delete your own fields, you +can simply do so by modifying the schema (if required) and creating your own custom +template. +<p><b>Sample:</b></p> +<p>The following sample writes all syslog messages to the +database "syslog_db" on mysqlsever.example.com. The server is +being accessed under the account of "user" with password "pwd". +</p> +<textarea rows="5" cols="80">$ModLoad ommysql +*.* action(type="ommysql" server="mysqlserver.example.com" serverport="1234" + db="syslog_db" uid="user" pwd="pwd") +</textarea> +<p><b>Legacy Sample:</b></p> +<p>The same as above, but in legacy config format (pre rsyslog-v6): +<textarea rows="5" cols="80">$ModLoad ommysql +$ActionOmmysqlServerPort 1234 # use non-standard port +*.* :ommysql:mysqlserver.example.com,syslog_db,user,pwd +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2012 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the ASL 2.0.</font></p> +</body></html> diff --git a/doc/omoracle.html b/doc/omoracle.html new file mode 100644 index 00000000..2bb6aa5d --- /dev/null +++ b/doc/omoracle.html @@ -0,0 +1,201 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Oracle Database Output Module</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">rsyslog module reference</a> + +<h1>Oracle Database Output Module</h1> +<p><b>Module Name: omoracle</b></p> +<p><b>Author: </b>Luis Fernando Muñoz Mejías <Luis.Fernando.Munoz.Mejias@cern.ch></p> +<p><b>Available since: </b>: 4.3.0 +<p><b>Status: </b>: contributed module, not maitained by rsyslog core authors +<p><b>Description</b>:</p> +<p>This module provides native support for logging to Oracle +databases. It offers superior performance over the more +generic <a href="omlibdbi.html">omlibdbi</a> module. It also includes +a number of enhancements, most importantly prepared statements and +batching, what provides a big performance improvement. +</p> +<p>Note that this module is maintained by its original author. If you need assistance with it, +it is suggested to post questions to the +<a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">rsyslog mailing list</a>. +<p>From the header comments of this module: +<p><pre> + + This is an output module feeding directly to an Oracle + database. It uses Oracle Call Interface, a propietary module + provided by Oracle. + + Selector lines to be used are of this form: + + :omoracle:;TemplateName + + The module gets its configuration via rsyslog $... directives, + namely: + + $OmoracleDBUser: user name to log in on the database. + + $OmoracleDBPassword: password to log in on the database. + + $OmoracleDB: connection string (an Oracle easy connect or a db + name as specified by tnsnames.ora) + + $OmoracleBatchSize: Number of elements to send to the DB on each + transaction. + + $OmoracleStatement: Statement to be prepared and executed in + batches. Please note that Oracle's prepared statements have their + placeholders as ':identifier', and this module uses the colon to + guess how many placeholders there will be. + + All these directives are mandatory. The dbstring can be an Oracle + easystring or a DB name, as present in the tnsnames.ora file. + + The form of the template is just a list of strings you want + inserted to the DB, for instance: + + $template TestStmt,"%hostname%%msg%" + + Will provide the arguments to a statement like + + $OmoracleStatement \ + insert into foo(hostname,message)values(:host,:message) + + Also note that identifiers to placeholders are arbitrary. You + need to define the properties on the template in the correct order + you want them passed to the statement! +</pre> +<p>Some additional documentation contributed by Ronny Egner: +<pre> +REQUIREMENTS: +-------------- + +- Oracle Instantclient 10g (NOT 11g) Base + Devel + (if you´re on 64-bit linux you should choose the 64-bit libs!) +- JDK 1.6 (not neccessary for oracle plugin but "make" didd not finsished successfully without it) + +- "oracle-instantclient-config" script + (seems to shipped with instantclient 10g Release 1 but i was unable to find it for 10g Release 2 so here it is) + + +====================== /usr/local/bin/oracle-instantclient-config ===================== +#!/bin/sh +# +# Oracle InstantClient SDK config file +# Jean-Christophe Duberga - Bordeaux 2 University +# + +# just adapt it to your environment +incdirs="-I/usr/include/oracle/10.2.0.4/client64" +libdirs="-L/usr/lib/oracle/10.2.0.4/client64/lib" + +usage="\ +Usage: oracle-instantclient-config [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs] [--static-libs]" + +if test $# -eq 0; then + echo "${usage}" 1>&2 + exit 1 +fi + +while test $# -gt 0; do + case "$1" in + -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + case $1 in + --prefix=*) + prefix=$optarg + if test $exec_prefix_set = no ; then + exec_prefix=$optarg + fi + ;; + --prefix) + echo $prefix + ;; + --exec-prefix=*) + exec_prefix=$optarg + exec_prefix_set=yes + ;; + --exec-prefix) + echo ${exec_prefix} + ;; + --version) + echo ${version} + ;; + --cflags) + echo ${incdirs} + ;; + --libs) + echo $libdirs -lclntsh -lnnz10 -locci -lociei -locijdbc10 + ;; + --static-libs) + echo "No static libs" 1>&2 + exit 1 + ;; + *) + echo "${usage}" 1>&2 + exit 1 + ;; + esac + shift +done + +=============== END ============== + + + + +COMPILING RSYSLOGD +------------------- + + +./configure --enable-oracle + + + + +RUNNING +------- + +- make sure rsyslogd is able to locate the oracle libs (either via LD_LIBRARY_PATH or /etc/ld.so.conf) +- set TNS_ADMIN to point to your tnsnames.ora +- create a tnsnames.ora and test you are able to connect to the database + +- create user in oracle as shown in the following example: + create user syslog identified by syslog default tablespace users quota unlimited on users; + grant create session to syslog; + create role syslog_role; + grant syslog_role to syslog; + grant create table to syslog_role; + grant create sequence to syslog_role; + +- create tables as needed + +- configure rsyslog as shown in the following example + $ModLoad omoracle + + $OmoracleDBUser syslog + $OmoracleDBPassword syslog + $OmoracleDB syslog + $OmoracleBatchSize 1 + $OmoracleBatchItemSize 4096 + + $OmoracleStatementTemplate OmoracleStatement + $template OmoracleStatement,"insert into foo(hostname,message) values (:host,:message)" + $template TestStmt,"%hostname%%msg%" + *.* :omoracle:;TestStmt + (you guess it: username = password = database = "syslog".... see $rsyslogd_source/plugins/omoracle/omoracle.c for me info) +</pre> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008, 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/ompipe.html b/doc/ompipe.html new file mode 100644 index 00000000..49915b78 --- /dev/null +++ b/doc/ompipe.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>Pipe Output Module</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>Pipe Output Module</h1> +<p><b>Module Name: omfwd</b></p> +<p><b>Author: </b>Rainer Gerhards <rgergards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>The ompipe plug-in provides the core functionality for logging output to named pipes (fifos). It is a built-in module that does not need to be loaded. </p> +<p> </p> + +<p><b>Global Configuration Directives</b>:</p> +<ul> + <li><strong>Template </strong>[templateName]<br> + sets a new default template for file actions.<br></li> + +</ul> +<p> </p> +<p><b>Action specific Configuration Directives</b>:</p> +<ul> + <li><strong>Pipe </strong>string<br> + A fifo or named pipe can be used as a destination for log messages.<br></li><br> + + + +</ul> +<p><b>Caveats/Known Bugs:</b></p><ul><li>None.</li></ul> +<p><b>Sample:</b></p> +<p>The following command sends all syslog messages to a remote server via TCP port 10514.</p> +<textarea rows="5" cols="60">Module (path="builtin:ompipe") +*.* action(type="ompipe" +Pipe="NameofPipe" +) +</textarea> + +<br><br> + +<p><b>Legacy Configuration Directives</b>:</p> +<p>rsyslog has support for logging output to named pipes (fifos). A fifo or named pipe can be used as a destination for log messages by prepending a pipe symbol ("|'') to the name of the file. This is handy for debugging. Note that the fifo must be created with the mkfifo(1) command before rsyslogd is started. + +</p> + +<p><b>Legacy Sample:</b></p> +<p>The following command sends all syslog messages to a remote server via TCP port 10514.</p> +<textarea rows="5" cols="60">$ModLoad ompipe +*.* |/var/log/pipe +</textarea> + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omprog.html b/doc/omprog.html new file mode 100644 index 00000000..471ab224 --- /dev/null +++ b/doc/omprog.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>omprog output module - sending messages to a program</title> +<a href="features.html">back</a> +</head> +<body> +<h1>Program integration Output module</h1> +<p><b>Module Name: omprog</b></p> +<p><b>Available since: </b> 4.3.0</p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module permits to integrate arbitrary external programs into rsyslog's +logging. It is similar to the "execute program (^)" action, but offers better security +and much higher performance. While "execute program (^)" can be a useful tool for +executing programs if rare events occur, omprog can be used to provide massive +amounts of log data to a program. +<p>Executes the configured program and feeds log messages to that binary via +stdin. The binary is free to do whatever it wants with the supplied data. +If the program terminates, it is re-started. If rsyslog terminates, the +program's stdin will see EOF. The program must than terminate. The message format +passed to the program can, as usual, be modified by defining rsyslog templates. +<p>Note that each time an omprog action is defined, the corresponding programm +is invoked. A single instance is <b>not</b> being re-used. There are arguments pro and +con re-using existing binaries. For the time being, it simply is not done. In the future, +we may add an option for such pooling, provided that some demand for that is voiced. +You can also mimic the same effect by defining multiple rulesets and including them (at +the price of some slight performance loss). +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$ActionOMProgBinary</b> <binary><br> +The binary program to be executed. +</ul> +<b>Caveats/Known Bugs:</b> +<p>Currently none known. +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2011 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/omrelp.html b/doc/omrelp.html new file mode 100644 index 00000000..a44ec319 --- /dev/null +++ b/doc/omrelp.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>RELP Output Module (omrelp)</title> + +</head> +<body> +<a href="rsyslog_conf_modules.html">back to rsyslog module documentation</a> + +<h1>RELP Output Module (omrelp)</h1> +<p><b>Module Name: omrelp</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module supports sending syslog messages over the reliable +RELP protocol. For RELP's advantages over plain tcp syslog, please see +the documentation for <a href="imrelp.html">imrelp</a> +(the server counterpart). </p> +<span style="font-weight: bold;">Setup</span> +<p>Please note that <a href="http://www.librelp.com">librelp</a> +is required for imrelp (it provides the core relp protocol +implementation).</p> +<p><b>Action Configuration Parameters</b>:</p> +<p>This module supports RainerScript configuration starting with +rsyslog 7.3.10. For older versions, legacy configuration directives +must be used. +<ul> + <li><b>target</b> (mandatory)<br> + The target server to connect to. + </li> + <li><b>template</b> (not mandatory, default "RSYSLOG_ForwardFormat")<br> + Defines the template to be used for the output. + </li> + <li><b>timeout</b> (not mandatory, default 90)<br> + Timeout for relp sessions. If set too low, valid sessions + may be considered dead and tried to recover. + </li> + <li><b>tls</b> (not mandatory, values "on","off", default "off")<br> + If set to "on", the RELP connection will be encrypted by TLS, so that the data is protected against observers. Please note that both the client and the server must have set TLS to either "on" or "off". Other combinations lead to unpredictable results. + </li> + <li><b>tls.compression</b> (not mandatory, values "on","off", default "off")<br> + The controls if the TLS stream should be compressed (zipped). While this + increases CPU use, the network bandwidth should be reduced. Note that + typical text-based log records usually compress rather well. + </li> + <li><b>tls.prioritystring</b> (not mandatory, string)<br> + This parameter permits to specify the so-called "priority string" to + GnuTLS. This string gives complete control over all crypto parameters, + including compression setting. For this reason, when the prioritystring + is specified, the "tls.compression" parameter has no effect and is + ignored. + <br>Full information about how to construct a priority string can be + found in the GnuTLS manual. At the time of this writing, this + information was contained in + <a href="http://gnutls.org/manual/html_node/Priority-Strings.html">section 6.10 of the GnuTLS manual</a>. + <br><b>Note: this is an expert parameter.</b> Do not use if you do + not exactly know what you are doing. + </li> +</ul> +<p><b>Sample:</b></p> +<p>The following sample sends all messages to the central server +"centralserv" at port 2514 (note that that server must run imrelp on +port 2514). +</p> +<textarea rows="3" cols="60">module(load="omrelp") +action(type="omrelp" target="centralserv" port="2514") +</textarea> +<p><b>Legacy Configuration Directives</b>:</p> +<p>This module uses old-style action configuration to keep +consistent with the forwarding rule. So far, no additional +configuration directives can be specified. To send a message via RELP, +use</p> +<p>*.* + :omrelp:<sever>:<port>;<template></p> +<p>just as you use </p> +<p>*.* + @@<sever>:<port>;<template></p> +<p>to forward a message via plain tcp syslog.</p> +<b>Caveats/Known Bugs:</b> +<p>See <a href="imrelp.html">imrelp</a>, +which documents them. </p> +<p><b>Legacy Sample:</b></p> +<p>The following sample sends all messages to the central server +"centralserv" at port 2514 (note that that server must run imrelp on +port 2514). +</p> +<textarea rows="3" cols="60">$ModLoad omrelp +*.* :omrelp:centralserv:2514 +</textarea> +<p>Note: to use IPv6 addresses, encode them in [::1] format. +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/omruleset.html b/doc/omruleset.html new file mode 100644 index 00000000..41d6ccfc --- /dev/null +++ b/doc/omruleset.html @@ -0,0 +1,140 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>ruleset output module (omruleset)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">rsyslog module reference</a> + +<h1>ruleset output/including module (omruleset)</h1> +<p><b>Module Name: omruleset</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Available Since</b>: 5.3.4</p> +<p><b>Description</b>:</p> +<p>This is a very special "output" module. It permits to pass a message object +to another rule set. While this is a very simple action, it enables very +complex configurations, e.g. it supports high-speed "and" conditions, sending +data to the same file in a non-racy way, include-ruleset functionality as well as +some high-performance optimizations (in case the rule sets have the necessary +queue definitions). +<p>While it leads to a lot of power, this output module offers seamingly easy functionaltiy. +The complexity (and capablities) arise from how everthing can be combined. +<p>With this module, a message can be sent to processing to another ruleset. This is somewhat +similar to a "#include" in the C programming language. However, one needs to keep +on the mind that a ruleset can contain its own queue and that a queue can run in various modes. +<p>Note that if no queue is defined in the ruleset, the message is enqueued into the main message +queue. This most often is not optimal and means that message processing may be severely defered. +Also note that when the ruleset's target queue is full and no free space can be aquired +within the usual timeout, the message object may actually be lost. This is an extreme scenario, +but users building an audit-grade system need to know this restriction. For regular installations, +it should not really be relevant. +<p><b>At minimum, be sure you understand the +<a href="rsconf1_rulesetcreatemainqueue.html">$RulesetCreateMainQueue</a> +directive as well as the importance of statement order in rsyslog.conf before using omruleset!</b> +<p><b>Recommended Use:</b> +<ul> +<li>create rulesets specifically for omruleset +<li>create these rulesets with their own main queue +<li> decent queueing parameters (sizes, threads, etc) should be used +for the ruleset main queue. If in doubt, use the same parameters as for the +overall main queue. +<li>if you use multiple levels of ruleset nesting, double check for endless loops - the rsyslog +engine does not detect these +</ul> + +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$ActionOmrulesetRulesetName</b> ruleset-to-submit-to<br> +This directive specifies the name of the ruleset that the message +provided to omruleset should be submitted to. This ruleset must already have +been defined. Note that the directive is automatically reset after each +:omruleset: action and there is no default. This is done to prevent accidential +loops in ruleset definition, what can happen very quickly. +The :omruleset: action will NOT be honored if no ruleset name has been defined. As usual, +the ruleset name must be specified in front of the action that it modifies. +</ul> +<p><b>Examples:</b></p> +<p>This example creates a ruleset for a write-to-file action. The idea here +is that the same file is written based on multiple filters, problems occur if the file is used +together with a buffer. That is because file buffers are action-specific, and so some partial +buffers would be written. With omruleset, we create a single action inside its own ruleset and +then pass all messages to it whenever we need to do so. Of course, such a simple situation could +also be solved by a more complex filter, but the method used here can also be utilized in +more complex scenarios (e.g. with multiple listeners). The example tries to keep it simple. +Note that we create a ruleset-specific main queue (for simplicity with the default main queue +parameters) in order to avoid re-queueing messages back into the main queue. +</p> +<textarea rows="30" cols="80">$ModLoad omruleset + +# define ruleset for commonly written file +$RuleSet commonAction +$RulesetCreateMainQueue on +*.* /path/to/file.log + +#switch back to default ruleset +$ruleset RSYSLOG_DefaultRuleset + +# begin first action +# note that we must first specify which ruleset to use for omruleset: +$ActionOmrulesetRulesetName CommonAction +mail.info :omruleset: +#end first action + +# begin second action +# note that we must first specify which ruleset to use for omruleset: +$ActionOmrulesetRulesetName CommonAction +:FROMHOST, isequal, "myhost.example.com" :omruleset: +#end second action + +# of course, we can have "regular" actions alongside :omrulset: actions +*.* /path/to/general-message-file.log +</textarea> +<p>The next example is used to creat a high-performance nested and filter condition. Here, +it is first checked if the message contains a string "error". If so, the message is forwarded +to another ruleset which then applies some filters. The advantage of this is that we can use +high-performance filters where we otherwise would need to use the (much slower) expression-based +filters. Also, this enables pipeline processing, in that second ruleset is executed in +parallel to the first one.</p> +<textarea rows="30" cols="80">$ModLoad omruleset + +# define "second" ruleset +$RuleSet nested +$RulesetCreateMainQueue on # again, we use our own queue +mail.* /path/to/mailerr.log +kernel.* /path/to/kernelerr.log +auth.* /path/to/autherr.log + +#switch back to default ruleset +$ruleset RSYSLOG_DefaultRuleset + +# begin first action - here we filter on "error" +# note that we must first specify which ruleset to use for omruleset: +$ActionOmrulesetRulesetName nested +:msg, contains, "error :omruleset: +#end first action + +# begin second action - as an example we can do anything else in +# this processing. Note that these actions are processed concurrently +# to the ruleset "nested" +:FROMHOST, isequal, "myhost.example.com" /path/to/host.log +#end second action + +# of course, we can have "regular" actions alongside :omrulset: actions +*.* /path/to/general-message-file.log +</textarea> +<p><b>Caveats/Known Bugs:</b> +<p>The current configuration file language is not really adequate for a complex construct +like omruleset. Unfortunately, more important work is currently preventing me from redoing the +config language. So use extreme care when nesting rulesets and be sure to test-run your +config before putting it into production, ensuring you have a suffciently large probe +of the traffic run over it. If problems arise, the +<a href="troubleshoot.html">rsyslog debug log</a> is your friend. +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/omsnmp.html b/doc/omsnmp.html new file mode 100644 index 00000000..202bb5bb --- /dev/null +++ b/doc/omsnmp.html @@ -0,0 +1,323 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>SNMP Output Module</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>SNMP Output Module</h1> +<p><b>Module Name: omsnmp</b></p> +<p><b>Author: Andre Lorbach <alorbach@adiscon.com></b></p> +<p><b>Description</b>:</p> +<p>Provides the ability to send syslog messages as an SNMPv1 & v2c traps. By +default, SNMPv2c is preferred. The syslog message is wrapped into a OCTED +STRING variable. This module uses the <a target="_blank" href="http://net-snmp.sourceforge.net/"> +NET-SNMP</a> library. In order to compile this module, you will need to have the +<a target="_blank" href="http://net-snmp.sourceforge.net/">NET-SNMP</a> +developer (headers) package installed. </p> +<p> </p> +<p><b>Action Line:</b></p> +<p>%omsnmp% without any further parameters.</p> +<p> </p> +<p><b>Configuration Directives</b>:</p> +<ul> + <li><strong>transport </strong>(This parameter is optional, the + default value is "udp")<br> + <br> + Defines the transport type you wish to use. Technically we can support all + transport types which are supported by NET-SNMP. <br> + To name a few possible values: <br> + <br> + udp, tcp, udp6, tcp6, icmp, icmp6 ...<br> + <br> + Example: <strong>transport udp<br> + </strong></li> + <li><strong>server</strong><br> + <br> + This can be a hostname or ip address, and is our snmp target host. This + parameter is required, if the snmptarget is not defined, nothing will be + send. <br> + <br> + Example: <strong>server server.domain.xxx</strong><br> + </li> + <li><strong>port </strong>(This parameter is optional, the + default value is "162")<br> + <br> + The port which will be used, common values are port 162 or 161. <br> + <br> + Example: <strong>port 162</strong><br> + </li> + <li><strong>version </strong>(This parameter is optional, the + default value is "1")<br> + <br> + There can only be two choices for this parameter for now. <br> + 0 means SNMPv1 will be used.<br> + 1 means SNMPv2c will be used. <br> + Any other value will default to 1. <br> + <br> + Example: <strong>version 1</strong><br> + </li> + <li><strong>community </strong>(This parameter is optional, the + default value is "public")<br> + <br> + This sets the used SNMP Community.<br> + <br> + Example:<strong> community public<br> + </strong><br> + </li> + <li><strong>trapoid </strong>(This parameter is + optional, the default value is "1.3.6.1.4.1.19406.1.2.1" which means + "ADISCON-MONITORWARE-MIB::syslogtrap")<br> + This configuration parameter is used for <strong>SNMPv2</strong> only.<br> + <br> + This is the OID which defines the trap-type, or notifcation-type rsyslog + uses to send the trap. <br> + In order to decode this OID, you will need to have the + ADISCON-MONITORWARE-MIB and ADISCON-MIB mibs installed on the receiver side. Downloads of these mib files + can be found here: <br> + <a href="http://www.adiscon.org/download/ADISCON-MIB.txt"> + http://www.adiscon.org/download/ADISCON-MIB.txt</a><br> + <a href="http://www.adiscon.org/download/ADISCON-MONITORWARE-MIB.txt"> + http://www.adiscon.org/download/ADISCON-MONITORWARE-MIB.txt</a><br> + <br> + Thanks to the net-snmp + mailinglist for the help and the recommendations ;).<br> + <br> + Example: <strong>trapoid 1.3.6.1.4.1.19406.1.2.1<br> + </strong>If you have this MIBS installed, you can also configured with the + OID Name: <strong>trapoid ADISCON-MONITORWARE-MIB::syslogtrap<br> + </strong> + </li> + <li><strong>messageoid </strong>(This parameter is + optional, the default value is "1.3.6.1.4.1.19406.1.1.2.1" which means + "ADISCON-MONITORWARE-MIB::syslogMsg")<br> + <br> + This OID will be used as a variable, type "OCTET STRING". This variable will + contain up to 255 characters of the original syslog message including syslog header. It is recommend to + use the default OID. <br> + In order to decode this OID, you will need to have the + ADISCON-MONITORWARE-MIB and ADISCON-MIB mibs installed on the receiver side. + To download these custom mibs, see the description of <strong>$actionsnmptrapoid. + </strong><br> + <br> + Example: <strong>messageoid 1.3.6.1.4.1.19406.1.1.2.1<br> + </strong>If you have this MIBS installed, you can also configured with the + OID Name: <strong>messageoid + ADISCON-MONITORWARE-MIB::syslogMsg<br> + </strong><br> + </li> + <li><strong>enterpriseoid </strong>(This parameter is optional, + the default value is "1.3.6.1.4.1.3.1.1" which means "enterprises.cmu.1.1")<br> + <br> + Customize this value if needed. I recommend to use the default value unless + you require to use a different OID. <br> + This configuration parameter is used for <strong>SNMPv1</strong> only. It + has no effect if <strong>SNMPv2</strong> is used. <br> + <br> + Example: <strong>enterpriseoid 1.3.6.1.4.1.3.1.1 <br> + </strong><br> + </li> + <li><strong>specifictype </strong>(This parameter is optional, + the default value is "0")<strong> </strong><br> + <br> + This is the specific trap number. This configuration parameter is used for + <strong>SNMPv1</strong> only. It has no effect if <strong>SNMPv2</strong> is + used. <br> + <br> + Example: <strong>specifictype 0<br> + </strong><br> + </li> + <li><strong>traptype</strong> (This parameter is optional, the + default value is "6" which means SNMP_TRAP_ENTERPRISESPECIFIC) <br> + <br> + There are only 7 Possible trap types defined which can be used here. These + trap types are: <br> + 0 = SNMP_TRAP_COLDSTART<br> + 1 = SNMP_TRAP_WARMSTART<br> + 2 = SNMP_TRAP_LINKDOWN<br> + 3 = SNMP_TRAP_LINKUP<br> + 4 = SNMP_TRAP_AUTHFAIL<br> + 5 = SNMP_TRAP_EGPNEIGHBORLOSS<br> + 6 = SNMP_TRAP_ENTERPRISESPECIFIC<br> + <br> + Any other value will default to 6 automatically. This configuration + parameter is used for <strong>SNMPv1</strong> only. It has no effect if + <strong>SNMPv2</strong> is used. <br> + <br> + Example: <strong>traptype 6</strong><br> + </li> + <li><strong>template </strong>[templateName]<strong> </strong><br> + <br> + sets a new default template for file actions. + </li> +</ul> +<p> </p> +<p><b>Caveats/Known Bugs:</b></p><ul><li>In order to decode the custom OIDs, you + will need to have the adiscon mibs installed. </li></ul> +<p><b>Sample:</b></p> +<p>The following commands send every message as a snmp trap.</p> +<textarea rows="10" cols="60">Module (path="omsnmp") +*.* action( type="omsnmp" +transport="udp" +target="localhost" +targetport="162" +version="1" +community="public") + +</textarea> + +<p><b>Legacy Configuration Directives</b>:</p> +<ul> + <li><strong>$actionsnmptransport </strong>(This parameter is optional, the + default value is "udp")<br> + <br> + Defines the transport type you wish to use. Technically we can support all + transport types which are supported by NET-SNMP. <br> + To name a few possible values: <br> + <br> + udp, tcp, udp6, tcp6, icmp, icmp6 ...<br> + <br> + Example: <strong>$actionsnmptransport udp<br> + </strong></li> + <li><strong>$actionsnmptarget</strong><br> + <br> + This can be a hostname or ip address, and is our snmp target host. This + parameter is required, if the snmptarget is not defined, nothing will be + send. <br> + <br> + Example: <strong>$actionsnmptarget server.domain.xxx</strong><br> + </li> + <li><strong>$actionsnmptargetport </strong>(This parameter is optional, the + default value is "162")<br> + <br> + The port which will be used, common values are port 162 or 161. <br> + <br> + Example: <strong>$actionsnmptargetport 162</strong><br> + </li> + <li><strong>$actionsnmpversion </strong>(This parameter is optional, the + default value is "1")<br> + <br> + There can only be two choices for this parameter for now. <br> + 0 means SNMPv1 will be used.<br> + 1 means SNMPv2c will be used. <br> + Any other value will default to 1. <br> + <br> + Example: <strong>$actionsnmpversion 1</strong><br> + </li> + <li><strong>$actionsnmpcommunity </strong>(This parameter is optional, the + default value is "public")<br> + <br> + This sets the used SNMP Community.<br> + <br> + Example:<strong> $actionsnmpcommunity public<br> + </strong><br> + </li> + <li><strong>$actionsnmptrapoid </strong>(This parameter is + optional, the default value is "1.3.6.1.4.1.19406.1.2.1" which means + "ADISCON-MONITORWARE-MIB::syslogtrap")<br> + This configuration parameter is used for <strong>SNMPv2</strong> only.<br> + <br> + This is the OID which defines the trap-type, or notifcation-type rsyslog + uses to send the trap. <br> + In order to decode this OID, you will need to have the + ADISCON-MONITORWARE-MIB and ADISCON-MIB mibs installed on the receiver side. Downloads of these mib files + can be found here: <br> + <a href="http://www.adiscon.org/download/ADISCON-MIB.txt"> + http://www.adiscon.org/download/ADISCON-MIB.txt</a><br> + <a href="http://www.adiscon.org/download/ADISCON-MONITORWARE-MIB.txt"> + http://www.adiscon.org/download/ADISCON-MONITORWARE-MIB.txt</a><br> + <br> + Thanks to the net-snmp + mailinglist for the help and the recommendations ;).<br> + <br> + Example: <strong>$actionsnmptrapoid 1.3.6.1.4.1.19406.1.2.1<br> + </strong>If you have this MIBS installed, you can also configured with the + OID Name: <strong>$actionsnmptrapoid ADISCON-MONITORWARE-MIB::syslogtrap<br> + </strong> + </li> + <li><strong>$actionsnmpsyslogmessageoid </strong>(This parameter is + optional, the default value is "1.3.6.1.4.1.19406.1.1.2.1" which means + "ADISCON-MONITORWARE-MIB::syslogMsg")<br> + <br> + This OID will be used as a variable, type "OCTET STRING". This variable will + contain up to 255 characters of the original syslog message including syslog header. It is recommend to + use the default OID. <br> + In order to decode this OID, you will need to have the + ADISCON-MONITORWARE-MIB and ADISCON-MIB mibs installed on the receiver side. + To download these custom mibs, see the description of <strong>$actionsnmptrapoid. + </strong><br> + <br> + Example: <strong>$actionsnmpsyslogmessageoid 1.3.6.1.4.1.19406.1.1.2.1<br> + </strong>If you have this MIBS installed, you can also configured with the + OID Name: <strong>$actionsnmpsyslogmessageoid + ADISCON-MONITORWARE-MIB::syslogMsg<br> + </strong><br> + </li> + <li><strong>$actionsnmpenterpriseoid </strong>(This parameter is optional, + the default value is "1.3.6.1.4.1.3.1.1" which means "enterprises.cmu.1.1")<br> + <br> + Customize this value if needed. I recommend to use the default value unless + you require to use a different OID. <br> + This configuration parameter is used for <strong>SNMPv1</strong> only. It + has no effect if <strong>SNMPv2</strong> is used. <br> + <br> + Example: <strong>$actionsnmpenterpriseoid 1.3.6.1.4.1.3.1.1 <br> + </strong><br> + </li> + <li><strong>$actionsnmpspecifictype </strong>(This parameter is optional, + the default value is "0")<strong> </strong><br> + <br> + This is the specific trap number. This configuration parameter is used for + <strong>SNMPv1</strong> only. It has no effect if <strong>SNMPv2</strong> is + used. <br> + <br> + Example: <strong>$actionsnmpspecifictype 0<br> + </strong><br> + </li> + <li><strong>$actionsnmptraptype</strong> (This parameter is optional, the + default value is "6" which means SNMP_TRAP_ENTERPRISESPECIFIC) <br> + <br> + There are only 7 Possible trap types defined which can be used here. These + trap types are: <br> + 0 = SNMP_TRAP_COLDSTART<br> + 1 = SNMP_TRAP_WARMSTART<br> + 2 = SNMP_TRAP_LINKDOWN<br> + 3 = SNMP_TRAP_LINKUP<br> + 4 = SNMP_TRAP_AUTHFAIL<br> + 5 = SNMP_TRAP_EGPNEIGHBORLOSS<br> + 6 = SNMP_TRAP_ENTERPRISESPECIFIC<br> + <br> + Any other value will default to 6 automatically. This configuration + parameter is used for <strong>SNMPv1</strong> only. It has no effect if + <strong>SNMPv2</strong> is used. <br> + <br> + Example: <strong>$actionsnmptraptype 6</strong><br> + </li> +</ul> +<p> </p> +<p><b>Caveats/Known Bugs:</b></p><ul><li>In order to decode the custom OIDs, you + will need to have the adiscon mibs installed. </li></ul> +<p><b>Sample:</b></p> +<p>The following commands send every message as a snmp trap.</p> +<textarea rows="10" cols="60">$ModLoad omsnmp + +$actionsnmptransport udp +$actionsnmptarget localhost +$actionsnmptargetport 162 +$actionsnmpversion 1 +$actionsnmpcommunity public + +*.* :omsnmp: +</textarea> + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omstdout.html b/doc/omstdout.html new file mode 100644 index 00000000..0bd10cfb --- /dev/null +++ b/doc/omstdout.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>stdout output module (omstdout)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">rsyslog module reference</a> + +<h1>stdout output module (stdout)</h1> +<p><b>Module Name: omstdout</b></p> +<p><b>Author: </b>Rainer Gerhards +<rgerhards@adiscon.com></p> +<p><b>Available Since</b>: 4.1.6</p> +<p><b>Description</b>:</p> +<p>This module writes any messages that are passed to it to stdout. +It was developed for the rsyslog test suite. However, there may +(limited) other uses exists. Please not that we do not put too much +effort into the quality of this module as we do not expect it to +be used in real deployments. If you do, please drop us a note so +that we can enhance its priority! +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$ActionOMStdoutArrayInterface</b> [on|<b>off</b><br> +This setting instructs omstdout to use the alternate +array based method of parameter passing. If used, the values +will be output with commas between the values but no other padding bytes. +This is a test aid for the alternate calling interface. +<li><b>$ActionOMStdoutEnsureLFEnding</b> [<b>on</b>|off<br> +Makes sure that each message is written with a terminating LF. This is needed for +the automatted tests. If the message contains a trailing LF, none is added. +</ul> +<b>Caveats/Known Bugs:</b> +<p>Currently none known. +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/omudpspoof.html b/doc/omudpspoof.html new file mode 100644 index 00000000..930412c8 --- /dev/null +++ b/doc/omudpspoof.html @@ -0,0 +1,207 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>UDP spoofing output module (omudpspoof)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">rsyslog module reference</a> + +<h1>UDP spoofing output module (omudpspoof)</h1> +<p><b>Module Name: omstdout</b></p> +<p><b>Authors: </b>Rainer Gerhards <rgerhards@adiscon.com> +and David Lang <david@lang.hm> +</p> +<p><b>Available Since</b>: 5.1.3 / v7 config since 7.2.5</p> +<p><b>Description</b>:</p> +<p>This module is similar to the regular UDP forwarder, but permits to +spoof the sender address. Also, it enables to circle through a number of +source ports. +<p><b>Important:</b> This module requires root priveleges for its low-level +socket access. As such, the <b>module will not work if rsyslog is configured to +drop privileges</b>. + +<p><b>load() Parameters</b>:</p> +<ul> + <li><strong>Template </strong>[templateName]<br> + sets a non-standard default template for this module.<br></li> + +</ul> +<p> </p> +<p><b>action() parameters</b>:</p> +<ul> + <li><strong>Target </strong>string<br> + Name or IP-Address of the system that shall receive messages. Any resolvable name is fine. <br></li><br> + + <li><strong>Port </strong>[Integer, Default 514]<br> + Name or numerical value of port to use when connecting to target. <br></li><br> + + <li><b>Template</b>[Word]<br> + Template to use as message text. + <br></li><br> + + <li><strong>SourceTemplate </strong>[Word]<br> + This is the name of the template that contains a + numerical IP address that is to be used as the source system IP address. + While it may often be a constant value, it can be generated as usual via the + property replacer, as long as it is a valid IPv4 address. If not specified, the + build-in default template RSYSLOG_omudpspoofDfltSourceTpl is used. This template is defined + as follows:<br> + template(name="RSYSLOG_omudpspoofDfltSourceTpl" type="string" string="%fromhost-ip%")<br> + So in essence, the default template spoofs the address of the system the message + was received from. This is considered the most important use case. + <br></li><br> + + <li><b>SourcePortStart</b>[Word]<br> + Specifies the start value for circeling the source ports. Must be less than or + equal to the end value. Default is 32000. + <br></li><br> + + <li><b>SourcePortEnd</b>[Word]<br> + Specifies the ending value for circeling the source ports. Must be less than or + equal to the start value. Default is 42000. + <br></li><br> + + <li><b>mtu</b>[Integer, default 1500]<br> + Maximum MTU supported by the network. Default respects Ethernet and must + usually not be adjusted. Setting a too-high MTU can lead to message loss, + too low to excess message fragmentation. Change only if you really know what + you are doing. This is always given in number of bytes. + <br></li><br> +</ul> +<p><b>pre-v7 Configuration Directives</b>:</p> +<ul> +<li><b>$ActionOMOMUDPSpoofSourceNameTemplate</b> <templatename> +- equivalent to the "sourceTemplate" parameter. +<li><b>$ActionOMUDPSpoofTargetHost</b> <hostname> - equivalent to the "target" parameter. +<li><b>$ActionOMUDPSpoofTargetPort</b> <port> - equivalent to the "target" parameter. +<li><b>$ActionOMUDPSpoofDefaultTemplate</b> <templatename> +- equivalent to the "template" load() parameter. +<li><b>$ActionOMUDPSpoofSourcePortStart</b> <number> +- equivalent to the "SourcePortStart" parameter. +<li><b>$ActionOMUDPSpoofSourcePortEnd</b> <number> +- equivalent to the "SourcePortEnd" parameter. +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li><b>IPv6</b> is currently not supported. If you need this capability, please let us +know via the rsyslog mailing list. +<li>Versions shipped prior to rsyslog 7.2.5 do not support message sizes over 1472 bytes (more +pricesely: over the network-supported MTU). Starting with 7.2.5, those messages will be +fragmented, up to a total upper limit of 64K (induced by UDP). Message sizes over +64K will be truncated. For older versions, messages over 1472 may be totally discarded +or truncated, depending on version and environment. +</ul> + +<p><b>Config Samples</b></p> +<p>The following sample forwards all syslog messages in standard form to the +remote server server.example.com. The original sender's address is used. We do not +care about the source port. This example is considered the typical use case for +omudpspoof. +</p> +<textarea rows="3" cols="80">module(load="omudpspoof") +action(type="omudpspoof" target="server.example.com") +</textarea> + +<p>The following sample forwards all syslog messages in unmodified form to the +remote server server.example.com. The sender address 192.0.2.1 with fixed +source port 514 is used. +</p> +<textarea rows="7" cols="80">module(load="omudpspoof") +template(name="spoofaddr" type="string" string="192.0.2.1") +template(name="spooftemplate" type="string" string="%rawmsg%") +action(type="omudpspoof" target="server.example.com" + sourcetemplate="spoofaddr" template="spooftemplate" + sourceport.start="514" sourceport.end="514) +</textarea> +<p>The following sample is exatly like the previous, but it specifies a larger size +MTU. If, for example, the envrionment supports Jumbo Ethernet frames, increasing the +MTU is useful as it reduces packet fragmentation, which most often is the source of +problems. Note that setting the MTU to a value larger than the local-attached network +supports will lead to send errors and loss of message. So use with care! +</p> +<textarea rows="8" cols="80">module(load="omudpspoof") +template(name="spoofaddr" type="string" string="192.0.2.1") +template(name="spooftemplate" type="string" string="%rawmsg%") +action(type="omudpspoof" target="server.example.com" + sourcetemplate="spoofaddr" template="spooftemplate" + sourceport.start="514" sourceport.end="514 + mtu="8000") +</textarea> +<p>Of course, the action can be combined with any type of filter, for +example a tradition PRI filter:</p> +<textarea rows="8" cols="80">module(load="omudpspoof") +template(name="spoofaddr" type="string" string="192.0.2.1") +template(name="spooftemplate" type="string" string="%rawmsg%") +local0.* action(type="omudpspoof" target="server.example.com" + sourcetemplate="spoofaddr" template="spooftemplate" + sourceport.start="514" sourceport.end="514 + mtu="8000") +</textarea> +<p>... or any complex expression-based filter:</p> +<textarea rows="8" cols="80">module(load="omudpspoof") +template(name="spoofaddr" type="string" string="192.0.2.1") +template(name="spooftemplate" type="string" string="%rawmsg%") +if prifilt("local0.*") and $msg contains "error" then + action(type="omudpspoof" target="server.example.com" + sourcetemplate="spoofaddr" template="spooftemplate" + sourceport.start="514" sourceport.end="514 + mtu="8000") +</textarea> +<p>and of course it can also be combined with as many other actions +as one likes:</p> +<textarea rows="11" cols="80">module(load="omudpspoof") +template(name="spoofaddr" type="string" string="192.0.2.1") +template(name="spooftemplate" type="string" string="%rawmsg%") +if prifilt("local0.*") and $msg contains "error" then { + action(type="omudpspoof" target="server.example.com" + sourcetemplate="spoofaddr" template="spooftemplate" + sourceport.start="514" sourceport.end="514 + mtu="8000") + action(type="omfile" file="/var/log/somelog") + stop # or whatever... +} +</textarea> + +<p><b>Legacy Sample (pre-v7):</b></p> +<p>The following sample forwards all syslog messages in standard form to the +remote server server.example.com. The original sender's address is used. We do not +care about the source port. This example is considered the typical use case for +omudpspoof. +</p> +<textarea rows="5" cols="80">$ModLoad omudpspoof +$ActionOMUDPSpoofTargetHost server.example.com +*.* :omudpspoof: +</textarea> + +<p>The following sample forwards all syslog messages in unmodified form to the +remote server server.example.com. The sender address 192.0.2.1 with fixed +source port 514 is used. +</p> +<textarea rows="8" cols="80">$ModLoad omudpspoof +$template spoofaddr,"192.0.2.1" +$template spooftemplate,"%rawmsg%" +$ActionOMUDPSpoofSourceNameTemplate spoofaddr +$ActionOMUDPSpoofTargetHost server.example.com +$ActionOMUDPSpoofSourcePortStart 514 +$ActionOMUDPSpoofSourcePortEnd 514 +*.* :omudpspoof:;spooftemplate +</textarea> +<p>The following sample is similar to the previous, but uses as many defaults as possible. +In that sample, a source port in the range 32000..42000 is used. The message is formatted +according to rsyslog's canned default forwarding format. Note that if any parameters +have been changed, the previously set defaults will be used! +</p> +<textarea rows="5" cols="80">$ModLoad omudpspoof +$template spoofaddr,"192.0.2.1" +$ActionOMUDPSpoofSourceNameTemplate spoofaddr +$ActionOMUDPSpoofTargetHost server.example.com +*.* :omudpspoof: +</textarea> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2009-2012 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/omusrmsg.html b/doc/omusrmsg.html new file mode 100644 index 00000000..eccfef2d --- /dev/null +++ b/doc/omusrmsg.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>User Message Output Module</title></head> + +<body> +<a href="rsyslog_conf_modules.html">back</a> + +<h1>User Message Output Module</h1> +<p><b>Module Name: omusrmsg</b></p> +<p><b>Author: </b>Rainer Gerhards <rgergards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>The omusrmsg plug-in provides the core functionality for logging output to a logged on user. It is a built-in module that does not need to be loaded. </p> +<p> </p> + +<p><b>Global Configuration Directives</b>:</p> +<ul> + <li><strong>Template </strong>[templateName]<br> + sets a new default template for file actions.<br></li> + +</ul> +<p> </p> +<p><b>Action specific Configuration Directives</b>:</p> +<ul> + <li><strong>Users </strong>string<br> + Must be a valid user name or root.<br></li><br> + + + +</ul> +<p><b>Caveats/Known Bugs:</b></p><ul><li>None.</li></ul> +<p><b>Sample:</b></p> +<p>The following command sends all critical syslog messages to a user and to root.</p> +<textarea rows="5" cols="60">Module (path="builtin:omusrmsg") +*.=crit action(type="omusrmsg" +Users="ExampleUser" +Users="root" +) +</textarea> + +<br><br> + +<p><b>Legacy Configuration Directives</b>:</p> +<p> + No specific configuration directives available. See configuration sample below for details on using the plugin. +</p> + +<p><b>Legacy Sample:</b></p> +<p>The following command sends all critical syslog messages to a user and to root.</p> +<textarea rows="5" cols="60">$ModLoad omusrmsg +*.=crit :omusrmsg:exampleuser +& root +</textarea> + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/omuxsock.html b/doc/omuxsock.html new file mode 100644 index 00000000..a1c09228 --- /dev/null +++ b/doc/omuxsock.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Unix sockets output module (omuxsock) - sending syslog messages to local socket</title> +<a href="features.html">back</a> +</head> +<body> +<h1>Unix sockets Output Module (omuxsock)</h1> +<p><b>Module Name: omuxsock</b></p> +<p><b>Available since: </b> 4.7.3, 5.5.7</p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Description</b>:</p> +<p>This module supports sending syslog messages to local Unix sockets. +Thus it provided a fast message-passing interface between different rsyslog +instances. The counterpart to omuxsock is <a href="imuxsock.html">imuxsock</a>. +Note that the template used together with omuxsock must be suitable to be +processed by the receiver. +<p><b>Configuration Directives</b>:</p> +<ul> +<li><b>$OMUxSockSocket</b><br> +Name of the socket to send data to. This has no default and <b>must</b> +be set. +</li> +<li><b>$OMUxSockDefaultTemplate</b><br> +This can be used to override the default template to be used together +with omuxsock. This is primarily useful if there are many forwarding +actions and each of them should use the same template.</li> +</ul> +<b>Caveats/Known Bugs:</b> +<p>Currently, only datagram sockets are supported. +<p><b>Sample:</b></p> +<p>The following sample writes all messages to the "/tmp/socksample" socket. +</p> +<textarea rows="4" cols="80">$ModLoad omuxsock +$OMUxSockSocket /tmp/socksample +*.* :omuxsock: +</textarea> +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the <a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> + +</body></html> diff --git a/doc/pmlastmsg.html b/doc/pmlastmsg.html new file mode 100644 index 00000000..fd26dbd5 --- /dev/null +++ b/doc/pmlastmsg.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"> +<title>parser module for "last message repeated n times" (pmlastmsg)</title> +</head> +<body> +<a href="rsyslog_conf_modules.html">rsyslog module reference</a> + +<h1>parser module for "last message repeated n times" (pmlastmsg)</h1> +<p><b>Module Name: pmlastmsg</b></p> +<p><b>Module Type: parser module</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Available Since</b>: 5.5.6</p> +<p><b>Description</b>:</p> +<p>Some syslogds are known to emit severily malformed messages with content +"last message repeated n times". These messages can mess up message reception, as +they lead to wrong interpretation with the standard RFC3164 parser. Rather than +trying to fix this issue in pmrfc3164, we have created a new parser module +specifically for these messages. The reason is that some processing overhead is +involved in processing these messages (they must be recognized) and we would +not like to place this toll on every user but only on those actually in need +of the feature. Note that the performance toll is not large -- but if you expect +a very high message rate with tenthousands of messages per second, you will notice +a difference. +<p>This module should be loaded first inside <a href="messageparser.html">rsyslog's +parser chain</a>. It processes all those messages that contain a PRI, then none or +some spaces and then the exact text (case-insensitive) "last message repeated n times" +where n must be an integer. All other messages are left untouched. + +<p><b>Please note:</b> this parser module makes it possible that these messages +are properly detected. It does <b>not</b> drop them. If you intend to drop those +messages, you need to use the usual filter logic in combination with the discard +action. As a side-note, please keep on your mind that the sender discarded messages +when the "last message repeated n times" message is emited. You want to consider if +that really is what you intend to happen. If not, go change the sender. + +<p><b>Configuration Directives</b>:</p> +<p>There do not currently exist any configuration directives for this module. +<p><b>Examples:</b></p> +<p>This example is the typical use case, where some systems emit malformed +"repeated msg" messages. Other than that, the default RFC5424 and RFC3164 parsers +should be used. Note that when a parser is specified, the default parser chain +is removed, so we need to specify all three parsers. We use this together with the +default ruleset. +</p> +<textarea rows="15" cols="80">$ModLoad pmlastmsg # this parser is NOT a built-in module + +# note that parser are tried in the +# order they appear in rsyslog.conf, so put pmlastmsg first +$RulesetParser rsyslog.lastline +# as we have removed the default parser chain, we +# need to add the default parsers as well. +$RulesetParser rsyslog.rfc5424 +$RulesetParser rsyslog.rfc3164 + +# now come the typical rules, like... +*.* /path/to/file.log +</textarea> +<p><b>Caveats/Known Bugs:</b> +<p>currently none +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/property_replacer.html b/doc/property_replacer.html new file mode 100644 index 00000000..13ff41c3 --- /dev/null +++ b/doc/property_replacer.html @@ -0,0 +1,766 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>The Rsyslogd Property Replacer</title></head> +<body> +<a href="rsyslog_conf_templates.html">back</a> +<h1>The Property Replacer</h1> +<p><b>The property replacer is a core component in +rsyslogd's output system.</b> A syslog message has a number of +well-defined properties (see below). Each of this properties can be +accessed <b>and</b> manipulated by the property replacer. +With it, it is easy to use only part of a property value or manipulate +the value, e.g. by converting all characters to lower case.</p> +<h1>Accessing Properties</h1> +<p>Syslog message properties are used inside templates. They are +accessed by putting them between percent signs. Properties can be +modified by the property replacer. The full syntax is as follows:</p> +<blockquote><b><code>%propname:fromChar:toChar:options:fieldname%</code></b></blockquote> +<h2>Available Properties</h2> +<p><b><code>propname</code></b> is the +name of the property to access. It is case-insensitive (prior to 3.17.0, they were case-senstive). +Currently supported are:</p> +<table> +<tbody> +<tr> +<td><b>msg</b></td> +<td>the MSG part of the message (aka "the message" ;))</td> +</tr> +<tr> +<td><b>rawmsg</b></td> +<td>the message excactly as it was received from the +socket. Should be useful for debugging.</td> +</tr> +<tr> +<td><b>hostname</b></td> +<td>hostname from the message</td> +</tr> +<tr> +<td><b>source</b></td> +<td>alias for HOSTNAME</td> +</tr> +<tr> +<td><b>fromhost</b></td> +<td>hostname of the system the message was received from +(in a relay chain, this is the system immediately in front of us and +not necessarily the original sender). This is a DNS-resolved name, except +if that is not possible or DNS resolution has been disabled.</td> +</tr> +<tr> +<td><b>fromhost-ip</b></td> +<td>The same as fromhost, but alsways as an IP address. Local inputs +(like imklog) use 127.0.0.1 in this property.</td> +</tr> +<tr> +<td><b>syslogtag</b></td> +<td>TAG from the message</td> +</tr> +<tr> +<td><b>programname</b></td> +<td>the "static" part of the tag, as defined by +BSD syslogd. For example, when TAG is "named[12345]", programname is +"named".</td> +</tr> +<tr> +<td><b>pri</b></td> +<td>PRI part of the message - undecoded (single value)</td> +</tr> +<tr> +<td><b>pri-text</b></td> +<td>the PRI part of the message in textual form (e.g. "syslog.info")</td> +</tr> +<tr> +<td><b>iut</b></td> +<td>the monitorware InfoUnitType - used when talking +to a <a href="http://www.monitorware.com">MonitorWare</a> +backend (also for <a href="http://www.phplogcon.org/">phpLogCon</a>)</td> +</tr> +<tr> +<td><b>syslogfacility</b></td> +<td>the facility from the message - in numerical form</td> +</tr> +<tr> +<td><b>syslogfacility-text</b></td> +<td>the facility from the message - in text form</td> +</tr> +<tr> +<td><b>syslogseverity</b></td> +<td>severity from the message - in numerical form</td> +</tr> +<tr> +<td><b>syslogseverity-text</b></td> +<td>severity from the message - in text form</td> +</tr> +<tr> +<td><b>syslogpriority</b></td> +<td>an alias for syslogseverity - included for historical +reasons (be careful: it still is the severity, not PRI!)</td> +</tr> +<tr> +<td><b>syslogpriority-text</b></td> +<td>an alias for syslogseverity-text</td> +</tr> +<tr> +<td><b>timegenerated</b></td> +<td>timestamp when the message was RECEIVED. Always in high +resolution</td> +</tr> +<tr> +<td><b>timereported</b></td> +<td>timestamp from the message. Resolution depends on +what was provided in the message (in most cases, +only seconds)</td> +</tr> +<tr> +<td><b>timestamp</b></td> +<td>alias for timereported</td> +</tr> +<tr> +<td><b>protocol-version</b></td> +<td>The contents of the PROTCOL-VERSION field from IETF +draft draft-ietf-syslog-protcol</td> +</tr> +<tr> +<td><b>structured-data</b></td> +<td>The contents of the STRUCTURED-DATA field from IETF +draft draft-ietf-syslog-protocol</td> +</tr> +<tr> +<td><b>app-name</b></td> +<td>The contents of the APP-NAME field from IETF draft +draft-ietf-syslog-protocol</td> +</tr> +<tr> +<td><b>procid</b></td> +<td>The contents of the PROCID field from IETF draft +draft-ietf-syslog-protocol</td> +</tr> +<tr> +<td><b>msgid</b></td> +<td>The contents of the MSGID field from +IETF draft draft-ietf-syslog-protocol</td> +</tr> +<tr> +<td><b>parsesuccess</b></td> +<td>This returns the status of the <b>last</b> called higher level parser, +like mmjsonparse. A higher level parser parses the actual message for additional +structured data and maintains an extra property table while doing so (this is +often referred to as "cee data" because the idea was originally rooted in the +cee effort, only (but has been extended since then). Note that higher level +parsers must explicitely support (and set) this property. So, depending on the +parser, it may not be set correctly. +<br>If the parser properly supports it, the value "OK" means that parsing was +successfull, while "FAIL" means the parser could not successfully obtain any data. +Failure state is not necessarily an error. For example, it may simple indicate +that the cee-enhanced syslog parser (mmjsonparse) did not detect cee-enhanced format, +what can be totally valid. Using this property, further processing of the message +can be directed based on this parsing outcome. If no parser has been called at the +time this property is accessed, it will contain "FAIL". +<br><b>This property is available since version 6.3.8.</b> +</td> +</tr> +<td><b>inputname</b></td> +<td>The name of the input module that generated the +message (e.g. "imuxsock", "imudp"). Note that not all modules +necessarily provide this property. If not provided, it is an +empty string. Also note that the input module may provide +any value of its liking. Most importantly, it is <b>not</b> +necessarily the module input name. Internal sources can also +provide inputnames. Currently, "rsyslogd" is defined as inputname +for messages internally generated by rsyslogd, for example startup +and shutdown and error messages. +This property is considered useful when trying to filter messages +based on where they originated - e.g. locally generated messages +("rsyslogd", "imuxsock", "imklog") should go to a different place +than messages generated somewhere. +</td> +</tr> +<tr> +<td><b>$bom</b></td> +<td>The UTF-8 encoded Unicode byte-order mask (BOM). This may be useful +in templates for RFC5424 support, when the character set is know to be +Unicode.</td> +</tr> +<td><b>$uptime</b></td> +<td>system-uptime in seconds (as reported by operating system). +</td> +</tr> +<tr> +<td><b>$now</b></td> +<td>The current date stamp in the format YYYY-MM-DD</td> +</tr> +<tr> +<td><b>$year</b></td> +<td>The current year (4-digit)</td> +</tr> +<tr> +<td><b>$month</b></td> +<td>The current month (2-digit)</td> +</tr> +<tr> +<td><b>$day</b></td> +<td>The current day of the month (2-digit)</td> +</tr> +<tr> +<td><b>$hour</b></td> +<td>The current hour in military (24 hour) time (2-digit)</td> +</tr> +<tr> +<td><b>$hhour</b></td> +<td>The current half hour we are in. From minute 0 to 29, +this is always 0 while +from 30 to 59 it is always 1.</td> +</tr> +<tr> +<td><b>$qhour</b></td> +<td>The current quarter hour we are in. Much like $HHOUR, but values +range from 0 to 3 (for the four quater hours that are in each hour)</td> +</tr> +<tr> +<td><b>$minute</b></td> +<td>The current minute (2-digit)</td> +</tr> +<tr> +<td><b>$myhostname</b></td> +<td>The name of the current host as it knows itself (probably useful +for filtering in a generic way)</td> +</tr> +<tr> +<td><b>$!<name></b></td> +<td>This is the "bridge" to syslog message normalization (via +<a href="mmnormalize.html">mmnormalize</a>): name is a name defined +inside the normalization rule. It has the value selected by the rule +or none if no rule with this field did match. You can also use these +properties to specify JSON fields from the CEE-enhanced syslog +message, once you parse it with <a href="mmjsonparse.html">mmjsonparse</a> +</td> +</tr> +<tr> +<td><b>$!all-json</b></td> +<td>This is the JSON part of the CEE-enhanced syslog message, which +can be parsed with <a href="mmjsonparse.html">mmjsonparse</a> +</td> +</tr> +</tbody> +</table> +<p>Properties starting with a $-sign are so-called system +properties. These do NOT stem from the message but are rather +internally-generated.</p> +<h2>Legacy Character Positions</h2> +<p><b><code>FromChar</code></b> and <b><code>toChar</code></b> +are used to build substrings. They specify the offset within the string +that should be copied. Offset counting starts at 1, so if you need to +obtain the first 2 characters of the message text, you can use this +syntax: "%msg:1:2%". If you do not whish to specify from and to, but +you want to specify options, you still need to include the colons. For +example, if you would like to convert the full message text to lower +case, use "%msg:::lowercase%". If you would like to extract from a +position until the end of the string, you can place a dollar-sign ("$") +in toChar (e.g. %msg:10:$%, which will extract from position 10 to the +end of the string).</p> +<p>There is also support for <b>regular expressions</b>. +To use them, you need to place a "R" into FromChar. This tells rsyslog +that a regular expression instead of position-based extraction is +desired. The actual regular expression must then be provided in toChar. +The regular expression <b>must</b> be followed by the +string "--end". It denotes the end of the regular expression and will +not become part of it. If you are using regular expressions, the +property replacer will return the part of the property text that +matches the regular expression. An example for a property replacer +sequence with a regular expression is: "%msg:R:.*Sev:. \(.*\) +\[.*--end%"</p> +<p>It is possible to specify some parametes after the "R". These are +comma-separated. They are: +<p>R,<regexp-type>,<submatch>,<<a href="rsyslog_conf_nomatch.html">nomatch</a>>,<match-number> +<p>regexp-type is either "BRE" for Posix basic regular expressions or +"ERE" for extended ones. The string must be given in upper case. The +default is "BRE" to be consistent with earlier versions of rsyslog that +did not support ERE. The submatch identifies the submatch to be used +with the result. A single digit is supported. Match 0 is the full match, +while 1 to 9 are the acutal submatches. The match-number identifies which match to +use, if the expression occurs more than once inside the string. Please note +that the first match is number 0, the second 1 and so on. Up to 10 matches +(up to number 9) are supported. Please note that it would be more +natural to have the match-number in front of submatch, but this would break +backward-compatibility. So the match-number must be specified after "nomatch". +<p><a href="rsyslog_conf_nomatch.html">nomatch</a> specifies what should +be used in case no match is found. +<p>The following is a sample of an ERE expression that takes the first +submatch from the message string and replaces the expression with +the full field if no match is found: +<p>%msg:R,ERE,1,FIELD:for (vlan[0-9]*):--end% +<p>and this takes the first submatch of the second match of said expression: +<p>%msg:R,ERE,1,FIELD,1:for (vlan[0-9]*):--end% +<p><b>Please note: there is also a +<a href="http://www.rsyslog.com/tool-regex">rsyslog regular expression checker/generator</a> +online tool available.</b> With that tool, you can check your regular expressions and +also generate a valid property replacer sequence. Usage of this tool is recommended. +Depending on the version offered, the tool may not cover all subleties that can +be done with the property replacer. It concentrates on the most often used cases. So it +is still useful to hand-craft expressions for demanding environments. +<p><b>Also, extraction can be done based on so-called +"fields"</b>. To do so, place a "F" into FromChar. A field in its +current definition is anything that is delimited by a delimiter +character. The delimiter by default is TAB (US-ASCII value 9). However, +if can be changed to any other US-ASCII character by specifying a comma +and the <b>decimal</b> US-ASCII value of the delimiter +immediately after the "F". For example, to use comma (",") as a +delimiter, use this field specifier: "F,44". If your syslog +data is delimited, this is a quicker way to extract than via regular +expressions (actually, a *much* quicker way). Field counting starts at +1. Field zero is accepted, but will always lead to a "field not found" +error. The same happens if a field number higher than the number of +fields in the property is requested. The field number must be placed in +the "ToChar" parameter. An example where the 3rd field (delimited by +TAB) from the msg property is extracted is as follows: "%msg:F:3%". The +same example with semicolon as delimiter is "%msg:F,59:3%".</p> +<p>Please note that the special characters "F" and "R" are +case-sensitive. Only upper case works, lower case will return an error. +There are no white spaces permitted inside the sequence (that will lead +to error messages and will NOT provide the intended result).</p> +<p>Each occurence of the field delimiter starts a new field. However, +if you add a plus sign ("+") after the field delimiter, multiple +delimiters, one immediately after the others, are treated as separate +fields. This can be useful in cases where the syslog message contains +such sequences. A frequent case may be with code that is written as +follows:</p> +<code><pre> +int n, m; +... +syslog(LOG_ERR, "%d test %6d", n, m); +</pre></code> +<p>This will result into things like this in syslog messages: +"1 test 2", +"1 test 23", +"1 test 234567" +<p>As you can see, the fields are delimited by space characters, but +their exact number is unknown. They can properly be extracted as follows: +<p> +"%msg:F,32:2%" to "%msg:F,32+:2%". +<p>This feature was suggested by Zhuang Yuyao and implemented by him. +It is modeled after perl compatible regular expressions. +</p> + +<h2>Property Options</h2> +<b><code>property options</code></b> are +case-insensitive. They are available as of version 6.5.0. +Currently, the following options are defined: +<p></p> +<table> +<tbody> +<tr> +<td><b>Name</b></td> +<td>New format. Name of the template / property / constant.</td> +</tr> +<tr> +<td><b>Outname</b></td> +<td>This field permits to specify a field name for structured-data emitting property replacer options. +It is most useful to set, for example, the name for JSON-based fields (like used in ommngodb). For +text-based modules, it is simply ignored. +If not specified, the original property name is used, with the exception of properties starting with +"$!", where that prefix is removed. Note that unnamaned constants are NOT forwarded to output modules +that expect structure (like ommnogodb). To pass constants, an outname must be set. +</tr> +<tr> +<td><b>CaseConversion</b></td> +<td>New format. Additional values below.</td> +</tr> +<tr> +<td>upper</td> +<td>convert property to lowercase only</td> +</tr> +<tr> +<td>lower</td> +<td>convert property text to uppercase only</td> +</tr> +<tr> +<td><b>DateFormat</b></td> +<td>New format, additional parameter is needed. See below.</td> +</tr> +<tr> +<td>mysql</td> +<td>format as mysql date</td> +</tr> +<tr> +<td>pgsql</td> +<td>format as pgsql date</td> +</tr> +<tr> +<td>rfc3164</td> +<td>format as RFC 3164 date</td> +</tr> +<tr> +<tr> +<td valign="top">rfc3164-buggyday</td> +<td>similar to date-rfc3164, but emulates a common coding error: RFC 3164 demands +that a space is written for single-digit days. With this option, a zero is +written instead. This format seems to be used by syslog-ng and the +date-rfc3164-buggyday option can be used in migration scenarios where otherwise +lots of scripts would need to be adjusted. It is recommended <i>not</i> to use this +option when forwarding to remote hosts - they may treat the date as invalid +(especially when parsing strictly according to RFC 3164).</td> +<br><i>This feature was introduced in rsyslog 4.6.2 and v4 versions above and +5.5.3 and all versions above.</i> +</tr> +<tr> +<td>rfc3339</td> +<td>format as RFC 3339 date</td> +</tr> +<tr> +<td>unixtimestamp</td> +<td>format as unix timestamp (seconds since epoch)</td> +</tr> +<tr> +<td>subseconds</td> +<td>just the subseconds of a timestamp (always 0 for a low precision timestamp)</td> +</tr> +<tr> +<td>pos-end-relative</td> + <td>the from and to position is relative to the end of the string + instead of the usual start of string. (available since rsyslog v7.3.10) + </td> +</tr> +<tr> +<td><b>ControlCharacters</b></td> +<td>Option values for how to process control characters</td> +</tr> +<tr> +<td valign="top">escape</td> +<td>replace control characters (ASCII value 127 and values +less then 32) with an escape sequence. The sequnce is +"#<charval>" where charval is the 3-digit decimal value +of the control character. For example, a tabulator would be replaced by +"#009".<br> +Note: using this option requires that <a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> +is set to off.</td> +</tr> +<tr> +<td valign="top">space</td> +<td>replace control characters by spaces<br> +Note: using this option requires that <a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> +is set to off.</td> +</tr> +<tr> +<td valign="top">drop</td> +<td>drop control characters - the resulting string will +neither contain control characters, escape sequences nor any other +replacement character like space.<br> +Note: using this option requires that <a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> +is set to off.</td> +</tr> +<tr> +<td><b>SecurePath</b></td> +<td>Option values for securing path templates.</td> +</tr> +<tr> +<td valign="top">drop</td> +<td>Drops slashes inside the field (e.g. "a/b" becomes "ab"). +Useful for secure pathname generation (with dynafiles). +</td> +</tr> +<tr> +<td valign="top">replace</td> +<td>Replace slashes inside the field by an underscore. (e.g. "a/b" becomes "a_b"). +Useful for secure pathname generation (with dynafiles). +</td> +</tr> +<tr> +<td><b>Format</b></td> +<td>Option values for the general output format.</td> +</tr> +<tr> +<td>json</td> +<td>encode the value so that it can be used inside a JSON field. This means +that several characters (according to the JSON spec) are being escaped, for +example US-ASCII LF is replaced by "\n". +The json option cannot be used together with either jsonf or csv options. +</td> +</tr> +<tr> +<td>jsonf</td> +<td><i>(available in 6.3.9+)</i> +This signifies that the property should be expressed as a json <b>f</b>ield. +That means not only the property is written, but rather a complete json field in +the format<br> +"fieldname"="value"</b> +where "filedname" is the assigend field name (or the property name if none was assigned) +and value is the end result of property replacer operation. Note that value supports +all property replacer options, like substrings, case converson and the like. +Values are properly json-escaped. However, field names are (currently) not. It is +expected that proper field names are configured. +The jsonf option cannot be used together with either json or csv options. +</td> +</tr> +<tr> +<td valign="top">csv</td> +<td>formats the resulting field (after all modifications) in CSV format +as specified in <a href="http://www.ietf.org/rfc/rfc4180.txt">RFC 4180</a>. +Rsyslog will always use double quotes. Note that in order to have full CSV-formatted +text, you need to define a proper template. An example is this one: +<br>$template csvline,"%syslogtag:::csv%,%msg:::csv%" +<br>Most importantly, you need to provide the commas between the fields +inside the template. +The csv option cannot be used together with either json or jsonf options. +<br><i>This feature was introduced in rsyslog 4.1.6.</i> +</td> +</tr> +<tr> +<td><b>droplastlf</b></td> +<td>The last LF in the message (if any), is dropped. +Especially useful for PIX.</td> +</tr> +<tr> +<td valign="top"><b>spifno1stsp</b></td> +<td>This option looks scary and should probably not be used by a user. For any field +given, it returns either a single space character or no character at all. Field content +is never returned. A space is returned if (and only if) the first character of the +field's content is NOT a space. This option is kind of a hack to solve a problem rooted +in RFC 3164: 3164 specifies no delimiter between the syslog tag sequence and the actual +message text. Almost all implementation in fact delemit the two by a space. As of +RFC 3164, this space is part of the message text itself. This leads to a problem when +building the message (e.g. when writing to disk or forwarding). Should a delimiting +space be included if the message does not start with one? If not, the tag is immediately +followed by another non-space character, which can lead some log parsers to misinterpret +what is the tag and what the message. The problem finally surfaced when the klog module +was restructured and the tag correctly written. It exists with other message sources, +too. The solution was the introduction of this special property replacer option. Now, +the default template can contain a conditional space, which exists only if the +message does not start with one. While this does not solve all issues, it should +work good enough in the far majority of all cases. If you read this text and have +no idea of what it is talking about - relax: this is a good indication you will never +need this option. Simply forget about it ;) +</td> +</tr> +<tr> +<td></td> +<td></td> +</tr> +<tr> +<td><b>New character position</b></td> +<td>In addition to the above mentioned Character Positions in the legacy format, +positions can be determined by specifying the correct options for the properties. +Again, this is mostly for using the list format.</td> +</tr> +<tr> +<td>position.From</td> +<td>Character position in the property to start from.</td> +</tr> +<tr> +<td>position.To</td> +<td>Character position that determines the end for extraction. If the value is "$" +then the end of the string will be used.</td> +</tr> +<tr> +<td>field.Number</td> +<td>The number of the field, which should be used for the search operation with Regex.</td> +</tr> +<tr> +<td>field.Delimiter</td> +<td>The Character that should delimit a field. Example: ",". Everything in a +property until this character is considered a field.</td> +</tr> +<tr> +<td>regex.Expression</td> +<td>Value to be compared to property.</td> +</tr> +<tr> +<td>regex.Type</td> +<td>Values BRE or ERE</td> +</tr> +<tr> +<td>regex.NoMatchMode</td> +<td>DFLT, BLANK, ZERO, FIELD</td> +</tr> +<tr> +<td>regex.Match</td> +<td>Match to use.</td> +</tr> +<tr> +<td>regex.Submatch</td> +<td>Submatch to use. Values 0-9 whereas 0 = All</td> +</tr> +</tbody> +</table> + + + +<h2>Legacy Property Options</h2> +<b><code>property options</code></b> are +case-insensitive. Currently, the following options are defined: +<p></p> +<table> +<tbody> +<tr> +<td><b>uppercase</b></td> +<td>convert property to lowercase only</td> +</tr> +<tr> +<td><b>lowercase</b></td> +<td>convert property text to uppercase only</td> +</tr> +<tr> +<td><b>json</b></td> +<td>encode the value so that it can be used inside a JSON field. This means +that several characters (according to the JSON spec) are being escaped, for +example US-ASCII LF is replaced by "\n". +The json option cannot be used together with either jsonf or csv options. +</td> +</tr> +<tr> +<td><b>jsonf</b></td> +<td><i>(available in 6.3.9+)</i> +This signifies that the property should be expressed as a json <b>f</b>ield. +That means not only the property is written, but rather a complete json field in +the format<br> +"fieldname"="value"</b> +where "filedname" is the assigend field name (or the property name if none was assigned) +and value is the end result of property replacer operation. Note that value supports +all property replacer options, like substrings, case converson and the like. +Values are properly json-escaped. However, field names are (currently) not. It is +expected that proper field names are configured. +The jsonf option cannot be used together with either json or csv options. +</td> +</tr> +<tr> +<td valign="top"><b>csv</b></td> +<td>formats the resulting field (after all modifications) in CSV format +as specified in <a href="http://www.ietf.org/rfc/rfc4180.txt">RFC 4180</a>. +Rsyslog will always use double quotes. Note that in order to have full CSV-formatted +text, you need to define a proper template. An example is this one: +<br>$template csvline,"%syslogtag:::csv%,%msg:::csv%" +<br>Most importantly, you need to provide the commas between the fields +inside the template. +The csv option cannot be used together with either json or jsonf options. +<br><i>This feature was introduced in rsyslog 4.1.6.</i> +</td> +</tr> +<tr> +<td><b>drop-last-lf</b></td> +<td>The last LF in the message (if any), is dropped. +Especially useful for PIX.</td> +</tr> +<tr> +<td><b>date-mysql</b></td> +<td>format as mysql date</td> +</tr> +<tr> +<td><b>date-rfc3164</b></td> +<td>format as RFC 3164 date</td> +</tr> +<tr> +<tr> +<td valign="top"><b>date-rfc3164-buggyday</b></td> +<td>similar to date-rfc3164, but emulates a common coding error: RFC 3164 demands +that a space is written for single-digit days. With this option, a zero is +written instead. This format seems to be used by syslog-ng and the +date-rfc3164-buggyday option can be used in migration scenarios where otherwise +lots of scripts would need to be adjusted. It is recommended <i>not</i> to use this +option when forwarding to remote hosts - they may treat the date as invalid +(especially when parsing strictly according to RFC 3164).</td> +<br><i>This feature was introduced in rsyslog 4.6.2 and v4 versions above and +5.5.3 and all versions above.</i> +</tr> +<tr> +<td><b>date-rfc3339</b></td> +<td>format as RFC 3339 date</td> +</tr> +<tr> +<td><b>date-unixtimestamp</b></td> +<td>format as unix timestamp (seconds since epoch)</td> +</tr> +<tr> +<td><b>date-subseconds</b></td> +<td>just the subseconds of a timestamp (always 0 for a low precision timestamp)</td> +</tr> +<tr> +<td valign="top"><b>escape-cc</b></td> +<td>replace control characters (ASCII value 127 and values +less then 32) with an escape sequence. The sequnce is +"#<charval>" where charval is the 3-digit decimal value +of the control character. For example, a tabulator would be replaced by +"#009".<br> +Note: using this option requires that <a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> +is set to off.</td> +</tr> +<tr> +<td valign="top"><b>space-cc</b></td> +<td>replace control characters by spaces<br> +Note: using this option requires that <a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> +is set to off.</td> +</tr> +<tr> +<td valign="top"><b>drop-cc</b></td> +<td>drop control characters - the resulting string will +neither contain control characters, escape sequences nor any other +replacement character like space.<br> +Note: using this option requires that <a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> +is set to off.</td> +</tr> +<tr> +<td valign="top"><b>sp-if-no-1st-sp</b></td> +<td>This option looks scary and should probably not be used by a user. For any field +given, it returns either a single space character or no character at all. Field content +is never returned. A space is returned if (and only if) the first character of the +field's content is NOT a space. This option is kind of a hack to solve a problem rooted +in RFC 3164: 3164 specifies no delimiter between the syslog tag sequence and the actual +message text. Almost all implementation in fact delemit the two by a space. As of +RFC 3164, this space is part of the message text itself. This leads to a problem when +building the message (e.g. when writing to disk or forwarding). Should a delimiting +space be included if the message does not start with one? If not, the tag is immediately +followed by another non-space character, which can lead some log parsers to misinterpret +what is the tag and what the message. The problem finally surfaced when the klog module +was restructured and the tag correctly written. It exists with other message sources, +too. The solution was the introduction of this special property replacer option. Now, +the default template can contain a conditional space, which exists only if the +message does not start with one. While this does not solve all issues, it should +work good enough in the far majority of all cases. If you read this text and have +no idea of what it is talking about - relax: this is a good indication you will never +need this option. Simply forget about it ;) +</td> +</tr> +<tr> +<td valign="top"><b>secpath-drop</b></td> +<td>Drops slashes inside the field (e.g. "a/b" becomes "ab"). +Useful for secure pathname generation (with dynafiles). +</td> +</tr> +<tr> +<td valign="top"><b>secpath-replace</b></td> +<td>Replace slashes inside the field by an underscore. (e.g. "a/b" becomes "a_b"). +Useful for secure pathname generation (with dynafiles). +</td> +</tr> +<tr> +<td><b>mandatory-field</b></td> +<td>In templates that are used for building field lists (in particular, ommongodb), include +this field, even if it is empty (or NULL). If not set, the field will be removed from +the output field set if empty. The latter is the default case. +</tr> +</tbody> +</table> +<p>To use multiple options, simply place them one after each other with a comma delmimiting +them. For example "escape-cc,sp-if-no-1st-sp". If you use conflicting options together, +the last one will override the previous one. For example, using "escape-cc,drop-cc" will +use drop-cc and "drop-cc,escape-cc" will use escape-cc mode. +<h2>Fieldname</h2> +<p><i>(available in 6.3.9+)</i> +<p>This field permits to specify a field name for structured-data emitting property replacer +options. It was initially introduced to support the "jsonf" option, for which it provides +the capability to set an alternative field name. If it is not specified, it defaults to +the property name. +<h2>Further Links</h2> +<ul> +<li>Article on "<a href="rsyslog_recording_pri.html">Recording +the Priority of Syslog Messages</a>" (describes use of templates +to record severity and facility of a message)</li> +<li><a href="rsyslog_conf.html">Configuration file +format</a>, this is where you actually use the property replacer.</li> +</ul> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008, 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body></html> diff --git a/doc/queueWorkerLogic.jpg b/doc/queueWorkerLogic.jpg Binary files differnew file mode 100644 index 00000000..fb143c4a --- /dev/null +++ b/doc/queueWorkerLogic.jpg diff --git a/doc/queueWorkerLogic_small.jpg b/doc/queueWorkerLogic_small.jpg Binary files differnew file mode 100644 index 00000000..4fae6d27 --- /dev/null +++ b/doc/queueWorkerLogic_small.jpg diff --git a/doc/queue_analogy_tv.png b/doc/queue_analogy_tv.png Binary files differnew file mode 100644 index 00000000..fedcb558 --- /dev/null +++ b/doc/queue_analogy_tv.png diff --git a/doc/queue_msg_state.dot b/doc/queue_msg_state.dot new file mode 100644 index 00000000..bfef2657 --- /dev/null +++ b/doc/queue_msg_state.dot @@ -0,0 +1,25 @@ +// This file is part of rsyslog. +// +// rsyslog message state in queue processing +// +// see http://www.graphviz.org for how to obtain the graphviz processor +// which is used to build the actual graph. +// +// generate the graph with +// $ dot file.dot -Tpng >file.png + +digraph msgState { + rankdir=LR + + prod [label="producer" style="dotted" shape="box"] + que [label="queued"] + deq [label="dequeued"] + del [label="deleted"] + + prod -> que [label="qEnq()" style="dotted"] + que -> deq [label="qDeq()"] + deq -> del [label="qDel()"] + deq -> que [label="fatal failure\n& restart"] + + //{rank=same; del apf pdn } +} diff --git a/doc/queue_msg_state.jpeg b/doc/queue_msg_state.jpeg Binary files differnew file mode 100644 index 00000000..a215f000 --- /dev/null +++ b/doc/queue_msg_state.jpeg diff --git a/doc/queues.html b/doc/queues.html new file mode 100644 index 00000000..75b70fbf --- /dev/null +++ b/doc/queues.html @@ -0,0 +1,398 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>Understanding rsyslog queues</title></head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h1>Understanding rsyslog Queues</h1> +<p>Rsyslog uses queues whenever two activities need to be loosely coupled. With a +queue, one part of the system "produces" something while another part "consumes" +this something. The "something" is most often syslog messages, but queues may +also be used for other purposes.</p> +<p>This document provides a good insight into technical details, operation modes +and implications. In addition to it, an +<a href="queues_analogy.html">rsyslog queue concepts overview</a> document +exists which tries to explain queues with the help of some analogies. This may +probably be a better place to start reading about queues. I assume that once you +have understood that document, the material here will be much easier to grasp +and look much more natural. +<p>The most prominent example is the main message queue. Whenever rsyslog +receives a message (e.g. locally, via UDP, TCP or in whatever else way), it +places these messages into the main message queue. Later, it is dequeued by the +rule processor, which then evaluates which actions are to be carried out. In +front of each action, there is also a queue, which potentially de-couples the +filter processing from the actual action (e.g. writing to file, database or +forwarding to another host).</p> +<h1>Where are Queues Used?</h1> +<p>Currently, queues are used for the main message queue and for the +actions.</p> +<p>There is a single main message queue inside rsyslog. Each input module +delivers messages to it. The main message queue worker filters messages based on +rules specified in rsyslog.conf and dispatches them to the individual action +queues. Once a message is in an action queue, it is deleted from the main +message queue.</p> +<p>There are multiple action queues, one for each configured action. By default, +these queues operate in direct (non-queueing) mode. Action queues are fully +configurable and thus can be changed to whatever is best for the given use case.</p> +<p>Future versions of rsyslog will most probably utilize queues at other places, +too.</p> +<p> Wherever "<i><object></i>" is used in the config file +statements, substitute "<i><object></i>" with either "MainMsg" or "Action". The +former will set main message queue +parameters, the later parameters for the next action that will be +created. Action queue parameters can not be modified once the action has been +specified. For example, to tell the main message queue to save its content on +shutdown, use <i>$MainMsgQueueSaveOnShutdown on</i>".</p> +<p>If the same parameter is specified multiple times before a queue is created, +the last one specified takes precedence. The main message queue is created after +parsing the config file and all of its potential includes. An action queue is +created each time an action selector is specified. Action queue parameters are +reset to default after an action queue has been created (to provide a clean +environment for the next action).</p> +<p>Not all queues necessarily support the full set of queue configuration +parameters, because not all are applicable. For example, in current output +module design, actions do not support multi-threading. Consequently, the number +of worker threads is fixed to one for action queues and can not be changed.</p> +<h1>Queue Modes</h1> +<p>Rsyslog supports different queue modes, some with submodes. Each of them has +specific advantages and disadvantages. Selecting the right queue mode is quite +important when tuning rsyslogd. The queue mode (aka "type") is set via the "<i>$<object>QueueType</i>" +config directive.</p> +<h2>Direct Queues</h2> +<p>Direct queues are <b>non</b>-queuing queues. A queue in direct mode does +neither queue nor buffer any of the queue elements but rather passes the element +directly (and immediately) from the producer to the consumer. This sounds strange, +but there is a good reason for this queue type.</p> +<p>Direct mode queues allow to use queues generically, even in places where +queuing is not always desired. A good example is the queue in front of output +actions. While it makes perfect sense to buffer forwarding actions or database +writes, it makes only limited sense to build up a queue in front of simple local +file writes. Yet, rsyslog still has a queue in front of every action. So for +file writes, the queue mode can simply be set to "direct", in which case no +queuing happens.</p> +<p>Please note that a direct queue also is the only queue type that passes back +the execution return code (success/failure) from the consumer to the producer. +This, for example, is needed for the backup action logic. Consequently, backup +actions require the to-be-checked action to use a "direct" mode queue.</p> +<p>To create a direct queue, use the "<i>$<object>QueueType Direct</i>" config +directive.</p> +<h2>Disk Queues</h2> +<p>Disk queues use disk drives for buffering. The important fact is that the +always use the disk and do not buffer anything in memory. Thus, the queue is +ultra-reliable, but by far the slowest mode. For regular use cases, this queue +mode is not recommended. It is useful if log data is so important that it must +not be lost, even in extreme cases.</p> +<p>When a disk queue is written, it is done in chunks. Each chunk receives its +individual file. Files are named with a prefix (set via the "<i>$<object>QueueFilename</i>" +config directive) and followed by a 7-digit number (starting at one and +incremented for each file). Chunks are 10mb by default, a different size can be +set via the"<i>$<object>QueueMaxFileSize</i>" config directive. Note that +the size limit is not a sharp one: rsyslog always writes one complete queue +entry, even if it violates the size limit. So chunks are actually a little but +(usually less than 1k) larger then the configured size. Each chunk also has a +different size for the same reason. If you observe different chunk sizes, you +can relax: this is not a problem.</p> +<p>Writing in chunks is used so that processed data can quickly be deleted and +is free for other uses - while at the same time keeping no artificial upper +limit on disk space used. If a disk quota is set (instructions further below), +be sure that the quota/chunk size allows at least two chunks to be written. +Rsyslog currently does not check that and will fail miserably if a single chunk +is over the quota.</p> +<p>Creating new chunks costs performance but provides quicker ability to free +disk space. The 10mb default is considered a good compromise between these two. +However, it may make sense to adapt these settings to local policies. For +example, if a disk queue is written on a dedicated 200gb disk, it may make sense +to use a 2gb (or even larger) chunk size.</p> +<p>Please note, however, that the disk queue by default does not update its +housekeeping structures every time it writes to disk. This is for performance +reasons. In the event of failure, data will still be lost (except when manually +is mangled with the file structures). However, disk queues can be set to write +bookkeeping information on checkpoints (every n records), so that this can be +made ultra-reliable, too. If the checkpoint interval is set to one, no data can +be lost, but the queue is exceptionally slow.</p> +<p>Each queue can be placed on a different disk for best performance and/or +isolation. This is currently selected by specifying different <i>$WorkDirectory</i> +config directives before the queue creation statement.</p> +<p>To create a disk queue, use the "<i>$<object>QueueType Disk</i>" config +directive. Checkpoint intervals can be specified via "<i>$<object>QueueCheckpointInterval</i>", +with 0 meaning no checkpoints. Note that disk-based queues can be made very reliable +by issuing a (f)sync after each write operation. Starting with version 4.3.2, this can +be requested via "<i><object>QueueSyncQueueFiles on/off</i> with the +default being off. Activating this option has a performance penalty, so it should +not be turned on without reason.</p> +<h2>In-Memory Queues</h2> +<p>In-memory queue mode is what most people have on their mind when they think +about computing queues. Here, the enqueued data elements are held in memory. +Consequently, in-memory queues are very fast. But of course, they do not survive +any program or operating system abort (what usually is tolerable and unlikely). +Be sure to use an UPS if you use in-memory mode and your log data is important +to you. Note that even in-memory queues may hold data for an infinite amount of +time when e.g. an output destination system is down and there is no reason to move +the data out of memory (lying around in memory for an extended period of time is +NOT a reason). Pure in-memory queues can't even store queue elements anywhere +else than in core memory. </p> +<p>There exist two different in-memory queue modes: LinkedList and FixedArray. +Both are quite similar from the user's point of view, but utilize different +algorithms. </p> +<p>A FixedArray queue uses a fixed, pre-allocated array that holds pointers to +queue elements. The majority of space is taken up by the actual user data +elements, to which the pointers in the array point. The pointer array itself is +comparatively small. However, it has a certain memory footprint even if the +queue is empty. As there is no need to dynamically allocate any housekeeping +structures, FixedArray offers the best run time performance (uses the least CPU +cycle). FixedArray is best if there is a relatively low number of queue elements +expected and performance is desired. It is the default mode for the main message +queue (with a limit of 10,000 elements).</p> +<p>A LinkedList queue is quite the opposite. All housekeeping structures are +dynamically allocated (in a linked list, as its name implies). This requires +somewhat more runtime processing overhead, but ensures that memory is only +allocated in cases where it is needed. LinkedList queues are especially +well-suited for queues where only occasionally a than-high number of elements +need to be queued. A use case may be occasional message burst. Memory +permitting, it could be limited to e.g. 200,000 elements which would take up +only memory if in use. A FixedArray queue may have a too large static memory +footprint in such cases.</p> +<p><b>In general, it is advised to use LinkedList mode if in doubt</b>. The +processing overhead compared to FixedArray is low and may be +outweigh by the reduction in memory use. Paging in most-often-unused +pointer array pages can be much slower than dynamically allocating them.</p> +<p>To create an in-memory queue, use the "<i>$<object>QueueType LinkedList</i>" +or "<i>$<object>QueueType FixedArray</i>" config directive.</p> +<h3>Disk-Assisted Memory Queues</h3> +<p>If a disk queue name is defined for in-memory queues (via <i> +$<object>QueueFileName</i>), they automatically +become "disk-assisted" (DA). In that mode, data is written to disk (and read +back) on an as-needed basis.</p> +<p>Actually, the regular memory queue (called the +"primary queue") and a disk queue (called the "DA queue") work in tandem in this +mode. Most importantly, the disk queue is activated if the primary queue is full +or needs to be persisted on shutdown. Disk-assisted queues combine the +advantages of pure memory queues with those of pure disk queues. Under normal +operations, they are very fast and messages will never touch the disk. But if +there is need to, an unlimited amount of messages can be buffered (actually +limited by free disk space only) and data can be persisted between rsyslogd runs.</p> +<p>With a DA-queue, both disk-specific and in-memory specific configuration +parameters can be set. From the user's point of view, think of a DA queue like a +"super-queue" which does all within a single queue [from the code perspective, +there is some specific handling for this case, so it is actually much like a +single object].</p> +<p>DA queues are typically used to de-couple potentially long-running and +unreliable actions (to make them reliable). For example, it is recommended to +use a disk-assisted linked list in-memory queue in front of each database and +"send via tcp" action. Doing so makes these actions reliable and de-couples +their potential low execution speed from the rest of your rules (e.g. the local +file writes). There is a howto on <a href="rsyslog_high_database_rate.html"> +massive database inserts</a> which nicely describes this use case. It may even +be a good read if you do not intend to use databases.</p> +<p>With DA queues, we do not simply write out everything to disk and then run as +a disk queue once the in-memory queue is full. A much smarter algorithm is used, +which involves a "high watermark" and a "low watermark". Both specify numbers of +queued items. If the queue size reaches high watermark elements, the queue +begins to write data elements to disk. It does so until it reaches the low water +mark elements. At this point, it stops writing until either high water mark is +reached again or the on-disk queue becomes empty, in which case the queue +reverts back to in-memory mode, only. While holding at the low watermark, new +elements are actually enqueued in memory. They are eventually written to disk, +but only if the high water mark is ever reached again. If it isn't, these items +never touch the disk. So even when a queue runs disk-assisted, there is +in-memory data present (this is a big difference to pure disk queues!).</p> +<p>This algorithm prevents unnecessary disk writes, but also leaves some +additional buffer space for message bursts. Remember that creating disk files +and writing to them is a lengthy operation. It is too lengthy to e.g. block +receiving UDP messages. Doing so would result in message loss. Thus, the queue +initiates DA mode, but still is able to receive messages and enqueue them - as +long as the maximum queue size is not reached. The number of elements between +the high water mark and the maximum queue size serves as this "emergency +buffer". Size it according to your needs, if traffic is very bursty you will +probably need a large buffer here. Keep in mind, though, that under normal +operations these queue elements will probably never be used. Setting the high +water mark too low will cause disk-assistance to be turned on more often than +actually needed.</p> +<p>The water marks can be set via the "<i>$<object>QueueHighWatermark</i>" and +"<i>$<object>QueueHighWatermark</i>" configuration file directives. Note that +these are actual numbers, not precentages. Be sure they make sense (also in +respect to "<i>$<object>QueueSize</i>"), as rsyslodg does currently not perform +any checks on the numbers provided. It is easy to screw up the system here (yes, +a feature enhancement request is filed ;)).</p> +<h1>Limiting the Queue Size</h1> +<p>All queues, including disk queues, have a limit of the number of elements +they can enqueue. This is set via the "<i>$<object>QueueSize</i>" config +parameter. Note that the size is specified in number of enqueued elements, not +their actual memory size. Memory size limits can not be set. A conservative +assumption is that a single syslog messages takes up 512 bytes on average +(in-memory, NOT on the wire, this *is* a difference).</p> +<p>Disk assisted queues are special in that they do <b>not</b> have any size +limit. The enqueue an unlimited amount of elements. To prevent running out of +space, disk and disk-assisted queues can be size-limited via the "<i>$<object>QueueMaxDiskSpace</i>" +configuration parameter. If it is not set, the limit is only available free +space (and reaching this limit is currently not very gracefully handled, so +avoid running into it!). If a limit is set, the queue can not grow larger than +it. Note, however, that the limit is approximate. The engine always writes +complete records. As such, it is possible that slightly more than the set limit +is used (usually less than 1k, given the average message size). Keeping strictly +on the limit would be a performance hurt, and thus the design decision was to +favour performance. If you don't like that policy, simply specify a slightly +lower limit (e.g. 999,999K instead of 1G).</p> +<p>In general, it is a good idea to limit the pysical disk space even if you +dedicate a whole disk to rsyslog. That way, you prevent it from running out of +space (future version will have an auto-size-limit logic, that then kicks in in +such situations).</p> +<h1>Worker Thread Pools</h1> +<p>Each queue (except in "direct" mode) has an associated pool of worker +threads. Worker threads carry out the action to be performed on the data +elements enqueued. As an actual sample, the main message queue's worker task is +to apply filter logic to each incoming message and enqueue them to the relevant +output queues (actions).</p> +<p>Worker threads are started and stopped on an as-needed basis. On a system +without activity, there may be no worker at all running. One is automatically +started when a message comes in. Similarily, additional workers are started if +the queue grows above a specific size. The "<i>$<object>QueueWorkerThreadMinimumMessages</i>" +config parameter controls worker startup. If it is set to the minimum number of +elements that must be enqueued in order to justify a new worker startup. For +example, let's assume it is set to 100. As long as no more than 100 messages are +in the queue, a single worker will be used. When more than 100 messages arrive, +a new worker thread is automatically started. Similarily, a third worker will be +started when there are at least 300 messages, a forth when reaching 400 and so +on.</p> +<p>It, however, does not make sense to have too many worker threads running in +parall. Thus, the upper limit ca be set via "<i>$<object>QueueWorkerThreads</i>". +If it, for example, is set to four, no more than four workers will ever be +started, no matter how many elements are enqueued. </p> +<p>Worker threads that have been started are kept running until an inactivity +timeout happens. The timeout can be set via "<i>$<object>QueueWorkerTimeoutThreadShutdown</i>" +and is specified in milliseconds. If you do not like to keep the workers +running, simply set it to 0, which means immediate timeout and thus immediate +shutdown. But consider that creating threads involves some overhead, and this is +why we keep them running. If you would like to never shutdown any worker +threads, specify -1 for this parameter.</p> +<h2>Discarding Messages</h2> +<p>If the queue reaches the so called "discard watermark" (a number of queued +elements), less important messages can automatically be discarded. This is in an +effort to save queue space for more important messages, which you even less like +to loose. Please note that whenever there are more than "discard watermark" +messages, both newly incoming as well as already enqueued low-priority messages +are discarded. The algorithm discards messages newly coming in and those at the +front of the queue.</p> +<p>The discard watermark is a last resort setting. It should be set sufficiently +high, but low enough to allow for large message burst. Please note that it take +effect immediately and thus shows effect promptly - but that doesn't help if the +burst mainly consist of high-priority messages...</p> +<p>The discard watermark is set via the "<i>$<object>QueueDiscardMark</i>" +directive. The priority of messages to be discarded is set via "<i>$<object>QueueDiscardSeverity</i>". +This directive accepts both the usual textual severity as well as a +numerical one. To understand it, you must be aware of the numerical +severity values. They are defined in RFC 3164:</p> +<pre> Numerical Severity<br> Code<br><br> 0 Emergency: system is unusable<br> 1 Alert: action must be taken immediately<br> 2 Critical: critical conditions<br> 3 Error: error conditions<br> 4 Warning: warning conditions<br> 5 Notice: normal but significant condition<br> 6 Informational: informational messages<br> 7 Debug: debug-level messages</pre> +<p>Anything of the specified severity and (numerically) above it is +discarded. To turn message discarding off, simply specify the discard +watermark to be higher than the queue size. An alternative is to +specify the numerical value 8 as DiscardSeverity. This is also the +default setting to prevent unintentional message loss. So if you would +like to use message discarding, you need to set" <i>$<object>QueueDiscardSeverity</i>" to an actual value.</p> +<p>An interesting application is with disk-assisted queues: if the discard +watermark is set lower than the high watermark, message discarding will start +before the queue becomes disk-assisted. This may be a good thing if you would +like to switch to disk-assisted mode only in cases where it is absolutely +unavoidable and you prefer to discard less important messages first.</p> +<h1>Filled-Up Queues</h1> +<p>If the queue has either reached its configured maximum number of entries or +disk space, it is finally full. If so, rsyslogd throttles the data element +submitter. If that, for example, is a reliable input (TCP, local log socket), +that will slow down the message originator which is a good +resolution for this scenario.</p> +<p>During throtteling, a disk-assisted queue continues to write to disk and +messages are also discarded based on severity as well as regular dequeuing and +processing continues. So chances are good the situation will be resolved by +simply throttling. Note, though, that throtteling is highly undesirable for +unreliable sources, like UDP message reception. So it is not a good thing to run +into throtteling mode at all.</p> +<p>We can not hold processing +infinitely, not even when throtteling. For example, throtteling the local +log socket too long would cause the system at whole come to a standstill. To +prevent this, rsyslogd times out after a configured period ("<i>$<object>QueueTimeoutEnqueue</i>", +specified in milliseconds) if no space becomes available. As a last resort, it +then discards the newly arrived message.</p> +<p>If you do not like throtteling, set the timeout to 0 - the message will then +immediately be discarded. If you use a high timeout, be sure you know what you +do. If a high main message queue enqueue timeout is set, it can lead to +something like a complete hang of the system. The same problem does not apply to +action queues.</p> +<h2>Rate Limiting</h2> +<p>Rate limiting provides a way to prevent rsyslogd from processing things too +fast. It can, for example, prevent overruning a receiver system.</p> +<p>Currently, there are only limited rate-limiting features available. The "<i>$<object>QueueDequeueSlowdown</i>" +directive allows to specify how long (in microseconds) dequeueing should be +delayed. While simple, it still is powerful. For example, using a +DequeueSlowdown delay of 1,000 microseconds on a UDP send action ensures that no +more than 1,000 messages can be sent within a second (actually less, as there is +also some time needed for the processing itself).</p><h2>Processing Timeframes</h2><p>Queues +can be set to dequeue (process) messages only during certain +timeframes. This is useful if you, for example, would like to transfer +the bulk of messages only during off-peak hours, e.g. when you have +only limited bandwidth on the network path the the central server.</p><p>Currently, +only a single timeframe is supported and, even worse, it can only be +specified by the hour. It is not hard to extend rsyslog's capabilities +in this regard - it was just not requested so far. So if you need more +fine-grained control, let us know and we'll probably implement it. +There are two configuration directives, both should be used together or +results are unpredictable:" <i>$<object>QueueDequeueTimeBegin <hour></i>" and "<i>$<object>QueueDequeueTimeEnd <hour></i>". The hour parameter must be specified in 24-hour format (so 10pm is 22). A use case for this parameter can be found in the <a href="http://wiki.rsyslog.com/index.php/OffPeakHours">rsyslog wiki</a>. </p> +<h2>Performance</h2> +<p>The locking involved with maintaining the queue has a potentially large +performance impact. How large this is, and if it exists at all, depends much on +the configuration and actual use case. However, the queue is able to work on +so-called "batches" when dequeueing data elements. With batches, +multiple data elements are dequeued at once (with a single locking call). +The queue dequeues all available elements up to a configured upper +limit (<i><object>DequeueBatchSize <number></i>). It is important +to note that the actual upper limit is dictated by availability. The queue engine +will never wait for a batch to fill. So even if a high upper limit is configured, +batches may consist of fewer elements, even just one, if there are no more elements +waiting in the queue. +<p>Batching +can improve performance considerably. Note, however, that it affects the +order in which messages are passed to the queue worker threads, as each worker +now receive as batch of messages. Also, the larger the batch size and the higher +the maximum number of permitted worker threads, the more main memory is needed. +For a busy server, large batch sizes (around 1,000 or even more elements) may be useful. +Please note that with batching, the main memory must hold BatchSize * NumOfWorkers +objects in memory (worst-case scenario), even if running in disk-only mode. So if you +use the default 5 workers at the main message queue and set the batch size to 1,000, you need +to be prepared that the main message queue holds up to 5,000 messages in main memory +<b>in addition</b> to the configured queue size limits! +<p>The queue object's default maximum batch size +is eight, but there exists different defaults for the actual parts of +rsyslog processing that utilize queues. So you need to check these object's +defaults. +<h2>Terminating Queues</h2> +<p>Terminating a process sounds easy, but can be complex. +Terminating a running queue is in fact the most complex operation a queue +object can perform. You don't see that from a user's point of view, but its +quite hard work for the developer to do everything in the right order.</p> +<p>The complexity arises when the queue has still data enqueued when it +finishes. Rsyslog tries to preserve as much of it as possible. As a first +measure, there is a regular queue time out ("<i>$<object>QueueTimeoutShutdown</i>", +specified in milliseconds): the queue workers are given that time period to +finish processing the queue.</p> +<p>If after that period there is still data in the queue, workers are instructed +to finish the current data element and then terminate. This essentially means +any other data is lost. There is another timeout ("<i>$<object>QueueTimeoutActionCompletion</i>", +also specified in milliseconds) that specifies how long the workers have to +finish the current element. If that timeout expires, any remaining workers are +cancelled and the queue is brought down.</p> +<p>If you do not like to lose data on shutdown, the "<i>$<object>QueueSaveOnShutdown</i>" +parameter can be set to "on". This requires either a disk or disk-assisted +queue. If set, rsyslogd ensures that any queue elements are saved to disk before +it terminates. This includes data elements there were begun being processed by +workers that needed to be cancelled due to too-long processing. For a large +queue, this operation may be lengthy. No timeout applies to a required shutdown +save.</p> +[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008, 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> + +</body></html> diff --git a/doc/queues_analogy.html b/doc/queues_analogy.html new file mode 100644 index 00000000..d7533ad5 --- /dev/null +++ b/doc/queues_analogy.html @@ -0,0 +1,259 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>turning lanes and rsyslog queues - an analogy</title></head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h1>Turning Lanes and Rsyslog Queues - an Analogy</h1> +<p>If there is a single object absolutely vital to understanding the way +rsyslog works, this object is queues. Queues offer a variety of services, +including support for multithreading. While there is elaborate in-depth +documentation on the ins and outs of <a href="queues.html">rsyslog queues</a>, +some of the concepts are hard to grasp even for experienced people. I think this +is because rsyslog uses a very high layer of abstraction which includes things +that look quite unnatural, like queues that do <b>not</b> actually queue... +<p>With this document, I take a different approach: I will not describe every specific +detail of queue operation but hope to be able to provide the core idea of how +queues are used in rsyslog by using an analogy. I will compare the rsyslog data flow +with real-life traffic flowing at an intersection. +<p>But first let's set the stage for the rsyslog part. The graphic below describes +the data flow inside rsyslog: +<p align="center"><img src="dataflow.png" alt="rsyslog data flow"> +<p>Note that there is a <a href="http://www.rsyslog.com/Article350.phtml">video tutorial</a> +available on the data flow. It is not perfect, but may aid in understanding this picture. +<p>For our needs, the important fact to know is that messages enter rsyslog on "the +left side" (for example, via UDP), are being preprocessed, put into the +so-called main queue, taken off that queue, be filtered and be placed into one or +several action queues (depending on filter results). They leave rsyslog on "the +right side" where output modules (like the file or database writer) consume them. +<p>So there are always <b>two</b> stages where a message (conceptually) is queued - first +in the main queue and later on in <i>n</i> action specific queues (with <i>n</i> being the number of +actions that the message in question needs to be processed by, what is being decided +by the "Filter Engine"). As such, a message will be in at least two queues +during its lifetime (with the exception of messages being discarded by the queue itself, +but for the purpose of this document, we will ignore that possibility). +<p>Also, it is vitally +important to understand that <b>each</b> action has a queue sitting in front of it. +If you have dug into the details of rsyslog configuration, you have probably seen that +a queue mode can be set for each action. And the default queue mode is the so-called +"direct mode", in which "the queue does not actually enqueue data". +That sounds silly, but is not. It is an important abstraction that helps keep the code clean. +<p>To understand this, we first need to look at who is the active component. In our data flow, +the active part always sits to the left of the object. For example, the "Preprocessor" +is being called by the inputs and calls itself into the main message queue. That is, the queue +receiver is called, it is passive. One might think that the "Parser & Filter Engine" +is an active component that actively pulls messages from the queue. This is wrong! Actually, +it is the queue that has a pool of worker threads, and these workers pull data from the queue +and then call the passively waiting Parser and Filter Engine with those messages. So the +main message queue is the active part, the Parser and Filter Engine is passive. +<p>Let's now try an analogy analogy for this part: Think about a TV show. The show is produced +in some TV studio, from there sent (actively) to a radio tower. The radio tower passively +receives from the studio and then actively sends out a signal, which is passively received +by your TV set. In our simplified view, we have the following picture: +<p align="center"><img src="queue_analogy_tv.png" alt="rsyslog queues and TV analogy"> +<p>The lower part of the picture lists the equivalent rsyslog entities, in an abstracted way. +Every queue has a producer (in the above sample the input) and a consumer (in the above sample the Parser +and Filter Engine). Their active and passive functions are equivalent to the TV entities +that are listed on top of the rsyslog entity. For example, a rsyslog consumer can never +actively initiate reception of a message in the same way a TV set cannot actively +"initiate" a TV show - both can only "handle" (display or process) +what is sent to them. +<p>Now let's look at the action queues: here, the active part, the producer, is the +Parser and Filter Engine. The passive part is the Action Processor. The later does any +processing that is necessary to call the output plugin, in particular it processes the template +to create the plugin calling parameters (either a string or vector of arguments). From the +action queue's point of view, Action Processor and Output form a single entity. Again, the +TV set analogy holds. The Output <b>does not</b> actively ask the queue for data, but +rather passively waits until the queue itself pushes some data to it. + +<p>Armed with this knowledge, we can now look at the way action queue modes work. My analogy here +is a junction, as shown below (note that the colors in the pictures below are <b>not</b> related to +the colors in the pictures above!): +<p align="center"><img src="direct_queue0.png"> +<p>This is a very simple real-life traffic case: one road joins another. We look at +traffic on the straight road, here shown by blue and green arrows. Traffic in the +opposing direction is shown in blue. Traffic flows without +any delays as long as nobody takes turns. To be more precise, if the opposing traffic takes +a (right) turn, traffic still continues to flow without delay. However, if a car in the red traffic +flow intends to do a (left, then) turn, the situation changes: +<p align="center"><img src="direct_queue1.png"> +<p>The turning car is represented by the green arrow. It cannot turn unless there is a gap +in the "blue traffic stream". And as this car blocks the roadway, the remaining +traffic (now shown in red, which should indicate the block condition), +must wait until the "green" car has made its turn. So +a queue will build up on that lane, waiting for the turn to be completed. +Note that in the examples below I do not care that much about the properties of the +opposing traffic. That is, because its structure is not really important for what I intend to +show. Think about the blue arrow as being a traffic stream that most of the time blocks +left-turners, but from time to time has a gap that is sufficiently large for a left-turn +to complete. +<p>Our road network designers know that this may be unfortunate, and for more important roads +and junctions, they came up with the concept of turning lanes: +<p align="center"><img src="direct_queue2.png"> +<p>Now, the car taking the turn can wait in a special area, the turning lane. As such, +the "straight" traffic is no longer blocked and can flow in parallel to the +turning lane (indicated by a now-green-again arrow). + +<p>However, the turning lane offers only finite space. So if too many cars intend to +take a left turn, and there is no gap in the "blue" traffic, we end up with +this well-known situation: +<p align="center"><img src="direct_queue3.png"> +<p>The turning lane is now filled up, resulting in a tailback of cars intending to +left turn on the main driving lane. The end result is that "straight" traffic +is again being blocked, just as in our initial problem case without the turning lane. +In essence, the turning lane has provided some relief, but only for a limited amount of +cars. Street system designers now try to weight cost vs. benefit and create (costly) +turning lanes that are sufficiently large to prevent traffic jams in most, but not all +cases. +<p><b>Now let's dig a bit into the mathematical properties of turning lanes.</b> We assume that +cars all have the same length. So, units of cars, the length is alsways one (which is nice, +as we don't need to care about that factor any longer ;)). A turning lane has finite capacity of +<i>n</i> cars. As long as the number of cars wanting to take a turn is less than or eqal +to <i>n</i>, "straigth traffic" is not blocked (or the other way round, traffic +is blocked if at least <i>n + 1</i> cars want to take a turn!). We can now find an optimal +value for <i>n</i>: it is a function of the probability that a car wants to turn +and the cost of the turning lane +(as well as the probability there is a gap in the "blue" traffic, but we ignore this +in our simple sample). +If we start from some finite upper bound of <i>n</i>, we can decrease +<i>n</i> to a point where it reaches zero. But let's first look at <i>n = 1</i>, in which case exactly +one car can wait on the turning lane. More than one car, and the rest of the traffic is blocked. +Our everyday logic indicates that this is actually the lowest boundary for <i>n</i>. +<p>In an abstract view, however, <i>n</i> can be zero and that works nicely. There still can be +<i>n</i> cars at any given time on the turning lane, it just happens that this means there can +be no car at all on it. And, as usual, if we have at least <i>n + 1</i> cars wanting to turn, +the main traffic flow is blocked. True, but <i>n + 1 = 0 + 1 = 1</i> so as soon as there is any +car wanting to take a turn, the main traffic flow is blocked (remember, in all cases, I assume +no sufficiently large gaps in the opposing traffic). +<p>This is the situation our everyday perception calls "road without turning lane". +In my math model, it is a "road with turning lane of size 0". The subtle difference +is important: my math model guarantees that, in an abstract sense, there always is a turning +lane, it may just be too short. But it exists, even though we don't see it. And now I can +claim that even in my small home village, all roads have turning lanes, which is rather +impressive, isn't it? ;) +<p><b>And now we finally have arrived at rsyslog's queues!</b> Rsyslog action queues exists for +all actions just like all roads in my village have turning lanes! And as in this real-life sample, +it may be hard to see the action queues for that reason. In rsyslog, the "direct" queue +mode is the equivalent to the 0-sized turning lane. And actions queues are the equivalent to turning +lanes in general, with our real-life <i>n</i> being the maximum queue size. +The main traffic line (which sometimes is blocked) is the equivalent to the +main message queue. And the periods without gaps in the opposing traffic are equivalent to +execution time of an action. In a rough sketch, the rsyslog main and action queues look like in the +following picture. +<p align="center"><img src="direct_queue_rsyslog.png"> +<p>We need to read this picture from right to left (otherwise I would need to redo all +the graphics ;)). In action 3, you see a 0-sized turning lane, aka an action queue in "direct" +mode. All other queues are run in non-direct modes, but with different sizes greater than 0. +<p>Let us first use our car analogy: +Assume we are in a car on the main lane that wants to take turn into the "action 4" +road. We pass action 1, where a number of cars wait in the turning lane and we pass +action 2, which has a slightly smaller, but still not filled up turning lane. So we pass that +without delay, too. Then we come to "action 3", which has no turning lane. Unfortunately, +the car in front of us wants to turn left into that road, so it blocks the main lane. So, this time +we need to wait. An observer standing on the sidewalk may see that while we need to wait, there are +still some cars in the "action 4" turning lane. As such, even though no new cars can +arrive on the main lane, cars still turn into the "action 4" lane. In other words, +an observer standing in "action 4" road is unable to see that traffic on the main lane +is blocked. +<p>Now on to rsyslog: Other than in the real-world traffic example, messages in rsyslog +can - at more or less the +same time - "take turns" into several roads at once. This is done by duplicating the message +if the road has a non-zero-sized "turning lane" - or in rsyslog terms a queue that is +running in any non-direct mode. If so, a deep copy of the message object is made, that placed into +the action queue and then the initial message proceeds on the "main lane". The action +queue then pushes the duplicates through action processing. This is also the reason why a +discard action inside a non-direct queue does not seem to have an effect. Actually, it discards the +copy that was just created, but the original message object continues to flow. +<p> +In action 1, we have some entries in the action queue, as we have in action 2 (where the queue is +slightly shorter). As we have seen, new messages pass action one and two almost instantaneously. +However, when a messages reaches action 3, its flow is blocked. Now, message processing must wait +for the action to complete. Processing flow in a direct mode queue is something like a U-turn: + +<p align="center"><img src="direct_queue_directq.png" alt="message processing in an rsyslog action queue in direct mode"> +<p>The message starts to execute the action and once this is done, processing flow continues. +In a real-life analogy, this may be the route of a delivery man who needs to drop a parcel +in a side street before he continues driving on the main route. As a side-note, think of what happens +with the rest of the delivery route, at least for today, if the delivery truck has a serious accident +in the side street. The rest of the parcels won't be delivered today, will they? This is exactly how the +discard action works. It drops the message object inside the action and thus the message will no +longer be available for further delivery - but as I said, only if the discard is done in a +direct mode queue (I am stressing this example because it often causes a lot of confusion). +<p>Back to the overall scenario. We have seen that messages need to wait for action 3 to +complete. Does this necessarily mean that at the same time no messages can be processed +in action 4? Well, it depends. As in the real-life scenario, action 4 will continue to +receive traffic as long as its action queue ("turn lane") is not drained. In +our drawing, it is not. So action 4 will be executed while messages still wait for action 3 +to be completed. +<p>Now look at the overall picture from a slightly different angle: +<p align="center"><img src="direct_queue_rsyslog2.png" alt="message processing in an rsyslog action queue in direct mode"> +<p>The number of all connected green and red arrows is four - one each for action 1, 2 and 3 +(this one is dotted as action 4 was a special case) and one for the "main lane" as +well as action 3 (this one contains the sole red arrow). <b>This number is the lower bound for +the number of threads in rsyslog's output system ("right-hand part" of the main message +queue)!</b> Each of the connected arrows is a continuous thread and each "turn lane" is +a place where processing is forked onto a new thread. Also, note that in action 3 the processing +is carried out on the main thread, but not in the non-direct queue modes. +<p>I have said this is "the lower bound for the number of threads...". This is with +good reason: the main queue may have more than one worker thread (individual action queues +currently do not support this, but could do in the future - there are good reasons for that, too +but exploring why would finally take us away from what we intend to see). Note that you +configure an upper bound for the number of main message queue worker threads. The actual number +varies depending on a lot of operational variables, most importantly the number of messages +inside the queue. The number <i>t_m</i> of actually running threads is within the integer-interval +[0,confLimit] (with confLimit being the operator configured limit, which defaults to 5). +Output plugins may have more than one thread created by themselves. It is quite unusual for an +output plugin to create such threads and so I assume we do not have any of these. +Then, the overall number of threads in rsyslog's filtering and output system is +<i>t_total = t_m + number of actions in non-direct modes</i>. Add the number of +inputs configured to that and you have the total number of threads running in rsyslog at +a given time (assuming again that inputs utilize only one thread per plugin, a not-so-safe +assumption). +<p>A quick side-note: I gave the lower bound for <i>t_m</i> as zero, which is somewhat in contrast +to what I wrote at the begin of the last paragraph. Zero is actually correct, because rsyslog +stops all worker threads when there is no work to do. This is also true for the action queues. +So the ultimate lower bound for a rsyslog output system without any work to carry out actually is zero. +But this bound will never be reached when there is continuous flow of activity. And, if you are +curios: if the number of workers is zero, the worker wakeup process is actually handled within the +threading context of the "left-hand-side" (or producer) of the queue. After being +started, the worker begins to play the active queue component again. All of this, of course, +can be overridden with configuration directives. +<p>When looking at the threading model, one can simply add n lanes to the main lane but otherwise +retain the traffic analogy. This is a very good description of the actual process (think what this +means to the "turning lanes"; hint: there still is only one per action!). +<p><b>Let's try to do a warp-up:</b> I have hopefully been able to show that in rsyslog, an action +queue "sits in front of" each output plugin. Messages are received and flow, from input +to output, over various stages and two level of queues to the outputs. Actions queues are always +present, but may not easily be visible when in direct mode (where no actual queuing takes place). +The "road junction with turning lane" analogy well describes the way - and intent - of the various +queue levels in rsyslog. +<p>On the output side, the queue is the active component, <b>not</b> the consumer. As such, the consumer +cannot ask the queue for anything (like n number of messages) but rather is activated by the queue +itself. As such, a queue somewhat resembles a "living thing" whereas the outputs are +just tools that this "living thing" uses. +<p><b>Note that I left out a couple of subtleties</b>, especially when it comes +to error handling and terminating +a queue (you hopefully have now at least a rough idea why I say "terminating <b>a queue</b>" +and not "terminating an action" - <i>who is the "living thing"?</i>). An action returns +a status to the queue, but it is the queue that ultimately decides which messages can finally be +considered processed and which not. Please note that the queue may even cancel an output right in +the middle of its action. This happens, if configured, if an output needs more than a configured +maximum processing time and is a guard condition to prevent slow outputs from deferring a rsyslog +restart for too long. Especially in this case re-queuing and cleanup is not trivial. Also, note that +I did not discuss disk-assisted queue modes. The basic rules apply, but there are some additional +constraints, especially in regard to the threading model. Transitioning between actual +disk-assisted mode and pure-in-memory-mode (which is done automatically when needed) is also far from +trivial and a real joy for an implementer to work on ;). +<p>If you have not done so before, it may be worth reading the +<a href="queues.html">rsyslog queue user's guide,</a> which most importantly lists all +the knobs you can turn to tweak queue operation. +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/rainerscript.html b/doc/rainerscript.html new file mode 100644 index 00000000..7cbbfa9f --- /dev/null +++ b/doc/rainerscript.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>RainerScript</title> + +</head> +<body> +<h1>RainerScript</h1> +<p><b>RainerScript is a scripting language specifically +designed and well-suited +for processing network events and configuring event processors</b> +(with the most prominent sample being syslog). While RainerScript is +theoritically usable with various softwares, it currently is being +used, and developed for, rsyslog. Please note that RainerScript may not +be abreviated as rscript, because that's somebody elses trademark.</p> +<p>RainerScript is currently under development. It has its first +appearance in rsyslog 3.12.0, where it provides complex expression +support. However, this is only a very partial implementatio of the +scripting language. Due to technical restrictions, the final +implementation will have a slightly different syntax. So while you are +invited to use the full power of expresssions, you unfortunatley need +to be prepared to change your configuration files at some later points. +Maintaining backwards-compatibility at this point would cause us to +make too much compromise. Defering the release until everything is +perfect is also not a good option. So use your own judgement.</p> +<p>A formal definition of the language can be found in <a href="rscript_abnf.html">RainerScript ABNF</a>. The +rest of this document describes the language from the user's point of +view. Please note that this doc is also currently under development and +can (and will) probably improve as time progresses. If you have +questions, use the rsyslog forum. Feedback is also always welcome.</p> +<h2>Data Types</h2> +RainerScript is a typeless language. That doesn't imply you don't need +to care about types. Of course, expressions like "A" + "B" will not +return a valid result, as you can't really add two letters (to +concatenate them, use the concatenation operator &). + However, all type conversions are automatically done by the +script interpreter when there is need to do so.<br> +<h2>Expressions</h2> +The language supports arbitrary complex expressions. All usual +operators are supported. The precedence of operations is as follows +(with operations being higher in the list being carried out before +those lower in the list, e.g. multiplications are done before additions.<br> +<ul> +<li>expressions in parenthesis</li><li>not, unary minus</li><li>*, /, % (modulus, as in C)</li><li>+, -, & (string concatenation)</li><li>==, !=, <>, <, >, <=, >=, contains (strings!), startswith (strings!)</li><li>and</li><li>or</li> +</ul>For example, "not a == b" probably returns not what you intended. +The script processor will first evaluate "not a" and then compare the +resulting boolean to the value of b. What you probably intended to do +is "not (a == b)". And if you just want to test for inequality, we +highly suggest to use "!=" or "<>". Both are exactly the same and +are provided so that you can pick whichever you like best. So inquality +of a and b should be tested as "a <> b". The "not" operator +should be reserved to cases where it actually is needed to form a +complex boolean expression. In those cases, parenthesis are highly +recommended. +<h2>Lookup Tables</h2> +<p><a href="lookup_tables.html">Lookup tables</a> are a powerful construct +to obtain "class" information based on message content (e.g. to build +log file names for different server types, departments or remote +offices). +<h2>Functions</h2> +<p>RainerScript supports a currently quite limited set of functions: +<ul> +<li>getenv(str) - like the OS call, returns the value of the environment +variable, if it exists. Returns an empty string if it does not exist. +<li>strlen(str) - returns the length of the provided string +<li>tolower(str) - converts the provided string into lowercase +<li>cstr(expr) - converts expr to a string value +<li>cnum(expr) - converts expr to a number (integer) +<li>re_match(expr, re) - returns 1, if expr matches re, 0 otherwise +<li>re_extract(expr, re, match, submatch, no-found) - extracts +data from a string (property) via a regular expression match. +POSIX ERE regular expressions are used. The variable "match" contains +the number of the match to use. This permits to pick up more than the +first expression match. Submatch is the submatch to match (max 50 supported). +The "no-found" parameter specifies which string is to be returned in case when +the regular expression is not found. Note that match and submatch start with +zero. It currently is not possible to extract more than one submatch with +a single call. +<li>field(str, delim, matchnbr) - returns a field-based substring. str is the string +to search, delim is the delimiter and matchnbr is the match to search +for (the first match starts at 1). This works similar as the field based +property-replacer option. +Versions prior to 7.3.7 only support a single character as delimiter character. +Starting with version 7.3.7, a full string can be used as delimiter. If a single +character is being used as delimiter, delim is the numerical ascii value of the +field delimiter character (so that non-printable characters can by specified). If a +string is used as delmiter, a multi-character string (e.g. "#011") is to be +specified. Samples:<br> +set $!usr!field = field($msg, 32, 3); -- the third field, delimited by space<br> +set $!usr!field = field($msg, "#011", 3); -- the third field, delmited by "#011"<br> +Note that when a single character is specified as string [field($msg, ",", 3)] a +string-based extraction is done, which is more performance intense than the +equivalent single-character [field($msg, 44 ,3)] extraction. +<li>prifilt(constant) - mimics a traditional PRI-based filter (like "*.*" or +"mail.info"). The traditional filter string must be given as a <b>constant string</b>. +Dynamic string evaluation is not permitted (for performance reasons). +</ul> +<p>The following example can be used to build a dynamic filter based on some environment +variable: +<pre> +if $msg contains getenv('TRIGGERVAR') then /path/to/errfile +</pre> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/rfc5424layers.png b/doc/rfc5424layers.png Binary files differnew file mode 100644 index 00000000..70192cc0 --- /dev/null +++ b/doc/rfc5424layers.png diff --git a/doc/rsconf1_abortonuncleanconfig.html b/doc/rsconf1_abortonuncleanconfig.html new file mode 100644 index 00000000..77526c07 --- /dev/null +++ b/doc/rsconf1_abortonuncleanconfig.html @@ -0,0 +1,37 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">rsyslog.conf configuration directive</a> + +<h2>$AboortOnUncleanConfig</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Parameter Values:</b> boolean (on/off, yes/no)</p> +<p><b>Available since:</b> 5.3.1+</p> +<p><b>Default:</b> off</p> +<p><b>Description:</b></p> +<p>This directive permits to prevent rsyslog from running when the configuration file +is not clean. "Not Clean" means there are errors or some other annoyances that rsyslgod +reports on startup. This is a user-requested feature to have a strict startup mode. Note +that with the current code base it is not always possible to differentiate between an +real error and a warning-like condition. As such, the startup will also prevented if +warnings are present. I consider this a good thing in being "strict", but I admit +there also currently is no other way of doing it. +<p><b>Caveats:</b></p> +Note that the consequences of a failed rsyslogd startup can be much more serious than a +startup with only partial configuration. For example, log data may be lost or systems that +depend on the log server in question will not be able to send logs, what in the ultimate +result could result in a system hang on those systems. Also, the local system may hang when +the local log socket has become full and is not read. There exist many such scenarios. +As such, it is strongly recommended not to turn on this directive. + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_actionexeconlywhenpreviousissuspended.html b/doc/rsconf1_actionexeconlywhenpreviousissuspended.html new file mode 100644 index 00000000..1626b4ca --- /dev/null +++ b/doc/rsconf1_actionexeconlywhenpreviousissuspended.html @@ -0,0 +1,31 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$ActionExecOnlyWhenPreviousIsSuspended</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> off</p> +<p><b>Description:</b></p> +<p>This directive allows to specify if actions should always be executed ("off," the default) or only if the previous action is suspended ("on"). This directive works hand-in-hand with the multiple actions per selector feature. It can be used, for example, to create rules that automatically switch destination servers or databases to a (set of) backup(s), if the primary server fails. Note that this feature depends on proper implementation of the suspend feature in the output module. All built-in output modules properly support it (most importantly the database write and the syslog message forwarder).</p> +<p>This selector processes all messages it receives (*.*). It tries to forward every message to primary-syslog.example.com (via tcp). If it can not reach that server, it tries secondary-1-syslog.example.com, if that fails too, it tries secondary-2-syslog.example.com. If neither of these servers can be connected, the data is stored in /var/log/localbuffer. Please note that the secondaries and the local log buffer are only used if the one before them does not work. So ideally, /var/log/localbuffer will never receive a message. If one of the servers resumes operation, it automatically takes over processing again.</p> +<p>We strongly advise not to use repeated line reduction together with ActionExecOnlyWhenPreviousIsSuspended. It may lead to "interesting" and undesired results (but you can try it if you like).</p> +<p><b>Sample:</b></p> +<p><code><b>*.* @@primary-syslog.example.com +<br>$ActionExecOnlyWhenPreviousIsSuspended on +<br>& @@secondary-1-syslog.example.com # & is used to have more than one action for +<br>& @@secondary-2-syslog.example.com # the same selector - the mult-action feature +<br>& /var/log/localbuffer +<br>$ActionExecOnlyWhenPreviousIsSuspended off # to re-set it for the next selector </b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_actionresumeinterval.html b/doc/rsconf1_actionresumeinterval.html new file mode 100644 index 00000000..c0365470 --- /dev/null +++ b/doc/rsconf1_actionresumeinterval.html @@ -0,0 +1,32 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$ActionResumeInterval</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> 30</p> +<p><b>Description:</b></p> +<p>Sets the ActionResumeInterval for all following actions. The interval +provided is always in seconds. Thus, multiply by 60 if you need minutes and +3,600 if you need hours (not recommended).</p> +<p>When an action is suspended (e.g. destination can not be connected), the +action is resumed for the configured interval. Thereafter, it is retried. If +multiple retires fail, the interval is automatically extended. This is to +prevent excessive ressource use for retires. After each 10 retries, the interval +is extended by itself. To be precise, the actual interval is (numRetries / 10 + +1) * $ActionResumeInterval. so after the 10th try, it by default is 60 and after +the 100th try it is 330.</p> +<p><b>Sample:</b></p> +<p><code><b>$ActionResumeInterval 30 </b></code></p> +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_allowedsender.html b/doc/rsconf1_allowedsender.html new file mode 100644 index 00000000..ac39e268 --- /dev/null +++ b/doc/rsconf1_allowedsender.html @@ -0,0 +1,30 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$AllowedSender</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> all allowed</p> +<p><b>Description:</b></p> +<p>Allowed sender lists can be used to specify which remote systems are allowed to send syslog messages to rsyslogd. With them, further hurdles can be placed between an attacker and rsyslogd. If a message from a system not in the allowed sender list is received, that message is discarded. A diagnostic message is logged, so that the fact is recorded (this message can be turned off with the "-w" rsyslogd command line option).</p> +<p>Allowed sender lists can be defined for UDP and TCP senders separately. There can be as many allowed senders as needed. The syntax to specify them is:</p> +<p><code><b>$AllowedSender <protocol>, ip[/bits], ip[/bits]</b></code></p> +<p>"$AllowedSender" is the directive - it must be written exactly as shown and the $ must start at the first column of the line. "<protocol>" is either "UDP" or "TCP". It must immediately be followed by the comma, else you will receive an error message. "ip[/bits]" is a machine or network ip address as in "192.0.2.0/24" or "127.0.0.1". If the "/bits" part is omitted, a single host is assumed (32 bits or mask 255.255.255.255). "/0" is not allowed, because that would match any sending system. If you intend to do that, just remove all $AllowedSender directives. If more than 32 bits are requested with IPv4, they are adjusted to 32. For IPv6, the limit is 128 for obvious reasons. Hostnames, with and without wildcards, may also be provided. If so, the result of revers DNS resolution is used for filtering. Multiple allowed senders can be specified in a comma-delimited list. Also, multiple $AllowedSender lines can be given. They are all combined into one UDP and one TCP list. Performance-wise, it is good to specify those allowed senders with high traffic volume before those with lower volume. As soon as a match is found, no further evaluation is necessary and so you can save CPU cycles.</p> +<p>Rsyslogd handles allowed sender detection very early in the code, nearly as the first action after receiving a message. This keeps the access to potential vulnerable code in rsyslog at a minimum. However, it is still a good idea to impose allowed sender limitations via firewalling.</p> +<p><b>WARNING:</b> by UDP design, rsyslogd can not identify a spoofed sender address in UDP syslog packets. As such, a malicious person could spoof the address of an allowed sender, send such packets to rsyslogd and rsyslogd would accept them as being from the faked sender. To prevent this, use syslog via TCP exclusively. If you need to use UDP-based syslog, make sure that you do proper egress and ingress filtering at the firewall and router level.</p> +<p>Rsyslog also detects some kind of malicious reverse DNS entries. In any case, using DNS names adds an extra layer of vulnerability. We recommend to stick with hard-coded IP addresses wherever possible.</p> +<p><b>Sample:</b></p> +<p><code><b>$AllowedSender UDP, 127.0.0.1, 192.0.2.0/24, [::1]/128, *.example.net, somehost.example.com</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_controlcharacterescapeprefix.html b/doc/rsconf1_controlcharacterescapeprefix.html new file mode 100644 index 00000000..45cd9230 --- /dev/null +++ b/doc/rsconf1_controlcharacterescapeprefix.html @@ -0,0 +1,25 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$ControlCharacterEscapePrefix</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> \</p> +<p><b>Description:</b></p> +<p>This option specifies the prefix character to be used for control character escaping (see option $EscapeControlCharactersOnReceive). By default, it is '\', which is backwards-compatible with sysklogd. Change it to '#' in order to be compliant to the value that is somewhat suggested by Internet-Draft syslog-protocol.</p> +<p><b>IMPORTANT</b>: do not use the ' character. This is reserved and will most probably be used in the future as a character delimiter. For the same reason, the syntax of this directive will probably change in future releases.</p> +<p><b>Sample:</b></p> +<p><code><b>$EscapeControlCharactersOnReceive # # as of syslog-protocol</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_debugprintcfsyslinehandlerlist.html b/doc/rsconf1_debugprintcfsyslinehandlerlist.html new file mode 100644 index 00000000..e158de43 --- /dev/null +++ b/doc/rsconf1_debugprintcfsyslinehandlerlist.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DebugPrintCFSyslineHandlerList</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> on</p> +<p><b>Description:</b></p> +<p>Specifies whether or not the configuration file sysline handler list should be written to the debug log. Possible values: on/off. Default is on. Does not affect operation if debugging is disabled.</p> +<p><b>Sample:</b></p> +<p><code><b></b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_debugprintmodulelist.html b/doc/rsconf1_debugprintmodulelist.html new file mode 100644 index 00000000..f25663fb --- /dev/null +++ b/doc/rsconf1_debugprintmodulelist.html @@ -0,0 +1,23 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> +<h2>$DebugPrintModuleList</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> on</p> +<p><b>Description:</b></p> +<p>Specifies whether or not the module list should be written to the debug log. Possible values: on/off. Default is on. Does not affect operation if debugging is disabled.</p> +<p><b>Sample:</b></p> +<p><code><b></b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_debugprinttemplatelist.html b/doc/rsconf1_debugprinttemplatelist.html new file mode 100644 index 00000000..b5f1f28f --- /dev/null +++ b/doc/rsconf1_debugprinttemplatelist.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DebugPrintTemplateList</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> on</p> +<p><b>Description:</b></p> +<p>Specifies whether or not the template list should be written to the debug log. Possible values: on/off. Default is on. Does not affect operation if debugging is disabled..</p> +<p><b>Sample:</b></p> +<p><code><b></b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_dircreatemode.html b/doc/rsconf1_dircreatemode.html new file mode 100644 index 00000000..b22b6c59 --- /dev/null +++ b/doc/rsconf1_dircreatemode.html @@ -0,0 +1,28 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DirCreateMode</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> 0700</p> +<p><b>Description:</b></p> +<p>This is the same as $FileCreateMode, but for directories automatically generated.</p> +<p>Please visit the +<a target="_blank" href="http://lists.adiscon.net/pipermail/rsyslog/2009-April/001986.html">rsyslog mailing list +archive</a> +to understand why the default is so restrictive.</p> +<p><b>Sample:</b></p> +<p><code><b></b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007-2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_dirgroup.html b/doc/rsconf1_dirgroup.html new file mode 100644 index 00000000..4bc8692f --- /dev/null +++ b/doc/rsconf1_dirgroup.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DirGroup</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>Set the group for directories newly created. Please note that this setting does not affect the group of directories already existing. The parameter is a group name, for which the groupid is obtained by rsyslogd on during startup processing. Interim changes to the user mapping are not detected.</p> +<p><b>Sample:</b></p> +<p><code><b>$DirGroup loggroup</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_dirowner.html b/doc/rsconf1_dirowner.html new file mode 100644 index 00000000..f779c008 --- /dev/null +++ b/doc/rsconf1_dirowner.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DirOwner</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>Set the file owner for directories newly created. Please note that this setting does not affect the owner of directories already existing. The parameter is a user name, for which the userid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.</p> +<p><b>Sample:</b></p> +<p><code><b>$DirOwner loguser</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_dropmsgswithmaliciousdnsptrrecords.html b/doc/rsconf1_dropmsgswithmaliciousdnsptrrecords.html new file mode 100644 index 00000000..95027a70 --- /dev/null +++ b/doc/rsconf1_dropmsgswithmaliciousdnsptrrecords.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DropMsgsWithMaliciousDnsPTRRecords</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> off</p> +<p><b>Description:</b></p> +<p>Rsyslog contains code to detect malicious DNS PTR records (reverse name resolution). An attacker might use specially-crafted DNS entries to make you think that a message might have originated on another IP address. Rsyslog can detect those cases. It will log an error message in any case. If this option here is set to "on", the malicious message will be completely dropped from your logs. If the option is set to "off", the message will be logged, but the original IP will be used instead of the DNS name.</p> +<p><b>Sample:</b></p> +<p><code><b>$DropMsgsWithMaliciousDnsPTRRecords on</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_droptrailinglfonreception.html b/doc/rsconf1_droptrailinglfonreception.html new file mode 100644 index 00000000..fb59b871 --- /dev/null +++ b/doc/rsconf1_droptrailinglfonreception.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DropTrailingLFOnReception</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> on</p> +<p><b>Description:</b></p> +<p>Syslog messages frequently have the line feed character (LF) as the last character of the message. In almost all cases, this LF should not really become part of the message. However, recent IETF syslog standardization recommends against modifying syslog messages (e.g. to keep digital signatures valid). This option allows to specify if trailing LFs should be dropped or not. The default is to drop them, which is consistent with what sysklogd does.</p> +<p><b>Sample:</b></p> +<p><code><b>$DropTrailingLFOnRecption on</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_dynafilecachesize.html b/doc/rsconf1_dynafilecachesize.html new file mode 100644 index 00000000..cacbf6e5 --- /dev/null +++ b/doc/rsconf1_dynafilecachesize.html @@ -0,0 +1,25 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$DynaFileCacheSize</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> 10</p> +<p><b>Description:</b></p> +<p>This directive specifies the maximum size of the cache for dynamically-generated file names. Selector lines with dynamic files names ('?' indicator) support writing to multiple files with a single selector line. This setting specifies how many open file handles should be cached. If, for example, the file name is generated with the hostname in it and you have 100 different hosts, a cache size of 100 would ensure that files are opened once and then stay open. This can be a great way to increase performance. If the cache size is lower than the number of different files, the least recently used one is discarded (and the file closed). The hardcoded maximum is 10,000 - a value that we assume should already be very extreme. Please note that if you expect to run with a very large number of files, you probably need to reconfigure the kernel to support such a large number. In practice, we do NOT recommend to use a cache of more than 1,000 entries. The cache lookup would probably require more time than the open and close operations. The minimum value is 1.</p> +<p>Numbers are always in decimal. Leading zeros should be avoided (in some later version, they may be mis-interpreted as being octal). Multiple directives may be given. They are applied to selector lines based on order of appearance.</p> +<p><b>Sample:</b></p> +<p><code><b>$DynaFileCacheSize 100 # a cache of 100 files at most</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_escape8bitcharsonreceive.html b/doc/rsconf1_escape8bitcharsonreceive.html new file mode 100644 index 00000000..408851c1 --- /dev/null +++ b/doc/rsconf1_escape8bitcharsonreceive.html @@ -0,0 +1,44 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$Escape8BitCharactersOnReceive</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> off</p> +<p><b>Available Since:</b> 5.5.2</p> +<p><b>Description:</b></p> +<p>This directive instructs rsyslogd to replace non US-ASCII characters (those that +have the 8th bit set) during reception of the message. +This may be useful for some systems. +Please note that this escaping breaks Unicode and many other encodings. Most importantly, +it can be assumed that Asian and European characters will be rendered hardly readable by +this settings. However, it may still be useful when the logs themself are primarily +in English and only occasionally contain local script. +If this option is turned on, all control-characters are converted to a 3-digit octal number and be prefixed with the $ControlCharacterEscapePrefix character (being '#' by default). +<p><b>Warning:</b></p> +<ul> + <li>turning on this option most probably destroys non-western character sets + (like Japanese, Chinese and Korean) as well as European character sets.</li> + <li>turning on this option destroys digital signatures if such exists inside + the message</li> + <li>if turned on, the drop-cc, space-cc and escape-cc + <a href="property_replacer.html">property replacer</a> options do not work + as expected because control characters are already removed upon message + reception. If you intend to use these property replacer options, you must + turn off $Escape8BitCharactersOnReceive.</li> +</ul> +<p><b>Sample:</b></p> +<p><code><b>$Escape8BitCharactersOnReceive on</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_escapecontrolcharactersonreceive.html b/doc/rsconf1_escapecontrolcharactersonreceive.html new file mode 100644 index 00000000..178f9a6f --- /dev/null +++ b/doc/rsconf1_escapecontrolcharactersonreceive.html @@ -0,0 +1,36 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$EscapeControlCharactersOnReceive</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> on</p> +<p><b>Description:</b></p> +<p>This directive instructs rsyslogd to replace control characters during reception of the message. The intent is to provide a way to stop non-printable messages from entering the syslog system as whole. If this option is turned on, all control-characters are converted to a 3-digit octal number and be prefixed with the $ControlCharacterEscapePrefix character (being '\' by default). For example, if the BEL character (ctrl-g) is included in the message, it would be converted to "\007". To be compatible to sysklogd, this option must be turned on.</p> +<p><b>Warning:</b></p> +<ul> + <li>turning on this option most probably destroys non-western character sets + (like Japanese, Chinese and Korean)</li> + <li>turning on this option destroys digital signatures if such exists inside + the message</li> + <li>if turned on, the drop-cc, space-cc and escape-cc + <a href="property_replacer.html">property replacer</a> options do not work + as expected because control characters are already removed upon message + reception. If you intend to use these property replacer options, you must + turn off $EscapeControlCharactersOnReceive.</li> +</ul> +<p><b>Sample:</b></p> +<p><code><b>$EscapeControlCharactersOnReceive on</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_failonchownfailure.html b/doc/rsconf1_failonchownfailure.html new file mode 100644 index 00000000..d8bbab82 --- /dev/null +++ b/doc/rsconf1_failonchownfailure.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$FailOnChownFailure</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> on</p> +<p><b>Description:</b></p> +<p>This option modifies behaviour of dynaFile creation. If different owners or groups are specified for new files or directories and rsyslogd fails to set these new owners or groups, it will log an error and NOT write to the file in question if that option is set to "on". If it is set to "off", the error will be ignored and processing continues. Keep in mind, that the files in this case may be (in)accessible by people who should not have permission. The default is "on".</p> +<p><b>Sample:</b></p> +<p><code><b>$FailOnChownFailure off</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_filecreatemode.html b/doc/rsconf1_filecreatemode.html new file mode 100644 index 00000000..10b0317b --- /dev/null +++ b/doc/rsconf1_filecreatemode.html @@ -0,0 +1,37 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$FileCreateMode</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> 0644</p> +<p><b>Description:</b></p> +<p>The $FileCreateMode directive allows to specify the creation mode with which rsyslogd creates new files. If not specified, the value 0644 is used (which retains backward-compatibility with earlier releases). The value given must always be a 4-digit octal number, with the initial digit being zero.</p> +<p>Please note that the actual permission depend on rsyslogd's process umask. If in doubt, use "$umask 0000" right at the beginning of the configuration file to remove any restrictions.</p> +<p>$FileCreateMode may be specified multiple times. If so, it specifies the creation mode for all selector lines that follow until the next $FileCreateMode directive. Order of lines is vitally important.</p> +<p><b>Sample:</b></p> +<p><code><b>$FileCreateMode 0600</b></code></p> +<p>This sample lets rsyslog create files with read and write access only for the users it runs under.</p> +<p>The following sample is deemed to be a complete rsyslog.conf: +<p><code><b>$umask 0000 # make sure nothing interferes with the following +definitions<br> +*.* /var/log/file-with-0644-default<br> +$FileCreateMode 0600<br> +*.* /var/log/file-with-0600<br> +$FileCreateMode 0644<br> +*.* /var/log/file-with-0644</b></code></p> +<p>As you can see, open modes depend on position in the config file. Note the +first line, which is created with the hardcoded default creation mode.</p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_filegroup.html b/doc/rsconf1_filegroup.html new file mode 100644 index 00000000..935f074a --- /dev/null +++ b/doc/rsconf1_filegroup.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$FileGroup</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>Set the group for dynaFiles newly created. Please note that this setting does not affect the group of files already existing. The parameter is a group name, for which the groupid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.</p> +<p><b>Sample:</b></p> +<p><code><b>$FileGroup loggroup</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_fileowner.html b/doc/rsconf1_fileowner.html new file mode 100644 index 00000000..62125c8d --- /dev/null +++ b/doc/rsconf1_fileowner.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$FileOwner</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>Set the file owner for dynaFiles newly created. Please note that this setting does not affect the owner of files already existing. The parameter is a user name, for which the userid is obtained by rsyslogd during startup processing. Interim changes to the user mapping are not detected.</p> +<p><b>Sample:</b></p> +<p><code><b>$FileOwner loguser</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_generateconfiggraph.html b/doc/rsconf1_generateconfiggraph.html new file mode 100644 index 00000000..3f0fd666 --- /dev/null +++ b/doc/rsconf1_generateconfiggraph.html @@ -0,0 +1,127 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$GenerateConfigGraph</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Available Since:</b> 4.3.1 <b>CURRENTLY NOT AVAILABLE</b></p> +<p><b>Description:</b></p> +<b>This directive is currently not supported. We had to disable it when we improved the +rule engine. It is considerable effort to re-enable it. On the other hand, we are about +to add a new config system, which will make yet another config graph method necessary. +As such we have decided to currently disable this functionality and re-introduce it when +the new config system has been instantiated. +</b></p> +<p>This directive permits to create (hopefully) good-looking visualizations of rsyslogd's +configuration. It does not affect rsyslog operation. If the directive is specified multiple +times, all but the last are ignored. If it is specified, a graph is created. This happens +both during a regular startup as well a config check run. It is recommended to include +this directive only for documentation purposes and remove it from a production +configuraton. +<p>The graph is not drawn by rsyslog itself. Instead, it uses the great open source tool +<a href="http://www.graphviz.org">Graphviz</a> to do the actual drawing. This has at least +two advantages: +<ul> +<li>the graph drawing support code in rsyslog is extremly slim and without overhead +<li>the user may change or further annotate the generated file, thus potentially +improving his documentation +</ul> +The drawback, of course, is that you need to run Graphviz once you have generated +the control file with rsyslog. Fortunately, the process to do so is rather easy: +<ol> +<li>add "$GenerateConfigGraph /path/to/file.dot" to rsyslog.conf (from now on, I +will call the file just file.dot). Optionally, add "$ActionName" statement +<b>in front of</b> those actions that you like to use friendly names with. If you do +this, keep the names short. +<li>run rsyslog at least once (either in regular or configuration check mode) +<li>remember to remove the $GenerateConfigGraph directive when you no longer need it (or +comment it out) +<li>change your working directory to where you place the dot file +<li>if you would like to edit the rsyslog-generated file, now is the time to do so +<li>do "dot -Tpng file.dot > file.png" +<li>remember that you can use "convert -resize 50% file.png resized.png" if +dot's output is too large (likely) or too small. Resizing can be especially useful if +you intend to get a rough overview over your configuration. +</ol> +After completing these steps, you should have a nice graph of your configuration. Details +are missing, but that is exactly the point. At the start of the graph is always (at least +in this version, could be improved) a node called "inputs" in a tripple hexagon +shape. This represents all inputs active in the system (assuming you have defined some, +what the current version does not check). Next comes the main queue. It is given in a +hexagon shape. That shape indicates that a queue is peresent and used to de-couple +the inbound from the outbound part of the graph. In technical terms, here is a +threading boundary. Action with "real" queues (other than in direct mode) +also utilize this shape. For actions, notice that a "hexagon action" creates +a deep copy of the message. As such, a "discard hexagon action" actually does +nothing, because it duplicates the message and then discards <b>the duplicate</b>. +At the end of the diagram, you always see a "discard" action. This indicates +that rsyslog discards messages which have been run through all available rules. +<p>Edges are labeled with information about when they are taken. For filters, the type of +filter, but not any specifics, are given. It is also indicated if no filter is +applied in the configuration file (by using a "*.*" selector). Edges without +labels are unconditionally taken. The actions themselfs are labeled with the name of +the output module that handles them. If provided, the name given via +"ActionName" is used instead. No further details are provided. +<p>If there is anything in red, this should draw your attention. In this case, rsyslogd +has detected something that does not look quite right. A typical example is a discard +action which is followed by some other actions in an action unit. Even though something +may be red, it can be valid - rsyslogd's graph generator does not yet check each and +every speciality, so the configuration may just cover a very uncommon case. +<p>Now let's look at some examples. The graph below was generated on a fairly standard +Fedora rsyslog.conf file. It had only the usually commented-out last forwarding action +activated: +<p align="center"> +<img src="rsyslog_confgraph_std.png" alt="rsyslog configuration graph for a default fedora rsyslog.conf"> +<p>This is the typical structure for a simple rsyslog configuration. There are a couple of +actions, each guarded by a filter. Messages run from top to bottom and control branches +whenever a filter evaluates to true. As there is no discard action, all messages will +run through all filters and discarded in the system default discard action right after +all configured actions. +</p> +<p>A more complex example can be seen in the next graph. This is a configuration I +created for testing the graph-creation features, so it contains a little bit of +everything. However, real-world configurations can look quite complex, too (and I +wouldn't say this one is very complex): +<p align="center"> +<img src="rsyslog_confgraph_complex.png"> +</p> +<p>Here, we have a user-defined discard action. You can immediately see this because +processing branches after the first "builtin-file" action. Those messages +where the filter evaluates to true for will never run through the left-hand action +branch. However, there is also a configuration error present: there are two more +actions (now shown red) after the discard action. As the message is discarded, these will +never be executed. Note that the discard branch contains no further filters. This is +because these actions are all part of the same action unit, which is guarded only by +an entry filter. The same is present a bit further down at the node labeled +"write_system_log_2". This note has one more special feature, that is label +was set via "ActionName", thus is does not have standard form (the same +happened to the node named "Forward" right at the top of the diagram. +Inside this diagram, the "Forward" node is executed asynchonously on its own +queue. All others are executed synchronously. +<p>Configuration graphs are useful for documenting a setup, but are also a great +<a href="troubleshoot.html">troubleshooting</a> resource. It is important to +remember that <b>these graphs are generated +from rsyslogd's in-memory action processing structures</b>. You can not get closer +to understanding on how rsyslog interpreted its configuration files. +So if the graph does not look +what you intended to do, there is probably something worng in rsyslog.conf. +<p>If something is not working as expected, but you do not spot the error immediately, +I recommend to generate a graph and zoom it so that you see all of it in one great picture. +You may not be able to read anything, but the structure should look good to you and +so you can zoom into those areas that draw your attention. +<p><b>Sample:</b></p> +<p><code><b>$DirOwner /path/to/graphfile-file.dot</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_gssforwardservicename.html b/doc/rsconf1_gssforwardservicename.html new file mode 100644 index 00000000..45d9ba98 --- /dev/null +++ b/doc/rsconf1_gssforwardservicename.html @@ -0,0 +1,26 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$GssForwardServiceName</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> host</p> +<p><b>Provided by:</b> <i>omgssapi</i></p> +<p><b>Description:</b></p> +<p>Specifies the service name used by the client when forwarding GSS-API wrapped messages.</p> +<p>The GSS-API service names are constructed by appending '@' and a hostname following "@@" in each selector.</p> +<p><b>Sample:</b></p> +<p><code><b>$GssForwardServiceName rsyslog</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_gsslistenservicename.html b/doc/rsconf1_gsslistenservicename.html new file mode 100644 index 00000000..5fdf3edc --- /dev/null +++ b/doc/rsconf1_gsslistenservicename.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$GssListenServiceName</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> host</p> +<p><b>Description:</b></p> +<p>Specifies the service name used by the server when listening for GSS-API wrapped messages.</p> +<p><b>Sample:</b></p> +<p><code><b>$GssForwardServiceName rsyslog</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_gssmode.html b/doc/rsconf1_gssmode.html new file mode 100644 index 00000000..2b1d5656 --- /dev/null +++ b/doc/rsconf1_gssmode.html @@ -0,0 +1,26 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$GssMode</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> encryption</p> +<p><b>Provided by:</b> <i>omgssapi</i></p> +<p><b>Description:</b></p> +<p>Specifies GSS-API mode to use, which can be "<b>integrity</b>" - clients are authenticated and + messages are checked for integrity, "<b>encryption</b>" - same as + "integrity", but messages are also encrypted if both sides support it.<p><b>Sample:</b></p> +<p><code><b>$GssMode Encryption</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_includeconfig.html b/doc/rsconf1_includeconfig.html new file mode 100644 index 00000000..132cee6f --- /dev/null +++ b/doc/rsconf1_includeconfig.html @@ -0,0 +1,48 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$IncludeConfig</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>This directive allows to include other files into the main configuration file. As soon as an IncludeConfig directive is found, the contents of the new file is processed. IncludeConfigs can be nested. Please note that from a logical point of view the files are merged. Thus, if the include modifies some parameters (e.g. $DynaFileChacheSize), these new parameters are in place for the "calling" configuration file when the include is completed. To avoid any side effects, do a $ResetConfigVariables after the $IncludeConfig. It may also be a good idea to do a $ResetConfigVariables right at the start of the include, so that the module knows exactly what it does. Of course, one might specifically NOT do this to inherit parameters from the main file. As always, use it as it best fits...</p> +<p>If all regular files in the /etc/rsyslog.d directory are included, then files starting with "." are ignored - so you can use them to place comments into the dir (e.g. "/etc/rsyslog.d/.mycomment" will be ignored). <a href="http://sourceforge.net/tracker/index.php?func=detail&aid=1764088&group_id=123448&atid=696555">Michael Biebl had the idea to this functionality</a>. Let me quote hím:</p> +<blockquote> +<p><i>Say you can add an option<br> +$IncludeConfig /etc/rsyslog.d/<br> +(which probably would make a good default)<br> +to /etc/rsyslog.conf, which would then merge and include all *.conf files<br> +in /etc/rsyslog.d/.<br> +<br> +This way, a distribution can modify its packages easily to drop a simple<br> +config file into this directory upon installation.<br> +<br> +As an example, the network-manager package could install a simple config<br> +file /etc/rsyslog.d/network-manager.conf which would contain.<br> +:programname, contains, "NetworkManager" -/var/log/NetworkManager.log<br> +<br> +Upon uninstallation, the file could be easily removed again. This approach<br> +would be much cleaner and less error prone, than having to munge around<br> +with the /etc/rsyslog.conf file directly.</i></p> +</blockquote> +<p><b>Sample:</b></p> +<p><code><b>$IncludeConfig /etc/some-included-file.conf</b></code></p> +<p>Directories can also be included. To do so, the name must end on a slash:</p> +<p><code><b>$IncludeConfig /etc/rsyslog.d/</b></code></p> +<p><b>And finally, only specific files matching a wildcard my be included +from a directory:</b></p> +<p><code><b>$IncludeConfig /etc/rsyslog.d/*.conf</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_mainmsgqueuesize.html b/doc/rsconf1_mainmsgqueuesize.html new file mode 100644 index 00000000..ffed1c09 --- /dev/null +++ b/doc/rsconf1_mainmsgqueuesize.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$MainMsgQueueSize</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> 10000</p> +<p><b>Description:</b></p> +<p>This allows to specify the maximum size of the message queue. This directive is only available when rsyslogd has been compiled with multithreading support. In this mode, receiver and output modules are de-coupled via an in-memory queue. This queue buffers messages when the output modules are not capable to process them as fast as they are received. Once the queue size is exhausted, messages will be dropped. The slower the output (e.g. MySQL), the larger the queue should be. Buffer space for the actual queue entries is allocated on an as-needed basis. Please keep in mind that a very large queue may exhaust available system memory and swap space. Keep this in mind when configuring the max size. The actual size of a message depends largely on its content and the originator. As a rule of thumb, typically messages should not take up more then roughly 1k (this is the memory structure, not what you see in a network dump!). For typical linux messages, 512 bytes should be a good bet. Please also note that there is a minimal amount of memory taken for each queue entry, no matter if it is used or not. This is one pointer value, so on 32bit systems, it should typically be 4 bytes and on 64bit systems it should typically be 8 bytes. For example, the default queue size of 10,000 entries needs roughly 40k fixed overhead on a 32 bit system.</p> +<p><b>Sample:</b></p> +<p><code><b>$MainMsgQueueSize 100000 # 100,000 may be a value to handle burst traffic</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_markmessageperiod.html b/doc/rsconf1_markmessageperiod.html new file mode 100644 index 00000000..a6486ba1 --- /dev/null +++ b/doc/rsconf1_markmessageperiod.html @@ -0,0 +1,32 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$MarkMessagePeriod</h2> +<p><b>Type:</b> specific to immark input module</p> +<p><b>Default:</b> 1200 (20 minutes)</p> +<p><b>Description:</b></p> +<p>This specifies when mark messages are to be written to output modules. The +time specified is in seconds. Specifying 0 is possible and disables mark +messages. In that case, however, it is more efficient to NOT load the immark +input module.</p> +<p>So far, there is only one mark message process and any subsequent +$MarkMessagePeriod overwrites the previous.</p> +<p><b>This directive is only available after the immark input module has been +loaded.</b></p> +<p><b>Sample:</b></p> +<p><code><b>$MarkMessagePeriod 600 # mark messages appear every 10 Minutes</b></code></p> +<p><b>Available since:</b> rsyslog 3.0.0</p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_maxopenfiles.html b/doc/rsconf1_maxopenfiles.html new file mode 100644 index 00000000..b6c9cc0e --- /dev/null +++ b/doc/rsconf1_maxopenfiles.html @@ -0,0 +1,35 @@ +<html> +<head> +<title>$MaxOpenFiles - rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">[rsyslog configuration directive overview]</a> + +<h2>$MaxOpenFiles</h2> +<p><b>Available Since:</b> 4.3.0</p> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> <i>operating system default</i></p> +<p><b>Description:</b></p> +<p>Set the maximum number of files that the rsyslog process can have open at any given +time. Note that this includes open tcp sockets, so this setting is the upper limit for +the number of open TCP connections as well. If you expect a large nubmer of concurrent +connections, it is suggested that the number is set to the max number connected plus 1000. +Please note that each dynafile also requires up to 100 open file handles. +<p>The setting is similar to running "ulimit -n number-of-files". +<p>Please note that depending on permissions and operating system configuration, the +setrlimit() request issued by rsyslog may fail, in which case the previous limit is kept +in effect. Rsyslog will emit a warning message in this case. +<p><b>Sample:</b></p> +<p><code><b>$MaxOpenFiles 2000</b></code></p> +<p><b>Bugs:</b></p> +<p>For some reason, this settings seems not to work on all platforms. If you experience +problems, please let us know so that we can (hopefully) narrow down the issue. +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_moddir.html b/doc/rsconf1_moddir.html new file mode 100644 index 00000000..889de05d --- /dev/null +++ b/doc/rsconf1_moddir.html @@ -0,0 +1,29 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$ModDir</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> system default for user libraries, e.g. +/usr/local/lib/rsyslog/</p> +<p><b>Description:</b></p> +<p>Provides the default directory in which loadable modules reside. This may be +used to specify an alternate location that is not based on the system default. +If the system default is used, there is no need to specify this directive. Please +note that it is vitally important to end the path name with a slash, else module +loads will fail.</p> +<p><b>Sample:</b></p> +<p><code><b>$ModDir /usr/rsyslog/libs/ # note the trailing slash!</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_modload.html b/doc/rsconf1_modload.html new file mode 100644 index 00000000..ce457ea5 --- /dev/null +++ b/doc/rsconf1_modload.html @@ -0,0 +1,34 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$ModLoad</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>Dynamically loads a plug-in into rsyslog's address space and activates it. +The plug-in must obey the rsyslog module API. Currently, only MySQL and Postgres +output modules are available +as a plugins, but users may create their own. A plug-in must be loaded BEFORE +any configuration file lines that reference it.</p> +<p>Modules must be present in the system default destination for rsyslog +modules. You can also set the directory via the <a href="rsconf1_moddir.html"> +$ModDir</a> directive.</p> +<p>If a full path name is specified, the module is loaded from that path. The +default module directory is ignored in that case.</p> +<p><b>Sample:</b></p> +<p><code><b>$ModLoad ommysql # load MySQL functionality<br> +$ModLoad /rsyslog/modules/ompgsql.so # load the postgres module via absolute path</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_omfileforcechown.html b/doc/rsconf1_omfileforcechown.html new file mode 100644 index 00000000..a680810b --- /dev/null +++ b/doc/rsconf1_omfileforcechown.html @@ -0,0 +1,67 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$omfileForceChown</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Parameter Values:</b> boolean (on/off, yes/no)</p> +<p><b>Available:</b> 4.7.0+, 5.3.0-5.8.x, <b>NOT</b> available in 5.9.x or higher</p> +<p><b>Note: this directive has been removed and is no longer available. The +documentation is currently being retained for historical reaons.</b> Expect +it to go away at some later stage as well. +<p><b>Default:</b> off</p> +<p><b>Description:</b></p> +<p>Forces rsyslogd to change the ownership for output files that already exist. Please note +that this tries to fix a potential problem that exists outside the scope of rsyslog. Actually, +it tries to fix invalid ownership/permission settings set by the original file creator. +<p>Rsyslog changes the ownership during initial execution with root privileges. When a privelege +drop is configured, privileges are dropped after the file owner ship is changed. Not that this currently +is a limitation in rsyslog's privilege drop code, which is on the TODO list to be removed. See Caveats +section below for the important implications. +<p><b>Caveats:</b></p> +<p>This directive tries to fix a problem that actually is outside the scope of rsyslog. As such, +there are a couple of restrictions and situations in which it will not work. <b>Users are strongly +encouraged to fix their system instead of turning this directive on</b> - it should only be used +as a last resort. +<p>At least in the following scenario, this directive will fail expectedly: +<p>It does not address +the situation that someone changes the ownership *after* rsyslogd has started. +Let's, for example, consider a log rotation script. +<ul> +<li>rsyslog is started +<li>ownership is changed +<li>privileges dropped +<li>log rotation (lr) script starts +<li>lr removes files +<li>lr creates new files with root:adm (or whatever else) +<li>lr HUPs rsyslogd +<li>rsyslogd closes files +<li>rsyslogd tries to open files +<li>rsyslogd tries to change ownership --> fail as we are non-root now +<li>file open fails +</ul> + +Please note that once the privilege drop code is refactored, this directive will +no longer work, because then privileges will be dropped before any action is performed, +and thus we will no longer be able to chown files that do not belong to the +user rsyslogd is configured to run under. + +<p>So <b>expect the directive to go away</b>. It will not +be removed in version 4, but may disappear at any time for any version greater than 4. + +<p><b>Sample:</b></p> +<p><code><b>$FileOwner loguser</b> +<br><b>$omfileForceChown on</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_repeatedmsgreduction.html b/doc/rsconf1_repeatedmsgreduction.html new file mode 100644 index 00000000..248e8343 --- /dev/null +++ b/doc/rsconf1_repeatedmsgreduction.html @@ -0,0 +1,25 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$RepeatedMsgReduction</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> depending on -e</p> +<p><b>Description:</b></p> +<p>This directive specifies whether or not repeated messages should be reduced (this is the "Last line repeated n times" feature). If set to on, repeated messages are reduced. If set to off, every message is logged. Please note that this directive overrides the -e command line option. In case -e is given, it is just the default value until the first RepeatedMsgReduction directive is encountered.</p> +<p>This directives affects selector lines until a new directive is specified.</p> +<p><b>Sample:</b></p> +<p><code><b>$RepeatedMsgReduction off # log every message</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_resetconfigvariables.html b/doc/rsconf1_resetconfigvariables.html new file mode 100644 index 00000000..46cf0bdf --- /dev/null +++ b/doc/rsconf1_resetconfigvariables.html @@ -0,0 +1,24 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$ResetConfigVariables</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>Resets all configuration variables to their default value. Any settings made will not be applied to configuration lines following the $ResetConfigVariables. This is a good method to make sure no side-effects exists from previous directives. This directive has no parameters.</p> +<p><b>Sample:</b></p> +<p><code><b>$ResetConfigVariables</b></code></p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_rulesetcreatemainqueue.html b/doc/rsconf1_rulesetcreatemainqueue.html new file mode 100644 index 00000000..5c1e0dec --- /dev/null +++ b/doc/rsconf1_rulesetcreatemainqueue.html @@ -0,0 +1,83 @@ +<html> +<head> +<title>RulesetCreateMainQueue - rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">rsyslog.conf configuration directive</a> + +<h2>$RulesetCreateMainQueue</h2> +<p><b>Type:</b> ruleset-specific configuration directive</p> +<p><b>Parameter Values:</b> boolean (on/off, yes/no)</p> +<p><b>Available since:</b> 5.3.5+</p> +<p><b>Default:</b> off</p> +<p><b>Description:</b></p> +<p> +Rulesets may use their own "main" message queue for message submission. Specifying +this directive, <b>inside a ruleset definition</b>, turns this on. This is both a performance +enhancement and also permits different rulesets (and thus different inputs within the same +rsyslogd instance) to use different types of main message queues. +<p>The ruleset queue is created with the parameters that are specified for the main message +queue at the time the directive is given. If different queue configurations are desired, +different main message queue directives must be used in front of the $RulesetCreateMainQueue +directive. Note that this directive may only be given once per ruleset. If multiple statements +are specified, only the first is used and for the others error messages are emitted. +<p>Note that the final set of ruleset configuration directives specifies the parameters for +the default main message queue. +<p>To learn more about this feature, please be sure to read about +<a href="multi_ruleset.html">multi-ruleset support in rsyslog</a>. +<p><b>Caveats:</b></p> +The configuration statement "$RulesetCreateMainQueue off" has no effect at all. +The capability to specify this is an artifact of the current (ugly!) configuration +language. + +<p><b>Example:</b></p> +<p>This example sets up a tcp server with three listeners. Each of these +three listener is bound to a specific ruleset. As a performance optimization, +the rulesets all receive their own private queue. The result is that received messages +can be independently processed. With only a single main message queue, we would have +some lock contention between the messages. This does not happen here. Note that in this +example, we use different processing. Of course, all messages could also have been +processed in the same way ($IncludeConfig may be useful in that case!). +</p> +<textarea rows="30" cols="60">$ModLoad imtcp +# at first, this is a copy of the unmodified rsyslog.conf +#define rulesets first +$RuleSet remote10514 +$RulesetCreateMainQueue on # create ruleset-specific queue +*.* /var/log/remote10514 + +$RuleSet remote10515 +$RulesetCreateMainQueue on # create ruleset-specific queue +*.* /var/log/remote10515 + +$RuleSet remote10516 +$RulesetCreateMainQueue on # create ruleset-specific queue +mail.* /var/log/mail10516 +& ~ +# note that the discard-action will prevent this messag from +# being written to the remote10516 file - as usual... +*.* /var/log/remote10516 + +# and now define listners bound to the relevant ruleset +$InputTCPServerBindRuleset remote10514 +$InputTCPServerRun 10514 + +$InputTCPServerBindRuleset remote10515 +$InputTCPServerRun 10515 + +$InputTCPServerBindRuleset remote10516 +$InputTCPServerRun 10516 +</textarea> +<p>Note the positions of the directives. With the current config language, +position is very important. This is ugly, but unfortunately the way it currently +works. +</p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_rulesetparser.html b/doc/rsconf1_rulesetparser.html new file mode 100644 index 00000000..433456c1 --- /dev/null +++ b/doc/rsconf1_rulesetparser.html @@ -0,0 +1,123 @@ +<html> +<head> +<title>RulesetParser - rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">rsyslog.conf configuration directive</a> + +<h2>$RulesetParser</h2> +<p><b>Type:</b> ruleset-specific configuration directive</p> +<p><b>Parameter Values:</b> string</p> +<p><b>Available since:</b> 5.3.4+</p> +<p><b>Default:</b> rsyslog.rfc5424 followed by rsyslog.rfc3164</p> +<p><b>Description:</b></p> +<p> +This directive permits to specify which +<a href="messageparser.html">message parsers</a> should be used for the ruleset +in question. It no ruleset is explicitely specified, the default ruleset is used. Message +parsers are contained in (loadable) parser modules with the most common cases +(RFC3164 and RFC5424) being build-in into rsyslogd. +<p>When this directive is specified the first time for a ruleset, it will not only add the +parser to the ruleset's parser chain, it will also wipe out the default parser chain. +So if you need to have +them in addition to the custom parser, you need to specify those as well. +<p>Order of directives is important. Parsers are tried one after another, in the order +they are specified inside the config. As soon as a parser is able to parse the message, +it will do so and no other parsers will be executed. If no matching parser can be found, +the message will be discarded and a warning message be issued (but only for the first +1,000 instances of this problem, to prevent message generation loops). +<p>Note that the rfc3164 parser will <b>always</b> be able to parse a message - it may +just not be the format that you like. This has two important implications: 1) always place +that parser at the END of the parser list, or the other parsers after it will never +be tried and 2) if you would like to make sure no message is lost, placing the rfc3164 +parser at the end of the parser list ensures that. +<p>Multiple parser modules are very useful if you have various devices that emit +messages that are malformed in various ways. The route to take then is +<ul> +<li>make sure you find a custom parser for that device; if there is no one, you +may consider writing one yourself (it is not that hard) or getting one written +as part of +<a href="http://www.rsyslog.com/professional-services">Adiscon's professional services +for rsyslog</a>. +<li>load your custom parsers via $ModLoad +<li>create a ruleset for each malformed format; assign the custom parser to it +<li>create a specific listening port for all devices that emit the same +malformed format +<li>bind the listener to the ruleset with the required parser +</ul> +<p>Note that it may be cumbersome to add all rules to all rulesets. To avoid this, +you can either use $Include or <a href="omruleset.html">omruleset</a> +(what probably provides the best solution). +<p>More information about rulesets in general can be found in +<a href="multi_ruleset.html">multi-ruleset support in rsyslog</a>. +<p><b>Caveats:</b></p> +<p>currently none known</p> + +<p><b>Example:</b></p> +<p>This example assumes there are two devices emiting malformed messages via UDP. +We have two custom parsers for them, named "device1.parser" and +"device2.parser". In addition to that, we have a number of other +devices sending wellformed messages, also via UDP. +<p>The solution is to listen for data from the two devices on two special +ports (10514 and 10515 in this example), create a ruleset for each and +assign the custom parsers to them. The rest of the messages are received via +port 514 using the regular parsers. Processing shall be equal for all messages. +So we simply forward the malformed messages to the regular queue once they are parsed (keep +in mind that a message is never again parsed once any parser properly processed it). +</p> +<textarea rows="40" cols="80">$ModLoad imudp +$ModLoad pmdevice1 # load parser "device1.parser" for device 1 +$ModLoad pmdevice2 # load parser "device2.parser" for device 2 + +# define ruleset for the first device sending malformed data +$Ruleset maldev1 +$RulesetCreateMainQueue on # create ruleset-specific queue +$RulesetParser "device1.parser" # note: this deactivates the default parsers +# forward all messages to default ruleset: +$ActionOmrulesetRulesetName RSYSLOG_DefaultRuleset +*.* :omruleset: + +# define ruleset for the second device sending malformed data +$Ruleset maldev2 +$RulesetCreateMainQueue on # create ruleset-specific queue +$RulesetParser "device2.parser" # note: this deactivates the default parsers +# forward all messages to default ruleset: +$ActionOmrulesetRulesetName RSYSLOG_DefaultRuleset +*.* :omruleset: + +# switch back to default ruleset +$Ruleset RSYSLOG_DefaultRuleset +*.* /path/to/file +auth.info @authlogger.example.net +# whatever else you usually do... + + +# now define the inputs and bind them to the rulesets +# first the default listener (utilizing the default ruleset) +$UDPServerRun 514 + +# now the one with the parser for device type 1: +$InputUDPServerBindRuleset maldev1 +$UDPServerRun 10514 + +# and finally the one for device type 2: +$InputUDPServerBindRuleset maldev2 +$UDPServerRun 10515 +</textarea> + +<p>For an example of how multiple parser can be chained (and an actual use case), please see +the example section on the <a href="pmlastmsg.html">pmlastmsg</a> parser +module. +<p>Note the positions of the directives. With the current config language, +<b>sequence of statements is very important</b>. This is ugly, but unfortunately +the way it currently works. +</p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rsconf1_umask.html b/doc/rsconf1_umask.html new file mode 100644 index 00000000..8e41e672 --- /dev/null +++ b/doc/rsconf1_umask.html @@ -0,0 +1,26 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<a href="rsyslog_conf_global.html">back</a> + +<h2>$UMASK</h2> +<p><b>Type:</b> global configuration directive</p> +<p><b>Default:</b> </p> +<p><b>Description:</b></p> +<p>The $umask directive allows to specify the rsyslogd processes' umask. If not specified, the system-provided default is used. The value given must always be a 4-digit octal number, with the initial digit being zero.</p> +<p>If $umask is specified multiple times in the configuration file, results may be somewhat unpredictable. It is recommended to specify it only once.</p> +<p><b>Sample:</b></p> +<p><code><b>$umask 0000</b></code></p> +<p>This sample removes all restrictions.</p> + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] [<a href="manual.html">manual +index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> diff --git a/doc/rscript_abnf.html b/doc/rscript_abnf.html new file mode 100644 index 00000000..9172d945 --- /dev/null +++ b/doc/rscript_abnf.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>RainerScript ABNF</title></head> +<body> +<h1>RainerScript ABNF</h1> +<p>This is the formal definition of RainerScript, as supported by +rsyslog configuration. Please note that this currently is working +document and the actual implementation may be quite different.</p> +<p>The +first glimpse of RainerScript will be available as part of rsyslog +3.12.0 expression support. However, the 3.12. series of rsyslog will +have a partial script implementaiton, which will not necessariy be +compatible with the later full implementation. So if you use it, be +prepared for some config file changes as RainerScript evolves.</p> +<p>C-like comments (/* some comment */) are supported in all pure +RainerScript lines. However, legacy-mapped lines do not support them. +All lines support the hash mark "#" as a comment initiator. Everything +between the hash and the end of line is a comment (just like // in C++ +and many other languages).</p> +<h2>Formal Definition</h2> +<p>Below is the formal language definitionin ABNF (RFC 2234) +format: <br> +</p> +<pre>; <span style="font-weight: bold;">all of this is a working document and may change!</span> -- rgerhards, 2008-02-24<br> +<br> +script := *stmt<br> +stmt := (if_stmt / block / vardef / run_s / load_s)<br> +vardef := "var" ["scope" = ("global" / "event")] <br> +block := "begin" stmt "end"<br> +load_s := "load" constraint ("module") modpath params ; load mod only if expr is true<br> +run_s := "run" constraint ("input") name<br> +constraint:= "if" expr ; constrains some one-time commands<br> +modpath := expr<br> +params := ["params" *1param *("," param) "endparams"]<br> +param := paramname) "=" expr<br> +paramname := [*(obqualifier ".") name]<br> +modpath:= ; path to module<br> +?line? := cfsysline / cfli<br> +cfsysline:= BOL "$" *char EOL ; how to handle the first line? (no EOL in front!)<br> +BOL := ; Begin of Line - implicitely set on file beginning and after each EOL<br> +EOL := 0x0a ;LF<br> +if_stmt := "if" expr "then"<br> +old_filter:= BOL facility "." severity ; no whitespace allowed between BOL and facility!<br> +facility := "*" / "auth" / "authpriv" / "cron" / "daemon" / "kern" / "lpr" / <br> +"mail" / "mark" / "news" / "security" / "syslog" / "user" / "uucp" / <br> +"local0" .. "local7" / "mark"<br> +; The keyword security should not be used anymore<br> +; mark is just internal<br> +severity := TBD ; not really relevant in this context<br> +<br> +; and now the actual expression<br> +expr := e_and *("or" e_and)<br> +e_and := e_cmp *("and" e_cmp)<br> +e_cmp := val 0*1(cmp_op val)<br> +val := term *(("+" / "-" / "&") term)<br> +term := factor *(("*" / "/" / "%") factor)<br> +factor := ["not"] ["-"] terminal<br> +terminal := var / constant / function / ( "(" expr ")" )<br> +function := name "(" *("," expr) ")"<br> +var := "$" varname<br> +varname := msgvar / sysvar / ceevar<br> +msgvar := name<br> +ceevar := "!" name<br> +sysvar := "$" name<br> +name := alpha *(alnum)<br> +constant := string / number<br> +string := simpstr / tplstr ; tplstr will be implemented in next phase<br> +simpstr := "'" *char "'" ; use your imagination for char ;)<br> +tplstr := '"' template '"' ; not initially implemented<br> +number := ["-"] 1*digit ; 0nn = octal, 0xnn = hex, nn = decimal<br> +cmp_op := "==" / "!=" / "<>" / "<" / ">" / "<=" / ">=" / "contains" / "contains_i" / "startswith" / "startswith_i"<br> +digit := %x30-39<br> +alpha := "a" ... "z" # all letters<br> +alnum :* alpha / digit / "_" /"-" # "-" necessary to cover currently-existing message properties<br> +</pre> +<h2>Samples</h2> +<p>Some samples of RainerScript:</p><p>define function IsLinux<br>begin<br> if $environ contains "linux" then return true else return false<br>end</p><p>load if IsLinux() 'imklog.so' params name='klog' endparams /* load klog under linux only */<br>run if IsLinux() input 'klog'<br>load 'ommysql.so'</p><p>if $message contains "error" then<br> action<br> type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,<br> action.dbname='events', action.dbuser='uid',<br> + [?action.template='templatename'?] or [?action.sql='insert into +table... values('&$facility&','&$severity&...?]<br> endaction<br><br>... or ...</p><p>define action writeMySQL<br> type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,<br> action.dbname='events', action.dbuser='uid',<br> [?action.template='templatename'?] or [?action.sql='insert into table... values('<span style="font-family: monospace;"> &</span> $facility & ',' & $severity &...?]<br> endaction</p><p>if $message contains "error" then action writeMySQL</p><p>ALTERNATE APPROACH</p><p>define function IsLinux(<br> if $environ contains "linux" then return true else return false<br>)</p><p>load if IsLinux() 'imklog.so' params name='klog' endparams /* load klog under linux only */<br>run if IsLinux() input 'klog'<br>load 'ommysql.so'</p><p>if $message contains "error" then<br> action(<br> type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,<br> action.dbname='events', action.dbuser='uid',<br> + [?action.template='templatename'?] or [?action.sql='insert into +table... values('&$facility&','&$severity&...?]<br> )<br><br>... or ...</p><p>define action writeMySQL(<br> type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,<br> action.dbname='events', action.dbuser='uid',<br> + [?action.template='templatename'?] or [?action.sql='insert into +table... values('&$facility&','&$severity&...?]<br> )</p><p>if $message contains "error" then action writeMySQL(action.dbname='differentDB')</p><p></p><p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +<h2>Implementation</h2> +RainerScript will be implemented via a hand-crafted LL(1) parser. I was tempted to use yacc, but it turned out the resulting code was not thread-safe and as such did not fit within the context of rsyslog. Also, limited error handling is not a real problem for us: if there is a problem in parsing the configuration file, we stop processing. Guessing what was meant and trying to recover would IMHO not be good choices for something like a syslogd. +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/rsyslog-example.conf b/doc/rsyslog-example.conf new file mode 100644 index 00000000..a3ec2f1f --- /dev/null +++ b/doc/rsyslog-example.conf @@ -0,0 +1,163 @@ +# A commented quick reference and sample configuration +# WARNING: This is not a manual, the full manual of rsyslog configuration is in +# rsyslog.conf (5) manpage +# +# "$" starts lines that contain new directives. The full list of directives +# can be found in /usr/share/doc/rsyslog-1.19.6/doc/rsyslog_conf.html or online +# at http://www.rsyslog.com/doc if you do not have (or find) a local copy. +# +# Set syslogd options + +# Some global directives +# ---------------------- + +# $AllowedSender - specifies which remote systems are allowed to send syslog messages to rsyslogd +# -------------- +$AllowedSender UDP, 127.0.0.1, 192.0.2.0/24, [::1]/128, *.example.net, somehost.example.com + +# $UMASK - specifies the rsyslogd processes' umask +# ------ +$umask 0000 + +# $FileGroup - Set the group for dynaFiles newly created +# ---------- +$FileGroup loggroup + +# $FileOwner - Set the file owner for dynaFiles newly created. +# ---------- +$FileOwner loguser + +# $IncludeConfig - include other files into the main configuration file +# -------------- +$IncludeConfig /etc/some-included-file.conf # one file +$IncludeConfig /etc/rsyslog.d/ # whole directory (must contain the final slash) + +# $ModLoad - Dynamically loads a plug-in and activates it +# -------- +$ModLoad ommysql # load MySQL functionality +$ModLoad /rsyslog/modules/somemodule.so # load a module via absolute path + + + +# Templates +# --------- + +# Templates allow to specify any format a user might want. +# They MUST be defined BEFORE they are used. + +# A template consists of a template directive, a name, the actual template text +# and optional options. A sample is: +# +$template MyTemplateName,"\7Text %property% some more text\n", + +# where: +# * $template - tells rsyslog that this line contains a template. +# * MyTemplateName - template name. All other config lines refer to this name. +# * "\7Text %property% some more text\n" - templage text + +# The backslash is an escape character, i.e. \7 rings the bell, \n is a new line. +# To escape: +# % = \% +# \ = \\ + +# Template options are case-insensitive. Currently defined are: +# sql format the string suitable for a SQL statement. This will replace single +# quotes ("'") by two single quotes ("''") to prevent the SQL injection +# (NO_BACKSLASH_ESCAPES turned off) +# stdsql - format the string suitable for a SQL statement that is to +# be sent to a standards-compliant sql server. +# (NO_BACKSLASH_ESCAPES turned on) + + + +# Properties inside templates +# --------------------------- + +# Properties can be modified by the property replacer. They are accessed +# inside the template by putting them between percent signs. The full syntax is as follows: + +# %propname:fromChar:toChar:options% + +# FromChar and toChar are used to build substrings. +# If you need to obtain the first 2 characters of the +# message text, you can use this syntax: +"%msg:1:2%". +# If you do not whish to specify from and to, but you want to +# specify options, you still need to include the colons. + +# For example, to convert the full message text to lower case only, use +# "%msg:::lowercase%". + +# The full list of property options can be found in rsyslog.conf(5) manpage + + + +# Samples of template definitions +# ------------------------------- + +# A template that resambles traditional syslogd file output: +$template TraditionalFormat,"%timegenerated% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n" + +# A more verbose template: +$template precise,"%syslogpriority%,%syslogfacility%,%timegenerated::fulltime%,%HOSTNAME%,%syslogtag%,%msg%\n" + +# A template that resembles RFC 3164 on-the-wire format: +# (yes, there is NO space betwen syslogtag and msg! that's important!) +$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%" + +# a template resembling traditional wallmessage format: +$template wallmsg,"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r" + +# The template below emulates winsyslog format, but we need to check the time +# stamps used. It is also a good sampleof the property replacer in action. +$template WinSyslogFmt,"%HOSTNAME%,%timegenerated:1:10:date-rfc3339%,%timegenerated:12:19:date-rfc3339%,%timegenerated:1:10:date-rfc3339%,%timegenerated:12:19:date-rfc3339%,%syslogfacility%,%syslogpriority%,%syslogtag%%msg%\n" + +# A template used for database writing (notice it *is* an actual +# sql-statement): +$template dbFormat,"insert into SystemEvents (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%',%syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')",sql + + + +# Samples of rules +# ---------------- +# Regular file +# ------------ +*.* /var/log/traditionalfile.log;TraditionalFormat # log to a file in the traditional format + +# Forwarding to remote machine +# ---------------------------- +*.* @172.19.2.16 # udp (standard for syslog) +*.* @@172.19.2.17 # tcp + +# Database action +# --------------- +# (you must have rsyslog-mysql package installed) +# !!! Don't forget to set permission of rsyslog.conf to 600 !!! +*.* >hostname,dbname,userid,password # (default Monitorware schema, can be created by /usr/share/doc/rsyslog-mysql-1.19.6/createDB.sql) + +# And this one uses the template defined above: +*.* >hostname,dbname,userid,password;dbFormat + +# Program to execute +# ------------------ +*.* ^alsaunmute # set default volume to soundcard + +# Filter using regex +# ------------------ +# if the user logges word rulez or rulezz or rulezzz or..., then we will shut down his pc +# (note, that + have to be double backslashed...) +:msg, regex, "rulez\\+" ^poweroff + +# A more complex example +# ---------------------- +$template bla_logged,"%timegenerated% the BLA was logged" +:msg, contains, "bla" ^logger;bla_logged + +# Pipes +# ----- +# first we need to create pipe by # mkfifo /a_big_pipe +*.* |/a_big_pipe + +# Discarding +# ---------- +*.* ~ # discards everything diff --git a/doc/rsyslog-vers.dot b/doc/rsyslog-vers.dot new file mode 100644 index 00000000..a5563f94 --- /dev/null +++ b/doc/rsyslog-vers.dot @@ -0,0 +1,82 @@ +// This file is part of rsyslog. +// +// rsyslog "family tree" compressed version +// +// see http://www.graphviz.org for how to obtain the graphviz processor +// which is used to build the actual graph. +// +// generate the graph with +// $ dot rsyslog-vers.dot -Tpng >rsyslog-vers.png + +digraph G { + label="\n\nrsyslog \"family tree\"\nhttp://www.rsyslog.com"; + fontsize=20; + + v1stable [label="v1-stable", shape=box, style=dotted]; + v2stable [label="v2-stable", shape=box, style=filled]; + v3stable [label="v3-stable", shape=box, style=filled]; + beta [label="beta", shape=box, style=filled]; + devel [label="devel", shape=box, style=filled]; + "1.0.5" [style=dotted]; + perf [style=dashed]; + "imudp-select" [style=dashed label="imudp-\nselect"]; + msgnolock [style=dashed]; + "file-errHdlr" [style=dashed]; + solaris [style=dashed]; + tests [style=dashed]; + "0.x.y" [group=master]; + "1.10.0" [group=master]; + "1.21.2" [group=master]; + "3.10.0" [group=master]; + "3.15.1" [group=master]; + "3.17.0" [group=master]; + "3.19.x" [group=master]; + "3.21.x" [group=master]; + "4.1.0" [group=master]; + "4.1.4" [group=master]; + "4.1.5" [group=master]; + "4.1.6" [group=master]; + + sysklogd -> "0.x.y" [color=red]; + "0.x.y" -> "1.0.0"; + "0.x.y" -> "1.10.0" [color=red]; + "1.0.0" -> "1.0.5" [style=dashed]; + "1.10.0" -> "1.21.2" [color=red style=dashed]; + "1.21.2" -> "2.0.0" [color=blue]; + "2.0.0" -> "2.0.5" [style=dashed, color=blue]; + "1.21.2" -> "3.10.0" [color=red]; + "3.10.0" -> "3.15.1" [color=red style=dashed]; + "3.15.1" -> "tests"; + "3.15.1" -> "3.17.0" [color=red style=dashed]; + "3.15.1" -> "3.16.x"; + "3.16.x" -> "3.18.x" [color=blue, style=dashed]; + "3.17.0" -> "3.18.x"; + "3.17.0" -> "3.19.x" [color=red, style=dashed]; + "3.19.x" -> "3.20.x"; + "3.19.x" -> "3.21.x" [color=red]; + "3.18.x" -> debian_lenny; + "3.18.x" -> "3.20.x" [color=blue, style=dashed]; + "3.21.x" -> "3.21.11"; + "3.21.x" -> "4.1.0" [color=red]; + "3.21.x" -> "perf"; + "perf" -> "4.1.0"; + "4.1.0" -> "4.1.4" [color=red, style=dashed]; + "4.1.4" -> "file-errHdlr"; + "4.1.4" -> "4.1.5" [color=red]; + "4.1.5" -> "4.1.6" [color=red]; + "3.21.x" -> msgnolock + "3.21.x" -> "imudp-select"; + "4.1.5" -> solaris; + "file-errHdlr" -> "4.1.6"; + "tests" -> "4.1.6"; + + "1.0.5" -> v1stable [dir=none, style=dotted]; + "2.0.5" -> v2stable [dir=none, style=dotted]; + "3.20.x" -> v3stable [dir=none, style=dotted]; + "3.21.11" -> beta [dir=none, style=dotted]; + "4.1.6" -> devel [dir=none, style=dotted]; + + {rank=same; "4.1.5" "solaris"} + {rank=same; "3.18.x" "debian_lenny"} + {rank=same; "1.0.5" "2.0.5" "3.20.x" "3.21.11" "4.1.6"} +} diff --git a/doc/rsyslog-vers.png b/doc/rsyslog-vers.png Binary files differnew file mode 100644 index 00000000..e8ec8b81 --- /dev/null +++ b/doc/rsyslog-vers.png diff --git a/doc/rsyslog_conf.html b/doc/rsyslog_conf.html new file mode 100644 index 00000000..c5f4d2e3 --- /dev/null +++ b/doc/rsyslog_conf.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog.conf configuration file</title></head> +<body> +<h1>rsyslog.conf configuration file</h1> +<p><b>Rsyslog is configured via the rsyslog.conf file</b>, +typically found in /etc. By default, rsyslogd reads the file +/etc/rsyslog.conf. This may be changed by command line option "-f".</p> +<p><a href="http://wiki.rsyslog.com/index.php/Configuration_Samples"> +Configuration file examples can be found in the rsyslog wiki</a>. Also +keep the +<a href="http://www.rsyslog.com/config-snippets/">rsyslog config snippets</a> +on your mind. These are ready-to-use +real building blocks for rsyslog configuration. +</p> +<p>While rsyslogd contains enhancements over standard syslogd, +efforts have been made to keep the configuration file as compatible as +possible. While, for obvious reasons, <a href="features.html">enhanced +features</a> require a different config file syntax, rsyslogd +should be able to work with a standard syslog.conf file. This is +especially useful while you are migrating from syslogd to rsyslogd.</p> + +<p><b>Follow the links below to learn more about specific topics:</b></p> +<ul> +<li><a href="rsyslog_conf_basic_structure.html">Basic Structure</a></li> +<li><a href="rsyslog_conf_modules.html">Modules</a></li> +<li><a href="rsyslog_conf_templates.html">Templates</a></li> +<li><a href="rsyslog_conf_filter.html">Filter Conditions</a></li> +<li><a href="rsyslog_conf_actions.html">Actions (legacy format)</a></li> +<li><a href="rsyslog_conf_output.html">Output Channels</a></li> +<!--<li><a href="rsyslog_conf_examples.html">Examples</a></li>--> +<li><a href="rsyslog_conf_global.html">Legacy Configuration Directives</a></li> +<li><a href="rsyslog_conf_sysklogd_compatibility.html">sysklogd compatibility</a></li> +</ul> + +<p>[<a href="rsyslog_conf.html">back to top</a>] +[<a href="manual.html">manual index</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_conf_actions.html b/doc/rsyslog_conf_actions.html new file mode 100644 index 00000000..fa240d97 --- /dev/null +++ b/doc/rsyslog_conf_actions.html @@ -0,0 +1,414 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Actions - rsyslog.conf</title></head> +<body> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">back</a> +<h2>Actions</h2> +Action object describe what is to be done with a message. They are +implemented via <a href="rsyslog_conf_modules.html#om">outpout modules</a>. +<p>The action object has different parameters: +<ul> +<li>those that apply to all actions and are action specific. These + are documented below. +<li>parameters for the action queue. While they also apply to + all parameters, they are queue-specific, not action-specific (they + are the same that are used in rulesets, for example). +<li>action-specific parameters. These are specific to a certain + type of actions. They are documented by the output module + in question. +</ul> +<h3>General Action Parameters</h3> +<ul> + <li><b>name</b> word + <br>used for statistics gathering and documentation + <li><b>type</b> string + <br>Mandatory parameter for every action. The name of the module that should be used. </li> + <li><b>action.writeAllMarkMessages</b> on/off + <br>Normally, mark messages are written to actions only if the action was not recently executed (by default, recently means within the past 20 minutes). If this setting is switched to "on", mark messages are always sent to actions, no matter how recently they have been executed. In this mode, mark messages can be used as a kind of heartbeat. Note that this option auto-resets to "off", so if you intend to use it with multiple actions, it must be specified in front off all selector lines that should provide this functionality. </li> + <li><b>action.execOnlyEveryNthTime</b> integer + <br>If configured, the next action will only be executed every n-th time. For example, if configured to 3, the first two messages that go into the action will be dropped, the 3rd will actually cause the action to execute, the 4th and 5th will be dropped, the 6th executed under the action, ... and so on. Note: this setting is automatically re-set when the actual action is defined.</li> + <li><b>action.execOnlyEveryNthTimeout</b> integer + <br>Has a meaning only if Action.ExecOnlyEveryNthTime is also configured for the same action. If so, the timeout setting specifies after which period the counting of "previous actions" expires and a new action count is begun. Specify 0 (the default) to disable timeouts. +Why is this option needed? Consider this case: a message comes in at, eg., 10am. That's count 1. Then, nothing happens for the next 10 hours. At 8pm, the next one occurs. That's count 2. Another 5 hours later, the next message occurs, bringing the total count to 3. Thus, this message now triggers the rule. +The question is if this is desired behavior? Or should the rule only be triggered if the messages occur within an e.g. 20 minute window? If the later is the case, you need a +<br>Action.ExecOnlyEveryNthTimeTimeout="1200" +<br>This directive will timeout previous messages seen if they are older than 20 minutes. In the example above, the count would now be always 1 and consequently no rule would ever be triggered. </li> + <li><b>action.execOnlyOnceEveryInterval</b> integer + <br>Execute action only if the last execute is at last <seconds> seconds in the past (more info in ommail, but may be used with any action)</li> + <li><b>action.execOnlyWhenpReviousIsSuspended</b> on/off + <br>This directive allows to specify if actions should always be executed ("off," the default) or only if the previous action is suspended ("on"). This directive works hand-in-hand with the multiple actions per selector feature. It can be used, for example, to create rules that automatically switch destination servers or databases to a (set of) backup(s), if the primary server fails. Note that this feature depends on proper implementation of the suspend feature in the output module. All built-in output modules properly support it (most importantly the database write and the syslog message forwarder).</li> + <li><b>action.repeatedmsgcontainsoriginalmsg</b> on/off + <br>"last message repeated n times" messages, if generated, have a different format that contains the message that is being repeated. Note that only the first "n" characters are included, with n to be at least 80 characters, most probably more (this may change from version to version, thus no specific limit is given). The bottom line is that n is large enough to get a good idea which message was repeated but it is not necessarily large enough for the whole message. (Introduced with 4.1.5). Once set, it affects all following actions.</li> + <li><b>action.resumeRetryCount</b> integer + <br>[default 0, -1 means eternal]</li> + <li><b>action.resumeInterval</b> integer + <br>Sets the ActionResumeInterval for the action. The interval provided is always in seconds. Thus, multiply by 60 if you need minutes and 3,600 if you need hours (not recommended). +When an action is suspended (e.g. destination can not be connected), the action is resumed for the configured interval. Thereafter, it is retried. If multiple retires fail, the interval is automatically extended. This is to prevent excessive ressource use for retires. After each 10 retries, the interval is extended by itself. To be precise, the actual interval is (numRetries / 10 + 1) * Action.ResumeInterval. so after the 10th try, it by default is 60 and after the 100th try it is 330.</li> +</ul> + + +<h2>Legacy Format</h2> +<p><b>Be warned that legacy action format is hard to get right. It is +recommended to use RainerScript-Style action format whenever possible!</b> +A key problem with legacy format is that a single action is defined via +multiple configurations lines, which may be spread all across rsyslog.conf. +Even the definition of multiple actions may be intermixed (often not +intentional!). If legacy actions format needs to be used (e.g. some modules +may not yet implement the RainerScript format), it is strongly recommended +to place all configuration statements pertaining to a single action +closely together. +<p>Please also note that legacy action parameters <b>do not</b> affect +RainerScript action objects. So if you define for example: + +<code><pre> +$actionResumeRetryCount 10 +action(type="omfwd" target="server1.example.net") +@@server2.example.net +</pre></code> + +server1's "action.resumeRetryCount" parameter is <b>not</b> set, instead +server2's is! +<p>A goal of the new RainerScript action format was to avoid confusion +which parameters are actually used. As such, it would be counter-productive +to honor legacy action parameters inside a RainerScript definition. As +result, both types of action definitions are strictly (and nicely) +separated from each other. The bottom line is that if RainerScript actions +are used, one does not need to care about which legacy action parameters may +(still...) be in effect. +<p> +<p>Note that not all modules necessarily support legacy action format. +Especially newer modules are recommended to NOT support it. +<h3>Legacy Description</h3> +<p>Templates can be used with many actions. If used, the specified template +is used to generate the message content (instead of the default +template). To specify a template, write a semicolon after the action +value immediately followed by the template name.<br> +<br> +Beware: templates MUST be defined BEFORE they are used. It is OK to +define some templates, then use them in selector lines, define more +templates and use use them in the following selector lines. But it is +NOT permitted to use a template in a selector line that is above its +definition. If you do this, the action will be ignored.</p> +<p><b>You can have multiple actions for a single selector </b> (or +more precisely a single filter of such a selector line). Each action +must be on its own line and the line must start with an ampersand +('&') character and have no filters. An example would be</p> +<p><code><b>*.=crit :omusrmsg:rger<br> +& root<br> +& /var/log/critmsgs</b></code></p> +<p>These three lines send critical messages to the user rger and +root and also store them in /var/log/critmsgs. <b>Using multiple +actions per selector is</b> convenient and also <b>offers +a performance benefit</b>. As the filter needs to be evaluated +only once, there is less computation required to process the directive +compared to the otherwise-equal config directives below:</p> +<p><code><b>*.=crit :omusrmsg:rger<br> +*.=crit root<br> +*.=crit /var/log/critmsgs</b></code></p> +<p> </p> +<h3>Regular File</h3> +<p>Typically messages are logged to real files. The file usually is +specified by full pathname, beginning with a slash "/". +Starting with version 4.6.2 and 5.4.1 (previous v5 version do NOT support this) +relative file names can also be specified. To do so, these must begin with a +dot. For example, use "./file-in-current-dir.log" to specify a file in the +current directory. Please note that rsyslogd usually changes its working +directory to the root, so relative file names must be tested with care (they +were introduced primarily as a debugging vehicle, but may have useful other applications +as well).<br> +<br> +<br> +You may prefix each entry with the minus "-'' sign to omit syncing the +file after every logging. Note that you might lose information if the +system crashes right behind a write attempt. Nevertheless this might +give you back some performance, especially if you run programs that use +logging in a very verbose manner.</p> +<p>If your system is connected to a reliable UPS and you receive +lots of log data (e.g. firewall logs), it might be a very good idea to +turn of +syncing by specifying the "-" in front of the file name. </p> +<p><b>The filename can be either static </b>(always +the same) or <b>dynamic</b> (different based on message +received). The later is useful if you would automatically split +messages into different files based on some message criteria. For +example, dynamic file name selectors allow you to split messages into +different files based on the host that sent them. With dynamic file +names, everything is automatic and you do not need any filters. </p> +<p>It works via the template system. First, you define a template +for the file name. An example can be seen above in the description of +template. We will use the "DynFile" template defined there. Dynamic +filenames are indicated by specifying a questions mark "?" instead of a +slash, followed by the template name. Thus, the selector line for our +dynamic file name would look as follows:</p> +<blockquote> +<code>*.* ?DynFile</code> +</blockquote> +<p>That's all you need to do. Rsyslog will now automatically +generate file names for you and store the right messages into the right +files. Please note that the minus sign also works with dynamic file +name selectors. Thus, to avoid syncing, you may use</p> +<blockquote> +<code>*.* -?DynFile</code></blockquote> +<p>And of course you can use templates to specify the output +format:</p> +<blockquote> +<code>*.* ?DynFile;MyTemplate</code></blockquote> +<p><b>A word of caution:</b> rsyslog creates files as +needed. So if a new host is using your syslog server, rsyslog will +automatically create a new file for it.</p> +<p><b>Creating directories is also supported</b>. For +example you can use the hostname as directory and the program name as +file name:</p> +<blockquote> +<code>$template DynFile,"/var/log/%HOSTNAME%/%programname%.log"</code></blockquote> +<h3>Named Pipes</h3> +<p>This version of rsyslogd(8) has support for logging output to +named pipes (fifos). A fifo or named pipe can be used as a destination +for log messages by prepending a pipe symbol ("|'') to the name of the +file. This is handy for debugging. Note that the fifo must be created +with the mkfifo(1) command before rsyslogd(8) is started.</p> +<h3>Terminal and Console</h3> +<p>If the file you specified is a tty, special tty-handling is +done, same with /dev/console.</p> +<h3>Remote Machine</h3> +<p>Rsyslogd provides full remote logging, i.e. is able to send +messages to a remote host running rsyslogd(8) and to receive messages +from remote hosts. Using this feature you're able to control all syslog +messages on one host, if all other machines will log remotely to that. +This tears down administration needs.</p> +<p>To forward messages to another host, prepend the hostname with +the at sign ("@"). A single at sign means that messages will +be forwarded via UDP protocol (the standard for syslog). If you prepend +two at signs ("@@"), the messages will be transmitted via TCP. Please +note that plain TCP based syslog is not officially standardized, but +most major syslogds support it (e.g. syslog-ng or +<a href="http://www.winsyslog.com/">WinSyslog</a>). The +forwarding action indicator (at-sign) can be followed by one or more +options. If they are given, they must be immediately (without a space) +following the final at sign and be enclosed in parenthesis. The +individual options must be separated by commas. The following options +are right now defined:</p> +<table id="table2" border="1" width="100%"> +<tbody> +<tr> +<td> +<p align="center"><b>z<number></b></p> +</td> +<td>Enable zlib-compression for the message. The +<number> is the compression level. It can be 1 (lowest +gain, lowest CPU overhead) to 9 (maximum compression, highest CPU +overhead). The level can also be 0, which means "no compression". If +given, the "z" option is ignored. So this does not make an awful lot of +sense. There is hardly a difference between level 1 and 9 for typical +syslog messages. You can expect a compression gain between 0% and 30% +for typical messages. Very chatty messages may compress up to 50%, but +this is seldom seen with typically traffic. Please note that rsyslogd +checks the compression gain. Messages with 60 bytes or less will never +be compressed. This is because compression gain is pretty unlikely and +we prefer to save CPU cycles. Messages over that size are always +compressed. However, it is checked if there is a gain in compression +and only if there is, the compressed message is transmitted. Otherwise, +the uncompressed messages is transmitted. This saves the receiver CPU +cycles for decompression. It also prevents small message to actually +become larger in compressed form. +<p><b>Please note that when a TCP transport is used, +compression will also turn on syslog-transport-tls framing. See the "o" +option for important information on the implications.</b></p> +<p>Compressed messages are automatically detected and +decompressed by the receiver. There is nothing that needs to be +configured on the receiver side.</p> +</td> +</tr> +<tr> +<td> +<p align="center"><b>o</b></p> +</td> +<td><b>This option is experimental. Use at your own +risk and only if you know why you need it! If in doubt, do NOT turn it +on.</b> +<p>This option is only valid for plain TCP based +transports. It selects a different framing based on IETF internet draft +syslog-transport-tls-06. This framing offers some benefits over +traditional LF-based framing. However, the standardization effort is +not yet complete. There may be changes in upcoming versions of this +standard. Rsyslog will be kept in line with the standard. There is some +chance that upcoming changes will be incompatible to the current +specification. In this case, all systems using -transport-tls framing +must be upgraded. There will be no effort made to retain compatibility +between different versions of rsyslog. The primary reason for that is +that it seems technically impossible to provide compatibility between +some of those changes. So you should take this note very serious. It is +not something we do not *like* to do (and may change our mind if enough +people beg...), it is something we most probably *can not* do for +technical reasons (aka: you can beg as much as you like, it won't +change anything...).</p> +<p>The most important implication is that compressed syslog +messages via TCP must be considered with care. Unfortunately, it is +technically impossible to transfer compressed records over traditional +syslog plain tcp transports, so you are left with two evil choices...</p> +</td> +</tr> +</tbody> +</table> +<p><br> +The hostname may be followed by a colon and the destination port.</p> +<p>The following is an example selector line with forwarding:</p> +<p>*.* @@(o,z9)192.168.0.1:1470</p> +<p>In this example, messages are forwarded via plain TCP with +experimental framing and maximum compression to the host 192.168.0.1 at +port 1470.</p> +<p>*.* @192.168.0.1</p> +<p>In the example above, messages are forwarded via UDP to the +machine 192.168.0.1, the destination port defaults to 514. Messages +will not be compressed.</p> +<p>Note that IPv6 addresses contain colons. So if an IPv6 address is specified +in the hostname part, rsyslogd could not detect where the IP address ends +and where the port starts. There is a syntax extension to support this: +put squary brackets around the address (e.g. "[2001::1]"). Square +brackets also work with real host names and IPv4 addresses, too. +<p>A valid sample to send messages to the IPv6 host 2001::1 at port 515 +is as follows: +<p>*.* @[2001::1]:515 +<p>This works with TCP, too. +<p><b>Note to sysklogd users:</b> sysklogd does <b>not</b> +support RFC 3164 format, which is the default forwarding template in +rsyslog. As such, you will experience duplicate hostnames if rsyslog is +the sender and sysklogd is the receiver. The fix is simple: you need to +use a different template. Use that one:</p> +<p class="MsoPlainText">$template +sysklogd,"<%PRI%>%TIMESTAMP% %syslogtag%%msg%\""<br> +*.* @192.168.0.1;sysklogd</p> +<h3>List of Users</h3> +<p>Usually critical messages are also directed to "root'' on +that machine. You can specify a list of users that shall get the +message by simply writing ":omusrmsg: followed by the login name. For example, +the send messages to root, use ":omusrmsg:root". +You may specify more than one user +by separating them with commas (",''). Do not repeat the ":omusrmsg:" prefix in +this case. For example, to send data to users root and rger, use +":omusrmsg:root,rger" (do not use ":omusrmsg:root,:omusrmsg:rger", this is invalid). +If they're logged in they get +the message.</p> +<h3>Everyone logged on</h3> +<p>Emergency messages often go to all users currently online to +notify them that something strange is happening with the system. To +specify this wall(1)-feature use an asterisk as the user message +destination(":omusrmsg:*'').</p> +<h3>Call Plugin</h3> +<p>This is a generic way to call an output plugin. The plugin +must support this functionality. Actual parameters depend on the +module, so see the module's doc on what to supply. The general syntax +is as follows:</p> +<p>:modname:params;template</p> +<p>Currently, the ommysql database output module supports this +syntax (in addtion to the ">" syntax it traditionally +supported). For ommysql, the module name is "ommysql" and the params +are the traditional ones. The ;template part is not module specific, it +is generic rsyslog functionality available to all modules.</p> +<p>As an example, the ommysql module may be called as follows:</p> +<p>:ommysql:dbhost,dbname,dbuser,dbpassword;dbtemplate</p> +<p>For details, please see the "Database Table" section of this +documentation.</p> +<p>Note: as of this writing, the ":modname:" part is hardcoded +into the module. So the name to use is not necessarily the name the +module's plugin file is called.</p> +<h3>Database Table</h3> +<p>This allows logging of the message to a database table. +Currently, only MySQL databases are supported. However, other database +drivers will most probably be developed as plugins. By default, a <a href="http://www.monitorware.com/">MonitorWare</a>-compatible +schema is required for this to work. You can create that schema with +the createDB.SQL file that came with the rsyslog package. You can also<br> +use any other schema of your liking - you just need to define a proper +template and assign this template to the action.<br> +<br> +The database writer is called by specifying a greater-then sign +(">") in front of the database connect information. Immediately +after that<br> +sign the database host name must be given, a comma, the database name, +another comma, the database user, a comma and then the user's password. +If a specific template is to be used, a semicolon followed by the +template name can follow the connect information. This is as follows:<br> +<br> +>dbhost,dbname,dbuser,dbpassword;dbtemplate</p> +<p><b>Important: to use the database functionality, the +MySQL output module must be loaded in the config file</b> BEFORE +the first database table action is used. This is done by placing the</p> +<p><code><b>$ModLoad ommysql</b></code></p> +<p>directive some place above the first use of the database write +(we recommend doing at the the beginning of the config file).</p> +<h3>Discard</h3> +<p>If the discard action is carried out, the received message is +immediately discarded. No further processing of it occurs. Discard has +primarily been added to filter out messages before carrying on any +further processing. For obvious reasons, the results of "discard" are +depending on where in the configuration file it is being used. Please +note that once a message has been discarded there is no way to retrieve +it in later configuration file lines.</p> +<p>Discard can be highly effective if you want to filter out some +annoying messages that otherwise would fill your log files. To do that, +place the discard actions early in your log files. This often plays +well with property-based filters, giving you great freedom in +specifying what you do not want.</p> +<p>Discard is just the single tilde character with no further +parameters:</p> +<p>~</p> +<p>For example,</p> +<p>*.* ~</p> +<p>discards everything (ok, you can achive the same by not +running rsyslogd at all...).</p> +<h3>Output Channel</h3> +<p>Binds an output channel definition (see there for details) to +this action. Output channel actions must start with a $-sign, e.g. if +you would like to bind your output channel definition "mychannel" to +the action, use "$mychannel". Output channels support template +definitions like all all other actions.</p> +<h3>Shell Execute</h3> +<p>This executes a program in a subshell. The program is passed +the template-generated message as the only command line parameter. +Rsyslog waits until the program terminates and only then continues to +run.</p> +<p>^program-to-execute;template</p> +<p>The program-to-execute can be any valid executable. It +receives the template string as a single parameter (argv[1]).</p> +<p><b>WARNING:</b> The Shell Execute action was added +to serve an urgent need. While it is considered reasonable save when +used with some thinking, its implications must be considered. The +current implementation uses a system() call to execute the command. +This is not the best way to do it (and will hopefully changed in +further releases). Also, proper escaping of special characters is done +to prevent command injection. However, attackers always find smart ways +to circumvent escaping, so we can not say if the escaping applied will +really safe you from all hassles. Lastly, rsyslog will wait until the +shell command terminates. Thus, a program error in it (e.g. an infinite +loop) can actually disable rsyslog. Even without that, during the +programs run-time no messages are processed by rsyslog. As the IP +stacks buffers are quickly overflowed, this bears an increased risk of +message loss. You must be aware of these implications. Even though they +are severe, there are several cases where the "shell execute" action is +very useful. This is the reason why we have included it in its current +form. To mitigate its risks, always a) test your program thoroughly, b) +make sure its runtime is as short as possible (if it requires a longer +run-time, you might want to spawn your own sub-shell asynchronously), +c) apply proper firewalling so that only known senders can send syslog +messages to rsyslog. Point c) is especially important: if rsyslog is +accepting message from any hosts, chances are much higher that an +attacker might try to exploit the "shell execute" action.</p> +<h3>Template Name</h3> +<p>Every ACTION can be followed by a template name. If so, that +template is used for message formatting. If no name is given, a +hard-coded default template is used for the action. There can only be +one template name for each given action. The default template is +specific to each action. For a description of what a template is and +what you can do with it, see "TEMPLATES" at the top of this document.</p> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_conf_basic_structure.html b/doc/rsyslog_conf_basic_structure.html new file mode 100644 index 00000000..f5d4891a --- /dev/null +++ b/doc/rsyslog_conf_basic_structure.html @@ -0,0 +1,103 @@ +<html><head><title>Basic Structure - rsyslog.conf</title></head> +<body> +<h1>Basic rsyslog.conf Structure</h1> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">Back to rsyslog.conf manual</a> +<p>Rsyslog supports three different types of configuration statements +concurrently: +<ul> +<li><b>sysklogd</b> - this is the plain old format, thaught everywhere +and still pretty useful for simple use cases. Note that some very +few constructs are no longer supported because they are incompatible +with newer features. These are mentioned in the compatibility docs. +<li><b>legacy rsyslog</b> - these are statements that begin with a dollar +sign. They set some configuration parameters and modify e.g. the way +actions operate. This is the only format supported in pre-v6 versions of +rsyslog. It is still fully supported in v6 and above. Note that some +plugins and features may still only be available through legacy format +(because plugins need to be explicitely upgraded to use the new style +format, and this hasn't happened to all plugins). +<li><b>RainerScript</b> - the new style format. This is the best and most +precise format to be used for more complex cases. The rest of this page +assumes RainerScript based rsyslog.conf. +</ul> +<p>The rsyslog.conf files consists of statements. For old style (sysklogd & legacy +rsyslog), lines do matter. For new style (RainerScript) line spacing is irrelevant. +Most importantly, this means with new style actions and all other objects can split +across lines as users want to. +<h2>Comments</h2> +<p>There are two types of comments: +<ul> +<li><b>#-Comments</b> - start with a hash sign (#) and run to the end of the line +<li><b>C-style Comments</b> - start with /* and end with */, just like in the C +programming language. They can be used to comment out multiple lines at one. Comment +nesting is not supported, but #-Comments can be contained inside a C-style comment. +</ul> + +<h2>Processing Order</h2> +<p>Directives are processed from the top of rsyslog.conf to the bottom. Sequence +matters. For example, if you stop processing of a message, obviously all statements +after the stop statement are never evaluated. + +<h3>Flow Control Statements</h3> +<ul> +<li><b>if expr then ... else ...</b> - conditional execution +<li><b>stop</b> - stops processing the current message +<li><b>call</b> - calls a ruleset (just like a subroutine call) +<li><b>continue</b> - a NOP, useful e.g. inside the then part of an if +</ul> + +<h3>Data Manipulation Statements</h3> +<ul> +<li><b>set</b> - <a href="http://www.rsyslog.com/how-to-set-variables-in-rsyslog-v7/">sets</a> +a user variable +<li><b>unset</b> - deletes a previously set user variable +</ul> + +<h2>Inputs</h2> +<p>Every input requires an input module to be loaded and a listener defined for it. +Full details can be found inside the <a href="rsyslog_conf_modules.html">rsyslog +modules</a> documentation. Once loaded, inputs are defined via the +<b>input()</b> object. + +<h2>Outputs</h2> +<p>Outputs are also called "actions". A small set of actions is pre-loaded (like +the output file writer, which is used in almost every rsyslog.conf), others must +be loaded just like inputs. +<p>An action is invoked via the <b>action(type="type" ...)</b> object. Type is +mandatory and must contain the name of the plugin to be called (e.g. "omfile" or +"ommongodb"). Other paramters may be present. Their type and use depends on +the output plugin in question. + +<h2>Rulesets and Rules</h2> +<p>Rulesets and rules form the basis of rsyslog processing. In short, a rule +is a way how rsyslog shall process a specific message. Usually, there is a type +of filter (if-statement) in front of the rule. Complex nesting of rules is possible, +much like in a programming language. +<p>Rulesets are containers for rules. A single ruleset can contain many rules. In +the programming language analogy, one may think of a ruleset like being a program. +A ruleset can be "bound" (assigned) to a specific input. In the analogy, this means that when +a message comes in via that input, the "program" (ruleset) bound to it will be executed +(but not any other!). +<p>There is detail documentation available for +<a href="multi_ruleset.html">rsyslog rulesets</a>. +<p>For quick reference, rulesets are defined as follows: +<pre> +ruleset(name="rulesetname") { + action(type="omfile" file="/path/to/file") + action(type="..." ...) + /* and so on... */ +} +</pre> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_conf_filter.html b/doc/rsyslog_conf_filter.html new file mode 100644 index 00000000..a795193f --- /dev/null +++ b/doc/rsyslog_conf_filter.html @@ -0,0 +1,288 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Filter Conditions - rsyslog.conf</title></head> +<body> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">back</a> +<h2>Filter Conditions</h2> +<p>Rsyslog offers three different types "filter conditions":</p> +<ul> +<li><a href="http://www.rainerscript.com/">RainerScript</a>-based filters</li> +<li>"traditional" severity and facility based selectors</li> +<li>property-based filters</li> +</ul> +<h3>RainerScript-Based Filters</h3> +RainerScript based filters are the prime means of creating complex rsyslog configuration. +The permit filtering on arbitrary complex expressions, which can include boolean, +arithmetic and string operations. They also support full nesting of filters, just +as you know from other scripting environments. +<br> +Scripts based filters are indicated by the keyword "if", as usual. +They have this format:<br> +<br> +if expr then block else block +<br> +"If" and "then" are fixed keywords that mus be present. "expr" is a +(potentially quite complex) expression. So the <a href="expression.html">expression documentation</a> for +details. +The keyword "else" and its associated block is optional. Note that a block can contain either +a single action (chain), or an arbitrary complex script enclosed in curly braces, e.g.: +<br> +<pre> +if $programname == 'prog1' then { + action(type="omfile" file="/var/log/prog1.log") + if $msg contains 'test' then + action(type="omfile" file="/var/log/prog1test.log") + else + action(type="omfile" file="/var/log/prog1notest.log") +} +</pre> +<br> +Other types of filtes can also be combined with the pure RainerScript ones. This makes +it particularly easy to migrate from early config files to RainerScript. Also, the traditional +syslog PRI-based filters are a good and easy to use addition. While they are legacy, we still +recommend there use where they are up to the job. We do NOT, however, recommend property-based +filters any longer. As an example, the following is perfectly valid: +<br> +<pre> +if $fromhost == 'host1' then { + mail.* action(type="omfile" file="/var/log/host1/mail.log") + *.err /var/log/host1/errlog # this is also still valid + # + # more "old-style rules" ... + # +} else { + mail.* action(type="omfile" file="/var/log/mail.log") + *.err /var/log/errlog + # + # more "old-style rules" ... + # +} +</pre> +<br> + +Right now, you need to specify numerical values if you would like to +check for facilities and severity. These can be found in <a href="http://www.ietf.org/rfc/rfc3164.txt">RFC 3164</a>. +If you don't like that, you can of course also use the textual property +- just be sure to use the right one. As expression support is enhanced, +this will change. For example, if you would like to filter on message +that have facility local0, start with "DEVNAME" and have either +"error1" or "error0" in their message content, you could use the +following filter:<br> +<br> +<code> +if $syslogfacility-text == 'local0' and $msg +startswith 'DEVNAME' and ($msg contains 'error1' or $msg contains +'error0') then /var/log/somelog<br> +</code> +<br> +Please note that the above <span style="font-weight: bold;">must +all be on one line</span>! And if you would like to store all +messages except those that contain "error1" or "error0", you just need +to add a "not":<br> +<br> +<code> +if $syslogfacility-text == 'local0' and $msg +startswith 'DEVNAME' and <span style="font-weight: bold;">not</span> +($msg contains 'error1' or $msg contains +'error0') then /var/log/somelog<br> +</code> +<br> +If you would like to do case-insensitive comparisons, use +"contains_i" instead of "contains" and "startswith_i" instead of +"startswith".<br> +<br> +Regular expressions are supported via functions (see function list). + +<h3>Selectors</h3> +<p><b>Selectors are the traditional way of filtering syslog +messages.</b> They have been kept in rsyslog with their original +syntax, because it is well-known, highly effective and also needed for +compatibility with stock syslogd configuration files. If you just need +to filter based on priority and facility, you should do this with +selector lines. They are <b>not</b> second-class citizens +in rsyslog and offer the best performance for this job.</p> +<p>The selector field itself again consists of two parts, a +facility and a priority, separated by a period (".''). Both parts are +case insensitive and can also be specified as decimal numbers, but +don't do that, you have been warned. Both facilities and priorities are +described in syslog(3). The names mentioned below correspond to the +similar LOG_-values in /usr/include/syslog.h.<br> +<br> +The facility is one of the following keywords: auth, authpriv, cron, +daemon, kern, lpr, mail, mark, news, security (same as auth), syslog, +user, uucp and local0 through local7. The keyword security should not +be used anymore and mark is only for internal use and therefore should +not be used in applications. Anyway, you may want to specify and +redirect these messages here. The facility specifies the subsystem that +produced the message, i.e. all mail programs log with the mail facility +(LOG_MAIL) if they log using syslog.<br> +<br> +The priority is one of the following keywords, in ascending order: +debug, info, notice, warning, warn (same as warning), err, error (same +as err), crit, alert, emerg, panic (same as emerg). The keywords error, +warn and panic are deprecated and should not be used anymore. The +priority defines the severity of the message.<br> +<br> +The behavior of the original BSD syslogd is that all messages of the +specified priority and higher are logged according to the given action. +Rsyslogd behaves the same, but has some extensions.<br> +<br> +In addition to the above mentioned names the rsyslogd(8) understands +the following extensions: An asterisk ("*'') stands for all facilities +or all priorities, depending on where it is used (before or after the +period). The keyword none stands for no priority of the given facility.<br> +<br> +You can specify multiple facilities with the same priority pattern in +one statement using the comma (",'') operator. You may specify as much +facilities as you want. Remember that only the facility part from such +a statement is taken, a priority part would be skipped.</p> +<p>Multiple selectors may be specified for a single action using +the semicolon (";'') separator. Remember that each selector in the +selector field is capable to overwrite the preceding ones. Using this +behavior you can exclude some priorities from the pattern.</p> +<p>Rsyslogd has a syntax extension to the original BSD source, +that makes its use more intuitively. You may precede every priority +with an equals sign ("='') to specify only this single priority and +not any of the above. You may also (both is valid, too) precede the +priority with an exclamation mark ("!'') to ignore all that +priorities, either exact this one or this and any higher priority. If +you use both extensions than the exclamation mark must occur before the +equals sign, just use it intuitively.</p> +<h3>Property-Based Filters</h3> +<p>Property-based filters are unique to rsyslogd. They allow to +filter on any property, like HOSTNAME, syslogtag and msg. A list of all +currently-supported properties can be found in the <a href="property_replacer.html">property replacer documentation</a> +(but keep in mind that only the properties, not the replacer is +supported). With this filter, each properties can be checked against a +specified value, using a specified compare operation.</p> +<p>A property-based filter must start with a colon in column 0. +This tells rsyslogd that it is the new filter type. The colon must be +followed by the property name, a comma, the name of the compare +operation to carry out, another comma and then the value to compare +against. This value must be quoted. There can be spaces and tabs +between the commas. Property names and compare operations are +case-sensitive, so "msg" works, while "MSG" is an invalid property +name. In brief, the syntax is as follows:</p> +<p><code><b>:property, [!]compare-operation, "value"</b></code></p> +<p>The following <b>compare-operations</b> are +currently supported:</p> +<table id="table1" border="1" width="100%"> +<tbody> +<tr> +<td>contains</td> +<td>Checks if the string provided in value is contained in +the property. There must be an exact match, wildcards are not supported.</td> +</tr> +<tr> +<td>isempty</td> +<td>Checks if the property is empty. The value is discarded. This is +especially useful when working with normalized data, where some fields +may be populated based on normalization result. +Available since 6.6.2. +</tr> +<tr> +<td>isequal</td> +<td>Compares the "value" string provided and the property +contents. These two values must be exactly equal to match. The +difference to contains is that contains searches for the value anywhere +inside the property value, whereas all characters must be identical for +isequal. As such, isequal is most useful for fields like syslogtag or +FROMHOST, where you probably know the exact contents.</td> +</tr> +<tr> +<td>startswith</td> +<td>Checks if the value is found exactly at the beginning +of the property value. For example, if you search for "val" with +<p><code><b>:msg, startswith, "val"</b></code></p> +<p>it will be a match if msg contains "values are in this +message" but it won't match if the msg contains "There are values in +this message" (in the later case, contains would match). Please note +that "startswith" is by far faster than regular expressions. So +it makes very much sense (performance-wise) to use "startswith".</p> +<p>Note: when processing syslog messages, please note that $msg usually +starts with a space. The reason for this is RFC3164. Please read the +<a href="http://www.rsyslog.com/log-normalization-and-the-leading-space/">detail +description</a> of what that means to you. In short, you need to make sure +that you include the first space if you use "startswith", otherwise you will +not get matches. +</td> +</tr> +<tr> +<td>regex</td> +<td>Compares the property against the provided POSIX +BRE regular +expression.</td> +</tr> +<tr> +<td>ereregex</td> +<td>Compares the property against the provided POSIX +ERE regular +expression.</td> +</tr> +</tbody> +</table> +<p>You can use the bang-character (!) immediately in front of a +compare-operation, the outcome of this operation is negated. For +example, if msg contains "This is an informative message", the +following sample would not match:</p> +<p><code><b>:msg, contains, "error"</b></code></p> +<p>but this one matches:</p> +<p><code><b>:msg, !contains, "error"</b></code></p> +<p>Using negation can be useful if you would like to do some +generic processing but exclude some specific events. You can use the +discard action in conjunction with that. A sample would be:</p> +<p><code><b>*.* +/var/log/allmsgs-including-informational.log<br> +:msg, contains, "informational" <font color="#ff0000" size="4">~</font> +<br> +*.* /var/log/allmsgs-but-informational.log</b></code></p> +<p>Do not overlook the red tilde in line 2! In this sample, all +messages are written to the file allmsgs-including-informational.log. +Then, all messages containing the string "informational" are discarded. +That means the config file lines below the "discard line" (number 2 in +our sample) will not be applied to this message. Then, all remaining +lines will also be written to the file allmsgs-but-informational.log.</p> +<p><b>Value</b> is a quoted string. It supports some +escape sequences:</p> +<p>\" - the quote character (e.g. "String with \"Quotes\"")<br> +\\ - the backslash character (e.g. "C:\\tmp")</p> +<p>Escape sequences always start with a backslash. Additional +escape sequences might be added in the future. Backslash characters <b>must</b> +be escaped. Any other sequence then those outlined above is invalid and +may lead to unpredictable results.</p> +<p>Probably, "msg" is the most prominent use case of property +based filters. It is the actual message text. If you would like to +filter based on some message content (e.g. the presence of a specific +code), this can be done easily by:</p> +<p><code><b>:msg, contains, "ID-4711"</b></code></p> +<p>This filter will match when the message contains the string +"ID-4711". Please note that the comparison is case-sensitive, so it +would not match if "id-4711" would be contained in the message.</p> +<p><code><b>:msg, regex, "fatal .* error"</b></code></p> +<p>This filter uses a POSIX regular expression. It matches when +the +string contains the words "fatal" and "error" with anything in between +(e.g. "fatal net error" and "fatal lib error" but not "fatal error" as +two spaces are required by the regular expression!).</p> +<p>Getting property-based filters right can sometimes be +challenging. In order to help you do it with as minimal effort as +possible, rsyslogd spits out debug information for all property-based +filters during their evaluation. To enable this, run rsyslogd in +foreground and specify the "-d" option.</p> +<p>Boolean operations inside property based filters (like +'message contains "ID17" or message contains "ID18"') are currently not +supported (except for "not" as outlined above). Please note that while +it is possible to query facility and severity via property-based +filters, it is far more advisable to use classic selectors (see above) +for those cases.</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_conf_global.html b/doc/rsyslog_conf_global.html new file mode 100644 index 00000000..e48ed6d3 --- /dev/null +++ b/doc/rsyslog_conf_global.html @@ -0,0 +1,333 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Configuration Directives - rsyslog.conf</title></head> +<body> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">back</a> +<h2>Configuration Directives</h2> +<p>All configuration directives need to be specified on a line by their +own and must start with a dollar-sign. Note that those starting with +the word "Action" modify the next action and should be specified +in front of it. +<p>Here is a list in alphabetical order. Follow links for a description.</p> +<p>Not all directives have an in-depth description right now. +Default values for them are in bold. A more in-depth description will +appear as implementation progresses. +</p> +<p><b>Be sure to read information about <a href="queues.html">queues in rsyslog</a></b> - +many parameter settings modify queue parameters. If in doubt, use the +default, it is usually well-chosen and applicable in most cases.</p> +<ul> +<li><a href="rsconf1_abortonuncleanconfig.html"><b>$AbortOnUncleanConfig</b></a> - abort startup if there is +any issue with the config file</li> +<li><a href="rsconf1_actionexeconlywhenpreviousissuspended.html">$ActionExecOnlyWhenPreviousIsSuspended</a></li> +<li><b>$ActionName</b> <a_single_word> - used primarily for documentation, e.g. when +generating a configuration graph. Available sice 4.3.1. +<li><b>$ActionExecOnlyOnceEveryInterval</b> <seconds> - +execute action only if the last execute is at last +<seconds> seconds in the past (more info in <a href="ommail.html">ommail</a>, +but may be used with any action)</li> +<li><i><b>$ActionExecOnlyEveryNthTime</b> <number></i> - If configured, the next action will +only be executed every n-th time. For example, if configured to 3, the first two messages +that go into the action will be dropped, the 3rd will actually cause the action to execute, +the 4th and 5th will be dropped, the 6th executed under the action, ... and so on. Note: +this setting is automatically re-set when the actual action is defined.</li> +<li><i><b>$ActionExecOnlyEveryNthTimeTimeout</b> <number-of-seconds></i> - has a meaning only if +$ActionExecOnlyEveryNthTime is also configured for the same action. If so, the timeout +setting specifies after which period the counting of "previous actions" expires and +a new action count is begun. Specify 0 (the default) to disable timeouts. +<br> +<i>Why is this option needed?</i> Consider this case: a message comes in at, eg., 10am. That's +count 1. Then, nothing happens for the next 10 hours. At 8pm, the next +one occurs. That's count 2. Another 5 hours later, the next message +occurs, bringing the total count to 3. Thus, this message now triggers +the rule. +<br> +The question is if this is desired behavior? Or should the rule only be +triggered if the messages occur within an e.g. 20 minute window? If the +later is the case, you need a +<br> +<b>$ActionExecOnlyEveryNthTimeTimeout 1200</b> +<br> +This directive will timeout previous messages seen if they are older +than 20 minutes. In the example above, the count would now be always 1 +and consequently no rule would ever be triggered. + +<li><a href="omfile.html"><b>$ActionFileDefaultTemplate</b></a> [templateName] - sets a new default template for file actions</li> +<li><a href="omfile.html"><b>$ActionFileEnableSync</b></a> [on/<span style="font-weight: bold;">off</span>] - enables file +syncing capability of omfile</li> +<li><a href="omfwd.html"><b>$ActionForwardDefaultTemplate</b></a> [templateName] - sets a new +default template for UDP and plain TCP forwarding action</li> +<li><b>$ActionGSSForwardDefaultTemplate</b> [templateName] - sets a +new default template for GSS-API forwarding action</li> +<li><b>$ActionQueueCheckpointInterval</b> <number></li> +<li><b>$ActionQueueDequeueBatchSize</b> <number> [default 16]</li> +<li><b>$ActionQueueDequeueSlowdown</b> <number> [number +is timeout in <i> micro</i>seconds (1000000us is 1sec!), +default 0 (no delay). Simple rate-limiting!]</li> +<li><b>$ActionQueueDiscardMark</b> <number> [default +9750]</li> +<li><b>$ActionQueueDiscardSeverity</b> <number> +[*numerical* severity! default 8 (nothing discarded)]</li> +<li><b>$ActionQueueFileName</b> <name></li> +<li><b>$ActionQueueHighWaterMark</b> <number> [default +8000]</li> +<li><b>$ActionQueueImmediateShutdown</b> [on/<b>off</b>]</li> +<li><b>$ActionQueueSize</b> <number></li> +<li><b>$ActionQueueLowWaterMark</b> <number> [default +2000]</li> +<li><b>$ActionQueueMaxFileSize</b> <size_nbr>, default 1m</li> +<li><b>$ActionQueueTimeoutActionCompletion</b> <number> +[number is timeout in ms (1000ms is 1sec!), default 1000, 0 means +immediate!]</li> +<li><b>$ActionQueueTimeoutEnqueue</b> <number> [number +is timeout in ms (1000ms is 1sec!), default 2000, 0 means indefinite]</li> +<li><b>$ActionQueueTimeoutShutdown</b> <number> [number +is timeout in ms (1000ms is 1sec!), default 0 (indefinite)]</li> +<li><b>$ActionQueueWorkerTimeoutThreadShutdown</b> +<number> [number is timeout in ms (1000ms is 1sec!), +default 60000 (1 minute)]</li> +<li><b>$ActionQueueType</b> [FixedArray/LinkedList/<b>Direct</b>/Disk]</li> +<li><b>$ActionQueueSaveOnShutdown </b> [on/<b>off</b>] +</li> +<li><b>$ActionQueueWorkerThreads</b> <number>, num worker threads, default 1, recommended 1</li> +<li><b>$ActionQueueWorkerThreadMinumumMessages</b> <number>, default 100</li> +<li><a href="rsconf1_actionresumeinterval.html"><b>$ActionResumeInterval</b></a></li> +<li><b>$ActionResumeRetryCount</b> <number> [default 0, -1 means eternal]</li> +<li><a href="omfwd.html"><b>$ActionSendResendLastMsgOnReconnect</b></a> <[on/<b>off</b>]> specifies if the last message is to be resend when a connecition breaks and has been reconnected. May increase reliability, but comes at the risk of message duplication. +<li><a href="omfwd.html"><b>$ActionSendStreamDriver</b></a> <driver basename> just like $DefaultNetstreamDriver, but for the specific action</li> +<li><a href="omfwd.html"><b>$ActionSendStreamDriverMode</b></a> <mode>, default 0, mode to use with the stream driver (driver-specific)</li> +<li><a href="omfwd.html"><b>$ActionSendStreamDriverAuthMode</b></a> <mode>, authentication mode to use with the stream driver. Note that this directive requires TLS +netstream drivers. For all others, it will be ignored. +(driver-specific)</li> +<li><a href="omfwd.html"><b>$ActionSendStreamDriverPermittedPeer</b></a> <ID>, accepted fingerprint (SHA1) or name of remote peer. Note that this directive requires TLS +netstream drivers. For all others, it will be ignored. +(driver-specific) -<span style="font-weight: bold;"> directive may go away</span>!</li> +<li><a href="omfwd.html"><b>$ActionSendTCPRebindInterval</b> nbr</a>- [available since 4.5.1] - instructs the TCP send +action to close and re-open the connection to the remote host every nbr of messages sent. +Zero, the default, means that no such processing is done. This directive is useful for +use with load-balancers. Note that there is some performance overhead associated with it, +so it is advisable to not too often "rebind" the connection (what +"too often" actually means depends on your configuration, a rule of thumb is +that it should be not be much more often than once per second).</li> +<li><a href="omfwd.html"><b>$ActionSendUDPRebindInterval</b> nbr</a>- [available since 4.3.2] - instructs the UDP send +action to rebind the send socket every nbr of messages sent. Zero, the default, means +that no rebind is done. This directive is useful for use with load-balancers.</li> +<li><b>$ActionWriteAllMarkMessages</b> [on/<b>off</b>]- [available since 5.1.5] - normally, mark messages +are written to actions only if the action was not recently executed (by default, recently means within the +past 20 minutes). If this setting is switched to "on", mark messages are always sent to actions, +no matter how recently they have been executed. In this mode, mark messages can be used as a kind of +heartbeat. Note that this option auto-resets to "off", so if you intend to use it with multiple +actions, it must be specified in front off <b>all</b> selector lines that should provide this +functionality. +</li> +<li><a href="rsconf1_allowedsender.html"><b>$AllowedSender</b></a></li> +<li><a href="rsconf1_controlcharacterescapeprefix.html"><b>$ControlCharacterEscapePrefix</b></a></li> +<li><a href="rsconf1_debugprintcfsyslinehandlerlist.html"><b>$DebugPrintCFSyslineHandlerList</b></a></li> + +<li><a href="rsconf1_debugprintmodulelist.html"><b>$DebugPrintModuleList</b></a></li> +<li><a href="rsconf1_debugprinttemplatelist.html"><b>$DebugPrintTemplateList</b></a></li> +<li><b>$DefaultNetstreamDriver</b> <drivername>, the default <a href="netstream.html">network stream driver</a> to use. Defaults to ptcp.$DefaultNetstreamDriverCAFile </path/to/cafile.pem></li> +<li><b>$DefaultNetstreamDriverCertFile</b> </path/to/certfile.pem></li> +<li><b>$DefaultNetstreamDriverKeyFile</b> </path/to/keyfile.pem></li> +<li><b>$DefaultRuleset</b> <i>name</i> - changes the default ruleset for unbound inputs to +the provided <i>name</i> (the default default ruleset is named +"RSYSLOG_DefaultRuleset"). It is advised to also read +our paper on <a href="multi_ruleset.html">using multiple rule sets in rsyslog</a>.</li> +<li><a href="omfile.html"><b>$CreateDirs</b></a> [<b>on</b>/off] - create directories on an as-needed basis</li> +<li><a href="omfile.html"><b>$DirCreateMode</b></a></li> +<li><a href="omfile.html"><b>$DirGroup</b></a></li> +<li><a href="omfile.html"><b>$DirOwner</b></a></li> +<li><a href="rsconf1_dropmsgswithmaliciousdnsptrrecords.html"><b>$DropMsgsWithMaliciousDnsPTRRecords</b></a></li> +<li><a href="rsconf1_droptrailinglfonreception.html"><b>$DropTrailingLFOnReception</b></a></li> +<li><a href="omfile.html"><b>$DynaFileCacheSize</b></a></li> +<li><a href="rsconf1_escape8bitcharsonreceive.html"><b>$Escape8BitCharactersOnReceive</b></a></li> +<li><a href="rsconf1_escapecontrolcharactersonreceive.html"><b>$EscapeControlCharactersOnReceive</b></a></li> +<li><b>$EscapeControlCharactersOnReceive</b> [<b>on</b>|off] - escape USASCII HT character</li> +<li><b>$SpaceLFOnReceive</b> [on/<b>off</b>] - instructs rsyslogd to replace LF with spaces during message reception (sysklogd compatibility aid)</li> +<li><b>$ErrorMessagesToStderr</b> [<b>on</b>|off] - direct rsyslogd error message to stderr (in addition to other targets)</li> +<li><a href="omfile.html"><b>$FailOnChownFailure</b></a></li> +<li><a href="omfile.html"><b>$FileCreateMode</b></a></li> +<li><a href="omfile.html"><b>$FileGroup</b></a></li> +<li><a href="omfile.html"><b>$FileOwner</b></a></li> +<li><a href="rsconf1_generateconfiggraph.html"><b>$GenerateConfigGraph</b></a></li> +<li><a href="rsconf1_gssforwardservicename.html"><b>$GssForwardServiceName</b></a></li> +<li><a href="rsconf1_gsslistenservicename.html"><b>$GssListenServiceName</b></a></li> +<li><a href="rsconf1_gssmode.html"><b>$GssMode</b></a></li> +<li><a href="rsconf1_includeconfig.html"><b>$IncludeConfig</b></a></li><li>MainMsgQueueCheckpointInterval <number></li> +<li><b>$LocalHostName</b> [name] - this directive permits to overwrite the system +hostname with the one specified in the directive. If the directive is given +multiple times, all but the last one will be ignored. Please note that startup +error messages may be issued with the real hostname. This is by design and not +a bug (but one may argue if the design should be changed ;)). Available since +4.7.4+, 5.7.3+, 6.1.3+. +<li><b>$LogRSyslogStatusMessages</b> [<b>on</b>/off] - If set to on (the default), +rsyslog emits message on startup and shutdown as well as when it is HUPed. +This information might be needed by some log analyzers. If set to off, no such +status messages are logged, what may be useful for other scenarios. +[available since 4.7.0 and 5.3.0] +<li><b>$MainMsgQueueDequeueBatchSize</b> <number> [default 32]</li> +<li><b>$MainMsgQueueDequeueSlowdown</b> <number> [number +is timeout in <i> micro</i>seconds (1000000us is 1sec!), +default 0 (no delay). Simple rate-limiting!]</li> +<li><b>$MainMsgQueueDiscardMark</b> <number> [default 9750]</li> +<li><b>$MainMsgQueueDiscardSeverity</b> <severity> +[either a textual or numerical severity! default 4 (warning)]</li> +<li><b>$MainMsgQueueFileName</b> <name></li> +<li><b>$MainMsgQueueHighWaterMark</b> <number> [default +8000]</li> +<li><b>$MainMsgQueueImmediateShutdown</b> [on/<b>off</b>]</li> +<li><a href="rsconf1_mainmsgqueuesize.html"><b>$MainMsgQueueSize</b></a></li> +<li><b>$MainMsgQueueLowWaterMark</b> <number> [default +2000]</li> +<li><b>$MainMsgQueueMaxFileSize</b> <size_nbr>, default +1m</li> +<li><b>$MainMsgQueueTimeoutActionCompletion</b> +<number> [number is timeout in ms (1000ms is 1sec!), +default +1000, 0 means immediate!]</li> +<li><b>$MainMsgQueueTimeoutEnqueue</b> <number> [number +is timeout in ms (1000ms is 1sec!), default 2000, 0 means indefinite]</li> +<li><b>$MainMsgQueueTimeoutShutdown</b> <number> [number +is timeout in ms (1000ms is 1sec!), default 0 (indefinite)]</li> +<li><b>$MainMsgQueueWorkerTimeoutThreadShutdown</b> +<number> [number is timeout in ms (1000ms is 1sec!), +default 60000 (1 minute)]</li> +<li><b>$MainMsgQueueType</b> [<b>FixedArray</b>/LinkedList/Direct/Disk]</li> +<li><b>$MainMsgQueueSaveOnShutdown </b> [on/<b>off</b>] +</li> +<li><b>$MainMsgQueueWorkerThreads</b> <number>, num +worker threads, default 1, recommended 1</li> +<li><b>$MainMsgQueueWorkerThreadMinumumMessages</b> <number>, default 100</li> +<li><a href="rsconf1_markmessageperiod.html"><b>$MarkMessagePeriod</b></a> (immark)</li> +<li><b><i>$MaxMessageSize</i></b> <size_nbr>, default 2k - allows to specify maximum supported message size +(both for sending and receiving). The default +should be sufficient for almost all cases. Do not set this below 1k, as it would cause +interoperability problems with other syslog implementations.<br> +Change the setting to e.g. 32768 if you would like to +support large message sizes for IHE (32k is the current maximum +needed for IHE). I was initially tempted to set the default to 32k, +but there is a some memory footprint with the current +implementation in rsyslog. +<br>If you intend to receive Windows Event Log data (e.g. via +<a href="http://www.eventreporter.com/">EventReporter</a>), you might want to +increase this number to an even higher value, as event +log messages can be very lengthy ("$MaxMessageSize 64k" is not a bad idea). +Note: testing showed that 4k seems to be +the typical maximum for <b>UDP</b> based syslog. This is an IP stack +restriction. Not always ... but very often. If you go beyond +that value, be sure to test that rsyslogd actually does what +you think it should do ;) It is highly suggested to use a TCP based transport +instead of UDP (plain TCP syslog, RELP). This resolves the UDP stack size restrictions. +<br>Note that 2k, the current default, is the smallest size that must be +supported in order to be compliant to the upcoming new syslog RFC series. +</li> +<li><a href="rsconf1_maxopenfiles.html"><b>$MaxOpenFiles</b></a></li> +<li><a href="rsconf1_moddir.html"><b>$ModDir</b></a></li> +<li><a href="rsconf1_modload.html"><b>$ModLoad</b></a></li> +<li><a href="omfile.html"><b>$OMFileAsyncWriting</b></a> [on/<b>off</b>], if turned on, the files will be written +in asynchronous mode via a separate thread. In that case, double buffers will be used so +that one buffer can be filled while the other buffer is being written. Note that in order +to enable $OMFileFlushInterval, $OMFileAsyncWriting must be set to "on". Otherwise, the flush +interval will be ignored. Also note that when $OMFileFlushOnTXEnd is "on" but +$OMFileAsyncWriting is off, output will only be written when the buffer is full. This may take +several hours, or even require a rsyslog shutdown. However, a buffer flush can be forced +in that case by sending rsyslogd a HUP signal. +<li><a href="omfile.html"><b>$OMFileZipLevel</b></a> 0..9 [default 0] - if greater 0, turns on gzip compression +of the output file. The higher the number, the better the compression, but also the +more CPU is required for zipping.</li> +<li><a href="omfile.html"><b>$OMFileIOBufferSize</b></a> <size_nbr>, default 4k, size of the buffer used to writing output data. The larger the buffer, the potentially better performance is. The default of 4k is quite conservative, it is useful to go up to 64k, and 128K if you used gzip compression (then, even higher sizes may make sense)</li> +<li><a href="omfile.html"><b>$OMFileFlushOnTXEnd</b></a> <[<b>on</b>/off]>, default on. Omfile has the +capability to +write output using a buffered writer. Disk writes are only done when the buffer is +full. So if an error happens during that write, data is potentially lost. In cases where +this is unacceptable, set $OMFileFlushOnTXEnd to on. Then, data is written at the end +of each transaction (for pre-v5 this means after <b>each</b> log message) and the usual +error recovery thus can handle write errors without data loss. Note that this option +severely reduces the effect of zip compression and should be switched to off +for that use case. Note that the default -on- is primarily an aid to preserve +the traditional syslogd behaviour.</li> +<li><a href="omfile.html"><b>$omfileForceChown</b></a> - force ownership change for all files</li> +<li><b>$RepeatedMsgContainsOriginalMsg</b> [on/<b>off</b>] - "last message repeated n times" messages, if generated, +have a different format that contains the message that is being repeated. +Note that only the first "n" characters are included, with n to be at least 80 characters, most +probably more (this may change from version to version, thus no specific limit is given). The bottom +line is that n is large enough to get a good idea which message was repeated but it is not necessarily +large enough for the whole message. (Introduced with 4.1.5). Once set, it affects all following actions.</li> +<li><a href="rsconf1_repeatedmsgreduction.html"><b>$RepeatedMsgReduction</b></a></li> +<li><a href="rsconf1_resetconfigvariables.html"><b>$ResetConfigVariables</b></a></li> +<li><b>$Ruleset</b> <i>name</i> - starts a new ruleset or switches back to one already defined. +All following actions belong to that new rule set. +the <i>name</i> does not yet exist, it is created. To switch back to rsyslog's +default ruleset, specify "RSYSLOG_DefaultRuleset") as the name. +All following actions belong to that new rule set. It is advised to also read +our paper on <a href="multi_ruleset.html">using multiple rule sets in rsyslog</a>.</li> +<li><b><a href="rsconf1_rulesetcreatemainqueue.html">$RulesetCreateMainQueue</a></b> on - creates +a ruleset-specific main queue. +<li><b><a href="rsconf1_rulesetparser.html">$RulesetParser</a></b> - enables to set +a specific (list of) message parsers to be used with the ruleset. +<li><b>$OptimizeForUniprocessor</b> [on/<b>off</b>] - turns on optimizatons which lead to better +performance on uniprocessors. If you run on multicore-machiens, turning this off lessens CPU load. The +default may change as uniprocessor systems become less common. [available since 4.1.0]</li> +<li><b>$PreserveFQDN</b> [on/<b>off</b>) - if set to off (legacy default to remain compatible +to sysklogd), the domain part from a name that is within the same domain as the receiving +system is stripped. If set to on, full names are always used.</li> +<li><b>$WorkDirectory</b> <name> (directory for spool and other work files. +Do <b>not</b> use trailing slashes)</li> +<li><b>$UDPServerAddress</b> <IP> (imudp) -- local IP +address (or name) the UDP listens should bind to</li> +<li><b>$UDPServerRun</b> <port> (imudp) -- former +-r<port> option, default 514, start UDP server on this +port, "*" means all addresses</li> +<li><b>$UDPServerTimeRequery</b> <nbr-of-times> (imudp) -- this is a performance +optimization. Getting the system time is very costly. With this setting, imudp can +be instructed to obtain the precise time only once every n-times. This logic is +only activated if messages come in at a very fast rate, so doing less frequent +time calls should usually be acceptable. The default value is two, because we have +seen that even without optimization the kernel often returns twice the identical time. +You can set this value as high as you like, but do so at your own risk. The higher +the value, the less precise the timestamp. +<li><a href="droppriv.html"><b>$PrivDropToGroup</b></a></li> +<li><a href="droppriv.html"><b>$PrivDropToGroupID</b></a></li> +<li><a href="droppriv.html"><b>$PrivDropToUser</b></a></li> +<li><a href="droppriv.html"><b>$PrivDropToUserID</b></a></li> +<li><b>$Sleep</b> <seconds> - puts the rsyslog main thread to sleep for the specified +number of seconds immediately when the directive is encountered. You should have a +good reason for using this directive!</li> +<li><b>$LocalHostIPIF</b> <interface name> - (available since 5.9.6) - if provided, the IP of the specified +interface (e.g. "eth0") shall be used as fromhost-ip for locall-originating messages. +If this directive is not given OR the interface cannot be found (or has no IP address), +the default of "127.0.0.1" is used. Note that this directive can be given only +once. Trying to reset will result in an error message and the new value will +be ignored. Please note that modules must have support for obtaining the local +IP address set via this directive. While this is the case for rsyslog-provided +modules, it may not always be the case for contributed plugins. +<br><b>Important:</b> This directive shall be placed <b>right at the top of +rsyslog.conf</b>. Otherwise, if error messages are triggered before this directive +is processed, rsyslog will fix the local host IP to "127.0.0.1", what than can +not be reset. +</li> +<li><a href="rsconf1_umask.html"><b>$UMASK</b></a></li> +</ul> +<p><b>Where <size_nbr> or integers are specified above,</b> +modifiers can be used after the number part. For example, 1k means +1024. Supported are k(ilo), m(ega), g(iga), t(era), p(eta) and e(xa). +Lower case letters refer to the traditional binary defintion (e.g. 1m +equals 1,048,576) whereas upper case letters refer to their new +1000-based definition (e.g 1M equals 1,000,000).</p> +<p>Numbers may include '.' and ',' for readability. So you can +for example specify either "1000" or "1,000" with the same result. +Please note that rsyslogd simply ignores the punctuation. From it's +point of view, "1,,0.0.,.,0" also has the value 1000. </p> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> + + diff --git a/doc/rsyslog_conf_modules.html b/doc/rsyslog_conf_modules.html new file mode 100644 index 00000000..c8c1c5d7 --- /dev/null +++ b/doc/rsyslog_conf_modules.html @@ -0,0 +1,192 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Modules - rsyslog.conf</title></head> +<body> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">Back to rsyslog.conf manual</a> +<h1>Modules</h1> +<p>Rsyslog has a modular design. This enables functionality to be +dynamically loaded from modules, which may also be written by any +third party. Rsyslog itself offers all non-core functionality as +modules. Consequently, there is a growing +number of modules. Here is the entry point to their documentation and +what they do (list is currently not complete)</p> +<p>Please note that each module provides configuration +directives, which are NOT necessarily being listed below. Also +remember, that a modules configuration directive (and functionality) is +only available if it has been loaded (using $ModLoad).</p> +<p>It is relatively easy to write a rsyslog module. <b>If none of the provided +modules solve your need, you may consider writing one or have one written +for you by +<a href="http://www.rsyslog.com/professional-services">Adiscon's professional services for rsyslog</a> +</b>(this often is a very cost-effective and efficient way of getting what you need). +<p>There exist different classes of loadable modules: +<ul> +<li><a href="rsyslog_conf_modules.html#im">Input Modules</a> +<li><a href="rsyslog_conf_modules.html#om">Output Modules</a> +<li><a href="rsyslog_conf_modules.html#pm">Parser Modules</a> +<li><a href="rsyslog_conf_modules.html#mm">Message Modification Modules</a> +<li><a href="rsyslog_conf_modules.html#sm">String Generator Modules</a> +<li><a href="rsyslog_conf_modules.html#lm">Library Modules</a> +</ul> + +<a name"im"></a><h2>Input Modules</h2> +<p>Input modules are used to gather messages from various sources. They interface +to message generators. +<ul> +<li><a href="imfile.html">imfile</a> - input module for text files</li> +<li><a href="imrelp.html">imrelp</a> - RELP input module</li> +<li><a href="imudp.html">imudp</a> - udp syslog message input</li> +<li><a href="imtcp.html">imtcp</a> - input plugin for tcp syslog</li> +<li><a href="imptcp.html">imptcp</a> - input plugin for plain tcp syslog (no TLS but faster)</li> +<li><a href="imgssapi.html">imgssapi</a> - input plugin for plain tcp and GSS-enabled syslog</li> +<li>immark - support for mark messages</li> +<li><a href="imklog.html">imklog</a> - kernel logging</li> +<li><a href="imuxsock.html">imuxsock</a> - unix sockets, including the system log socket</li> +<li><a href="imsolaris.html">imsolaris</a> - input for the Sun Solaris system log source</li> +<li><a href="im3195.html">im3195</a> - accepts syslog messages via RFC 3195</li> +<li><a href="impstats.html">impstats</a> - provides periodic statistics of rsyslog internal counters</li> +<li><a href="imjournal.html">imjournal</a> - Linux journal inuput module</li> +</ul> + +<a name"om"></a><h2>Output Modules</h2> +<p>Output modules process messages. With them, message formats can be transformed +and messages be transmitted to various different targets. +<ul> +<li><a href="omfile.html">omfile</a> - file output module</li> +<li><a href="omfwd.html">omfwd</a> - syslog forwarding output module</li> +<li><a href="omjournal.html">omjournal</a> - Linux journal output module</li> +<li><a href="ompipe.html">ompipe</a> - named pipe output module</li> +<li><a href="omusrmsg.html">omusrmsg</a> - user message output module</li> +<li><a href="omsnmp.html">omsnmp</a> - SNMP trap output module</li> +<li><a href="omstdout.html">omtdout</a> - stdout output module (mainly a test tool)</li> +<li><a href="omrelp.html">omrelp</a> - RELP output module</li> +<li><a href="omruleset.html">omruleset</a> - forward message to another ruleset</li> +<li>omgssapi - output module for GSS-enabled syslog</li> +<li><a href="ommysql.html">ommysql</a> - output module for MySQL</li> +<li>ompgsql - output module for PostgreSQL</li> +<li><a href="omlibdbi.html">omlibdbi</a> - +generic database output module (Firebird/Interbase, MS SQL, Sybase, +SQLLite, Ingres, Oracle, mSQL)</li> +<li><a href="ommail.html">ommail</a> - +permits rsyslog to alert folks by mail if something important happens</li> +<li><a href="omprog.html">omprog</a> - permits sending messages to a program for custom processing</li> +<li><a href="omoracle.html">omoracle</a> - output module for Oracle (native OCI interface)</li> +<li><a href="omudpspoof.html">omudpspoof</a> - output module sending UDP syslog messages with a spoofed address</li> +<li><a href="omuxsock.html">omuxsock</a> - output module Unix domain sockets</li> +<li><a href="omhdfs.html">omhdfs</a> - output module for Hadoop's HDFS file system</li> +<li><a href="ommongodb.html">ommongodb</a> - output module for MongoDB</li> +<li><a href="omelasticsearch.html">omelasticsearch</a> - output module for ElasticSearch</li> +</ul> + +<a name="pm"></a><h2>Parser Modules</h2> +<p>Parser modules are used to parse message content, once the message has been +received. They can be used to process custom message formats or invalidly formatted +messages. For details, please see the <a href="messageparser.html">rsyslog +message parser documentation</a>. +<p>The current modules are currently provided as part of rsyslog: +<ul> +<li>pmrfc5424[builtin] - rsyslog.rfc5424 - +parses RFC5424-formatted messages (the new syslog standard) +<li>pmrfc3164[builtin] - rsyslog.rfc3164 - +the traditional/legacy syslog parser +<li>pmrfc3164sd - rsyslog.rfc3164sd - +a contributed module supporting RFC5424 structured data inside +RFC3164 messages (not supported by the rsyslog team) +<li><a href="pmlastmsg.html">pmlastmsg</a> - rsyslog.lastmsg - +a parser module that handles the typically malformed "last messages +repated n times" messages emitted by some syslogds. +</ul> + +<a name="mm"></a><h2>Message Modification Modules</h2> +<p>Message modification modules are used to change the content of messages being processed. +They can be implemented using either the output module or the parser module interface. +From the rsyslog core's point of view, they actually are output or parser modules, it is their +implementation that makes them special. +<p>Currently, there exists only a limited set of such modules, but new ones could be written with +the methods the engine provides. They could be used, for example, to +add dynamically computed content to message (fields). +<p>Message modification modules are usually written for one specific task and thus +usually are not generic enough to be reused. However, existing module's code is +probably an excellent starting base for writing a new module. Currently, the following +modules exist inside the source tree: +<ul> +<li><a href="mmanon.html">mmanon</a> - used to anonymize log messages. +<li><a href="mmcount.html">mmcount</a> - message modification plugin which counts messages +<li><a href="mmnormalize.html">mmnormalize</a> - used to normalize log messages. +Note that this actually is a <b>generic</b> module. +<li><a href="mmjsonparse.html">mmjsonparse</a> - used to interpret CEE/lumberjack +enabled structured log messages. +<li><a href="mmsnmptrapd.html">mmsnmptrapd</a> - uses information provided by snmptrapd inside +the tag to correct the original sender system and priority of messages. Implemented via +the output module interface. +</ul> + +<a name="lm"></a><h2>String Generator Modules</h2> +<p>String generator modules are used, as the name implies, to generate strings based +on the message content. They are currently tightly coupled with the template system. +Their primary use is to speed up template processing by providing a native C +interface to template generation. These modules exist since 5.5.6. To get an idea +of the potential speedup, the default file format, when generated by a string generator, +provides a roughly 5% speedup. For more complex strings, especially those that include +multiple regular expressions, the speedup may be considerably higher. +<p>String generator modules are written to a quite simple interface. However, a word of +caution is due: they access the rsyslog message object via a low-level interface. +That interface is not guaranteed yet to stay stable. So it may be necessary to +modify string generator modules if the interface changes. Obviously, we will not do that +without good reason, but it may happen. +<p>Rsyslog comes with a set of core, build-in string generators, which are used +to provide those default templates that we consider to be time-critical: +<ul> +<li>smfile - the default rsyslog file format +<li>smfwd - the default rsyslog (network) forwarding format +<li>smtradfile - the traditional syslog file format +<li>smfwd - the traditional syslog (network) forwarding format +</ul> +<p>Note that when you replace these defaults be some custom strings, you will +loose some performance (around 5%). For typical systems, this is not really relevant. +But for a high-performance systems, it may be very relevant. To solve that issue, create +a new string generator module for your custom format, starting out from one of the +default generators provided. If you can not do this yourself, you may want to +contact <a href="mailto:info%40adiscon.com">Adiscon</a> as we offer custom development +of string generators at a very low price. +<p>Note that string generator modules can be dynamically loaded. However, the default +ones provided are so important that they are build right into the executable. But this +does not need to be done that way (and it is straightforward to do it dynamic). + + +<a name="lm"></a><h2>Library Modules</h2> +<p>Library modules provide dynamically loadable functionality for parts of rsyslog, +most often for other loadable modules. They can not be user-configured and are loaded +automatically by some components. They are just mentioned so that error messages that +point to library moduls can be understood. No module list is provided. + +<h2>Where are the modules integrated into the Message Flow?</h2> +<p>Depending on their module type, modules may access and/or modify messages at +various stages during rsyslog's processing. Note that only the "core type" (e.g. input, +output) but not any type derived from it (message modification module) specifies when +a module is called. +<p>The simplified workflow is as follows: +<p align="center"> +<img src="module_workflow.png" alt"rsyslog: loadable modules and message flow"> +<p>As can be seen, messages are received by input modules, then passed to one or many +parser modules, which generate the in-memory representation of the message and may +also modify the message itself. The, the internal representation is passed to +output modules, which may output a message and (with the interfaces newly introduced +in v5) may also modify messageo object content. +<p>String generator modules are not included inside this picture, because they are +not a required part of the workflow. If used, they operate "in front of" the +output modules, because they are called during template generation. +<p>Note that the actual flow is much more complex and depends a lot on queue and +filter settings. This graphic above is a high-level message flow diagram. + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_conf_nomatch.html b/doc/rsyslog_conf_nomatch.html new file mode 100644 index 00000000..5f25f3e4 --- /dev/null +++ b/doc/rsyslog_conf_nomatch.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>nomatch mode - property replacer - rsyslog.conf</title></head> +<body> +<h1>nomatch mode - property replacer - rsyslog.con</h1> +<p>This is a part of the <a href="rsyslog_conf.html">rsyslog.conf documentation</a> +of the <a href="property_replacer.html">property replacer</a>.</p> +<p><b>The "nomatch-Mode" specifies which string the property replacer +shall return if a regular expression did not find the search string.</b>. Traditionally, +the string "**NO MATCH**" was returned, but many people complained this was almost never useful. +Still, this mode is support as "<b>DFLT</b>" for legacy configurations. +<p>Three additional and potentially useful modes exist: in one (<b>BLANK</b>) a blank string +is returned. This is probably useful for inserting values into databases where no +value shall be inserted if the expression could not be found. +<p>A similar mode is "<b>ZERO</b>" where the string "0" is returned. This is suitable +for numerical values. A use case may be +that you record a traffic log based on firewall rules and the "bytes transmitted" counter +is extracted via a regular expression. If no "bytes transmitted" counter is available +in the current message, it is probably a good idea to return an empty string, which the +database layer can turn into a zero. +<p>The other mode is "<b>FIELD</b>", in which the complete field is returned. This may be useful +in cases where absense of a match is considered a failure and the message that triggered +it shall be logged. +<p>If in doubt, <b>it is highly suggested to use the +<a href="http://www.rsyslog.com/tool-regex">rsyslog online regular expression +checker and generator</a> to see these options in action</b>. With that online tool, +you can craft regular expressions based on samples and try out the different modes. + +<h2>Summary of nomatch Modes</h2> +<table border="1" cellspacing="0"> +<tr><td><b>Mode</b></td><td><b>Returned</b></td></tr> +<tr><td>DFLT</td><td>"**NO MATCH**"</td></tr> +<tr><td>BLANK</td><td>"" (empty string)</td></tr> +<tr><td>ZERO</td><td>"0"</td></tr> +<tr><td>FIELD</td><td>full content of original field</td></tr> +<tr><td> </td><td><a href="http://www.rsyslog.com/tool-regex">Interactive Tool</a></td></tr> +</table> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> + + diff --git a/doc/rsyslog_conf_output.html b/doc/rsyslog_conf_output.html new file mode 100644 index 00000000..426f2f27 --- /dev/null +++ b/doc/rsyslog_conf_output.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Output Channels - rsyslog.conf</title></head> +<body> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">back</a> +<h2>Output Channels</h2> +<p>Output Channels are a new concept first introduced in rsyslog +0.9.0. <b>As of this writing, it is most likely that they will +be replaced by something different in the future.</b> So if you +use them, be prepared to change you configuration file syntax when you +upgrade to a later release.<br> +<br> +The idea behind output channel definitions is that it shall provide an +umbrella for any type of output that the user might want. In essence,<br> +this is the "file" part of selector lines (and this is why we are not +sure output channel syntax will stay after the next review). There is a<br> +difference, though: selector channels both have filter conditions +(currently facility and severity) as well as the output destination. +they can only be used to write to files - not pipes, ttys or whatever +Output channels define the output definition, only. As of this build, +else. If we stick with output channels, this will change over time.</p> +<p>In concept, an output channel includes everything needed to +know about an output actions. In practice, the current implementation +only carries<br> +a filename, a maximum file size and a command to be issued when this +file size is reached. More things might be present in future version, +which might also change the syntax of the directive.</p> +<p>Output channels are defined via an $outchannel directive. It's +syntax is as follows:<br> +<br> +$outchannel name,file-name,max-size,action-on-max-size<br> +<br> +name is the name of the output channel (not the file), file-name is the +file name to be written to, max-size the maximum allowed size and +action-on-max-size a command to be issued when the max size is reached. +This command always has exactly one parameter. The binary is that part +of action-on-max-size before the first space, its parameter is +everything behind that space.<br> +<br> +Please note that max-size is queried BEFORE writing the log message to +the file. So be sure to set this limit reasonably low so that any +message might fit. For the current release, setting it 1k lower than +you expected is helpful. The max-size must always be specified in bytes +- there are no special symbols (like 1k, 1m,...) at this point of +development.<br> +<br> +Keep in mind that $outchannel just defines a channel with "name". It +does not activate it. To do so, you must use a selector line (see +below). That selector line includes the channel name plus an $ sign in +front of it. A sample might be:<br> +<br> +*.* :omfile:$mychannel<br> +<br> +In its current form, output channels primarily provide the ability to +size-limit an output file. To do so, specify a maximum size. When this +size is reached, rsyslogd will execute the action-on-max-size command +and then reopen the file and retry. The command should be something +like a <a href="log_rotation_fix_size.html">log rotation +script</a> or a similar thing.</p> +<p>If there is no action-on-max-size command or the command did +not resolve the situation, the file is closed and never reopened by +rsyslogd (except, of course, by huping it). This logic was integrated +when we first experienced severe issues with files larger 2gb, which +could lead to rsyslogd dumping core. In such cases, it is more +appropriate to stop writing to a single file. Meanwhile, rsyslogd has +been fixed to support files larger 2gb, but obviously only on file +systems and operating system versions that do so. So it can still make +sense to enforce a 2gb file size limit.</p> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> + + diff --git a/doc/rsyslog_conf_sysklogd_compatibility.html b/doc/rsyslog_conf_sysklogd_compatibility.html new file mode 100644 index 00000000..c95d6fda --- /dev/null +++ b/doc/rsyslog_conf_sysklogd_compatibility.html @@ -0,0 +1,31 @@ +<html><head><title>sysklogdcompatibility - rsyslog.conf</title></head> +<body> +<h1>sysklogd compatibility</h1> +<p>This is a part of the rsyslog.conf documentation.</p> +<a href="rsyslog_conf.html">Back to rsyslog.conf manual</a> +<p>Rsyslog supports standard sysklogd's configuration file format +and extends it. So in general, you can take a "normal" syslog.conf and +use it together with rsyslogd. It will understand everything. However, +to use most of rsyslogd's unique features, you need to add extended +configuration directives.</p> +<p>Rsyslogd supports the classical, selector-based rule lines. +They are still at the heart of it and all actions are initiated via +rule lines. +However, there are ample new directives, either in rsyslog traditional +format (starting with a dollar sign) or in RainerScript format. These +work together with sysklogd statements. A few select statements are +no longer supported and may generate error messages. They are mentioned +in the compatibility notes. +</p> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_conf_templates.html b/doc/rsyslog_conf_templates.html new file mode 100644 index 00000000..9a6e1619 --- /dev/null +++ b/doc/rsyslog_conf_templates.html @@ -0,0 +1,538 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Templates - rsyslog.conf</title></head> +<body> +<p>This is a part of the rsyslog.conf - documentation.</p> +<a href="rsyslog_conf.html">back</a> +<h1>Templates</h1> +<p>Templates are a key feature of rsyslog. They allow to specify +any +format a user might want. They are also used for dynamic file name +generation. Every output in rsyslog uses templates - this holds true +for files, user messages and so on. The database writer expects its +template to be a proper SQL statement - so this is highly customizable +too. You might ask how does all of this work when no templates at all +are specified. Good question ;) The answer is simple, though. Templates +compatible with the stock syslogd formats are hardcoded into rsyslogd. +So if no template is specified, we use one of these hardcoded +templates. Search for "template_" in syslogd.c and you will find the +hardcoded ones.</p> +<p>Templates are specified by template() statements. They can also be specified +via $Template legacy statements. Note that these are scheduled for removal in +later versions of rsyslog, so it is probably a good idea to avoid them +for new uses. +<h2>The template() statement</h2> +<p>The template() statement is used to define templates. Note that it is a +<b>static</b> statement, that means all templates are defined when rsyslog +reads the config file. As such, templates are not affected by if-statements +or config nesting. +<p>The basic structure of the template statement is as follows: +<br><br> +<code>template(parameters)</code> +<br><br> +In addition to this simpler syntax, list templates (to be described below) +support an extended syntax: +<br><br> +<code>template(parameters) { list-descriptions }</code> +<p>Each template has a parameter <b>name</b>, which specifies the templates +name, and a parameter <b>type</b>, which specifies the template type. The name +parameter must be unique, and behaviour is unpredictable if it is not. The <b>type</b> +parameter specifies different template types. Different types simply enable +different ways to specify the template content. The template type <b>does not</b> +affect what an (output) plugin can do with it. So use the type that best fits your +needs (from a config writing point of view!). The following types are available: +<ul> +<li>list +<li>subtree +<li>string +<li>plugin +</ul> +The various types are described below. + +<h3>list</h3> +<p>In this case, the template is generated by a list of constant and +variable statements. These follow the template spec in curly braces. This type is +also primarily meant for use with structure-aware outputs, like ommongodb. However, +it also works perfectly with text-based outputs. We recommend to use this mode +if more complex property substitutions needs to be done. In that case, the list-based +template syntax is much clearer than the simple string-based one. +<p>The list template contains the template header (with <b>type="list"</b>) and is followed +by <b>constant</b> and <b>property</b> statements, given in curly braces to signify +the template statement they belong to. As the name says, <b>constant</b> statements +describe constant text and <b>property</b> describes property access. There are many options +to <b>property</b>, described further below. Most of these options are used to extract +only partial property contents or to modify the text obtained (like to change its case +to upper or lower case, only). +<p>To grasp the idea, an actual sample is: +<br><pre><code>template(name="tpl1" type="list") { + constant(value="Syslog MSG is: '") + property(name="msg") + constant(value="', ") + property(name="timereported" dateFormat="rfc3339" caseConversion="lower") + constant(value="\n") + } +</code></pre> +<br>This sample is probably primarily targeted at the usual file-based output.</p> + + +<h4>constant statement</h4> +<p>This provides a way to specify constant text. The text is used literally. It is +primarily intended for text-based output, so that some constant text can be included. For +example, if a complex template is build for file output, one usually needs to finish it +by a newline, which can be introduced by a constant statement. Here is an actual sample +of that use case from the rsylsog testbench: +<br><pre><code>template(name="outfmt" type="list") { + property(name="$!usr!msgnum") + constant(value="\n") +}</code></pre> +The following escape sequences are recogniced inside the constant text: +<ul> +<li>\\ - single backslash +<li>\n - LF +<li>\ooo - (three octal digits) - represents character with this numerical value (e.g. \101 +equals "A"). Note that three +octal digits must be given (in contrast to some languagues, where between one and three are valid). +While we support octal notation, we recommend to use hex notation as this is better known. +<li>\xhh - (where h is a hex digit) - represents character with this numerical value (e.g. \x41 +equals "A"). Note that two hexadecimal digits must be given (in contrast to some languagues +where one or two are valid). +<li>... some others ... list needs to be extended +</ul> +<p>Note: if an unsupported character follows a backslash, this is treated as an error. Behaviour +is unpredictable in this case. +<p>To aid usage of the same template both for text-based outputs and structured ones, constant +text without an "outname" parameter will be ignored when creating the name/value tree +for structured outputs. So if you want to supply some constant text e.g. to mongodb, you must +include an outname, as can be seen here: +<br><pre><code>template(name="outfmt" type="list") { + property(name="$!usr!msgnum") + constant(value="\n" <b>outname="IWantThisInMyDB"</b>) +}</code></pre> + +The "constant" statement supports the following parameters: +<ul> +<li>value - the constant value to use +<li>outname - output field name (for structured outputs) +</ul> + + +<h4>property statement</h4> +<p>This statement is used to include property text. It can access all properties. Also, +options permit to specify picking only part of a property or modifying it. +It supports the following parameters: +<ul> +<li>name - the name of the property to access +<li>outname - output field name (for structured outputs) +<li>dateformat - date format to use (only for date-related properties) +<li>caseconversion - permits to convert case of the text. supported values are +"lower" and "upper" +<li>controlcharacters - specifies how to handle control characters. Supported values are +"escape", which escapes them, "space", which replaces them by a single space, and +"drop", which simply removes them from the string. +<li>securepath - used for creating pathnames suitable for use in dynafile templates +<li>format - specifiy format on a field basis. Supported values are "csv", for use when +csv-data is generated, "json", which formats proper json content (but without a field +header) and "jsonf", which formats as a complete json field. +<li>position.from - obtain substring starting from this position (1 is the first position) +<li>position.to - obtain substring up to this position +<li>position.relativeToEnd - the from and to position is relative to the end of the string + instead of the usual start of string. (available since rsyslog v7.3.10) +<li>field.number - obtain this field match +<li>field.delimiter - decimal value of delimiter character for field extraction +<li>regex.expression - expression to use +<li>regex.type - either ERE or BRE +<li>regex.nomatchmode - what to do if we have no match +<li>regex.match - match to use +<li>regex.submatch - submatch to use +<li>droplastlf - drop a trailing LF, if it is present +<li>mandatory - signifies a field as mandatory. If set to "on", this field will always +be present in data passed to structured outputs, even if it is empty. If "off" (the default) +empty fields will not be passed to structured outputs. This is especially useful for outputs +that support dynamic schemas (like ommongodb). +<li>spifno1stsp - expert options for RFC3164 template processing +</ul> + + +<h3>subtree</h3> +<p>Available since rsyslog 7.1.4 +<p> +In this case, the template is generated based on a complete +(CEE) subtree. This type of template is most useful for outputs that know how to +process hierarchical structure, like ommongodb. With that type, the parameter +<b>subtree</b> must be specified, which tells which subtree to use. For example +template(name="tpl1" type="subtree" subtree="$!") includes all CEE data, while +template(name="tpl2" type="subtree" subtree="$!usr!tpl2") includes only the +subtree starting at $!usr!tpl2. The core idea when using this type of template +is that the actual data is prefabricated via set and unset script statements, +and the resulting strucuture is then used inside the template. This method MUST +be used if a complete subtree needs to be placed <i>directly</i> into the +object's root. With all other template types, only subcontainers can be generated. +Note that subtree type can also be used with text-based outputs, like omfile. HOWEVER, +you do not have any capability to specify constant text, and as such cannot include +line breaks. As a consequence, using this template type for text outputs is usually +only useful for debugging or very special cases (e.g. where the text is interpreted +by a JSON parser later on). +<h4>Use case</h4> +<p>A typical use case is to first create a custom subtree and then include it into +the template, like in this small example: +<br><blockquote><code>set $!usr!tpl2!msg = $msg; +<br>set $!usr!tpl2!dataflow = field($msg, 58, 2); +<br>template(name="tpl2" type="subtree" subtree="$!usr!tpl2") +</code></blockquote> +<p>Here, we assume that $msg contains various fields, and the data from a field +is to be extracted and stored - together with the message - as field content. +<h3>string</h3> +<p>This closely resembles the legacy template statement. It +has a mandatory parameter <b>string</b>, which holds the template string to be +applied. A template string is a mix of constant text and replacement variables +(see property replacer). These variables are taken from message or other dynamic +content when the final string to be passed to a plugin is generated. String-based +templates are a great way to specify textual content, especially if no complex +manipulation to properties is necessary. Full details on how to specify template +text can be found below. +<br>Config example: +<br><blockquote><code>template(name="tpl3" type="string" string="%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n") +</code></blockquote> +<h3>plugin</h3> +In this case, the template is generated by a plugin (which +is then called +a "strgen" or "string generator"). The format is fix as it is coded. While this +is inflexible, it provides superior performance, and is often used for that +reason (not that "regular" templates are slow - but in very demanding environments +that "last bit" can make a difference). Refer to the plugin's documentation +for further details. For this type, the paramter <b>plugin</b> must be specified and +must contain the name of the plugin as it identifies itself. Note that the +plugin must be loaded prior to being used inside a template. +<br>Config example: +<br><blockquote><code>template(name="tpl4" type="plugin" plugin="mystrgen") +</code></blockquote> + +<h3>options</h3> +The <options> part is optional. It carries options +influencing the template as whole and is part of the template parameters. +See details below. Be sure NOT to mistake template options with property +options - the latter ones are processed by the property replacer and +apply to a SINGLE property, only (and not the whole template).<br> +<br> +Template options are case-insensitive. Currently defined are: </p> +<p><b>option.sql</b> - format the string suitable for a SQL +statement in MySQL format. This will replace single quotes ("'") and +the backslash character by their backslash-escaped counterpart ("\'" +and "\\") inside each field. Please note that in MySQL configuration, +the <code class="literal">NO_BACKSLASH_ESCAPES</code> +mode must be turned off for this format to work (this is the default).</p> +<p><b>option.stdsql</b> - format the string suitable for a +SQL statement that is to be sent to a standards-compliant sql server. +This will replace single quotes ("'") by two single quotes ("''") +inside each field. You must use stdsql together with MySQL if in MySQL +configuration the +<code class="literal">NO_BACKSLASH_ESCAPES</code> is +turned on.</p> +<p><b>option.json</b> - format the string suitable for a +json statement. +This will replace single quotes ("'") by two single quotes ("''") +inside each field.</p> +<p>At no time, multiple template option should be used. This can cause +unpredictable behaviour and is against all logic.</p> +<p>Either the <b>sql</b> or <b>stdsql</b> +option <b>must</b> be specified when a template is used +for writing to a database, otherwise injection might occur. Please note +that due to the unfortunate fact that several vendors have violated the +sql standard and introduced their own escape methods, it is impossible +to have a single option doing all the work. So you yourself +must make sure you are using the right format. <b>If you choose +the wrong one, you are still vulnerable to sql injection.</b><br> +<br> +Please note that the database writer *checks* that the sql option is +present in the template. If it is not present, the write database +action is disabled. This is to guard you against accidental forgetting +it and then becoming vulnerable to SQL injection. The sql option can +also be useful with files - especially if you want to import them into +a database on another machine for performance reasons. However, do NOT +use it if you do not have a real need for it - among others, it takes +some toll on the processing time. Not much, but on a really busy system +you might notice it ;)</p> +<p>The default template for the write to database action has the +sql option set. As we currently support only MySQL and the sql option +matches the default MySQL configuration, this is a good choice. +However, if you have turned on +<code class="literal">NO_BACKSLASH_ESCAPES</code> in +your MySQL config, you need to supply a template with the stdsql +option. Otherwise you will become vulnerable to SQL injection. <br> +<br> +To escape:<br> +% = \%<br> +\ = \\ --> '\' is used to escape (as in C)<br> +template (name="TraditionalFormat" type="string" string="%timegenerated% %HOSTNAME% %syslogtag%%msg%\n"<br> +<br> + +<h3>Examples</h3> +<h4>Standard Template for Writing to Files</h4> +<p><pre><code>template(name="FileFormat" type="list") { + property(name="timestamp" dateFormat="rfc3339") + constant(value=" ") + property(name="hostname") + constant(value=" ") + property(name="syslogtag") + constant(value=" ") + property(name="msg" spifno1stsp="on" ) + property(name="msg" droplastlf="on" ) + constant(value="\n") + } +</code></pre> +<p>The equivalent string template looks like this: +<br><pre><code>template(name="FileFormat" type="string" + string= "%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" +)</code></pre> +Note that the template string itself must be on a single line. + +<h4>Standard Template for Forwarding to a Remote Host (RFC3164 mode)</h4> +<p><pre><code>template(name="ForwardFormat" type="list") { + constant(value="<") + property(name="PRI") + constant(value="<") + property(name="timestamp" dateFormat="rfc3339") + constant(value=" ") + property(name="hostname") + constant(value=" ") + property(name="syslogtag" position.from="1" position.to="32") + constant(value=" ") + property(name="msg" spifno1stsp="on" ) + } +</code></pre> +<p>The equivalent string template looks like this: +<br><pre><code>template(name="forwardFormat" type="string" + string="<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%" +)</code></pre> +Note that the template string itself must be on a single line. + +<h4>Standard Template for write to the MySQL database</h4> +<p><pre><code>template(name="StdSQLformat" type="list" option.sql="on") { + constant(value="insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag)") + constant(value=" values ('") + property(name="msg") + constant(value="', ") + property(name="syslogfacility") + constant(value=", '") + property(name="hostname") + constant(value="', ") + property(name="syslogpriority") + constant(value=", '") + property(name="timereported" dateFormat="mysql") + constant(value="', '") + property(name="timegenerated" dateFormat="mysql") + constant(value="', ") + property(name="iut") + constant(value=", '") + property(name="syslogtag") + constant(value="')") + } +</code></pre> +<p>The equivalent string template looks like this: +<br><pre><code>template(name="stdSQLformat" type="string" option.sql="on" + string="insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')" +)</code></pre> +Note that the template string itself must be on a single line. + +<h2>legacy format</h2> +<p>In pre v6-versions of rsyslog, you need to use the <code>$template</code> +statement to configure templates. They provide the equivalent to string- and +plugin-based templates. The legacy syntax continous to work in v7, however +we recommend to avoid legacy format for newly written config files. Legacy and +current config statements can coexist within the same config file. +<p>The general format is +<br><br><code>$template name,param[,options]</code></br></br> +where "name" is the template name and "param" is a single parameter +that specifies template content. The optional "options" part is used to +set template options. +<h3>string</h3> +The parameter is the same string that with the current-style format you +specify in the <b>string</b> parameter, for example: +<br><br><code>$template strtpl,"PRI: %pri%, MSG: %msg%\n"</code> +<p>Note that list templates are not available in legacy format, so you need +to use complex property replacer constructs to do complex things. + +<h3>plugin</h3> +This is equivalent to the "plugin"-type template directive. Here, the +parameter is the plugin name, with an equal sign prepended. An example +is: +<br><br><code>$template plugintpl,=myplugin</code> + +<h2>Reserved Template Names</h2> +<p>Template +names beginning with "RSYSLOG_" are reserved for rsyslog use. Do NOT +use them if, otherwise you may receive a conflict in the future (and +quite unpredictable behaviour). There is a small set of pre-defined +templates that you can use without the need to define it:</p> +<ul> +<li><span style="font-weight: bold;">RSYSLOG_TraditionalFileFormat</span> +- the "old style" default log file format with low-precision timestamps</li> +<li><span style="font-weight: bold;">RSYSLOG_FileFormat</span> +- a modern-style logfile format similar to TraditionalFileFormat, buth +with high-precision timestamps and timezone information</li> +<li><span style="font-weight: bold;">RSYSLOG_TraditionalForwardFormat</span> +- the traditional forwarding format with low-precision timestamps. Most +useful if you send messages to other syslogd's or rsyslogd +below +version 3.12.5.</li> +<li><span style="font-weight: bold;">RSYSLOG_SysklogdFileFormat</span> +- sysklogd compatible log file format. If used with options: $SpaceLFOnReceive on; +$EscapeControlCharactersOnReceive off; $DropTrailingLFOnReception off, +the log format will conform to sysklogd log format.</li> +<li><span style="font-weight: bold;">RSYSLOG_ForwardFormat</span> +- a new high-precision forwarding format very similar to the +traditional one, but with high-precision timestamps and timezone +information. Recommended to be used when sending messages to rsyslog +3.12.5 or above.</li> +<li><span style="font-weight: bold;">RSYSLOG_SyslogProtocol23Format</span> +- the format specified in IETF's internet-draft +ietf-syslog-protocol-23, which is assumed to be come the new syslog +standard RFC. This format includes several improvements. The rsyslog +message parser understands this format, so you can use it together with +all relatively recent versions of rsyslog. Other syslogd's may get +hopelessly confused if receiving that format, so check before you use +it. Note that the format is unlikely to change when the final RFC comes +out, but this may happen.</li> +<li><span style="font-weight: bold;">RSYSLOG_DebugFormat</span> +- a special format used for troubleshooting property problems. This format +is meant to be written to a log file. Do <b>not</b> use for production or remote +forwarding.</li> +</ul> + +<h2>The following is legacy documentation soon to be integrated.</h2> + +<!--<table> +<tr><td>param name</td><td>meaning</td></tr> +<tr><td>name</td><td>name of the template</td></tr> +</table> +--> + +<p>Starting with 5.5.6, there are actually two different types of template: +<ul> +<li>string based +<li>string-generator module based +</ul> +<p><a href="rsyslog_conf_modules.html#sm">String-generator module</a> based templates +have been introduced in 5.5.6. They permit a string generator, actually a C "program", +the generate a format. Obviously, it is more work required to code such a generator, +but the reward is speed improvement. If you do not need the ultimate throughput, you +can forget about string generators (so most people never need to know what they are). +You may just be interested in learning that for the most important default formats, +rsyslog already contains highly optimized string generators and these are called +without any need to configure anything. But if you have written (or purchased) a +string generator module, you need to know how to call it. Each such module has a name, +which you need to know (look it up in the module doc or ask the developer). Let's assume +that "mystrgen" is the module name. Then you can define a template for that strgen +in the following way: + +<blockquote><code>template(name="MyTemplateName" type="plugin" string="mystrgen")</code></blockquote> +<p>Legacy example:</p> +<blockquote><code>$template MyTemplateName,=mystrgen</code></blockquote> +(Of course, you must have first loaded the module via $ModLoad). +<p>The important part is the equal sign in the legacy format: it tells the rsyslog config parser that +no string follows but a strgen module name. +<p>There are no additional parameters but the module name supported. This is because +there is no way to customize anything inside such a "template" other than by +modifying the code of the string generator. + +<p>So for most use cases, string-generator module based templates are <b>not</b> +the route to take. Usually, we use <b>string based templates</b> instead. +This is what the rest of the documentation now talks about. + +<p>A template consists of a template directive, a name, the +actual template text and optional options. A sample is:</p> +<blockquote><code>template(name="MyTemplateName" type="string" string="Example: Text %property% some more text\n" options)</code></blockquote> +<p>Legacy example:</p> +<blockquote><code>$template MyTemplateName,"\7Text +%property% some more text\n",<options></code></blockquote> +<p>The "template" (legacy: $template) is the template directive. It tells rsyslog +that this line contains a template. "MyTemplateName" is the template +name. All +other config lines refer to this name. The text within "string" is the +actual template text. The backslash is an escape character, much as it +is in C. It does all these "cool" things. For example, \7 rings the +bell (this is an ASCII value), \n is a new line. C programmers and perl +coders have the advantage of knowing this, but the set in rsyslog is a +bit restricted currently. +</p> +<p>All text in the template is used literally, except for things +within percent signs. These are properties and allow you access to the +contents of the syslog message. Properties are accessed via the +<a href="property_replacer.html">property replacer</a> +(nice name, huh) and it can do cool things, too. For +example, it can pick a substring or do date-specific formatting. More +on this is below, on some lines of the property replacer.<br> +<br> + +<br> +Properties can be accessed by the <a href="property_replacer.html">property +replacer</a> (see there for details).</p> +<p>Templates can be used in the form of a <b>list</b> as well. This has been +introduced with <b>6.5.0</b> The list consists of two parts which are either +a <b>constant</b> or a <b>property</b>. The constants +are taking the part of "text" that you usually enter in string-based templates. +The properties stay variable, as they are a substitute for different values of a +certain type. This type of template is extremely useful for complicated cases, +as it helps you to easily keep an overview over the template. Though, it has +the disadvantage of needing more effort to create it.</p> +<br>Config example: +<br><blockquote><code>template(name="MyTemplate" type="list" option.json="off") { + <br>constant(value="Test: ") + <br>property(name="msg" outname="mymessage") + <br>constant(value=" --!!!-- ") + <br>property(name="timereported" dateFormat="rfc3339" caseConversion="lower") + <br>constant(value="\n") + <br>} +</code></blockquote> +<p>First, the general template option will be defined. The values of the template +itself get defined in the curly brackets. As it can be seen, we have constants +and properties in exchange. Whereas constants will be filled with a value and probably +some options, properties do direct to a property and the options that could be needed +additional format definitions.</p> +<p>We suggest to use separate lines for all constants and properties. This +helps to keep a good overview over the different parts of the template. +Though, writing it in a single line will work, it is much harder to debug +if anything goes wrong with the template. </p> + +<p><b>Please note that templates can also be +used to generate selector lines with dynamic file names.</b> For +example, if you would like to split syslog messages from different +hosts to different files (one per host), you can define the following +template:</p> +<blockquote><code>template (name="DynFile" type="string" string="/var/log/system-%HOSTNAME%.log")</code></blockquote> +<p>Legacy example:</p> +<blockquote><code>$template +DynFile,"/var/log/system-%HOSTNAME%.log"</code></blockquote> +<p>This template can then be used when defining an output +selector line. It will result in something like +"/var/log/system-localhost.log"</p> +<h3>Legacy String-based Template Samples</h3> +<p>This section provides some default templates in legacy format, as used in rsyslog +previous to version 6. Note that this format is still supported, so there is no hard need +to upgrade existing configurations. However, it is strongly recommended that the legacy +constructs are not used when crafting new templates. +Note that each $Template statement is on a <b>single</b> line, but probably broken +accross several lines for display purposes by your browsers. Lines are separated by +empty lines. Keep in mind, that line breaks are important in legacy format. +<p><code> +$template FileFormat,"%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" +<br><br> +$template TraditionalFileFormat,"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" +<br><br> +$template ForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%" +<br><br> +$template TraditionalForwardFormat,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%" +<br><br> +$template StdSQLFormat,"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')",SQL +</code></p> + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2012 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> + diff --git a/doc/rsyslog_confgraph_complex.conf b/doc/rsyslog_confgraph_complex.conf new file mode 100644 index 00000000..3d7ec0a3 --- /dev/null +++ b/doc/rsyslog_confgraph_complex.conf @@ -0,0 +1,108 @@ +$DebugPrintTemplateList off +$DebugPrintCfSysLineHandlerList off +$DebugPrintModuleList off +#$ResetConfigVariables +$ErrorMessagesToStderr off +$ModLoad /home/rger/proj/rsyslog/plugins/imuxsock/.libs/imuxsock.so +#$ModLoad /home/rger/proj/rsyslog/plugins/imklog/.libs/imklog +#$ModLoad /home/rger/proj/rsyslog/plugins/imtcp/.libs/imtcp +$ModLoad /home/rger/proj/rsyslog/plugins/imtcp/.libs/imtcp +$ModLoad /home/rger/proj/rsyslog/plugins/imudp/.libs/imudp +$ModLoad /home/rger/proj/rsyslog/plugins/omstdout/.libs/omstdout +$ModLoad /home/rger/proj/rsyslog/plugins/omprog/.libs/omprog +$ModLoad /home/rger/proj/rsyslog/plugins/omtesting/.libs/omtesting +#$ModLoad /home/rger/proj/rsyslog/plugins/ommail/.libs/ommail +# +# +# PGSQL testing +$ModLoad /home/rger/proj/rsyslog/plugins/ompgsql/.libs/ompgsql.so +$template pgfmt,"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-pgsql%', '%timegenerated:::date-pgsql%', %iut%, '%syslogtag%');",STDSQL +#$ActionQueueType linkedlist +#*.* :ompgsql:127.0.0.1,rsyslog,postgres,;pgfmt + +#$ActionOMStdoutArrayInterface on +#*.* :omstdout: + +$ActionResumeInterval 4 +$ActionResumeRetryCount 3 +$ActionQueueType LinkedList # run asynchronously +$ActionName Forward to 172.19.3.9 +*.* @@172.19.3.9:10514 +#*.* :omtesting:randfail +#*.* :omtesting:always_suspend +#*.* :omtesting:fail 2 2 + +#$UDPServerTimeRequery 10 +$UDPServerRun 514 +$inputtcpmaxsessions 2000 +$InputTCPServerRun 12514 + +#$PrivDropToUser rger +#$InputTCPServerInputName tcp/514 +#$InputTCPServerAddtlFrameDelimiter 10 +#$InputTCPServerRun 514 +#$AllowedSender UDP,127.0.0.1/32 +#$AllowedSender TCP,127.0.0.1/32 + +$PreserveFQDN off + +#$HUPisRestart on + +#$MainMsgQueueType direct +$MainMsgQueueType linkedlist +$MainMsgQueueDequeueBatchSize 200 +#$MainMsgQueueWorkerTimeoutThreadShutdown -1 + +#---- test DA mode +# set spool locations and switch queue to disk assisted mode +$WorkDirectory spool +$MainMsgQueueSize 200 # this *should* trigger moving on to DA mode... +# note: we must set QueueSize sufficiently high, so that 70% (light delay mark) +# is high enough above HighWatermark! +$MainMsgQueueHighWatermark 80 +$MainMsgQueueLowWatermark 40 +$MainMsgQueueFilename mainq +$MainMsgQueueType linkedlist +# ucomment, as we now have an issue (finally the test case works ;)) +#$MainMsgQueueDequeueBatchSize 80 +#---- end test DA mode + +#$template test,"%timereported:::date-rfc3339%,%timereported:::date-mysql%,%timereported:::date-subseconds%, %timegenerated:::date-mysql%, %timegenerated:::date-subseconds%, msg: %msg%\n" +#$template db,"re: '%msg:R,ERE,1,FIELD:dsn=([0-9]+\.[0-9]+\.[0-9])--end%', msg: '%msg%'\n" +#$template db,"re: '%msg:R,ERE,1,ZERO:dsn=([0-9]+\.[0-9]+\.[0-9])--end%', msg: '%msg%'\n" +#$template DEBUG,"Debug line with all properties:\nFROMHOST: '%FROMHOST%', fromhost-ip: '%fromhost-ip%, HOSTNAME: '%HOSTNAME%', PRI: %PRI%,\nsyslogtag '%syslogtag%', programname: '%programname%', APP-NAME: '%APP-NAME%', PROCID: '%PROCID%', MSGID: '%MSGID%',\nTIMESTAMP: '%TIMESTAMP%', STRUCTURED-DATA: '%STRUCTURED-DATA%',\nmsg: '%msg%'\nescaped msg: '%msg:::drop-cc%'\nrawmsg: '%rawmsg%'\n\n" +$template csv,"%syslogtag:::csv%,%msg:::upppercase,csv%,%msg%\n" +*.* -/home/rger/proj/rsyslog/logfile +kern.* -/home/rger/proj/rsyslog/logfile +$ActionExecOnlyWhenPreviousIsSuspended on +& -/tmp/xyz/uuu +$ActionExecOnlyWhenPreviousIsSuspended off +& ~ +& -/tmp/xyz/uuu2 +& -/tmp/xyz/uuu3 + + +#$template dynfile,"/home/rger/proj/rsyslog/test-%syslogtag%" +#*.* -?dynfile +#:msg, ereregex, "test|tast" /home/rger/proj/rsyslog/ere +#if strlen($syslogtag & strlen($msg)) > 10 then /home/rger/proj/rsyslog/longlog +#if strlen($msg) > 10 then /home/rger/proj/rsyslog/longlog +#if tolower($msg) contains 'test' then /home/rger/proj/rsyslog/longlog +#if $msg contains 'test' then /home/rger/proj/rsyslog/longlog + +#$ActionOMProgBinary /home/rger/proj/rsyslog/consumer +#*.* :omprog: + +#$actionresumeretryCount -1 +#$actionResumeInterval 4 +#$template dynfile,"/mnt2/logs/logfile.log" +#*.* /mnt2/logs/logfile.log +#if $msg contains 'test' then ?dynfile +#*.* ?dynfile +:msg, contains, "test " /tmpo/sdafsdf + +$ActionName write_system_log_2 +if $msg == 'test' then /tmpo/sdafsdf2 +& /tmpo/234234 +*.* @@(o,z9)172.19.3.21:10514 +$GenerateConfigGraph /home/rger/proj/rsyslog/rsyslog.dot diff --git a/doc/rsyslog_confgraph_complex.png b/doc/rsyslog_confgraph_complex.png Binary files differnew file mode 100644 index 00000000..21c04c57 --- /dev/null +++ b/doc/rsyslog_confgraph_complex.png diff --git a/doc/rsyslog_confgraph_std.conf b/doc/rsyslog_confgraph_std.conf new file mode 100644 index 00000000..64c9a18a --- /dev/null +++ b/doc/rsyslog_confgraph_std.conf @@ -0,0 +1,79 @@ +#rsyslog v3 config file + +# if you experience problems, check +# http://www.rsyslog.com/troubleshoot for assistance + +#### MODULES #### + +$ModLoad imuxsock.so # provides support for local system logging (e.g. via logger command) +$ModLoad imklog.so # provides kernel logging support (previously done by rklogd) +#$ModLoad immark.so # provides --MARK-- message capability + +# Provides UDP syslog reception +#$ModLoad imudp.so +#$UDPServerRun 514 + +# Provides TCP syslog reception +#$ModLoad imtcp.so +#$InputTCPServerRun 514 + + +#### GLOBAL DIRECTIVES #### + +# Use default timestamp format +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# File syncing capability is disabled by default. This feature is usually not required, +# not useful and an extreme performance hit +#$ActionFileEnableSync on + + +#### RULES #### + +# Log all kernel messages to the console. +# Logging much else clutters up the screen. +#kern.* /dev/console + +# Log anything (except mail) of level info or higher. +# Don't log private authentication messages! +*.info;mail.none;authpriv.none;cron.none /var/log/messages + +# The authpriv file has restricted access. +authpriv.* /var/log/secure + +# Log all the mail messages in one place. +mail.* -/var/log/maillog + + +# Log cron stuff +cron.* /var/log/cron + +# Everybody gets emergency messages +*.emerg * + +# Save news errors of level crit and higher in a special file. +uucp,news.crit /var/log/spooler + +# Save boot messages also to boot.log +local7.* /var/log/boot.log + + + +# ### begin forwarding rule ### +# The statement between the begin ... end define a SINGLE forwarding +# rule. They belong together, do NOT split them. If you create multiple +# forwarding rules, duplicate the whole block! +# Remote Logging (we use TCP for reliable delivery) +# +# An on-disk queue is created for this action. If the remote host is +# down, messages are spooled to disk and sent when it is up again. +#$WorkDirectory /var/spppl/rsyslog # where to place spool files +#$ActionQueueFileName fwdRule1 # unique name prefix for spool files +#$ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible) +#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown +$ActionQueueType LinkedList # run asynchronously +#$ActionResumeRetryCount -1 # infinite retries if host is down +# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional +*.* @@remote-host:514 +# ### end of the forwarding rule ### +$GenerateConfigGraph /home/rger/proj/rsyslog/rsyslog.dot diff --git a/doc/rsyslog_confgraph_std.png b/doc/rsyslog_confgraph_std.png Binary files differnew file mode 100644 index 00000000..655a7f82 --- /dev/null +++ b/doc/rsyslog_confgraph_std.png diff --git a/doc/rsyslog_high_database_rate.html b/doc/rsyslog_high_database_rate.html new file mode 100644 index 00000000..2bae58c6 --- /dev/null +++ b/doc/rsyslog_high_database_rate.html @@ -0,0 +1,186 @@ +<html><head> + +<title>Handling a massive syslog database insert rate with Rsyslog</title> + +<meta name="KEYWORDS" content="syslog, rsyslog, reliable, howto, database, postgresql, mysql, buffering, disk, queue"> + +</head> + +<body> +<a href="features.html">back</a> + +<h1>Handling a massive syslog database insert rate with Rsyslog</h1> + + <P><small><i>Written by + + <a href="http://www.gerhards.net/rainer">Rainer + + Gerhards</a> (2008-01-31)</i></small></P> + +<h2>Abstract</h2> + +<p><i><b>In this paper, I describe how log massive amounts of +<a href="http://www.monitorware.com/en/topics/syslog/">syslog</a> + +messages to a database. </b>This HOWTO is currently under development and thus a +bit brief. Updates are promised ;).</i></p> + +<h2>The Intention</h2> + +<p>Database updates are inherently slow when it comes to storing syslog +messages. However, there are a number of applications where it is handy to have +the message inside a database. Rsyslog supports native database writing via +output plugins. As of this writing, there are plugins available for MySQL an +PostgreSQL. Maybe additional plugins have become available by the time you read +this. Be sure to check.</p> +<p>In order to successfully write messages to a database backend, the backend +must be capable to record messages at the expected average arrival rate. This is +the rate if you take all messages that can arrive within a day and divide it by +86400 (the number of seconds per day). Let's say you expect 43,200,000 messages +per day. That's an average rate of 500 messages per second (mps). Your database +server MUST be able to handle that amount of message per second on a sustained +rate. If it doesn't, you either need to add an additional server, lower the +number of message - or forget about it.</p> +<p>However, this is probably not your peak rate. Let's simply assume your +systems work only half a day, that's 12 hours (and, yes, I know this is +unrealistic, but you'll get the point soon). So your average rate is actually +1,000 mps during work hours and 0 mps during non-work hours. To make matters +worse, workload is not divided evenly during the day. So you may have peaks of +up to 10,000mps while at other times the load may go down to maybe just 100mps. +Peaks may stay well above 2,000mps for a few minutes.</p> +<p>So how the hack you will be able to handle all of this traffic (including the +peaks) with a database server that is just capable of inserting a maximum of +500mps?</p> +<p>The key here is buffering. Messages that the database server is not capable +to handle will be buffered until it is. Of course, that means database insert +are NOT real-time. If you need real-time inserts, you need to make sure your +database server can handle traffic at the actual peak rate. But lets assume you +are OK with some delay.</p> +<p>Buffering is fine. But how about these massive amounts of data? That can't be +hold in memory, so don't we run out of luck with buffering? The key here is that +rsyslog can not only buffer in memory but also buffer to disk (this may remind +you of "spooling" which gets you the right idea). There are several queuing +modes available, offering differnent throughput. In general, the idea is to +buffer in memory until the memory buffer is exhausted and switch to +disk-buffering when needed (and only as long as needed). All of this is handled +automatically and transparently by rsyslog.</p> +<p>With our above scenario, the disk buffer would build up during the day and +rsyslog would use the night to drain it. Obviously, this is an extreme example, +but it shows what can be done. Please note that queue content survies rsyslogd +restarts, so even a reboot of the system will not cause any message loss.</p> +<h2>How To Setup</h2> +<p>Frankly, it's quite easy. You just need to do is instruct rsyslog to use a +disk queue and then configure your action. There is nothing else to do. With the +following simple config file, you log anything you receive to a MySQL database +and have buffering applied automatically.</p> +<textarea rows="11" cols="80"> +$ModLoad ommysql # load the output driver (use ompgsql for PostgreSQL) +$ModLoad imudp # network reception +$UDPServerRun 514 # start a udp server at port 514 +$ModLoad imuxsock # local message reception + +$WorkDirectory /rsyslog/work # default location for work (spool) files +$MainMsgQueueFileName mainq # set file name, also enables disk mode + +$ActionResumeRetryCount -1 # infinite retries on insert failure +# for PostgreSQL replace :ommysql: by :ompgsql: below: +*.* :ommysql:hostname,dbname,userid,password; +</textarea> +<p>The simple setup above has one drawback: the write database action is +executed together with all other actions. Typically, local files are also +written. These local file writes are now bound to the speed of the database +action. So if the database is down, or threre is a large backlog, local files +are also not (or late) written.</p> +<p><b>There is an easy way to avoid this with rsyslog.</b> It involves a +slightly more complicated setup. In rsyslog, each action can utilize its own +queue. If so, messages are simply pulled over from the main queue and then the +action queue handles action processing on its own. This way, main processing and +the action are de-coupled. In the above example, this means that local file +writes will happen immediately while the database writes are queued. As a +side-note, each action can have its own queue, so if you would like to more than +a single database or send messages reliably to another host, you can do all of +this on their own queues, de-coupling their processing speeds.</p> +<p>The configuration for the de-coupled database write involves just a few more +commands:</p> +<textarea rows="11" cols="80"> +$ModLoad ommysql # load the output driver (use ompgsql for PostgreSQL) +$ModLoad imudp # network reception +$UDPServerRun 514 # start a udp server at port 514 +$ModLoad imuxsock # local message reception + +$WorkDirectory /rsyslog/work # default location for work (spool) files + +$ActionQueueType LinkedList # use asynchronous processing +$ActionQueueFileName dbq # set file name, also enables disk mode +$ActionResumeRetryCount -1 # infinite retries on insert failure +# for PostgreSQL replace :ommysql: by :ompgsql: below: +*.* :ommysql:hostname,dbname,userid,password; +</textarea> +<p><b>This is the recommended configuration for this use case.</b> It requires +rsyslog 3.11.0 or above.</p> +<p>In this example, the main message queue is NOT disk-assisted (there is no +$MainMsgQueueFileName directive). We still could do that, but have not done it +because there seems to be no need. The only slow running action is the database +writer and it has its own queue. So there is no real reason to use a large main +message queue (except, of course, if you expect *really* heavy traffic bursts).</p> +<p>Note that you can modify a lot of queue performance parameters, but the above +config will get you going with default values. If you consider using this on a real +busy server, it is strongly recommended to invest some time in setting the tuning +parameters to appropriate values.</p> + +<h3>Feedback requested</h3> + +<P>I would appreciate feedback on this tutorial. If you have additional ideas, + +comments or find bugs (I *do* bugs - no way... ;)), please + +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</P> + +<h2>Revision History</h2> + +<ul> + + <li>2008-01-28 * + + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> * Initial Version created</li> + <li>2008-01-28 * + + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * Updated to new v3.11.0 capabilities</li> + +</ul> +<h2>Copyright</h2> + +<p>Copyright (c) 2008 + +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> and + +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> + +<p> Permission is granted to copy, distribute and/or modify this document + + under the terms of the GNU Free Documentation License, Version 1.2 + + or any later version published by the Free Software Foundation; + + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + + Texts. A copy of the license can be viewed at + +<a href="http://www.gnu.org/copyleft/fdl.html"> + +http://www.gnu.org/copyleft/fdl.html</a>.</p> + + +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body> + +</html> diff --git a/doc/rsyslog_mysql.html b/doc/rsyslog_mysql.html new file mode 100644 index 00000000..a27bd59e --- /dev/null +++ b/doc/rsyslog_mysql.html @@ -0,0 +1,271 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Writing syslog Data to MySQL</title> +<a href="features.html">back</a> +<meta name="KEYWORDS" content="syslog, mysql, syslog to mysql, howto"></head> +<body> +<h1>Writing syslog messages to MySQL</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-02-28)</i></small></p> +<h2>Abstract</h2> +<p><i><b>In this paper, I describe how to write +<a href="http://www.monitorware.com/en/topics/syslog/">syslog</a> +messages to a <a href="http://www.mysql.com">MySQL</a> +database.</b> Having syslog messages in a database is often +handy, especially when you intend to set up a front-end for viewing +them. This paper describes an approach with <a href="http://www.rsyslog.com/">rsyslogd</a>, +an +alternative enhanced syslog daemon natively supporting MySQL. I +describe the components needed to be installed and how to configure +them. Please note that as of this writing, rsyslog supports a variety +of databases. While this guide is still MySQL-focussed, you +can probably use it together with other ones too. You just need to +modify a few settings.</i></p> +<h2>Background</h2> +<p>In many cases, syslog data is simply written to text files. +This approach has some advantages, most notably it is very fast and +efficient. However, data stored in text files is not readily accessible +for real-time viewing and analysis. To do that, the messages need to be +in a database. There are various ways to store syslog messages in a +database. For example, some have the syslogd write text files which are +later feed via a separate script into the database. Others have written +scripts taking the data (via a pipe) from a non-database-aware syslogd +and store them as they appear. Some others use database-aware syslogds +and make them write the data directly to the database. In this paper, I +use that "direct write" approach. I think it is superior, because the +syslogd itself knows the status of the database connection and thus can +handle it intelligently (well ... hopefully ;)). I use rsyslogd to +acomplish this, simply because I have initiated the rsyslog project +with database-awareness as one goal.</p> +<p><b>One word of caution:</b> while message storage +in the database provides an excellent foundation for interactive +analysis, it comes at a cost. Database i/o is considerably slower than +text file i/o. As such, directly writing to the database makes sense +only if your message volume is low enough to allow a) the syslogd, b) +the network, and c) the database server to catch up with it. Some time +ago, I have written a paper on +<a href="http://www.monitorware.com/Common/en/Articles/performance-optimizing-syslog-server.php">optimizing +syslog server performance</a>. While this paper talks about +Window-based solutions, the ideas in it are generic enough to apply +here, too. So it might be worth reading if you anticipate medium high +to high traffic. If you anticipate really high traffic (or very large +traffic spikes), you should seriously consider forgetting about direct +database writes - in my opinion, such a situation needs either a very +specialised system or a different approach (the text-file-to-database +approach might work better for you in this case). +</p> +<h2>Overall System Setup</h2> +<p>In this paper, I concentrate on the server side. If you are +thinking about interactive syslog message review, you probably want to +centralize syslog. In such a scenario, you have multiple machines (the +so-called clients) send their data to a central machine (called server +in this context). While I expect such a setup to be typical when you +are interested in storing messages in the database, I do not describe +how to set it up. This is beyond the scope of this paper. If you search +a little, you will probably find many good descriptions on how to +centralize syslog. If you do that, it might be a good idea to do it +securely, so you might also be interested in my paper on <a href="rsyslog_stunnel.html"> +ssl-encrypting syslog message transfer</a>.</p> +<p>No matter how the messages arrive at the server, their +processing is always the same. So you can use this paper in combination +with any description for centralized syslog reporting.</p> +<p>As I already said, I use rsyslogd on the server. It has +intrinsic support for talking to MySQL databases. For obvious reasons, +we also need an instance of MySQL running. To keep us focussed, the +setup of MySQL itself is also beyond the scope of this paper. I assume +that you have successfully installed MySQL and also have a front-end at +hand to work with it (for example, +<a href="http://www.phpmyadmin.net/">phpMyAdmin</a>). +Please make sure that this is installed, actually working and you have +a basic understanding of how to handle it.</p> +<h2>Setting up the system</h2> +<p>You need to download and install rsyslogd first. Obtain it +from the +<a href="http://www.rsyslog.com/">rsyslog site</a>. +Make sure that you disable stock syslogd, otherwise you will experience +some difficulties. On some distributions (Fedora 8 and above, for +example), rsyslog may already by the default syslogd, in which case you +obviously do not need to do anything specific. For many others, there +are prebuild packages available. If you use either, please make sure +that you have the required database plugins for your database +available. It usually is a separate package and typically <span style="font-weight: bold;">not</span> installed by default.</p> +<p>It is important to understand how rsyslogd talks to the +database. In rsyslogd, there is the concept of "templates". Basically, +a template is a string that includes some replacement characters, which +are called "properties" in rsyslog. Properties are accessed via the "<a href="property_replacer.html">Property Replacer</a>". +Simply said, you access properties by including their name between +percent signs inside the template. For example, if the syslog message +is "Test", the template "%msg%" would be expanded to "Test". Rsyslogd +supports sending template text as a SQL statement to MySQL. As such, +the template must be a valid SQL statement. There is no limit in what +the statement might be, but there are some obvious and not so obvious +choices. For example, a template "drop table xxx" is possible, but does +not make an awful lot of sense. In practice, you will always use an +"insert" statment inside the template.</p> +<p>An example: if you would just like to store the msg part of +the full syslog message, you have probably created a table "syslog" +with a single column "message". In such a case, a good template would +be "insert into syslog(message) values ('%msg%')". With the example +above, that would be expanded to "insert into syslog(message) +values('Test')". This expanded string is then sent to the database. +It's that easy, no special magic. The only thing you must ensure is +that your template expands to a proper SQL statement and that this +statement matches your database design.</p> +<p>Does that mean you need to create database schema yourself and +also must fully understand rsyslogd's properties? No, that's not +needed. Because we anticipated that folks are probably more interested +in getting things going instead of designing them from scratch. So we +have provided a default schema as well as build-in support for it. This +schema also offers an additional benefit: rsyslog is part of <a href="http://www.adiscon.com/en/">Adiscon</a>'s +<a href="http://www.monitorware.com/en/">MonitorWare +product line</a> (which includes open source and closed source +members). All of these tools share the same default schema and know how +to operate on it. For this reason, the default schema is also called +the "MonitorWare Schema". If you use it, you can simply add <a href="http://www.phplogcon.org/">phpLogCon, a GPLed syslog +web interface</a>, to your system and have instant interactive +access to your database. So there are some benefits in using the +provided schema.</p> +<p>The schema definition is contained in the file "createDB.sql". +It comes with the rsyslog package. Review it to check that the database +name is acceptable for you. Be sure to leave the table and field names +unmodified, because otherwise you need to customize rsyslogd's default +sql template, which we do not do in this paper. Then, run the script +with your favourite MySQL tool. Double-check that the table was +successfully created.</p> +<p>MySQL support in rsyslog is integrated via a loadable plug-in +module. To use the database +functionality, MySQL must be enabled in the config file BEFORE the +first database table action is +used. This is done by placing the</p> +<blockquote> +<p><code>$ModLoad ommysql</code></p> +</blockquote> +<p>directive at the begining of /etc/rsyslog.conf. For other databases, use their plugin name (e.g. ompgsql).</p> +<p>Next, we need to tell rsyslogd to write data to the database. +As we use the default schema, we do NOT need to define a template for +this. We can use the hardcoded one (rsyslogd handles the proper +template linking). So all we need to do is add a simple selector line +to /etc/rsyslog.conf:</p> +<blockquote> +<p><code>*.* :ommysql:database-server,database-name,database-userid,database-password</code></p> +</blockquote> +<p>Again, other databases have other selector names, e.g. ":ompgsql:" +instead of ":ommysql:". See the output plugin's documentation for +details.</p><p>In many cases, MySQL will run on the local machine. In this +case, you can simply use "127.0.0.1" for <i>database-server</i>. +This can be especially advisable, if you do not need to expose MySQL to +any process outside of the local machine. In this case, you can simply +bind it to 127.0.0.1, which provides a quite secure setup. Of course, +also supports remote MySQL instances. In that case, use the remote +server name (e.g. mysql.example.com) or IP-address. The <i> +database-name</i> by default is "syslog". If you have modified +the default, use your name here. <i>Database-userid</i> +and <i>-password</i> are the credentials used to connect +to the database. As they are stored in clear text in rsyslog.conf, that +user should have only the least possible privileges. It is sufficient +to grant it INSERT privileges to the systemevents table, only. As a +side note, it is strongly advisable to make the rsyslog.conf file +readable by root only - if you make it world-readable, everybody could +obtain the password (and eventually other vital information from it). +In our example, let's assume you have created a MySQL user named +"syslogwriter" with a password of "topsecret" (just to say it bluntly: +such a password is NOT a good idea...). If your MySQL database is on +the local machine, your rsyslog.conf line might look like in this +sample:</p> +<blockquote> +<p><code>*.* :ommysql:127.0.0.1,Syslog,syslogwriter,topsecret</code></p> +</blockquote> +<p>Save rsyslog.conf, restart rsyslogd - and you should see +syslog messages being stored in the "systemevents" table!</p> +<p>The example line stores every message to the database. +Especially if you have a high traffic volume, you will probably limit +the amount of messages being logged. This is easy to acomplish: the +"write database" action is just a regular selector line. As such, you +can apply normal selector-line filtering. If, for example, you are only +interested in messages from the mail subsystem, you can use the +following selector line:</p> +<blockquote> +<p><code>mail.* </code><code>:ommysql:</code><code>127.0.0.1,syslog,syslogwriter,topsecret</code></p> +</blockquote> +<p>Review the <a href="rsyslog_conf.html">rsyslog.conf</a> +documentation for details on selector lines and their filtering.</p> +<p><b>You have now completed everything necessary to store +syslog messages to the MySQL database.</b> If you would like to +try out a front-end, you might want to look at <a href="http://www.phplogcon.org/">phpLogCon</a>, which +displays syslog data in a browser. As of this writing, phpLogCon is not +yet a powerful tool, but it's open source, so it might be a starting +point for your own solution.</p> +<h2>On Reliability...</h2> +<p>Rsyslogd writes syslog messages directly to the database. This +implies that the database must be available at the time of message +arrival. If the database is offline, no space is left or something else +goes wrong - rsyslogd can not write the database record. If rsyslogd is +unable to store a message, it performs one retry. This is helpful if +the database server was restarted. In this case, the previous +connection was broken but a reconnect immediately succeeds. However, if +the database is down for an extended period of time, an immediate retry +does not help.</p> +<p>Message loss in this scenario can easily be prevented with +rsyslog. All you need to do is run the database writer in queued mode. +This is now described in a generic way and I do not intend to duplicate +it here. So please be sure to read "<a href="rsyslog_high_database_rate.html">Handling a massive +syslog database insert rate with Rsyslog</a>", which describes +the scenario and also includes configuration examples.</p> +<h2>Conclusion</h2> +<p>With minimal effort, you can use rsyslogd to write syslog +messages to a MySQL database. You can even make it absolutely fail-safe +and protect it against database server downtime. Once the messages are +arrived there, you +can interactivley review and analyse them. In practice, the messages +are also stored in text files for longer-term archival and the +databases are cleared out after some time (to avoid becoming too slow). +If you expect an extremely high syslog message volume, storing it in +real-time to the database may outperform your database server. In such +cases, either filter out some messages or used queued mode (which in +general is recommended with databases).</p> +<p>The method outlined in this paper provides an easy to setup +and maintain solution for most use cases.</p> +<h3>Feedback Requested</h3> +<p>I would appreciate feedback on this paper. If you have +additional ideas, comments or find bugs, please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</p> +<h2>References and Additional Material</h2> +<ul> +<li><a href="http://www.rsyslog.com">www.rsyslog.com</a> +- the rsyslog site</li> +<li> <a href="http://www.monitorware.com/Common/en/Articles/performance-optimizing-syslog-server.php"> +Paper on Syslog Server Optimization</a></li> +</ul> +<h2>Revision History</h2> +<ul> +<li>2005-08-02 * <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> * initial version created</li> +<li>2005-08-03 * <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> * added references to demo site</li> +<li>2007-06-13 * <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> * removed demo site - was torn down because too +expensive for usage count</li> +<li>2008-02-21 * <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> * updated reliability section, can now be done with +on-demand disk queues</li><li>2008-02-28 * <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> * added info on other databases, updated syntax to more recent one</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2005-2008 +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p>Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at <a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body></html> diff --git a/doc/rsyslog_ng_comparison.html b/doc/rsyslog_ng_comparison.html new file mode 100644 index 00000000..44c895f7 --- /dev/null +++ b/doc/rsyslog_ng_comparison.html @@ -0,0 +1,613 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog vs. syslog-ng - a comparison</title></head> +<body> +<a href="features.html">back</a> +<h1>rsyslog vs. syslog-ng</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2008-05-06), slightly updated 2012-01-09</i></small></p> +<p><b>This comparison page is rooted nearly 5 years in the past and has become severely +outdated since then.</b> It was unmaintained for several years and contained false +information on both syslog-ng and rsyslog as technology had advanced so much. +<p>This page was initially written because so many people asked about a comparison when +rsyslog was in its infancy. So I tried to create one, but it was hard to maintain as both +projects grew and added feature after feature. I have to admit we did not try hard to keep +it current -- there were many other priorities. I even had forgetten about this page, when I +saw that Peter Czanik blogged about its +<a href="http://blogs.balabit.com/2012/01/05/rsyslog-vs-syslog-ng/">incorrectness</a> (it must be noted +that Peter is wrong on RELP -- it is well alive). I now remember +that he asked me some time ago about this page, what I somehow lost... I guess he must have been +rather grumpy about that :-( +<p>Visiting this page after so many years is interesting, because it shows how much has changed since then. +Obviously, one of my main goals in regard to syslog-ng is reached: in 2007, I blogged that +<a href="http://blog.gerhards.net/2007/08/why-does-world-need-another-syslogd.html">the +world needs another syslogd</a> in order to have healthy competition and a greate feature +set in the free editions. In my opinion, the timeline clearly tells that rsyslog's competition +has driven more syslog-ng features from the commercial to the free edition. Also, I found +it interesting to see that syslog-ng has adapted rsyslog's licensing scheme, modular design and +multi-threadedness. On the other hand, the Balabit folks have obviously done a quicker and +better move on log normalization with what they call patterndb (it is very roughly equivalent +to what rsyslog has just recently introduced with the help of liblognorm). + +<p>To that account, I think the projects are closer together than 5 years ago. I should now +go ahead and create a new feature comparison. Given previous experience, I think this does not +work out. In the future, we will probably focus on some top features, as Balabit does. However, +that requires some time and I have to admit I do not like to drop this page that has a lot of +inbound links. So I think I do the useful thing by providing these notes and removing the +syslog-ng information. So it can't be wrong on syslog-ng any more. Note that it still contains +some incorrect information about rsyslog (it's the state it had 5 years ago!). The core idea is +to start with updating the <a href="features.html">rsyslog feature sheet</a> and from there +on work to a complete comparision. Of course, feel free to read on if you like to get some sense +of history (and inspiration on what you can still do -- but more ;)). +<br><br> +Thanks,<br> +Rainer Gerhards +<p> + +<table border="1"> +<tbody> +<tr> +<td valign="top"><b>Feature</b></td> +<td valign="top"><b>rsyslog</b></td> +<td valign="top"><b>syslog-ng</b></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Input Sources</b><br> +</td> +</tr> +<tr> +<td valign="top">UNIX domain socket</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">UDP</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">TCP</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top"><a href="http://www.librelp.com">RELP</a></td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">RFC 3195/BEEP</td> +<td valign="top">yes (via <a href="im3195.html">im3195</a>)</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">kernel log</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">file</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">mark message generator as an +optional input</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">Windows Event Log</td> +<td valign="top">via a Windows event logging software such as +<a href="http://www.eventreporter.com">EventReporter</a> +or <a href="http://www.mwagent.com">MonitorWare Agent</a> +(both commercial software, both fund rsyslog development)</td> +<td valign="top"></td> +</tr> +<tr> +<td colspan="3" valign="top"><b><br> +Network (Protocol) Support</b><br> +</td> +</tr> +<tr> +<td valign="top">support for (plain) tcp based syslog</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for GSS-API</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to limit the allowed +network senders (syslog ACLs)</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for syslog-transport-tls +based framing on syslog/tcp connections</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">udp syslog</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">syslog over RELP<br> +truly reliable message delivery (<a href="http://blog.gerhards.net/2008/05/why-you-cant-build-reliable-tcp.html">Why +is plain tcp syslog not reliable?</a>)</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">on the wire (zlib) message +compression</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for receiving messages via +reliable <a href="http://www.monitorware.com/Common/en/glossary/rfc3195.php">RFC +3195</a> delivery</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for <a href="rsyslog_tls.html">TLS/SSL-protected +syslog</a> </td> +<td valign="top"><a href="rsyslog_tls.html">natively</a> (since 3.19.0)<br><a href="rsyslog_stunnel.html">via +stunnel</a></td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for IETF's new syslog-protocol draft</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for IETF's new syslog-transport-tls draft</td> +<td valign="top">yes<br>(since 3.19.0 - world's first implementation)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for IPv6</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">native ability to send SNMP traps</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to preserve the original +hostname in NAT environments and relay chains</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Message Filtering</b><br> +</td> +</tr> +<tr> +<td valign="top">Filtering for syslog facility and +priority</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for hostname</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for application</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for message contents</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for sending IP address</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">ability to filter on any other message +field not mentioned above (including substrings and the like)</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td>support for complex filters, using full boolean algebra +with and/or/not operators and parenthesis</td> +<td>yes</td> +<td></td> +</tr> +<tr> +<td>Support for reusable filters: specify a filter once and +use it in multiple selector lines</td> +<td>no</td> +<td></td> +</tr> +<tr> +<td>support for arbritrary complex arithmetic and string +expressions inside filters</td> +<td>yes</td> +<td></td> +</tr> +<tr> +<td valign="top">ability to use regular expressions +in filters</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for discarding messages +based on filters</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">ability to filter out messages based on sequence of appearing</td> +<td valign="top">yes (starting with 3.21.3)</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">powerful BSD-style hostname and +program name blocks for easy multi-host support</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td></td> +<td></td> +<td></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Supported Database Outputs</b><br> +</td> +</tr> +<tr> +<td valign="top">MySQL</td> +<td valign="top"><a href="rsyslog_mysql.html">yes</a> +(native ommysql, <a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">PostgreSQL</td> +<td valign="top">yes (native ompgsql, <a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">Oracle</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">SQLite</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">Microsoft SQL (Open TDS)</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">Sybase (Open TDS)</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">Firebird/Interbase</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">Ingres</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">mSQL</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top"></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Enterprise Features</b><br> +</td> +</tr> +<tr> +<td valign="top">support for on-demand on-disk +spooling of messages</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to limit disk space used +by spool files</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">each action can use its own, +independant +set of spool files</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">different sets of spool files can +be placed on different disk</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to process spooled +messages only during a configured timeframe (e.g. process messages only +during off-peak hours, during peak hours they are enqueued only)</td> +<td valign="top"><a href="http://wiki.rsyslog.com/index.php/OffPeakHours">yes</a><br> +(can independently be configured for the main queue and each action +queue)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to configure backup +syslog/database servers </td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td>Professional Support</td> +<td><a href="professional_support.html">yes</a></td> +<td></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Config File</b><br> +</td> +</tr> +<tr> +<td valign="top">config file format</td> +<td valign="top">compatible to legacy syslogd but +ugly</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to include config file from +within other config files</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td height="25" valign="top">ability to +include all config files +existing in a specific directory</td> +<td height="25" valign="top">yes</td> +<td height="25" valign="top"></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Extensibility</b><br> +</td> +</tr> +<tr> +<td valign="top">Functionality split in separately +loadable +modules</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">Support for third-party input +plugins</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +</tr> +<tr> +<td valign="top">Support for third-party output +plugins</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td colspan="3" valign="top"><br> +<b>Other Features</b><br> +</td> +</tr> +<tr> +</tr> +<tr> +<td valign="top">ability to generate file names and +directories (log targets) dynamically</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">control of log output format, +including ability to present channel and priority as visible log data</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr><td valign="top">native ability to send mail messages</td> +<td valign="top">yes (<a href="ommail.html">ommail</a>, introduced in 3.17.0)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">good timestamp format control; at a +minimum, ISO 8601/RFC 3339 second-resolution UTC zone</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to reformat message +contents and work with substrings</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for log files larger than +2gb</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for log file size +limitation +and automatic rollover command execution</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">support for running multiple +syslogd instances on a single machine</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to execute shell scripts on +received messages</td> +<td valign="top"></td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">ability to pipe messages to a +continously running program</td> +<td valign="top"></td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">massively multi-threaded for +tomorrow's multi-core machines</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to control repeated line +reduction ("last message repeated n times") on a per selector-line basis</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">supports multiple actions per +selector/filter condition</td> +<td valign="top">yes</td> +<td valign="top"></td> +<td></td> +</tr> +<tr> +<td valign="top">web interface</td> +<td valign="top"><a href="http://www.phplogcon.org">phpLogCon</a><br> +[also works with <a href="http://freshmeat.net/projects/php-syslog-ng/"> +php-syslog-ng</a>]</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">using text files as input source</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">rate-limiting output actions</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">discard low-priority messages under +system stress</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td height="43" valign="top">flow control +(slow down message reception when system is busy)</td> +<td height="43" valign="top">yes (advanced, +with multiple ways to slow down inputs depending on individual input +capabilities, based on watermarks)</td> +<td height="43" valign="top"></td> +</tr> +<tr> +<td valign="top">rewriting messages</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">output data into various formats</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">ability to control "message +repeated n times" generation</td> +<td valign="top">yes</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">license</td> +<td valign="top">GPLv3 (GPLv2 for v2 branch)</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">supported platforms</td> +<td valign="top">Linux, BSD, anecdotical seen on +Solaris; compilation and basic testing done on HP UX</td> +<td valign="top"></td> +</tr> +<tr> +<td valign="top">DNS cache</td> +<td valign="top"></td> +<td valign="top"></td> +</tr> +</tbody> +</table> +<p>While the <span style="font-weight: bold;">rsyslog</span> +project was initiated in 2004, it <span style="font-weight: bold;">is +build on the main author's (Rainer Gerhards) 12+ years of +logging experience</span>. Rainer, for example, also +wrote the first <a href="http://www.winsyslog.com/Common/en/News/WinSyslog-1996-03-31.php">Windows +syslog server</a> in early 1996 and invented the <a href="http://www.eventreporter.com/Common/en/News/EvntSLog-1997-03-23.php">eventlog-to-syslog</a> +class of applications in early 1997. He did custom logging development +and consulting even before he wrote these products. Rsyslog draws on +that vast experience and sometimes even on the code.</p> +<p>Based on a discussion I had, I also wrote about the <b>political +argument why it is good to have another strong syslogd besides syslog-ng</b>. +You may want to read it at my blog at "<a href="http://rgerhards.blogspot.com/2007/08/why-does-world-need-another-syslogd.html">Why +does the world need another syslogd?</a>".</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body></html> diff --git a/doc/rsyslog_packages.html b/doc/rsyslog_packages.html new file mode 100644 index 00000000..80ba96c5 --- /dev/null +++ b/doc/rsyslog_packages.html @@ -0,0 +1,76 @@ +<html> +<head> +<title>rsyslog precompiled packages (RPM and such...)</title> +</head> +<body> +<h1>rsyslog packages</h1> +<p><b>Thanks to some volunteers, rsyslog is also available in package form on +some distributions.</b> All currently known packages are listed below. If I have forgotten +one or if you would +like to maintain a package for a new distribution, please mail me at +<a href="mailto:rgerhards@adiscon.com">rgerhards@adiscon.com</a>. Any help is *deeply* +appreciated. While I create the core daemon, the package maintainers are really +filling it with life, making it available to the average user. I am very +grateful for that!</p> +<p>This list has last been updated on 2008-07-11 by +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a>. +New packages may appear at any time, so be sure to check this page whenever you +need a new one.</p> +<ul> +<li><b>BSD</b> (maintained by infofarmer) + <ul> + <li><a href="http://www.freshports.org/sysutils/rsyslog/"> http://www.freshports.org/sysutils/rsyslog/</a> + </ul> + +<li><b>CentOS 4.3</b> (maintained by James Bergamin) + <ul> + <li><a href="http://www.se-community.com/~james/rsyslog/"> +http://www.se-community.com/~james/rsyslog/</a> + </ul> + +<li><b>Debian</b> (maintained by Michael Biebl) + <ul> + <li><a href="http://packages.debian.org/sid/rsyslog">http://packages.debian.org/sid/rsyslog</a> + </ul> + +<li><b>Fedora</b> + <ul> + <li>Starting with Fedora 8, rsyslog is available as part of the core distribution. + </ul> + +<li><b>openSUSE</b> (maintained by darix) + <ul> + <li><a href="http://download.opensuse.org/repositories/home:/darix/">http://download.opensuse.org/repositories/home:/darix/</a> + </ul> + +<li><b>Red Hat Enterprise Linux</b> + <ul> + <li>Starting with RHEL 5.2, rsyslog is available as part of the core distribution. + </ul> + +<li><b>Ubuntu</b> + <ul> + <li>Starting with hardy, rsyslog is available from the universe repository. + </ul> + +<li>Almost any Linux</h2> + <ul> + <li>Bennet Todd maintains packages that should work on almost any Linux. +He keeps a current i386 tree. There is also a PPC tree, but that one is not paid +much attention for (anyhow, it is known to typically work well, too). +Please visit <a href="http://bent.latency.net/bent/"> +http://bent.latency.net/bent/</a>, select the relevant tree and then do a search +for rsyslog. +Please note, however, that as of this writing the versions in this repository +have been aged a bit. So it may be worth trying to find some other places first. + </ul> +</ul> + +<p>Just in case you are interested, the list of distribution is sorted by alphabetic order +of the distribution name. + +<p>If you do not find a suitable package for your distribution, there is no reason +to panic. It is quite simple to install rsyslog from the source tarball, so you +should consider that. +</body> +</html> diff --git a/doc/rsyslog_pgsql.html b/doc/rsyslog_pgsql.html new file mode 100644 index 00000000..21516ec8 --- /dev/null +++ b/doc/rsyslog_pgsql.html @@ -0,0 +1,336 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> + <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8"> + <TITLE></TITLE> + <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.1 (Unix)"> + <META NAME="AUTHOR" CONTENT="Marc Schiffbauer"> + <META NAME="CREATED" CONTENT="20100129;15054500"> + <META NAME="CHANGEDBY" CONTENT="Marc Schiffbauer"> + <META NAME="CHANGED" CONTENT="20100129;16035000"> + <META NAME="Info 1" CONTENT=""> + <META NAME="Info 2" CONTENT=""> + <META NAME="Info 3" CONTENT=""> + <META NAME="Info 4" CONTENT=""> + <STYLE TYPE="text/css"> + <!-- + @page { size: 8.27in 11.69in; margin: 0.79in } + P { margin-bottom: 0.08in } + P.western { font-family: "Arial", sans-serif } + H1 { margin-bottom: 0.08in } + H1.western { font-family: "Times New Roman", serif } + H1.cjk { font-family: "DejaVu Sans" } + H1.ctl { font-family: "DejaVu Sans" } + H2 { margin-bottom: 0.08in } + H2.western { font-family: "Times New Roman", serif } + BLOCKQUOTE.western { font-family: "Arial", sans-serif } + H3 { margin-bottom: 0.08in } + H3.western { font-family: "Times New Roman", serif } + A:link { so-language: zxx } + --> + </STYLE> +</HEAD> +<BODY> +<H1 CLASS="western"><SPAN LANG="en-US">Writing </SPAN>syslog messages +to MySQL, PostgreSQL or any other supported Database</H1> +<P CLASS="western"><FONT SIZE=2><I>Written by </I></FONT><A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php"><FONT SIZE=2><I>Rainer +Gerhards</I></FONT></A><FONT SIZE=2><I> with some additions by Marc +Schiffbauer (2008-02-28)</I></FONT></P> +<H2 CLASS="western">Abstract</H2> +<P CLASS="western"><SPAN LANG="en-US"><I><B>In this paper, I describe +how to write </B></I></SPAN><A HREF="http://www.monitorware.com/en/topics/syslog/">syslog</A><SPAN LANG="en-US"><I><B> +messages to a </B></I></SPAN><A HREF="http://www.mysql.com/">MySQL</A><SPAN LANG="en-US"><I><B> +or </B></I></SPAN><A HREF="http://www.postgresql.org/">PostgreSQL</A><SPAN LANG="en-US"><I><B> +database.</B></I></SPAN><SPAN LANG="en-US"><I> Having syslog messages +in a database is often handy, especially when you intend to set up a +front-end for viewing them. This paper describes an approach with +</I></SPAN><A HREF="http://www.rsyslog.com/">rsyslogd</A><SPAN LANG="en-US"><I>, +an alternative enhanced syslog daemon natively supporting MySQL and +PostgreSQL. I describe the components needed to be installed and how +to configure them. Please note that as of this writing, rsyslog +supports a variety of databases. While this guide is still MySQL- and +PostgreSQL-focused, you can probably use it together with other ones +too. You just need to modify a few settings.</I></SPAN></P> +<H2 CLASS="western">Background</H2> +<P LANG="en-US" CLASS="western">In many cases, syslog data is simply +written to text files. This approach has some advantages, most +notably it is very fast and efficient. However, data stored in text +files is not readily accessible for real-time viewing and analysis. +To do that, the messages need to be in a database. There are various +ways to store syslog messages in a database. For example, some have +the syslogd write text files which are later feed via a separate +script into the database. Others have written scripts taking the data +(via a pipe) from a non-database-aware syslogd and store them as they +appear. Some others use database-aware syslogds and make them write +the data directly to the database. In this paper, I use that "direct +write" approach. I think it is superior, because the syslogd +itself knows the status of the database connection and thus can +handle it intelligently (well ... hopefully ;)). I use rsyslogd to +acomplish this, simply because I have initiated the rsyslog project +with database-awareness as one goal.</P> +<P CLASS="western"><SPAN LANG="en-US"><B>One word of caution:</B></SPAN><SPAN LANG="en-US"> +while message storage in the database provides an excellent +foundation for interactive analysis, it comes at a cost. Database i/o +is considerably slower than text file i/o. As such, directly writing +to the database makes sense only if your message volume is low enough +to allow a) the syslogd, b) the network, and c) the database server +to catch up with it. Some time ago, I have written a paper on +</SPAN><A HREF="http://www.monitorware.com/Common/en/Articles/performance-optimizing-syslog-server.php">optimizing +syslog server performance</A><SPAN LANG="en-US">. While this paper +talks about Window-based solutions, the ideas in it are generic +enough to apply here, too. So it might be worth reading if you +anticipate medium high to high traffic. If you anticipate really high +traffic (or very large traffic spikes), you should seriously consider +forgetting about direct database writes - in my opinion, such a +situation needs either a very specialized system or a different +approach (the text-file-to-database approach might work better for +you in this case). </SPAN> +</P> +<H2 CLASS="western">Overall System Setup</H2> +<P CLASS="western"><SPAN LANG="en-US">In this paper, I concentrate on +the server side. If you are thinking about interactive syslog message +review, you probably want to centralize syslog. In such a scenario, +you have multiple machines (the so-called clients) send their data to +a central machine (called server in this context). While I expect +such a setup to be typical when you are interested in storing +messages in the database, I do not describe how to set it up. This is +beyond the scope of this paper. If you search a little, you will +probably find many good descriptions on </SPAN><SPAN LANG="en-US">how +to centralize syslog. If you do that, it might be a good idea to do +it securely, so you might also be interested in my paper on +</SPAN><A HREF="http://www.rsyslog.com/doc-rsyslog_stunnel.html">ssl-encrypting +syslog message transfer</A><SPAN LANG="en-US">.</SPAN></P> +<P LANG="en-US" CLASS="western">No matter how the messages arrive at +the server, their processing is always the same. So you can use this +paper in combination with any description for centralized syslog +reporting.</P> +<P CLASS="western"><SPAN LANG="en-US">As I already said, I use +rsyslogd on the server. It has intrinsic support for talking to the +supported databases. For obvious reasons, we also need an instance of +MySQL or PostgreSQL running. To keep us focused, the setup of the +database itself is also beyond the scope of this paper. I assume that +you have successfully installed the database and also have a +front-end at hand to work with it (for example, </SPAN><A HREF="http://www.phpmyadmin.net/">phpMyAdmin</A><SPAN LANG="en-US"> +or </SPAN><A HREF="http://phppgadmin.sourceforge.net/">phpPgAdmin</A><SPAN LANG="en-US">. +Please make sure that this is installed, actually working and you +have a basic understanding of how to handle it.</SPAN></P> +<H2 CLASS="western">Setting up the system</H2> +<P CLASS="western"><SPAN LANG="en-US">You need to download and +install rsyslogd first. Obtain it from the </SPAN><A HREF="http://www.rsyslog.com/">rsyslog +site</A><SPAN LANG="en-US">. Make sure that you disable stock +syslogd, otherwise you will experience some difficulties. On some +distributions (Fedora 8 and above, for example), rsyslog may +already by the default syslogd, in which case you obviously do not +need to do anything specific. For many others, there are prebuild +packages available. If you use either, please make sure that you have +the required database plugins for your database available. It usually +is a separate package and typically </SPAN><SPAN LANG="en-US"><B>not</B></SPAN><SPAN LANG="en-US"> +installed by default.</SPAN></P> +<P CLASS="western"><SPAN LANG="en-US">It is important to understand +how rsyslogd talks to the database. In rsyslogd, there is the concept +of "templates". Basically, a template is a string that +includes some replacement characters, which are called "properties" +in rsyslog. Properties are accessed via the "</SPAN><A HREF="http://www.rsyslog.com/doc-property_replacer.html">Property +Replacer</A><SPAN LANG="en-US">". Simply said, you access +properties by including their name between percent signs inside the +template. For example, if the syslog message is "Test", the +template "%msg%" would be expanded to "Test". +Rsyslogd supports sending template text as a SQL statement to the +database. As such, the template must be a valid SQL statement. There +is no limit in what the statement might be, but there are some +obvious and not so obvious choices. For example, a template "drop +table xxx" is possible, but does not make an awful lot of sense. +In practice, you will always use an "insert" statement +inside the template.</SPAN></P> +<P LANG="en-US" CLASS="western">An example: if you would just like to +store the msg part of the full syslog message, you have probably +created a table "syslog" with a single column "message". +In such a case, a good template would be "insert into +syslog(message) values ('%msg%')". With the example above, that +would be expanded to "insert into syslog(message) +values('Test')". This expanded string is then sent to the +database. It's that easy, no special magic. The only thing you must +ensure is that your template expands to a proper SQL statement and +that this statement matches your database design.</P> +<P CLASS="western"><SPAN LANG="en-US">Does that mean you need to +create database schema yourself and also must fully understand +rsyslogd's properties? No, that's not needed. Because we anticipated +that folks are probably more interested in getting things going +instead of designing them from scratch. So we have provided a default +schema as well as build-in support for it. This schema also offers an +additional benefit: rsyslog is part of </SPAN><A HREF="http://www.adiscon.com/en/">Adiscon</A><SPAN LANG="en-US">'s +</SPAN><A HREF="http://www.monitorware.com/en/">MonitorWare product +line</A><SPAN LANG="en-US"> (which includes open source and closed +source members). All of these tools share the same default schema and +know how to operate on it. For this reason, the default schema is +also called the "MonitorWare Schema". If you use it, you +can simply add </SPAN><A HREF="http://www.phplogcon.org/">phpLogCon, +a GPLed syslog web interface</A><SPAN LANG="en-US">, to your system +and have instant interactive access to your database. So there are +some benefits in using the provided schema.</SPAN></P> +<P LANG="en-US" CLASS="western">The schema definition is contained in +the file "createDB.sql". It comes with the rsyslog package +and one can be found for each supported database type (in the plugins +directory). Review it to check that the database name is acceptable +for you. Be sure to leave the table and field names unmodified, +because otherwise you need to customize rsyslogd's default sql +template, which we do not do in this paper. Then, run the script with +your favorite SQL client. Double-check that the table was +successfully created.</P> +<P LANG="en-US" CLASS="western">It is important to note that the +correct database encoding must be used so that the database will +accept strings independend of the string encoding. This is an +important part because it can not be guarantied that all syslog +messages will have a defined character encoding. This is especially +true if the rsyslog-Server will collect messages from different +clients and different products. +</P> +<P LANG="en-US" CLASS="western">For example PostgreSQL may refuse to +accept messages if you would set the database encoding to “UTF8†+while a client is sending invalid byte sequences for that encoding. +</P> +<P LANG="en-US" CLASS="western">Database support in rsyslog is +integrated via loadable plugin modules. To use the database +functionality, the database plugin must be enabled in the config file +BEFORE the first database table action is used. This is done by +placing the</P> +<BLOCKQUOTE CLASS="western"><CODE>$ModLoad ommysql</CODE></BLOCKQUOTE> +<P CLASS="western">directive at the begining of /etc/rsyslog.conf for +MySQL and</P> +<BLOCKQUOTE CLASS="western"><CODE>$ModLoad ompgsql</CODE></BLOCKQUOTE> +<P CLASS="western"><CODE><FONT FACE="Arial, sans-serif">for +PostgreSQL.</FONT></CODE></P> +<P LANG="en-US" CLASS="western"><FONT FACE="Arial, sans-serif">For +other databases, use their plugin name (e.g. omoracle).</FONT></P> +<P CLASS="western">Next, we need to tell rsyslogd to write data to +the database. As we use the default schema, we do NOT need to define +a template for this. We can use the hardcoded one (rsyslogd handles +the proper template linking). So all we need to do e.g. for MySQL is +add a simple selector line to /etc/rsyslog.conf:</P> +<BLOCKQUOTE CLASS="western"><CODE>*.* +:ommysql:database-server,database-name,database-userid,database-password</CODE></BLOCKQUOTE> +<P CLASS="western">Again, other databases have other selector names, +e.g. ":ompgsql:" instead of ":ommysql:". See the +output plugin's documentation for details.</P> +<P LANG="en-US" CLASS="western">In many cases, the database will run +on the local machine. In this case, you can simply use "127.0.0.1" +for <I>database-server</I>. This can be especially advisable, if you +do not need to expose the database to any process outside of the +local machine. In this case, you can simply bind it to 127.0.0.1, +which provides a quite secure setup. Of course, rsyslog also supports +remote database instances. In that case, use the remote server name +(e.g. mydb.example.com) or IP-address. The <I>database-name</I> by +default is "Syslog". If you have modified the default, use +your name here. <I>Database-userid</I> and <I>-password</I> are the +credentials used to connect to the database. As they are stored in +clear text in rsyslog.conf, that user should have only the least +possible privileges. It is sufficient to grant it INSERT privileges +to the systemevents table, only. As a side note, it is strongly +advisable to make the rsyslog.conf file readable by root only - if +you make it world-readable, everybody could obtain the password (and +eventually other vital information from it). In our example, let's +assume you have created a database user named "syslogwriter" +with a password of "topsecret" (just to say it bluntly: +such a password is NOT a good idea...). If your database is on the +local machine, your rsyslog.conf line might look like in this sample:</P> +<BLOCKQUOTE CLASS="western"><CODE>*.* +:ommysql:127.0.0.1,Syslog,syslogwriter,topsecret</CODE></BLOCKQUOTE> +<P CLASS="western">Save rsyslog.conf, restart rsyslogd - and you +should see syslog messages being stored in the "systemevents" +table!</P> +<P LANG="en-US" CLASS="western">The example line stores every message +to the database. Especially if you have a high traffic volume, you +will probably limit the amount of messages being logged. This is easy +to accomplish: the "write database" action is just a +regular selector line. As such, you can apply normal selector-line +filtering. If, for example, you are only interested in messages from +the mail subsystem, you can use the following selector line:</P> +<BLOCKQUOTE CLASS="western"><CODE>mail.* :ommysql:127.0.0.1,syslog,syslogwriter,topsecret</CODE></BLOCKQUOTE> +<P CLASS="western">Review the <A HREF="http://www.rsyslog.com/doc-rsyslog_conf.html">rsyslog.conf</A> +documentation for details on selector lines and their filtering.</P> +<P CLASS="western"><SPAN LANG="en-US"><B>You have now completed +everything necessary to store syslog messages to the a database.</B></SPAN><SPAN LANG="en-US"> +If you would like to try out a front-end, you might want to look at +</SPAN><A HREF="http://www.phplogcon.org/">phpLogCon</A><SPAN LANG="en-US">, +which displays syslog data in a browser. As of this writing, +phpLogCon is not yet a powerful tool, but it's open source, so it +might be a starting point for your own solution.</SPAN></P> +<H2 CLASS="western">On Reliability...</H2> +<P LANG="en-US" CLASS="western">Rsyslogd writes syslog messages +directly to the database. This implies that the database must be +available at the time of message arrival. If the database is offline, +no space is left or something else goes wrong - rsyslogd can not +write the database record. If rsyslogd is unable to store a message, +it performs one retry. This is helpful if the database server was +restarted. In this case, the previous connection was broken but a +reconnect immediately succeeds. However, if the database is down for +an extended period of time, an immediate retry does not help.</P> +<P CLASS="western"><SPAN LANG="en-US">Message loss in this scenario +can easily be prevented with rsyslog. All you need to do is run the +database writer in queued mode. This is now described in a generic +way and I do not intend to duplicate it here. So please be sure to +read "</SPAN><A HREF="http://www.rsyslog.com/doc-rsyslog_high_database_rate.html">Handling +a massive syslog database insert rate with Rsyslog</A><SPAN LANG="en-US">", +which describes the scenario and also includes configuration +examples.</SPAN></P> +<H2 CLASS="western">Conclusion</H2> +<P LANG="en-US" CLASS="western">With minimal effort, you can use +rsyslogd to write syslog messages to a database. You can even make it +absolutely fail-safe and protect it against database server downtime. +Once the messages are arrived there, you can interactively review and +analyze them. In practice, the messages are also stored in text files +for longer-term archival and the databases are cleared out after some +time (to avoid becoming too slow). If you expect an extremely high +syslog message volume, storing it in real-time to the database may +outperform your database server. In such cases, either filter out +some messages or used queued mode (which in general is recommended +with databases).</P> +<P LANG="en-US" CLASS="western">The method outlined in this paper +provides an easy to setup and maintain solution for most use cases.</P> +<H3 CLASS="western">Feedback Requested</H3> +<P CLASS="western">I would appreciate feedback on this paper. If you +have additional ideas, comments or find bugs, please <A HREF="mailto:rgerhards@adiscon.com">let +me know</A>.</P> +<H2 CLASS="western">References and Additional Material</H2> +<UL> + <LI><P CLASS="western" STYLE="margin-bottom: 0in"><A HREF="http://www.rsyslog.com/">www.rsyslog.com</A> + - the rsyslog site + </P> + <LI><P CLASS="western"><A HREF="http://www.monitorware.com/Common/en/Articles/performance-optimizing-syslog-server.php">Paper + on Syslog Server Optimization</A> + </P> +</UL> +<H2 CLASS="western">Revision History</H2> +<UL> + <LI><P CLASS="western" STYLE="margin-bottom: 0in">2005-08-02 * + <A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</A> * initial version created + </P> + <LI><P CLASS="western" STYLE="margin-bottom: 0in">2005-08-03 * + <A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</A> * added references to demo site + </P> + <LI><P CLASS="western" STYLE="margin-bottom: 0in">2007-06-13 * + <A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</A> * removed demo site - was torn down because too + expensive for usage count + </P> + <LI><P CLASS="western" STYLE="margin-bottom: 0in">2008-02-21 * + <A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</A> * updated reliability section, can now be done with + on-demand disk queues</P> + <LI><P CLASS="western">2008-02-28 * <A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</A> * added info on other databases, updated syntax to more + recent one + </P> + <LI><P CLASS="western">2010-01-29 * Marc Schiffbauer * added some + PostgreSQL stuff, made wording more database generic, fixed some + typos</P> +</UL> +<H2 CLASS="western">Copyright</H2> +<P CLASS="western">Copyright (c) 2005-2010 <A HREF="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</A>, Marc Schiffbauer and <A HREF="http://www.adiscon.com/en/">Adiscon</A>.</P> +<P CLASS="western"><BR><BR> +</P> +</BODY> +</HTML> diff --git a/doc/rsyslog_php_syslog_ng.html b/doc/rsyslog_php_syslog_ng.html new file mode 100644 index 00000000..ed4d72fc --- /dev/null +++ b/doc/rsyslog_php_syslog_ng.html @@ -0,0 +1,153 @@ +<html><head> +<title>Using php-syslog-ng with rsyslog</title> +<meta name="KEYWORDS" content="syslog, php-syslog-ng, mysql, howto, rsyslog"> +</head> +<body> +<h1>Using php-syslog-ng with rsyslog</h1> + <P><small><i>Written by + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</a> (2005-08-04)</i></small></P> +<p>Note: it has been reported that this guide is somewhat outdated. Please +use with care. Also, please note that <b>rsyslog's "native" web frontend is +<a href="http://www.phplogcon.org">phpLogCon</a></b>, which provides best integration +and a lot of extra functionality.</p> +<h2>Abstract</h2> +<p><i><b>In this paper, I describe how to use +<a href="http://www.vermeer.org/projects/php-syslog-ng">php-syslog-ng</a> with +<a href="http://www.rsyslog.com/">rsyslogd</a>. </b> Php-syslog-ng is a +popular web interface to syslog data. Its name stem from the fact that it +usually picks up its data from a database created by +<a href="http://www.balabit.com/products/syslog_ng/">syslog-ng</a> and some +helper scripts. However, there is nothing syslog-ng specific in the database. +With rsyslogd's high customizability, it is easy to write to a syslog-ng like +schema. I will tell you how to do this, enabling you to use php-syslog-ng as a +front-end for rsyslogd - or save the hassle with syslog-ng database +configuration and simply go ahead and use rsyslogd instead.</i></p> +<h2>Overall System Setup</h2> +<p>The setup is pretty straightforward. Basically, php-syslog-ng's interface to +the syslogd is the database. We use the schema that php-syslog-ng expects and +make rsyslogd write to it in its format. Because of this, php-syslog-ng does not +even know there is no syslog-ng present.</p> +<h2>Setting up the system</h2> +<p>For php-syslog-ng, you can follow its usual setup instructions. Just skip any +steps refering to configure syslog-ng. Make sure you create the database schema +in <a href="http://www.mysql.com/">MySQL</a>. As of this writing, the expected schema can be created via this script:</p> +<blockquote> + <code>CREATE DATABASE syslog<br> + !<br> + USE syslog<br> + !<br> + CREATE TABLE logs (<br> + host varchar(32) default NULL,<br> + facility varchar(10) default NULL,<br> + priority varchar(10) default NULL,<br> + level varchar(10) default NULL,<br> + tag varchar(10) default NULL,<br> + date date default NULL,<br> + time time default NULL,<br> + program varchar(15) default NULL,<br> + msg text,<br> + seq int(10) unsigned NOT NULL auto_increment,<br> + PRIMARY KEY (seq),<br> + KEY host (host),<br> + KEY seq (seq),<br> + KEY program (program),<br> + KEY time (time),<br> + KEY date (date),<br> + KEY priority (priority),<br> + KEY facility (facility)<br> + ) TYPE=MyISAM;</code> +</blockquote> +<p>Please note that at the time you are reading this paper, the schema might have changed. +Check for any differences. As we customize rsyslogd to the schema, it is vital +to have the correct one. If this paper is outdated, +<a href="mailto:rgerhards@adiscon.com">let me know</a> so that I can fix it.</p> +<p>Once this schema is created, we simply instruct rsyslogd to store received +data in it. I wont go into too much detail here. If you are interested in some +more details, you might find my paper "<a href="rsyslog_mysql.html">Writing +syslog messages to MySQL</a>" worth reading. For this article, we simply modify +<a href="rsyslog_conf.html">rsyslog.conf </a>so that it writes to the database. +That is easy. Just these two lines are needed:</p> +<blockquote> + <code><font color="green">$template syslog-ng,"insert into logs(host, facility, priority, tag, date, + time, msg) values ('%HOSTNAME%', %syslogfacility%, %syslogpriority%, + '%syslogtag%', '%timereported:::date-mysql%', '%timereported:::date-mysql%', + '%msg%')", SQL</font> <br> + <font color="red">*.* >mysql-server,syslog,user,pass;syslog-ng</font> + </code> +</blockquote> +<p>These are just <b>two</b> lines. I have color-coded them so that you see what +belongs together (the colors have no other meaning). The green line is the +actual SQL statement being used to take care of the syslog-ng schema. Rsyslogd +allows you to fully control the statement sent to the database. This allows you +to write to any database format, including your homegrown one (if you so desire). +Please note that there is a small inefficiency in our current usage: the + <code><font color="green">'%timereported:::date-mysql%'</font></code> +property is used for both the time and the date (if you wonder about what all +these funny characters mean, see the <a href="property_replacer.html">rsyslogd +property replacer manual</a>) . We could have extracted just the date and time +parts of the respective properties. However, this is more complicated and also +adds processing time to rsyslogd's processing (substrings must be extracted). So we take a full mysql-formatted timestamp and supply it to MySQL. The sql engine in turn +discards the unneeded part. It works pretty well. As of my understanding, the +inefficiency of discarding the unneeded part in MySQL is lower than the +effciency gain from using the full timestamp in rsyslogd. So it is most probably +the best solution.</p> +<p>Please note that rsyslogd knows two different timestamp properties: one is +timereported, used here. It is the timestamp from the message itself. Sometimes +that is a good choice, in other cases not. It depends on your environment. The other one is the timegenerated +property. This is the time when rsyslogd received the message. For obvious +reasons, that timestamp is consistent, even when your devices are in multiple +time zones or their clocks are off. However, it is not "the real thing". It's +your choice which one you prefer. If you prefer timegenerated ... simply use it +;)</p> +<p>The line in red tells rsyslogd which messages to log and where to store it. +The "*.*" selects all messages. You can use standard syslog selector line filters here if +you do not like to see everything in your database. The ">" tells +rsyslogd that a MySQL connection +must be established. Then, "mysql-server" is the name or IP address of the +server machine, "syslog" is the database name (default from the schema) and "user" +and "pass" are the logon credentials. Use a user with low privileges, insert into the +logs table is sufficient. "syslog-ng" is the template name and tells rsyslogd to +use the SQL statement shown above.</p> +<p>Once you have made the changes, all you need to do is restart +rsyslogd. Then, you should see syslog messages flow into your database - and +show up in php-syslog-ng.</p> +<h2>Conclusion</h2> +<P>With minumal effort, you can use php-syslog-ng together with rsyslogd. For +those unfamiliar with syslog-ng, this configuration is probably easier to set up +then switching to syslog-ng. For existing rsyslogd users, php-syslog-ng might be a nice +add-on to their logging infrastructure.</P> +<P>Please note that the <a href="http://www.monitorware.com/en/">MonitorWare family</a> (to which rsyslog belongs) also +offers a web-interface: <a href="http://www.phplogcon.org/">phpLogCon</a>. +From my point of view, obviously, <b>phpLogCon is the more natural choice for a web interface +to be used together with rsyslog</b>. It also offers superb functionality and provides, +for example,native display of Windows event log entries. +I have set up a <a href="http://demo.phplogcon.org/">demo server</a>., +You can have a peek at it +without installing anything.</P> +<h2>Feedback Requested</h2> +<P>I would appreciate feedback on this paper. If you have additional ideas, +comments or find bugs, please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</P> +<h2>References and Additional Material</h2> +<ul> + <li><a href="http://www.vermeer.org/projects/php-syslog-ng">php-syslog-ng</a></li> +</ul> +<h2>Revision History</h2> +<ul> + <li>2005-08-04 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> * + initial version created</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2005 +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> +and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p>Permission is granted to copy, distribute and/or modify this document under +the terms of the GNU Free Documentation License, Version 1.2 or any later +version published by the Free Software Foundation; with no Invariant Sections, +no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be +viewed at <a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body> +</html> diff --git a/doc/rsyslog_queue_pointers.jpeg b/doc/rsyslog_queue_pointers.jpeg Binary files differnew file mode 100644 index 00000000..809dd446 --- /dev/null +++ b/doc/rsyslog_queue_pointers.jpeg diff --git a/doc/rsyslog_queue_pointers2.jpeg b/doc/rsyslog_queue_pointers2.jpeg Binary files differnew file mode 100644 index 00000000..2ad60113 --- /dev/null +++ b/doc/rsyslog_queue_pointers2.jpeg diff --git a/doc/rsyslog_recording_pri.html b/doc/rsyslog_recording_pri.html new file mode 100644 index 00000000..abcadf2a --- /dev/null +++ b/doc/rsyslog_recording_pri.html @@ -0,0 +1,134 @@ +<html><head> +<title>Recording the Priority of Syslog Messages</title> +<meta name="KEYWORDS" content="syslog, mysql, syslog to mysql, howto"> +</head> +<body> +<h1>Recording the Priority of Syslog Messages</h1> + <P><small><i>Written by + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</a> (2007-06-18)</i></small></P> +<h2>Abstract</h2> +<p><i><b>The so-called priority (PRI) is very important in syslog messages, +because almost all filtering in syslog.conf is based on it.</b> However, many +syslogds (including the Linux stock sysklogd) do not provide a way to record +that value. In this article, I'll give a brief overview of how PRI can be +written to a log file.</i></p> +<h2>Background</h2> +<p>The PRI value is a combination of so-called severity and facility. The +facility indicates where the message originated from (e.g. kernel, mail +subsystem) while the severity provides a glimpse of how important the message +might be (e.g. error or informational). Be careful with these values: they are +in no way consistent across applications (especially severity). However, they +still form the basis of most filtering in syslog.conf. For example, the +directive (aka "selector line)</p> +<p align="center"> +<code>mail.* /var/log/mail.log</code> +</p> +<p>means that messages with the mail facility should be stored to +/var/log/mail.log, no matter which severity indicator they have (that is telling +us the asterisk). If you set up complex conditions, it can be annoying to find +out which PRI value a specific syslog message has. Most stock syslogds do not +provide any way to record them.</p> +<h2>How is it done?</h2> +<p>With <a href="http://www.rsyslog.com/">rsyslog</a>, PRI recording is simple. +All you need is the correct template. Even if you do not use rsyslog on a regular +basis, it might be a handy tool for finding out the priority.</p> +<p>Rsyslog provides a flexible system to specify the output formats. It is +template-based. A template with the traditional syslog format looks as follows:</p> +<p align="center"> +<code>$template TraditionalFormat,"%timegenerated% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n"</code> +</p> +<p>The part in quotes is the output formats. Things between percent-signs are +so-called <a href="property_replacer.html">messages properties</a>. They are replaced with the respective content +from the syslog message when output is written. Everything outside of the +percent signs is literal text, which is simply written as specified.</p> +<p>Thankfully, rsyslog provides message properties for the priority. These are +called "PRI", "syslogfacility" and "syslogpriority" (case is important!). They are numerical +values. Starting with rsyslog 1.13.4, there is also a property "pri-text", which +contains the priority in friendly text format (e.g. "syslog.info"). For the rest +of this article, I assume that you run version 1.13.4 or higher.</p> +<p>Recording the priority is now a simple matter of adding the respective field +to the template. It now looks like this:</p> +<p align="center"> +<code>$template TraditionalFormatWithPRI,"%pri-text%: %timegenerated% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n"</code> +</p> +<p>Now we have the right template - but how to write it to a file? You probably +have a line like this in your syslog.conf:</p> +<p align="center"><code>*.* -/var/log/messages.log</code></p> +<p>It does not specify a template. Consequently, rsyslog uses the traditional +format. In order to use some other format, simply specify the template after the +semicolon:</p> +<p align="center"><code>*.* -/var/log/messages.log;TraditionalFormatWithPRI</code></p> +<p>That's all you need to do. There is one common pitfall: you need to define +the template before you use it in a selector line. Otherwise, you will receive +an error.</p> +<p>Once you have applied the changes, you need to restart rsyslogd. It +will then pick the new configuration.</p> +<h2>What if I do not want rsyslogd to be the standard syslogd?</h2> +<p>If you do not want to switch to rsyslog, you can still use it as a setup aid. +A little bit of configuration is required.</p> +<ol> + <li>Download, make and install rsyslog</li> + <li>copy your syslog.conf over to rsyslog.conf</li> + <li>add the template described above to it; select the file that should use + it</li> + <li>stop your regular syslog daemon for the time being</li> + <li>run rsyslogd (you may even do this interactively by calling it with the + -n additional option from a shell)</li> + <li>stop rsyslogd (press ctrl-c when running interactively)</li> + <li>restart your regular syslogd</li> +</ol> +<p>That's it - you can now review the priorities.</p> +<h2>Some Sample Data</h2> +<p>Below is some sample data created with the template specified above. Note the +priority recording at the start of each line.</p> +<p> +<code>kern.info: Jun 15 18:10:38 host kernel: PCI: Sharing IRQ 11 with 00:04.0<br> +kern.info: Jun 15 18:10:38 host kernel: PCI: Sharing IRQ 11 with 01:00.0<br> +kern.warn: Jun 15 18:10:38 host kernel: Yenta IRQ list 06b8, PCI irq11<br> +kern.warn: Jun 15 18:10:38 host kernel: Socket status: 30000006<br> +kern.warn: Jun 15 18:10:38 host kernel: Yenta IRQ list 06b8, PCI irq11<br> +kern.warn: Jun 15 18:10:38 host kernel: Socket status: 30000010<br> +kern.info: Jun 15 18:10:38 host kernel: cs: IO port probe 0x0c00-0x0cff: clean.<br> +kern.info: Jun 15 18:10:38 host kernel: cs: IO port probe 0x0100-0x04ff: excluding 0x100-0x107 0x378-0x37f 0x4d0-0x4d7<br> +kern.info: Jun 15 18:10:38 host kernel: cs: IO port probe 0x0a00-0x0aff: clean.<br> +local7.notice: Jun 15 18:17:24 host dd: 1+0 records out<br> +local7.notice: Jun 15 18:17:24 host random: Saving random seed: succeeded<br> +local7.notice: Jun 15 18:17:25 host portmap: portmap shutdown succeeded<br> +local7.notice: Jun 15 18:17:25 host network: Shutting down interface eth1: succeeded<br> +local7.notice: Jun 15 18:17:25 host network: Shutting down loopback interface: succeeded<br> +local7.notice: Jun 15 18:17:25 host pcmcia: Shutting down PCMCIA services: cardmgr<br> +user.notice: Jun 15 18:17:25 host /etc/hotplug/net.agent: NET unregister event not supported<br> +local7.notice: Jun 15 18:17:27 host pcmcia: modules.<br> +local7.notice: Jun 15 18:17:29 host rc: Stopping pcmcia: succeeded<br> +local7.notice: Jun 15 18:17:30 host rc: Starting killall: succeeded<br> +syslog.info: Jun 15 18:17:33 host [origin software="rsyslogd" swVersion="1.13.3" x-pid="2464"] exiting on signal 15.<br> +syslog.info: Jun 18 10:55:47 host [origin software="rsyslogd" swVersion="1.13.3" x-pid="2367"][x-configInfo udpReception="Yes" udpPort="514" tcpReception="Yes" tcpPort="1470"] restart<br> +user.notice: Jun 18 10:55:50 host rger: test<br> +syslog.info: Jun 18 10:55:52 host [origin software="rsyslogd" swVersion="1.13.3" x-pid="2367"] exiting on signal 2.</code></p> +<h2>Feedback Requested</h2> +<P>I would appreciate feedback on this paper. If you have additional ideas, +comments or find bugs, please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</P> +<h2>References and Additional Material</h2> +<ul> + <li><a href="http://www.rsyslog.com">www.rsyslog.com</a> - the rsyslog site</li> +</ul> +<h2>Revision History</h2> +<ul> + <li>2007-06-18 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * initial version created</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2007 +<a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> +and <a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p>Permission is granted to copy, distribute and/or modify this document under +the terms of the GNU Free Documentation License, Version 1.2 or any later +version published by the Free Software Foundation; with no Invariant Sections, +no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be +viewed at <a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body> +</html> diff --git a/doc/rsyslog_reliable_forwarding.html b/doc/rsyslog_reliable_forwarding.html new file mode 100644 index 00000000..d04d9ead --- /dev/null +++ b/doc/rsyslog_reliable_forwarding.html @@ -0,0 +1,152 @@ +<html><head> +<title>Reliable Forwarding of syslog Messages (via plain TCP syslog)</title> +</head> +<body> +<h1>Reliable Forwarding of syslog Messages with Rsyslog</h1> + <P><small><i>Written by + <a href="http://www.gerhards.net/rainer">Rainer + Gerhards</a> (2008-06-27)</i></small></P> +<h2>Abstract</h2> +<p><i><b>In this paper, I describe how to forward +<a href="http://www.monitorware.com/en/topics/syslog/">syslog</a> + + messages (quite) reliable to a central rsyslog server.</b> +This depends on rsyslog being installed on the client system and +it is recommended to have it installed on the server system. Please note +that industry-standard +<a href="http://blog.gerhards.net/2008/04/on-unreliability-of-plain-tcp-syslog.html">plain TCP syslog protocol is not fully reliable</a> +(thus the "quite reliable"). If you need a truely reliable solution, you need +to look into RELP (natively supported by rsyslog).</i></p> + +<h2>The Intention</h2> +<p>Whenever two systems talk over a network, something can go wrong. +For example, the communications link may go down, or a client or server may abort. +Even in regular cases, the server may be offline for a short period of time +because of routine maintenance. +<p>A logging system should be capable of avoiding message loss in situations where the +server is not reachable. To do so, unsent data needs to be buffered at the client while the +server is offline. Then, once the server is up again, this data is to be sent. +<p>This can easily be acomplished by rsyslog. In rsyslog, every action runs on its own queue +and each queue can be set to buffer data if the action is not ready. Of course, +you must be able to detect that "the action is not ready", which means the remote +server is offline. This can be detected with plain TCP syslog and RELP, but not with UDP. +So you need to use either of the two. In this howto, we use plain TCP syslog. +<p>Please note that we are using rsyslog-specific features. The are required on the +client, but not on the server. So the client system must run rsyslog (at least version 3.12.0), while on the +server another syslogd may be running, as long as it supports plain tcp syslog. +<p><b>The rsyslog queueing subsystem tries to buffer to memory. So even if the +remote server goes +offline, no disk file is generated.</b> File on disk are created only if there is +need to, for example if rsyslog runs out of (configured) memory queue space or needs +to shutdown (and thus persist yet unsent messages). Using main memory and going to the +disk when needed is a huge performance benefit. You do not need to care about it, +because, all of it is handled automatically and transparently by rsyslog.</p> +<h2>How To Setup</h2> +<p>First, you need to create a working directory for rsyslog. This is where it +stores its queue files (should need arise). You may use any location on your +local system. +<p>Next, you need to do is instruct rsyslog to use a +disk queue and then configure your action. There is nothing else to do. With the +following simple config file, you forward anything you receive to a remote server +and have buffering applied automatically when it goes down. This must be done on the +client machine.</p> +<textarea rows="9" cols="80"> +$ModLoad imuxsock # local message reception + +$WorkDirectory /rsyslog/work # default location for work (spool) files + +$ActionQueueType LinkedList # use asynchronous processing +$ActionQueueFileName srvrfwd # set file name, also enables disk mode +$ActionResumeRetryCount -1 # infinite retries on insert failure +$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down +*.* @@server:port +</textarea> +<p>The port given above is optional. It may not be specified, in which case you only +provide the server name. The "$ActionQueueFileName" is used to create queue files, should need +arise. This value must be unique inside rsyslog.conf. No two rules must use the same queue file. +Also, for obvious reasons, it must only contain those characters that can be used inside a +valid file name. Rsyslog possibly adds some characters in front and/or at the end of that name +when it creates files. So that name should not be at the file size name length limit (which +should not be a problem these days). +<p>Please note that actual spool files are only created if the remote server is down +<b>and</b> there is no more space in the in-memory queue. By default, a short failure +of the remote server will never result in the creation of a disk file as a couple of +hundered messages can be held in memory by default. [These parameters can be fine-tuned. However, +then you need to either fully understand how the queue works +(<a href="http://www.rsyslog.com/doc-queues.html">read elaborate doc</a>) or +use <a href="http://www.rsyslog.com/doc-professional_support.html">professional services</a> +to have it done based on +your specs ;) - what that means is that fine-tuning queue parameters is far from +being trivial...] +<p>If you would like to test if your buffering scenario works, you need to +stop, wait a while and restart you central server. Do <b>not</b> watch for files being created, +as this usually does not happen and never happens immediately. + +<h3>Forwarding to More than One Server</h3> +<p>If you have more than one server you would like to forward to, that's quickly done. +Rsyslog has no limit on the number or type of actions, so you can define as many targets +as you like. What is important to know, however, is that the full set of directives make +up an action. So you can not simply add (just) a second forwarding rule, but need to +duplicate the rule configuration as well. Be careful that you use different queue +file names for the second action, else you will mess up your system. +<p>A sample for forwarding to two hosts looks like this: +<p> +<textarea rows="20" cols="80"> +$ModLoad imuxsock # local message reception + +$WorkDirectory /rsyslog/work # default location for work (spool) files + +# start forwarding rule 1 +$ActionQueueType LinkedList # use asynchronous processing +$ActionQueueFileName srvrfwd1 # set file name, also enables disk mode +$ActionResumeRetryCount -1 # infinite retries on insert failure +$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down +*.* @@server1:port +# end forwarding rule 1 + +# start forwarding rule 2 +$ActionQueueType LinkedList # use asynchronous processing +$ActionQueueFileName srvrfwd2 # set file name, also enables disk mode +$ActionResumeRetryCount -1 # infinite retries on insert failure +$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down +*.* @@server2 +# end forwarding rule 2 +</textarea> +<p>Note the filename used for the first rule it is "srvrfwd1" and for the second it +is "srvrfwd2". I have used a server without port name in the second forwarding rule. +This was just to illustrate how this can be done. You can also specify a port there +(or drop the port from server1). +<p>When there are multiple action queues, they all work independently. Thus, if server1 +goes down, server2 still receives data in real-time. The client will <b>not</b> block +and wait for server1 to come back online. Similarily, server1's operation will not +be affected by server2's state. + +<h2>Some Final Words on Reliability ...</h2> +<p>Using plain TCP syslog provides a lot of reliability over UDP syslog. However, +plain TCP syslog is <b>not</b> a fully reliable transport. In order to get full reliability, +you need to use the RELP protocol. +<p>Folow the next link to learn more about +<a href="http://blog.gerhards.net/2008/04/on-unreliability-of-plain-tcp-syslog.html">the +problems you may encounter with plain tcp syslog</a>. +<h3>Feedback requested</h3> +<P>I would appreciate feedback on this tutorial. If you have additional ideas, +comments or find bugs (I *do* bugs - no way... ;)), please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</P> +<h2>Revision History</h2> +<ul> + <li>2008-06-27 * + <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> * Initial Version created</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2008 +<a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body> +</html> diff --git a/doc/rsyslog_secure_tls.html b/doc/rsyslog_secure_tls.html new file mode 100644 index 00000000..b15e5a4e --- /dev/null +++ b/doc/rsyslog_secure_tls.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: recommended scenario</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-06-17)</i></small></p> +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +<li><a href="tls_cert_errmsgs.html">Frequently seen Error Messages</a> +</ul> + +<h2>Overview</h2> +<p>This document describes a secure way to set up rsyslog TLS. A secure logging +environment requires more than just encrypting the transmission channel. This document +provides one possible way to create such a secure system. +<p>Rsyslog's TLS authentication can be used very flexible and thus supports a +wide range of security policies. This section tries to give some advise on a +scenario that works well for many environments. However, it may not be suitable +for you - please assess you security needs before using the recommendations +below. Do not blame us if it doesn't provide what you need ;)</p> +<p>Our policy offers these security benefits:</p> +<ul> + <li>syslog messages are encrypted while traveling on the wire</li> + <li>the syslog sender authenticates to the syslog receiver; thus, the + receiver knows who is talking to it</li> + <li>the syslog receiver authenticates to the syslog sender; thus, the sender + can check if it indeed is sending to the expected receiver</li> + <li>the mutual authentication prevents man-in-the-middle attacks</li> +</ul> +<p>Our secrity goals are achived via public/private key security. As such, it is +vital that private keys are well protected and not accessible to third parties. +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +If private keys have become known to third parties, the system does not provide +any security at all. Also, our solution bases on X.509 certificates and a (very +limited) chain of trust. We have one instance (the CA) that issues all machine +certificates. The machine certificate indentifies a particular machine. hile in +theory (and practice), there could be several "sub-CA" that issues machine +certificates for a specific adminitrative domain, we do not include this in our +"simple yet secure" setup. If you intend to use this, rsyslog supports it, but +then you need to dig a bit more into the documentation (or use the forum to ask). +In general, if you depart from our simple model, you should have good reasons +for doing so and know quite well what you are doing - otherwise you may +compromise your system security.</p> +<p>Please note that security never comes without effort. In the scenario +described here, we have limited the effort as much as possible. What remains is +some setup work for the central CA, the certificate setup for each machine as +well as a few configuration commands that need to be applied to all of them. +Proably the most important limiting factor in our setup is that all senders and +receivers must support IETF's syslog-transport-tls standard (which is not +finalized yet). We use mandatory-to-implement technology, yet you may have +trouble finding all required features in some implementations. More often, +unfortunately, you will find that an implementation does not support the +upcoming IETF standard at all - especially in the "early days" (starting May +2008) when rsyslog is the only implementation of said standard.</p> +<p>Fortunately, rsyslog supports allmost every protocol that is out there in the +syslog world. So in cases where transport-tls is not available on a sender, we +recommend to use rsyslog as the initial relay. In that mode, the not-capabe +sender sends to rsyslog via another protocol, which then relays the message via +transport-tls to either another interim relay or the final destination (which, +of course, must by transport-tls capable). In such a scenario, it is best to try +see what the sender support. Maybe it is possible to use industry-standard plain +tcp syslog with it. Often you can even combine it with stunnel, which then, too, +enables a secure delivery to the first rsyslog relay. If all of that is not +possible, you can (and often must...) resort to UDP. Even though this is now +lossy and insecure, this is better than not having the ability to listen to that +device at all. It may even be reasonale secure if the uncapable sender and the +first rsyslog relay communicate via a private channel, e.g. a dedicated network +link.</p> +<p>One final word of caution: transport-tls protects the connection between the +sender and the receiver. It does not necessarily protect against attacks that +are present in the message itself. Especially in a relay environment, the +message may have been originated from a malicious system, which placed invalid +hostnames and/or other content into it. If there is no provisioning against such +things, these records may show up in the receivers' repository. -transport-tls +does not protect against this (but it may help, properly used). Keep in mind +that syslog-transport-tls provides hop-by-hop security. It does not provide +end-to-end security and it does not authenticate the message itself (just the +last sender).</p> +<h3>A very quick Intro</h3> +<p>If you'd like to get all information very rapidly, the graphic below contains +everything you need to know (from the certificate perspective) in a very condensed +manner. It is no surprise if the graphic puzzles you. In this case, <a href="tls_cert_scenario.html">simply read on</a> +for full instructions. +<p> +<img align="center" alt="TLS/SSL protected syslog" src="tls_cert.jpg"> +<h3>Feedback requested</h3> +<p>I would appreciate feedback on this tutorial. If you have +additional ideas, comments or find bugs (I *do* bugs - no way... ;)), +please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</p> +<h2>Revision History</h2> +<ul> +<li>2008-06-06 * <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> * Initial Version created</li> +<li>2008-06-18 * <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> * Greatly enhanced and modularized the doc</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/rsyslog_stunnel.html b/doc/rsyslog_stunnel.html new file mode 100644 index 00000000..f0c0b3af --- /dev/null +++ b/doc/rsyslog_stunnel.html @@ -0,0 +1,249 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<a href="features.html">back</a> + +<title>SSL Encrypting syslog with stunnel</title><meta name="KEYWORDS" content="syslog encryption, rsyslog, stunnel, secure syslog, tcp, reliable, howto, ssl"></head><body> +<h1>SSL Encrypting Syslog with Stunnel</h1> + <p><small><i>Written by + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer + Gerhards</a> (2005-07-22)</i></small></p> +<h2>Abstract</h2> +<p><i><b>In this paper, I describe how to encrypt <a href="http://www.monitorware.com/en/topics/syslog/">syslog</a> +messages on the network.</b> Encryption +is vital to keep the confidiental content of syslog messages secure. I describe the overall +approach and provide an HOWTO do it with the help of +<a href="http://www.rsyslog.com">rsyslogd</a> and <a href="http://www.stunnel.org">stunnel</a>.</i></p><p><span style="font-weight: bold;">Please note that starting with rsyslog 3.19.0, </span><a style="font-weight: bold;" href="rsyslog_tls.html">rsyslog provides native TLS/SSL encryption</a><span style="font-weight: bold;"> <span style="font-style: italic;">without</span> the need of stunnel. </span>I +strongly recomend to use that feature instead of stunnel. The stunnel +documentation here is mostly provided for backwards compatibility. New +deployments are advised to use native TLS mode.<i></i></p> +<h2>Background</h2> +<p><b>Syslog is a +clear-text protocol. That means anyone with a sniffer can have +a peek at your data.</b> In some environments, this is no problem at all. In +others, it is a huge setback, probably even preventing deployment of syslog +solutions. Thankfully, there is an easy way to encrypt syslog communication. I +will describe one approach in this paper.</p> +<p>The most straightforward solution would be that the syslogd itself encrypts +messages. Unfortuantely, encryption is only standardized in +<a href="http://www.monitorware.com/Common/en/glossary/rfc3195.php">RFC 3195</a>. But there +is currently no syslogd that implements RFC 3195's encryption features, +so this route leads to nothing. Another approach would be to use vendor- or +project-specific syslog extensions. There are a few around, but the problem here +is that they have compatibility issues. However, there is one surprisingly easy +and interoperable solution: though not standardized, many vendors and projects +implement plain tcp syslog. In a nutshell, plain tcp syslog is a mode where +standard syslog messages are transmitted via tcp and records are separated by +newline characters. This mode is supported by all major syslogd's (both on Linux/Unix +and Windows) as well as log sources (for example, +<a href="http://www.eventreporter.com/en/">EventReporter</a> for Windows +Event Log forwarding). Plain tcp syslog offers reliability, but it does not +offer encryption in itself. However, since it operates on a tcp stream, it is now easy +to add encryption. There are various ways to do that. In this paper, I will +describe how it is done with stunnel (an +other alternative would be <a href="http://en.wikipedia.org/wiki/IPSec">IPSec</a>, for example).</p> +<p>Stunnel is open source and it is available both for Unix/Linux and Windows. +It provides a way to + use ssl communication for any non-ssl aware client and server - in this case, + our syslogd.</p> + <p>Stunnel works much like a wrapper. Both on the client and on the server machine, + tunnel portals are created. The non-ssl aware client and server software is + configured to not directly talk to the remote partner, but to the local + (s)tunnel portal instead. Stunnel, in turn, takes the data received from the + client, encrypts it via ssl, sends it to the remote tunnel portal and that + remote portal sends it to the recipient process on the remote machine. The + transfer to the portals is done via unencrypted communication. As such, + it is vital that + the portal and the respective program that is talking to it are on the same + machine, otherwise data would travel partly unencrypted. Tunneling, as done by stunnel, + requires connection oriented communication. This is why you need to use + tcp-based syslog. As a side-note, you can also encrypt a plain-text RFC + 3195 session via stunnel, though this definitely is not what the + protocol designers had on their mind ;)</p> +<p>In the rest of this document, I assume that you use rsyslog on both the +client and the server. For the samples, I use <a href="http://www.debian.org/">Debian</a>. +Interestingly, there are +some annoying differences between stunnel implementations. For example, on +Debian a comment line starts with a semicolon (';'). On +<a href="http://www.redhat.com">Red Hat</a>, it starts with +a hash sign ('#'). So you need to watch out for subtle issues when setting up +your system.</p> +<h2>Overall System Setup</h2> +<p>In ths paper, I assume two machines, one named "client" and the other named "server". +It is obvious that, in practice, you will probably have multiple clients but +only one server. Syslog traffic shall be transmitted via stunnel over the +network. Port 60514 is to be used for that purpose. The machines are set up as +follows:</p> +<p><b>Client</b></p> +<ul> + <li>rsyslog forwards message to stunnel local portal at port 61514</li> + <li>local stunnel forwards data via the network to port 60514 to its remote + peer</li> +</ul> +<p><b>Server</b></p> +<ul> + <li>stunnel listens on port 60514 to connections from its client peers</li> + <li>all connections are forwarded to the locally-running rsyslog listening + at port 61514</li> +</ul> +<h2>Setting up the system</h2> +<p>For Debian, you need the "stunnel4" package. The "stunnel" package is the +older 3.x release, which will not support the configuration I describe below. +Other distributions might have other names. For example, on Red Hat it is just "stunnel". +Make sure that you install the appropriate package on both the client and the +server. It is also a good idea to check if there are updates for either stunnel +or openssl (which stunnel uses) - there are often security fixes available and +often the latest fixes are not included in the default package.</p> +<p>In my sample setup, I use only the bare minimum of options. For example, I do +not make the server check client cerficiates. Also, I do not talk much about +certificates at all. If you intend to really secure your system, you should +probably learn about certificates and how to manage and deploy them. This is +beyond the scope of this paper. For additional information, +<a href="http://www.stunnel.org/faq/certs.html"> +http://www.stunnel.org/faq/certs.html</a> is a good starting point.</p> +<p>You also need to install rsyslogd on both machines. Do this before starting +with the configuration. You should also familarize yourself with its +configuration file syntax, so that you know which actions you can trigger with +it. Rsyslogd can work as a drop-in replacement for stock +<a href="http://www.infodrom.org/projects/sysklogd/">sysklogd</a>. So if you know +the standard syslog.conf syntax, you do not need to learn any more to follow +this paper.</p> +<h3>Server Setup</h3> +<p>At the server, you need to have a digital certificate. That certificate +enables SSL operation, as it provides the necessary crypto keys being used to +secure the connection. Many versions of stunnel come with a default certificate, +often found in /etc/stunnel/stunnel.pem. If you have it, it is good for testing +only. If you use it in production, it is very easy to break into your secure +channel as everybody is able to get hold of your private key. I didn't find an +stunnel.pem on my Debian machine. I guess the Debian folks removed it because of +its insecurity.</p> +<p>You can create your own certificate with a simple openssl tool - you need to +do it if you have none and I highly recommend to create one in any case. To +create it, cd to /etc/stunnel and type:</p> +<p></p><blockquote><code>openssl req -new -x509 -days 3650 -nodes -out +stunnel.pem -keyout stunnel.pem</code></blockquote><p></p> +<p>That command will ask you a number of questions. Provide some answer for +them. If you are unsure, read +<a href="http://www.stunnel.org/faq/certs.html"> +http://www.stunnel.org/faq/certs.html</a>. After the command has finished, you +should have a usable stunnel.pem in your working directory.</p> +<p>Next is to create a configuration file for stunnel. It will direct stunnel +what to do. You can used the following basic file:</p> +<p></p><blockquote><code></code><pre>; Certificate/key is needed in server mode<br>cert = /etc/stunnel/stunnel.pem<br><br><i>; Some debugging stuff useful for troubleshooting<br>debug = 7<br>foreground=yes</i> + +[ssyslog] +accept = 60514 +connect = 61514</pre> +</blockquote><p></p> +<p>Save this file to e.g. /etc/stunnel/syslog-server.conf. Please note that the +settings in <i>italics</i> are for debugging only. They run stunnel +with a lot of debug information in the foreground. This is very valuable while +you setup the system - and very useless once everything works well. So be sure +to remove these lines when going to production.</p> +<p>Finally, you need to start the stunnel daemon. Under Debian, this is done via +"stunnel /etc/stunnel/syslog.server.conf". If you have enabled the debug +settings, you will immediately see a lot of nice messages.</p> +<p>Now you have stunnel running, but it obviously unable to talk to rsyslog - +because it is not yet running. If not already done, configure it so that it does +everything you want. If in doubt, you can simply copy /etc/syslog.conf to /etc/rsyslog.conf +and you probably have what you want. The really important thing in rsyslogd +configuration is that you must make it listen to tcp port 61514 (remember: this +is where stunnel send the messages to). Thankfully, this is easy to achive: just +add "-t 61514" to the rsyslogd startup options in your system startup script. +After done so, start (or restart) rsyslogd.</p> +<p>The server should now be fully operational.</p> +<h3>Client Setup</h3> +<p>The client setup is simpler. Most importantly, you do not need a certificate +(of course, you can use one if you would like to authenticate the client, but +this is beyond the scope of this paper). So the basic thing you need to do is +create the stunnel configuration file.</p> +<p></p><blockquote><code></code><pre><i>; Some debugging stuff useful for troubleshooting<br>debug = 7<br>foreground=yes</i> + +<b>client=yes</b> + +[ssyslog] +accept = 127.0.0.1:61514 +connect = <font color="#ff0000">192.0.2.1</font>:60514<br></pre> +</blockquote><p></p> +<p>Again, the text in <i>italics</i> is for debugging purposes only. I suggest +you leave it in during your initial testing and then remove it. The most +important difference to the server configuration outlined above is the "client=yes" +directive. It is what makes this stunnel behave like a client. The accept +directive binds stunnel only to the local host, so that it is protected from +receiving messages from the network (somebody might fake to be the local sender). +The address "192.0.2.1" is the address of the server machine. You must change it +to match your configuration. Save this file to /etc/stunnel/syslog-client.conf.</p> +<p>Then, start stunnel via "stunnel4 /etc/stunnel/syslog-client.conf". Now +you should see some startup messages. If no errors appear, you have a running +client stunnel instance.</p> +<p>Finally, you need to tell rsyslogd to send data to the remote host. In stock +syslogd, you do this via the "@host" forwarding directive. The same works with +rsyslog, but it suppports extensions to use tcp. Add the following line to your +/etc/rsyslog.conf:</p> +<p></p><blockquote><code></code><pre>*.* @<font color="#ff0000">@</font>127.0.0.1:61514<br></pre> +</blockquote><i><p></p> + +</i> + +<p>Please note the double at-sign (@@). This is no typo. It tells rsyslog to use +tcp instead of udp delivery. In this sample, all messages are forwarded to the +remote host. Obviously, you may want to limit this via the usual rsyslog.conf +settings (if in doubt, use man rsyslog.con).</p> +<p>You do not need to add any special startup settings to rsyslog on the client. +Start or restart rsyslog so that the new configuration setting takes place.</p> +<h3>Done</h3> +<p>After following these steps, you should have a working secure syslog +forwarding system. To verify, you can type "logger test" or a similar smart +command on the client. It should show up in the respective server log file. If +you dig out you sniffer, you should see that the traffic on the wire is actually +protected. In the configuration use above, the two stunnel endpoints should be +quite chatty, so that you can follow the action going on on your system.</p> +<p>If you have only basic security needs, you can probably just remove the debug +settings and take the rest of the configuration to production. If you are +security-sensitve, you should have a look at the various stunnel settings that +help you further secure the system.</p> +<h2>Preventing Systems from talking directly to the rsyslog Server</h2> +<p>It is possible that remote systems (or attackers) talk to the rsyslog server +by directly connecting to its port 61514. Currently (July of 2005), rsyslog does +not offer the ability to bind to the local host, only. This feature is planned, +but as long as it is missing, rsyslog must be protected via a firewall. This can +easily be done via e.g iptables. Just be sure not to forget it.</p> +<h2>Conclusion</h2> +<p>With minumal effort, you can set up a secure logging infrastructure employing +ssl encrypted syslog message transmission. As a side note, you also have the +benefit of reliable tcp delivery which is far less prone to message loss than +udp.</p> +<h3>Feedback requested</h3> +<p>I would appreciate feedback on this tutorial. If you have additional ideas, +comments or find bugs (I *do* bugs - no way... ;)), please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</p> +<h2>Revision History</h2> +<ul> + <li>2005-07-22 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> * Initial Version created</li> + <li>2005-07-26 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> * Some text brush-up, hyperlinks added</li> + <li>2005-08-03 * + <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * license added</li><li>2008-05-05 * <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> + * updated to reflect native TLS capability of rsyslog 3.19.0 and above</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html"> +http://www.gnu.org/copyleft/fdl.html</a>.</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body></html> diff --git a/doc/rsyslog_tls.html b/doc/rsyslog_tls.html new file mode 100644 index 00000000..286660d2 --- /dev/null +++ b/doc/rsyslog_tls.html @@ -0,0 +1,303 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS (SSL) Encrypting syslog</title> +<a href="features.html">back</a> + +<meta name="KEYWORDS" content="syslog encryption, rsyslog, secure syslog, tcp, reliable, howto, ssl, tls"> +</head> +<body> +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-05-06)</i></small></p> +<h2>Abstract</h2> +<p><i><b>In this paper, I describe how to encrypt <a href="http://www.monitorware.com/en/topics/syslog/">syslog</a> +messages on the network.</b> Encryption +is vital to keep the confidiental content of syslog messages secure. I +describe the overall +approach and provide an HOWTO do it with <a href="http://www.rsyslog.com">rsyslog's</a> TLS +features. </i></p> +<p>Please +note that TLS is the more secure successor of SSL. While people often +talk about "SSL encryption" they actually mean "TLS encryption". So +don't look any further if you look for how to SSL-encrypt syslog. You +have found the right spot.</p> +<p>This is a quick guide. There is a more elaborate guide currently +under construction which provides a much more secure environment. It +is highly recommended to +<a href="rsyslog_secure_tls.html">at least have a look at it</a>. +<h2>Background</h2> +<p><b>Traditional syslog is a clear-text protocol. That +means anyone with a sniffer can have a peek at your data.</b> In +some environments, this is no problem at all. In others, it is a huge +setback, probably even preventing deployment of syslog solutions. +Thankfully, there are easy ways to encrypt syslog +communication. </p> +The traditional approach involves <a href="rsyslog_stunnel.html">running +a wrapper like stunnel around the syslog session</a>. This works +quite well and is in widespread use. However, it is not thightly +coupled with the main syslogd and some, even severe, problems can +result from this (follow a mailing list thread that describes <a href="http://lists.adiscon.net/pipermail/rsyslog/2008-March/000580.html">total +loss of syslog messages due to stunnel mode</a> and the <a href="http://rgerhards.blogspot.com/2008/04/on-unreliability-of-plain-tcp-syslog.html">unreliability +of TCP syslog</a>). +<p><a href="gssapi.html">Rsyslog supports syslog via +GSSAP</a>I since long to overcome these limitatinos. However, +syslog via GSSAPI is a rsyslog-exclusive transfer mode and it requires +a proper Kerberos environment. As such, it isn't a really universal +solution. The <a href="http://www.ietf.org/">IETF</a> +has begun standardizing syslog over plain tcp over +TLS for a while now. While I am not fully satisfied with the results so +far, this obviously has the potential to become the long-term +solution. The Internet Draft in question, syslog-transport-tls has been +dormant for some time but is now (May of 2008) again being worked on. I +expect it to turn into a RFC within the next 12 month (but don't take +this for granted ;)). I didn't want to wait for it, because there +obviously is need for TLS syslog right now (and, honestly, I have +waited long enough...). Consequently, I have +implemented the current draft, with some interpretations I made (there +will be a compliance doc soon). So in essence, a TLS-protected syslog +transfer mode is available right now. As a side-note, Rsyslog +is the world's first +implementation of syslog-transport-tls.</p> +<p>Please note that in theory it should be compatible with other, +non IETF syslog-transport-tls implementations. If you would like to run +it with something else, please let us know so that we can create a +compatibility list (and implement compatbility where it doesn't yet +exist). </p> +<h2>Overall System Setup</h2> +<p>Encryption requires a reliable stream. So It will not work +over UDP syslog. In rsyslog, network transports utilize a so-called +"network stream layer" (netstream for short). This layer provides a +unified view of the transport to the application layer. The plain TCP +syslog sender and receiver are the upper layer. The driver layer +currently consists of the "ptcp" and "gtls" library plugins. "ptcp" +stands for "plain tcp" and is used for unencrypted message transfer. It +is also used internally by the gtls driver, so it must always be +present on a system. The "gtls" driver is for GnutTLS, a TLS library. +It is used for encrypted message transfer. In the future, additional +drivers will become available (most importantly, we would like to +include a driver for NSS).</p> +<p>What you need to do to build an encrypted syslog channel is to +simply use the proper netstream drivers on both the client and the +server. Client, in the sense of this document, is the rsyslog system +that is sending syslog messages to a remote (central) loghost, which is +called the server. In short, the setup is as follows:</p> +<p><b>Client</b></p> +<ul> +<li>forwards messages via plain tcp syslog using gtls netstream +driver to central sever on port 10514<br> +</li> +</ul> +<p><b>Server</b></p> +<ul> +<li>accept incoming messages via plain tcp syslog using gtls +netstream driver on port 10514</li> +</ul> +<h2>Setting up the system</h2> +<h3>Server Setup</h3> +<p>At the server, you need to have a digital certificate. That +certificate enables SSL operation, as it provides the necessary crypto +keys being used to secure the connection. There is a set of default +certificates in ./contrib/gnutls. These are key.pem and cert.pem. These +are good for testing. If you use it in production, +it is very easy to break into your secure channel as everybody is able +to get hold of your private key. So it is a good idea to +generate the key and certificate yourself.</p> +<p>You also need a root CA certificate. Again, there is a sample +CA certificate in ./contrib/gnutls, named ca.cert. It is suggested to +generate your own.</p> +<p>To configure the server, you need to tell it where are its +certificate files, to use the gtls driver and start up a listener. This +is done as follows:<br> +</p> +<blockquote><code></code> +<pre># make gtls driver the default +$DefaultNetstreamDriver gtls + +# certificate files +$DefaultNetstreamDriverCAFile /path/to/contrib/gnutls/ca.pem +$DefaultNetstreamDriverCertFile /path/to/contrib/gnutls/cert.pem +$DefaultNetstreamDriverKeyFile /path/to/contrib/gnutls/key.pem + +$ModLoad imtcp # load TCP listener + +$InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode +$InputTCPServerStreamDriverAuthMode anon # client is NOT authenticated +$InputTCPServerRun 10514 # start up listener at port 10514 +</pre> +</blockquote> +This is all you need to do. You can use the rest of your rsyslog.conf +together with this configuration. The way messages are received does +not interfer with any other option, so you are able to do anything else +you like without any restrictions. +<p>Restart rsyslogd. The server should now be fully +operational.</p> +<h3>Client Setup</h3> +<p>The client setup is equally simple. You need less +certificates, just the CA cert. </p> +<blockquote> +<pre># certificate files - just CA for a client +$DefaultNetstreamDriverCAFile /path/to/contrib/gnutls/ca.pem + +# set up the action +$DefaultNetstreamDriver gtls # use gtls netstream driver +$ActionSendStreamDriverMode 1 # require TLS for the connection +$ActionSendStreamDriverAuthMode anon # server is NOT authenticated +*.* @@(o)server.example.net:10514 # send (all) messages + +</pre> +</blockquote> +<p>Note that we use the regular TCP forwarding syntax (@@) here. +There is nothing special, because the encryption is handled by the +netstream driver. So I have just forwarded every message (*.*) for +simplicity - you can use any of rsyslog's filtering capabilities (like +epxression-based filters or regular expressions). Note that the "(o)" +part is not strictly necessary. It selects octet-based framing, which +provides compatiblity to IETF's syslog-transport-tls draft. Besides +compatibility, this is also a more reliable transfer mode, so I suggest +to always use it.</p> +<h3>Done</h3> +<p>After +following these steps, you should have a working secure +syslog forwarding system. To verify, you can type "logger test" or a +similar "smart" command on the client. It should show up in the +respective server log file. If you dig out your sniffer, you should see +that the traffic on the wire is actually protected.</p> +<h3>Limitations</h3> +<p>The +RELP transport can currently not be protected by TLS. A work-around is +to use stunnel. TLS support for RELP will be added once plain TCP +syslog has sufficiently matured and there either is some time left to do this +or we find a sponsor ;).</p> +<h2>Certificates</h2> +<p>In order to be really secure, certificates are needed. This is +a short summary on how to generate the necessary certificates with +GnuTLS' certtool. You can also generate certificates via other tools, +but as we currently support GnuTLS as the only TLS library, we thought +it is a good idea to use their tools.<br> +</p> +<p>Note that this section aims at people who are not involved +with PKI at all. The main goal is to get them going in a reasonable +secure way. </p> +<h3>CA Certificate</h3> +<p>This is used to sign all of your other certificates. The CA +cert must be trusted by all clients and servers. The private key must +be well-protected and not given to any third parties. The certificate +itself can (and must) be distributed. To generate it, do the following:</p> +<ol> +<li>generate the private key: +<pre>certtool --generate-privkey --outfile ca-key.pem</pre> +<br> +This takes a short while. Be sure to do some work on your workstation, +it waits for radom input. Switching between windows is sufficient ;) +</li> +<li>now create the (self-signed) CA certificate itself:<br> +<pre>certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca.pem</pre> +This generates the CA certificate. This command queries you for a +number of things. Use appropriate responses. When it comes to +certificate validity, keep in mind that you need to recreate all +certificates when this one expires. So it may be a good idea to use a +long period, eg. 3650 days (roughly 10 years). You need to specify that +the certificates belongs to an authrity. The certificate is used to +sign other certificates.<br> +</li> +<li>You need to distribute this certificate +to all peers and you need to point to it via the +$DefaultNetstreamDriverCAFile config directive. All other certificates +will be issued by this CA.<br> +Important: do only distribute the ca.pem, NOT ca-key.pem (the private +key). Distributing the CA private key would totally breach security as +everybody could issue new certificates on the behalf of this CA. +</li> +</ol> +<h3>Individual Peer Certificate</h3> +<p>Each peer (be it client, server or both), needs a certificate +that conveys its identity. Access control is based on these +certificates. You can, for example, configure a server to accept +connections only from configured clients. The client ID is taken from +the client instances certificate. So as a general rule of thumb, you +need to create a certificate for each instance of rsyslogd that you +run. That instance also needs the private key, so that it can properly +decrypt the traffic. Safeguard the peer's private key file. If somebody +gets hold of it, it can malicously pretend to be the compromised host. +If such happens, regenerate the certificate and make sure you use a +different name instead of the compromised one (if you use name-based +authentication). </p> +<p>These are the steps to generate the indivudual certificates +(repeat: you need to do this for every instance, do NOT share the +certificates created in this step):</p> +<ol> +<li>generate a private key (do NOT mistake this with the CA's +private key - this one is different):<br> +<pre>certtool --generate-privkey --outfile key.pem</pre> +Again, this takes a short while.</li> +<li>generate a certificate request:<br> +<pre>certtool --generate-request --load-privkey key.pem --outfile request.pem</pre> +If you do not have the CA's private key (because you are not authorized +for this), you can send the certificate request to the responsible +person. If you do this, you can skip the remaining steps, as the CA +will provide you with the final certificate. If you submit the request +to the CA, you need to tell the CA the answers that you would normally +provide in step 3 below. +</li> +<li>Sign (validate, authorize) the certificate request and +generate the instances certificate. You need to have the CA's +certificate and private key for this:<br> +<pre>certtool --generate-certificate --load-request request.pem --outfile cert.pem \<br> --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem</pre> +Answer questions as follows: Cert does not belogn to an authority; it +is a TLS web server and client certificate; the dnsName MUST be the +name of the peer in question (e.g. centralserver.example.net) - this is +the name used for authenticating the peers. Please note that you may +use an IP address in dnsName. This is a good idea if you would like to +use default server authentication and you use selector lines with IP +addresses (e.g. "*.* @@192.168.0.1") - in that case you need to select +a dnsName of 192.168.0.1. But, of course, changing the server IP then +requires generating a new certificate.</li> +</ol> +After you have generated the certificate, you need to place it onto the +local machine running rsyslogd. Specify the certificate and key via the +$DefaultNetstreamDriverCertFile /path/to/cert.pem and +$DefaultNetstreamDriverKeyFile /path/to/key.pem configuration +directives. Make sure that nobody has access to key.pem, as that would +breach security. And, once again: do NOT use these files on more than +one instance. Doing so would prevent you from distinguising between the +instances and thus would disable useful authentication. +<h3>Troubleshooting Certificates</h3> +<p>If you experience trouble with your certificate setup, it may +be +useful to get some information on what is contained in a specific +certificate (file). To obtain that information, do </p> +<pre>$ certtool --certificate-info --infile cert.pem</pre> +<p>where "cert.pem" can be replaced by the various certificate pem files (but it does not work with the key files).</p> +<h2>Conclusion</h2> +<p>With minumal effort, you can set up a secure logging +infrastructure employing TLS encrypted syslog message transmission.</p> +<h3>Feedback requested</h3> +<p>I would appreciate feedback on this tutorial. If you have +additional ideas, comments or find bugs (I *do* bugs - no way... ;)), +please +<a href="mailto:rgerhards@adiscon.com">let me know</a>.</p> +<h2>Revision History</h2> +<ul> +<li>2008-05-06 * <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> * Initial Version created</li><li>2008-05-26 * <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> * added information about certificates</li> +</ul> +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> + +</body></html> diff --git a/doc/sigprov_gt.html b/doc/sigprov_gt.html new file mode 100644 index 00000000..caeee116 --- /dev/null +++ b/doc/sigprov_gt.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<meta http-equiv="Content-Language" content="en"> +<title>GuardTime Log Signature Provider (gt)</title> +</head> + +<body> +<a href="rsyslog_conf_modules.html">back to rsyslog module overview</a> + +<h1>GuardTime Log Signature Provider (gt)</h1> +<p><b>Signature Provider Name: gt</b></p> +<p><b>Author: </b>Rainer Gerhards <rgerhards@adiscon.com></p> +<p><b>Supported Since: </b>since 7.3.9 +<p><b>Description</b>:</p> +<p>Provides the ability to sign syslog messages via the +GuardTime signature services. +</p> + +<p><b>Configuration Parameters</b>:</p> +<p>Signature providers are loaded by omfile, when the +provider is selected in its "sig.providerName" parameter. +Parameters for the provider are given in the omfile action instance +line. +<p>This provider creates a signature file with the same base name but +the extension ".gtsig" for each log file (both for fixed-name files +as well as dynafiles). Both files together form a set. So you need to +archive both in order to prove integrity. +<ul> +<li><b>sig.hashFunction</b> <Hash Algorithm><br> +The following hash algorithms are currently supported: + <ul> + <li>SHA1 + <li>RIPEMD-160 + <li>SHA2-224 + <li>SHA2-256 + <li>SHA2-384 + <li>SHA2-512 + </ul> +</li> +<li><b>sig.timestampService</b> <timestamper URL><br> +This provides the URL of the timestamper service. If not selected, +a default server is selected. This may not necessarily be a good +one for your region. +</li> +<li><b>sig.block.sizeLimit</b> <nbr-records><br> +The maximum number of records inside a single signature block. By +default, there is no size limit, so the signature is only written +on file closure. Note that a signature request typically takes between +one and two seconds. So signing to frequently is probably not a good +idea. +</li> +<li><b>sig.keepRecordHashes</b> <on/<b>off</b>><br> +Controls if record hashes are written to the .gtsig file. This +enhances the ability to spot the location of a signature breach, +but costs considerable disk space (65 bytes for each log record +for SHA2-512 hashes, for example). +</li> +<li><b>sig.keepTreeHashes</b> <on/<b>off</b>><br> +Controls if tree (intermediate) hashes are written to the .gtsig file. This +enhances the ability to spot the location of a signature breach, +but costs considerable disk space (a bit mire than the amount +sig.keepRecordHashes requries). Note that both Tree and Record +hashes can be kept inside the signature file. +</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>currently none known +</li> +</ul> +<p><b>Samples:</b></p> +<p>This writes a log file with it's associated signature file. Default +parameters are used. +</p> +<textarea rows="3" cols="60"> +action(type="omfile" file="/var/log/somelog" + sig.provider="gt") +</textarea> + +<p>In the next sample, we use the more secure SHA2-512 hash function, +sign every 10,000 records and Tree and Record hashes are kept. +<textarea rows="3" cols="60"> +action(type="omfile" file="/var/log/somelog" + sig.provider="gt" sig.hashfunction="SHA2-512" + sig.block.sizelimit="10000" + sig.keepTreeHashes="on" sig.keepRecordHashes="on") +</textarea> + + +<p>[<a href="rsyslog_conf.html">rsyslog.conf overview</a>] +[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> +project.<br> +Copyright © 2013 by +<a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/src/classes.dia b/doc/src/classes.dia Binary files differnew file mode 100644 index 00000000..8cfcbd0c --- /dev/null +++ b/doc/src/classes.dia diff --git a/doc/src/dataflow.dia b/doc/src/dataflow.dia Binary files differnew file mode 100644 index 00000000..3875fc61 --- /dev/null +++ b/doc/src/dataflow.dia diff --git a/doc/src/direct_queue0.dia b/doc/src/direct_queue0.dia Binary files differnew file mode 100644 index 00000000..4446619b --- /dev/null +++ b/doc/src/direct_queue0.dia diff --git a/doc/src/direct_queue1.dia b/doc/src/direct_queue1.dia Binary files differnew file mode 100644 index 00000000..7a64ea09 --- /dev/null +++ b/doc/src/direct_queue1.dia diff --git a/doc/src/direct_queue2.dia b/doc/src/direct_queue2.dia Binary files differnew file mode 100644 index 00000000..b0c394c0 --- /dev/null +++ b/doc/src/direct_queue2.dia diff --git a/doc/src/direct_queue3.dia b/doc/src/direct_queue3.dia Binary files differnew file mode 100644 index 00000000..bc477b25 --- /dev/null +++ b/doc/src/direct_queue3.dia diff --git a/doc/src/direct_queue_directq.dia b/doc/src/direct_queue_directq.dia Binary files differnew file mode 100644 index 00000000..37fdb44c --- /dev/null +++ b/doc/src/direct_queue_directq.dia diff --git a/doc/src/direct_queue_rsyslog.dia b/doc/src/direct_queue_rsyslog.dia Binary files differnew file mode 100644 index 00000000..9a030117 --- /dev/null +++ b/doc/src/direct_queue_rsyslog.dia diff --git a/doc/src/direct_queue_rsyslog2.dia b/doc/src/direct_queue_rsyslog2.dia Binary files differnew file mode 100644 index 00000000..c596f39f --- /dev/null +++ b/doc/src/direct_queue_rsyslog2.dia diff --git a/doc/src/module_workflow.dia b/doc/src/module_workflow.dia Binary files differnew file mode 100644 index 00000000..178571f4 --- /dev/null +++ b/doc/src/module_workflow.dia diff --git a/doc/src/queueWorkerLogic.dia b/doc/src/queueWorkerLogic.dia Binary files differnew file mode 100644 index 00000000..068ea50c --- /dev/null +++ b/doc/src/queueWorkerLogic.dia diff --git a/doc/src/queue_analogy_tv.dia b/doc/src/queue_analogy_tv.dia Binary files differnew file mode 100644 index 00000000..00fbdeb5 --- /dev/null +++ b/doc/src/queue_analogy_tv.dia diff --git a/doc/src/rfc5424layers.dia b/doc/src/rfc5424layers.dia Binary files differnew file mode 100644 index 00000000..300b7796 --- /dev/null +++ b/doc/src/rfc5424layers.dia diff --git a/doc/src/rsyslog_pgsql.odt b/doc/src/rsyslog_pgsql.odt Binary files differnew file mode 100644 index 00000000..5034c5fb --- /dev/null +++ b/doc/src/rsyslog_pgsql.odt diff --git a/doc/src/rsyslog_queue_pointers.dia b/doc/src/rsyslog_queue_pointers.dia Binary files differnew file mode 100644 index 00000000..2ad4cacb --- /dev/null +++ b/doc/src/rsyslog_queue_pointers.dia diff --git a/doc/src/rsyslog_queue_pointers2.dia b/doc/src/rsyslog_queue_pointers2.dia Binary files differnew file mode 100644 index 00000000..6a35c664 --- /dev/null +++ b/doc/src/rsyslog_queue_pointers2.dia diff --git a/doc/src/tls.dia b/doc/src/tls.dia Binary files differnew file mode 100644 index 00000000..d7c9811d --- /dev/null +++ b/doc/src/tls.dia diff --git a/doc/src/tls_cert.dia b/doc/src/tls_cert.dia Binary files differnew file mode 100644 index 00000000..e76431df --- /dev/null +++ b/doc/src/tls_cert.dia diff --git a/doc/src/tls_cert_100.dia b/doc/src/tls_cert_100.dia Binary files differnew file mode 100644 index 00000000..baed5e0f --- /dev/null +++ b/doc/src/tls_cert_100.dia diff --git a/doc/src/tls_cert_ca.dia b/doc/src/tls_cert_ca.dia Binary files differnew file mode 100644 index 00000000..7ce27a8d --- /dev/null +++ b/doc/src/tls_cert_ca.dia diff --git a/doc/syslog_parsing.html b/doc/syslog_parsing.html new file mode 100644 index 00000000..1ccec6f1 --- /dev/null +++ b/doc/syslog_parsing.html @@ -0,0 +1,210 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>syslog parsing in rsyslog</title> +</head> +<body> +<h1>syslog parsing in rsyslog</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2008-09-23)</i></small></p> +<p><b>We regularly receive messages asking why <a href="http://www.rsyslog.com">rsyslog</a> +parses this or that message incorrectly.</b> Of course, it turns out that rsyslog does +the right thing, but the message sender does not. And also of course, this is not even +of the slightest help to the end user experiencing the problem ;). So I thought I write this +paper. It describes the problem source and shows potential solutions (aha!). +<h2>Syslog Standardization</h2> +The syslog protocol has not been standardized until relatively recently.The first document "smelling" a bit +like a standard is <a href="http://www.ietf.org/rfc/rfc3164.txt">RFC 3164</a>, which dates back +to August 2001. The problem is that this document is no real standard. It has assigned "informational" +status by the <a href="http://www.ietf.org">IETF</a> which means it provides some hopefully +useful information but does not demand anything. It is impossible to "comply" to an informational +document. This, of course, doesn't stop marketing guys from telling they comply to RFC3164 and +it also does not stop some techs to tell you "this and that does not comply to RFC3164, so it is +<anybody else but them>'s fault". +<p>Then, there is <a href="http://www.ietf.org/rfc/rfc3195.txt">RFC3195</a>, which is +a real standard. In it's section 3 it makes (a somewhat questionable) reference to (informational) +RFC 3164 which may be interpreted in a way that RFC3195 standardizes the format layed out +in RFC 3164 by virtue of referencing them. So RFC3195 seems to extend its standardization +domain to the concepts layed out in RFC 3164 (which is why I tend to find that refrence +questionable). In that sense, RFC3195 standardizes the format informationally described in +RFC3164, Section 4. But it demands it only for the scope of RFC3195, which is syslog over +BEEP - and NOT syslog over UDP. So one may argue whether or not the RFC3164 format could +be considered a standard for any non-BEEP (including UDP) syslog, too. In the strict view +I tend to have, it does not. Refering to the RFC3195 context usually does not help, +because there are virtually no RFC3195 implementations available (at this time, +I would consider this RFC a failure). +<p>Now let's for a short moment assume that RFC3195 would somehow be able to demand +RFC3164 format for non-BEEP syslog. So we could use RFC3164 format as a standard. But does +that really help? Let's cite RFC 3164, right at the begining of section 4 (actually, this +is the first sentence): +<blockquote> +<pre> + The payload of any IP packet that has a UDP destination port of 514 + MUST be treated as a syslog message. +<pre> +</blockquote> +<p>Think a bit about it: this means that whatever is send to port 514 must be considered +a valid syslog message. No format at all is demanded. So if "this is junk" is sent to +UDP port 514 - voila, we have a valid message (interestingly, it is no longer a syslog +message if it is sent to port 515 ;)). You may now argue that I am overdoing. So let's +cite RFC 3164, Section 5.4, Example 2: +<blockquote> +<pre> + Example 2 + + Use the BFG! + + While this is a valid message, it has extraordinarily little useful + information. +</pre> +</blockquote> +<p>As you can see, RFC3164 explicitely states that no format at all is required. +<p>Now a side-note is due: all of this does not mean that the RFC3164 authors +did not know what they were doing. No, right the contrary is true: RFC3164 mission +is to describe what has been seen in practice as syslog messages and the +conclusion is quite right that there is no common understanding on the +message format. This is also the reason why RFC3164 is an informational document: +it provides useful information, but does not precisely specify anything. +<p>After all of this bashing, I now have to admit that RFC3164 has some format +recommendations layed out in section 4. The format described has quite some +value in it and implementors recently try to follow it. This format is usually meant +when someone tells you that a software is "RFC3164 compliant" or expects "RFC3164 compliant messages". +I also have to admit that rsyslog also uses this format and, in the sense outlined here, +expects messages received to be "RFC3164 compliant" (knowingly that such a beast does not +exist - I am simply lying here ;)). +<p>Please note that there is some relief of the situation in reach. There is a new normative +syslog RFC series upcoming, and it specifies a standard message format. At the time of +this writing, the main documents are sitting in the RFC editor queue waiting for a transport +mapping to be completed. I personally expect them to be assigned RFC numbers in 2009. +<h2>Practical Format Requirements</h2> +<p>From a practical point of view, the message format expected (and generated by +default in legacy mode) is: +<pre><code> +<PRI>TIMESTAMP SP HOST SP TAG MSG(Freetext) +</code></pre> +<p>SP is the ASCII "space" character and the definition of the rest of the fields +can be taken from RFC3164. Please note that there also is a lot of confusion on what +syntax and semantics the TAG actually has. This format is called "legacy syslog" because +it is not well specified (as you know by now) and has been "inherited from the real world". +<p>Rsyslog offers two parsers: one for the upcoming RFC series and one for legacy format. We +concentrate on the later. That parser applies some logic to detect missing hostnames, +is able to handle various ways the TIMESTAMP is typically malformed. In short it applies +a lot of guesswork in trying to figure out what a message really means. I am sure the +guessing algorithm can be improved, and I am always trying that when I see new malformed +messages (and there is an ample set of them...). However, this finds its limits where +it is not possible to differentiate between two entities which could be either. +For example, look at this message: +<pre><code> +<144>Tue Sep 23 11:40:01 taghost sample message +</code></pre> +<p>Does it contain a hostname? Mabye. The value "taghost" is a valid hostname. Of course, it is +also a valid tag. If it is a hostname, the tag's value is "sample" and the msg value is "message". +Or is the hostname missing, the tag is "taghost" and msg is "sample message"? As a human, I tend +to say the later interpretation is correct. But that's hard to tell the message parser (and, no, I do +not intend to apply artificial intelligence just to guess what the hostname value is...). +<p>One approach is to configure the parser so that it never expects hostnames. This becomes problematic +if you receive messages from multiple devices. Over time, I may implement parser conditionals, +but this is not yet available and I am not really sure if it is needed comlexity... +<p>Things like this, happen. Even more scary formats happen in practice. Even from mainstream +vendors. For example, I was just asked about this message (which, btw, finally made me +write this article here): +<pre></code> +"<130> [ERROR] iapp_socket_task.c 399: iappSocketTask: iappRecvPkt returned error" +</code></pre> +<p>If you compare it with the format RFC3164 "suggests", you'll quickly notice that +the message is "a bit" malformed. Actually, even my human intelligence is not sufficient +to guess if there is a TAG or not (is "[ERROR]" a tag or part of the message). I may not be +the smartest guy, but don't expect me to program a parser that is smarter than me. +<p>To the best of my konwledge, these vendor's device's syslog format can be configured, so it +would proabably be a good idea to include a (sufficiently well-formed) timestamp, +the sending hostname and (maybe?) a tag to make this message well parseable. +I will also once again take this sample and see if we can apply some guesswork. +For example, "[" can not be part of a well-formed TIMESTAMP, so logic can conclude +there is not TIMESTAMP. Also, "[" can not be used inside a valid hostname, so +logic can conclude that the message contains no hostname. Even if I implement this +logic (which I will probably do), this is a partial solution: it is impossible to +guess if there is a tag or not (honestly!). And, even worse, it is a solution only for +those set of messages that can be handled by the logic described. Now consider this +hypothetical message: +<pre></code> +"<130> [ERROR] host.example.net 2008-09-23 11-40-22 PST iapp_socket_task.c 399: iappSocketTask: iappRecvPkt returned error" +</code></pre> +<p>Obviously, it requires additional guesswork. If we iterate over all the cases, we +can very quickly see that it is impossible to guess everything correct. In the example above +we can not even surely tell if PST should be a timezone or some other message property. +<p>A potential solution is to generate a parser-table based parser, but this requires +considerable effort and also has quite some runtime overhead. I try to avoid this for +now (but I may do it, especially if someone sponsors this work ;)). Side-note: if you want +to be a bit scared about potential formats, you may want to have a look at my paper +<i>"<a href="http://www.monitorware.com/en/workinprogress/nature-of-syslog-data.php">On the Nature of Syslog Data</a>"</i>. +<h2>Work-Around</h2> +<p><b>The number one work-around is to configure your devices so that they emit +(sufficiently) well-formed messages.</b> You should by now know what these look +like. +<p>If that cure is not available, there are some things you can do in rsyslog to +handle the situation. First of all, be sure to read about +<a href="rsyslog_conf.html">rsyslog.conf format</a> +and the <a href="property_replacer.html">property replacer and properties</a> specifically. +You need to understand that everything is configured in rsyslog. And that the message is parsed +into properties. There are also properties available which do not stem back directly to parsing. +Most importantly, %fromhost% property holds the name of the system rsyslog received +the message from. In non-relay cases, this can be used instead of hostname. In relay cases, +there is no cure other than to either fix the orginal sender or at least one of the +relays in front of the rsyslog instance in question. Similarly, you can use %timegenerated% +instead of %timereported%. Timegenerated is the time the message hit rsyslog for the first +time. For non-relayed, locally connected peers, Timegenerated should be a very close approximation +of the actual time a message was formed at the sender (depending, of course, on potential +internal queueing inside the sender). +Also, you may use the +%rawmsg% property together with the several extraction modes the property replacer supports. +Rawmsg contains the message as it is received from the remote peer. In a sense, you can +implement a post-parser with this method. +<p>To use these properties, you need to define your own templates and assign them. Details +can be found in the above-quoted documentation. Just let's do a quick example. Let's say +you have the horrible message shown above and can not fix the sending device for +some good reason. In rsyslog.conf, you used to say: +<pre><code> +*.* /var/log/somefile +</code></pre> +<p>Of course, things do not work out well with that ill-formed message. So you decide +to dump the rawmsg to the file and pull the remote host and time of message generation +from rsyslog's internal properties (which, btw, is clever, because otherwise there is no +indication of these two properties...). So you need to define a template for that and +make sure the template is used with your file logging action. This is how it may look: +<pre><code> +$template, MalfromedMsgFormater,"%timegenerated% %fromhost% %rawmsg:::drop-last-lf%\n" +*.* /var/log/somefile;MalformedMsgFormatter +</code></pre> +<p>This will make your log much nicer, but not look perfect. Experiment a bit +with the available properties and replacer extraction options to fine-tune it +to your needs. +<h2>The Ultimate Solution...</h2> +<p>Is available with rsyslog 5.3.4 and above. Here, we can define so-called custom +parsers. These are plugin modules, written in C and adapted to a specific message format +need. The big plus of custom parsers is that they offer excellent performance and unlimited +possibilities - far better than any work-around could do. Custom parsers can be +<a href="rsconf1_rulesetparser.html">bound to specific rule sets</a> +(and thus listening) ports with relative ease. The only con is that they must be written. +However, if you are lucky, a parser for your device may already exist. If not, you can +opt to write it yourself, what is not too hard if you know some C. Alternatively, +Adiscon can program one for you as part of the +<a href="http://www.rsyslog.com/professional-services">rsyslog professional services offering</a>. +In any case, you should seriously consider custom parsers as an alternative if you can not +reconfigure your device to send decent message format. +<h2>Wrap-Up</h2> +<p>Syslog message format is not sufficiently standardized. There exists a weak +"standard" format, which is used by a good number of implementations. However, there +exist many others, including mainstream vendor implementations, which have a +(sometimes horribly) different format. Rsyslog tries to deal with anomalies but +can not guess right in all instances. If possible, the sender should be configured +to submit well-formed messages. If that is not possible, you can work around these +issues with rsyslog's property replacer and template system. Or you can use a suitable +message parser or write one for your needs. +<p>I hope this is a useful guide. You may also have a look at the +<a href="troubleshoot.html">rsyslog troubleshooting guide</a> for further help and places where +to ask questions. +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2009 by <a href="http://www.gerhards.net/rainer">Rainer +Gerhards</a> and <a href="http://www.adiscon.com/">Adiscon</a>. +Released under the GNU GPL version 3 or higher.</font></p> +</body></html> diff --git a/doc/syslog_protocol.html b/doc/syslog_protocol.html new file mode 100644 index 00000000..57eb9ffe --- /dev/null +++ b/doc/syslog_protocol.html @@ -0,0 +1,205 @@ +<html> +<head> +<title>syslog-protocol support in rsyslog</title> +</head> +<body> +<a href="features.html">back</a> +<h1>syslog-protocol support in rsyslog</h1> +<p><b><a href="http://www.rsyslog.com/">Rsyslog</a> provides a trial +implementation of the proposed +<a href="http://www.monitorware.com/Common/en/glossary/syslog-protocol.php"> +syslog-protocol</a> standard.</b> The intention of this implementation is to +find out what inside syslog-protocol is causing problems during implementation. +As syslog-protocol is a standard under development, its support in rsyslog is +highly volatile. It may change from release to release. So while it provides +some advantages in the real world, users are cautioned against using it right +now. If you do, be prepared that you will probably need to update all of your +rsyslogds with each new release. If you try it anyhow, please provide feedback +as that would be most beneficial for us.</p> +<h2>Currently supported message format</h2> +<p>Due to recent discussion on syslog-protocol, we do not follow any specific +revision of the draft but rather the candidate ideas. The format supported +currently is:</p> +<p><b><code><PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s +SP MSG</code></b></p> +<p>Field syntax and semantics are as defined in IETF I-D syslog-protocol-15.</p> +<h2>Capabilities Implemented</h2> +<ul> + <li>receiving message in the supported format (see above)</li> + <li>sending messages in the supported format</li> + <li>relaying messages</li> + <li>receiving messages in either legacy or -protocol format and transforming + them into the other one</li> + <li>virtual availability of TAG, PROCID, APP-NAME, MSGID, SD-ID no matter if + the message was received via legacy format, API or syslog-protocol format (non-present + fields are being emulated with great success)</li> + <li>maximum message size is set via preprocessor #define</li> + <li>syslog-protocol messages can be transmitted both over UDP and plain TCP + with some restrictions on compliance in the case of TCP</li> +</ul> +<h2>Findings</h2> +<p>This lists what has been found during implementation:</p> +<ul> + <li>The same receiver must be able to support both legacy and + syslog-protocol syslog messages. Anything else would be a big inconvenience + to users and would make deployment much harder. The detection must be done + automatically (see below on how easy that is).</li> + <li><b>NUL characters inside MSG</b> cause the message to be truncated at + that point. This is probably a major point for many C-based implementations. + No measures have yet been taken against this. Modifying the code to "cleanly" + support NUL characters is non-trivial, even though rsyslogd already has some + byte-counted string library (but this is new and not yet available + everywhere).</li> + <li><b>character encoding in MSG</b>: is is problematic to do the right + UTF-8 encoding. The reason is that we pick up the MSG from the local domain + socket (which got it from the syslog(3) API). The text obtained does not + include any encoding information, but it does include non US-ASCII + characters. It may also include any other encoding. Other than by guessing + based on the provided text, I have no way to find out what it is. In order + to make the syslogd do anything useful, I have now simply taken the message + as is and stuffed it into the MSG part. Please note that I think this will + be a route that other implementors would take, too.</li> + <li>A minimal parser is easy to implement. It took me roughly 2 hours to add + it to rsyslogd. This includes the time for restructuring the code to be able + to parse both legacy syslog as well as syslog-protocol. The parser has some + restrictions, though<ul> + <li>STRUCTURED-DATA field is extracted, but not validated. Structured data + "[test ]]" is not caught as an error. Nor are any other errors caught. For + my needs with this syslogd, that level of structured data processing is + probably sufficient. I do not want to parse/validate it in all cases. This + is also a performance issue. I think other implementors could have the same + view. As such, we should not make validation a requirement.</li> + <li>MSG is not further processed (e.g. Unicode not being validated)</li> + <li>the other header fields are also extracted, but no validation is + performed right now. At least some validation should be easy to add (not + done this because it is a proof-of-concept and scheduled to change).</li> +</ul> + </li> + <li>Universal access to all syslog fields (missing ones being emulated) was + also quite easy. It took me around another 2 hours to integrate emulation of + non-present fields into the code base.</li> + <li>The version at the start of the message makes it easy to detect if we + have legacy syslog or syslog-protocol. Do NOT move it to somewhere inside + the middle of the message, that would complicate things. It might not be + totally fail-safe to just rely on "1 " as the "cookie" for a syslog-protocol. + Eventually, it would be good to add some more uniqueness, e.g. "@#1 ".</li> + <li>I have no (easy) way to detect truncation if that happens on the UDP + stack. All I see is that I receive e.g. a 4K message. If the message was e.g. + 6K, I received two chunks. The first chunk (4K) is correctly detected as a + syslog-protocol message, the second (2K) as legacy syslog. I do not see what + we could do against this. This questions the usefulness of the TRUNCATE bit. + Eventually, I could look at the UDP headers and see that it is a fragment. I + have looked at a network sniffer log of the conversation. This looks like + two totally-independent messages were sent by the sender stack.</li> + <li>The maximum message size is currently being configured via a + preprocessor #define. It can easily be set to 2K or 4K, but more than 4K is + not possible because of UDP stack limitations. Eventually, this can be + worked around, but I have not done this yet.</li> + <li>rsyslogd can accept syslog-protocol formatted messages but is able to + relay them in legacy format. I find this a must in real-life deployments. + For this, I needed to do some field mapping so that APP-NAME/PROCID are + mapped into a TAG.</li> + <li>rsyslogd can also accept legacy syslog message and relay them in + syslog-protocol format. For this, I needed to apply some sub-parsing of the + TAG, which on most occasions provides correct results. There might be some + misinterpretations but I consider these to be mostly non-intrusive. </li> + <li>Messages received from the syslog API (the normal case under *nix) also + do not have APP-NAME and PROCID and I must parse them out of TAG as + described directly above. As such, this algorithm is absolutely vital to + make things work on *nix.</li> + <li>I have an issue with messages received via the syslog(3) API (or, to be + more precise, via the local domain socket this API writes to): These + messages contain a timestamp, but that timestamp does neither have the year + nor the high-resolution time. The year is no real issue, I just take the + year of the reception of that message. There is a very small window of + exposure for messages read from the log immediately after midnight Jan 1st. + The message in the domain socket might have been written immediately before + midnight in the old year. I think this is acceptable. However, I can not + assign a high-precision timestamp, at least it is somewhat off if I take the + timestamp from message reception on the local socket. An alternative might + be to ignore the timestamp present and instead use that one when the message + is pulled from the local socket (I am talking about IPC, not the network - + just a reminder...). This is doable, but eventually not advisable. It looks + like this needs to be resolved via a configuration option.</li> + <li>rsyslogd already advertised its origin information on application + startup (in a syslog-protocol-14 compatible format). It is fairly easy to + include that with any message if desired (not currently done).</li> + <li>A big problem I noticed are malformed messages. In -syslog-protocol, we + recommend/require to discard malformed messages. However, in practice users + would like to see everything that the syslogd receives, even if it is in + error. For the first version, I have not included any error handling at all. + However, I think I would deliberately ignore any "discard" requirement. My + current point of view is that in my code I would eventually flag a message + as being invalid and allow the user to filter on this invalidness. So these + invalid messages could be redirected into special bins.</li> + <li>The error logging recommendations (those I insisted on;)) are not really + practical. My application has its own error logging philosophy and I will + not change this to follow a draft.</li> + <li>Relevance of support for leap seconds and senders without knowledge of + time is questionable. I have not made any specific provisions in the code + nor would I know how to handle that differently. I could, however, pull the + local reception timestamp in this case, so it might be useful to have this + feature. I do not think any more about this for the initial proof-of-concept. + Note it as a potential problem area, especially when logging to databases.</li> + <li>The HOSTNAME field for internally generated messages currently contains + the hostname part only, not the FQDN. This can be changed inside the code + base, but it requires some thinking so that thinks are kept compatible with + legacy syslog. I have not done this for the proof-of-concept, but I think it + is not really bad. Maybe an hour or half a day of thinking.</li> + <li>It is possible that I did not receive a TAG with legacy syslog or via + the syslog API. In this case, I can not generate the APP-NAME. For + consistency, I have used "-" in such cases (just like in PROCID, MSGID and + STRUCTURED-DATA).</li> + <li>As an architectural side-effect, syslog-protocol formatted messages can + also be transmitted over non-standard syslog/raw tcp. This implementation + uses the industry-standard LF termination of tcp syslog records. As such, + syslog-protocol messages containing a LF will be broken invalidly. There is + nothing that can be done against this without specifying a TCP transport. + This issue might be more important than one thinks on first thought. The + reason is the wide deployment of syslog/tcp via industry standard.</li> +</ul> +<p><b>Some notes on syslog-transport-udp-06</b></p> +<ul> + <li>I did not make any low-level modifications to the UDP code and think I + am still basically covered with this I-D.</li> + <li>I deliberately violate section 3.3 insofar as that I do not necessarily + accept messages destined to port 514. This feature is user-required and a + must. The same applies to the destination port. I am not sure if the "MUST" + in section 3.3 was meant that this MUST be an option, but not necessarily be + active. The wording should be clarified.</li> + <li>section 3.6: I do not check checksums. See the issue with discarding + messages above. The same solution will probably be applied in my code.</li> +</ul> +<p> </p> +<h2>Conlusions/Suggestions</h2> +<p>These are my personal conclusions and suggestions. Obviously, they must be +discussed ;)</p> +<ul> + <li>NUL should be disallowed in MSG</li> + <li>As it is not possible to definitely know the character encoding of the + application-provided message, MSG should <b>not</b> be specified to use UTF-8 + exclusively. Instead, it is suggested that any encoding may be used but + UTF-8 is preferred. To detect UTF-8, the MSG should start with the UTF-8 + byte order mask of "EF BB BF" if it is UTF-8 encoded (see section 155.9 of + <a href="http://www.unicode.org/versions/Unicode4.0.0/ch15.pdf"> + http://www.unicode.org/versions/Unicode4.0.0/ch15.pdf</a>) </li> + <li>Requirements to drop messages should be reconsidered. I guess I would + not be the only implementor ignoring them.</li> + <li>Logging requirements should be reconsidered and probably be removed.</li> + <li>It would be advisable to specify "-" for APP-NAME is the name is not + known to the sender.</li> + <li>The implications of the current syslog/tcp industry standard on + syslog-protocol should be further evaluated and be fully understood</li> +</ul> +<p> </p> +<p>[<a href="manual.html">manual index</a>] +[<a href="rsyslog_conf.html">rsyslog.conf</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body> +</html> + diff --git a/doc/tls_cert.jpg b/doc/tls_cert.jpg Binary files differnew file mode 100644 index 00000000..920e998d --- /dev/null +++ b/doc/tls_cert.jpg diff --git a/doc/tls_cert_100.jpg b/doc/tls_cert_100.jpg Binary files differnew file mode 100644 index 00000000..beeedc58 --- /dev/null +++ b/doc/tls_cert_100.jpg diff --git a/doc/tls_cert_ca.html b/doc/tls_cert_ca.html new file mode 100644 index 00000000..2cae4040 --- /dev/null +++ b/doc/tls_cert_ca.html @@ -0,0 +1,168 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: scenario</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-06-17)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +</ul> + +<h3>Setting up the CA</h3> +<p>The first step is to set up a certificate authority (CA). It must be +maintained by a trustworthy person (or group) and approves the indentities of +all machines. It does so by issuing their certificates. In a small setup, the +administrator can provide the CA function. What is important is the the CA's +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +private key is well-protocted and machine certificates are only issued if it is +know they are valid (in a single-admin case that means the admin should not +issue certificates to anyone else except himself).</p> +<p>The CA creates a so-called self-signed certificate. That is, it approves its +own authenticy. This sounds useless, but the key point to understand is that +every machine will be provided a copy of the CA's certificate. Accepting this +certificate is a matter of trust. So by configuring the CA certificate, the +administrator tells <a href="http://www.rsyslog.com">rsyslog</a> which certificates to trust. This is the root of all +trust under this model. That is why the CA's private key is so important - +everyone getting hold of it is trusted by our rsyslog instances.</p> +<center><img src="tls_cert_ca.jpg"></center> +<p>To create a self-signed certificate, use the following commands with GnuTLS (which +is currently the only supported TLS library, what may change in the future). +Please note that GnuTLS' tools are not installed by default on many platforms. Also, +the tools do not necessarily come with the GnuTLS core package. If you do not +have certtool on your system, check if there is package for the GnuTLS tools available +(under Fedora, for example, this is named gnutls-utils-<version> and +it is NOT installed by default). </p> +<ol> +<li>generate the private key: +<pre>certtool --generate-privkey --outfile ca-key.pem</pre> +<br> +This takes a short while. Be sure to do some work on your workstation, +it waits for radom input. Switching between windows is sufficient ;) +</li> +<li>now create the (self-signed) CA certificate itself:<br> +<pre>certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca.pem</pre> +This generates the CA certificate. This command queries you for a +number of things. Use appropriate responses. When it comes to +certificate validity, keep in mind that you need to recreate all +certificates when this one expires. So it may be a good idea to use a +long period, eg. 3650 days (roughly 10 years). You need to specify that +the certificates belongs to an authority. The certificate is used to +sign other certificates.<br> +</li> +</ol> +<h3>Sample Screen Session</h3> +<p>Text in red is user input. Please note that for some questions, there is no +user input given. This means the default was accepted by simply pressing the +enter key. +<code><pre> +[root@rgf9dev sample]# <font color="red">certtool --generate-privkey --outfile ca-key.pem --bits 2048</font> +Generating a 2048 bit RSA private key... +[root@rgf9dev sample]# <font color="red">certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca.pem</font> +Generating a self signed certificate... +Please enter the details of the certificate's distinguished name. Just press enter to ignore a field. +Country name (2 chars): <font color="red">US</font> +Organization name: <font color="red">SomeOrg</font> +Organizational unit name: <font color="red">SomeOU</font> +Locality name: <font color="red">Somewhere</font> +State or province name: <font color="red">CA</font> +Common name: <font color="red">someName (not necessarily DNS!)</font> +UID: +This field should not be used in new certificates. +E-mail: +Enter the certificate's serial number (decimal): + + +Activation/Expiration time. +The certificate will expire in (days): <font color="red">3650</font> + + +Extensions. +Does the certificate belong to an authority? (Y/N): <font color="red">y</font> +Path length constraint (decimal, -1 for no constraint): +Is this a TLS web client certificate? (Y/N): +Is this also a TLS web server certificate? (Y/N): +Enter the e-mail of the subject of the certificate: <font color="red">someone@example.net</font> +Will the certificate be used to sign other certificates? (Y/N): <font color="red">y</font> +Will the certificate be used to sign CRLs? (Y/N): +Will the certificate be used to sign code? (Y/N): +Will the certificate be used to sign OCSP requests? (Y/N): +Will the certificate be used for time stamping? (Y/N): +Enter the URI of the CRL distribution point: +X.509 Certificate Information: + Version: 3 + Serial Number (hex): 485a365e + Validity: + Not Before: Thu Jun 19 10:35:12 UTC 2008 + Not After: Sun Jun 17 10:35:25 UTC 2018 + Subject: C=US,O=SomeOrg,OU=SomeOU,L=Somewhere,ST=CA,CN=someName (not necessarily DNS!) + Subject Public Key Algorithm: RSA + Modulus (bits 2048): + d9:9c:82:46:24:7f:34:8f:60:cf:05:77:71:82:61:66 + 05:13:28:06:7a:70:41:bf:32:85:12:5c:25:a7:1a:5a + 28:11:02:1a:78:c1:da:34:ee:b4:7e:12:9b:81:24:70 + ff:e4:89:88:ca:05:30:0a:3f:d7:58:0b:38:24:a9:b7 + 2e:a2:b6:8a:1d:60:53:2f:ec:e9:38:36:3b:9b:77:93 + 5d:64:76:31:07:30:a5:31:0c:e2:ec:e3:8d:5d:13:01 + 11:3d:0b:5e:3c:4a:32:d8:f3:b3:56:22:32:cb:de:7d + 64:9a:2b:91:d9:f0:0b:82:c1:29:d4:15:2c:41:0b:97 + Exponent: + 01:00:01 + Extensions: + Basic Constraints (critical): + Certificate Authority (CA): TRUE + Subject Alternative Name (not critical): + RFC822name: someone@example.net + Key Usage (critical): + Certificate signing. + Subject Key Identifier (not critical): + fbfe968d10a73ae5b70d7b434886c8f872997b89 +Other Information: + Public Key Id: + fbfe968d10a73ae5b70d7b434886c8f872997b89 + +Is the above information ok? (Y/N): <font color="red">y</font> + + +Signing certificate... +[root@rgf9dev sample]# <font color="red">chmod 400 ca-key.pem</font> +[root@rgf9dev sample]# <font color="red">ls -l</font> +total 8 +-r-------- 1 root root 887 2008-06-19 12:33 ca-key.pem +-rw-r--r-- 1 root root 1029 2008-06-19 12:36 ca.pem +[root@rgf9dev sample]# +</pre></code> +<p><font color="red"><b>Be sure to safeguard ca-key.pem!</b> Nobody except the CA itself +needs to have it. If some third party obtains it, you security is broken!</font> +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_ca.jpg b/doc/tls_cert_ca.jpg Binary files differnew file mode 100644 index 00000000..f2da0454 --- /dev/null +++ b/doc/tls_cert_ca.jpg diff --git a/doc/tls_cert_client.html b/doc/tls_cert_client.html new file mode 100644 index 00000000..dbe7961b --- /dev/null +++ b/doc/tls_cert_client.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: client setup</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-07-03)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +</ul> + +<h3>Setting up a client</h3> +<p>In this step, we configure a client machine. We from our scenario, we use +zuse.example.net. You need to do the same steps for all other clients, too (in the +example, that meanst turng.example.net). The client check's the server's identity and +talks to it only if it is the expected server. This is a very important step. +Without it, you would not detect man-in-the-middle attacks or simple malicious servers +who try to get hold of your valuable log data. +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +<p><center><img src="tls_cert_100.jpg"></center> +<p>Steps to do: +<ul> +<li>make sure you have a functional CA (<a href="tls_cert_ca.html">Setting up the CA</a>) +<li>generate a machine certificate for zuse.example.net (follow instructions in + <a href="tls_cert_machine.html">Generating Machine Certificates</a>) +<li>make sure you copy over ca.pem, machine-key.pem ad machine-cert.pem to the client. +Ensure that no user except root can access them (<b>even read permissions are really bad</b>). +<li>configure the client so that it checks the server identity and sends messages only +if the server identity is known. Please note that you have the same options as when +configuring a server. However, we now use a single name only, because there is only one +central server. No using wildcards make sure that we will exclusively talk to that server +(otherwise, a compromised client may take over its role). If you load-balance to different +server identies, you obviously need to allow all of them. It still is suggested to use +explcit names. +</ul> +<p><b>At this point, please be reminded once again that your security needs may be quite different from +what we assume in this tutorial. Evaluate your options based on your security needs.</b> +<h3>Sample syslog.conf</h3> +<p>Keep in mind that this rsyslog.conf sends messages via TCP, only. Also, we do not +show any rules to write local files. Feel free to add them. +<code><pre> +# make gtls driver the default +$DefaultNetstreamDriver gtls + +# certificate files +$DefaultNetstreamDriverCAFile /rsyslog/protected/ca.pem +$DefaultNetstreamDriverCertFile /rsyslog/protected/machine-cert.pem +$DefaultNetstreamDriverKeyFile /rsyslog/protected/machine-key.pem + +$ActionSendStreamDriverAuthMode x509/name +$ActionSendStreamDriverPermittedPeer central.example.net +$ActionSendStreamDriverMode 1 # run driver in TLS-only mode +*.* @@central.example.net:10514 # forward everything to remote server +</pre></code> +<p>Note: the example above forwards every message to the remote server. Of course, +you can use the normal filters to restrict the set of information that is sent. +Depending on your message volume and needs, this may be a smart thing to do. +<p><font color="red"><b>Be sure to safeguard at least the private key (machine-key.pem)!</b> +If some third party obtains it, you security is broken!</font> +<h2>Copyright</h2> +<p>Copyright © 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_errmsgs.html b/doc/tls_cert_errmsgs.html new file mode 100644 index 00000000..d002174c --- /dev/null +++ b/doc/tls_cert_errmsgs.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: error messages</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-06-17)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +<li><a href="tls_cert_errmsgs.html">Frequently seen Error Messages</a> +</ul> + +<h3>Error Messages</h3> +<p>This page covers error message you may see when setting up +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +<a href="http://www.rsyslog.com">rsyslog</a> with TLS. Please note that many +of the message stem back to the TLS library being used. In those cases, there is +not always a good explanation available in rsyslog alone. +<p>A single error typically results in two or more message being emitted: (at +least) one is the actual error cause, followed by usually one message with additional +information (like certificate contents). In a typical system, these message should +immediately follow each other in your log. Kepp in mind that they are reported +as syslog.err, so you need to capture these to actually see errors (the default +rsyslog.conf's shipped by many systems will do that, recording them e.g. in +/etc/messages). +<h3>certificate invalid</h3> +<p>Sample: +<code> +not permitted to talk to peer, certificate invalid: <font color="red">insecure algorithm</font> +</code> +<p>This message may occur during connection setup. It indicates that the remote peer's +certificate can not be accepted. The reason for this is given in the message part that +is shown in red. Please note that this red part directly stems back to the TLS library, +so rsyslog does acutally not have any more information about the reason. +<p>With GnuTLS, the following reasons have been seen in practice: +<h4>insecure algorith</h4> +<p>The certificate contains information on which encryption algorithms are to be used. +This information is entered when the certificate is created. +Some older alogrithms are no longer secure and the TLS library does not accept +them. Thus the connection request failed. The cure is to use a certificate with sufficiently secure +alogorithms. +<p>Please note that noi encryption algorithm is totally secure. It only is secure based +on our current knowledge AND on computing power available. As computers get more and more +powerful, previously secure algorithms become insecure over time. As such, algorithms +considered secure today may not be accepted by the TLS library in the future. +<p>So in theory, after a system upgrade, a connection request may fail with the "insecure +algorithm" failure without any change in rsyslog configuration or certificates. This could be +caused by a new perception of the TLS library of what is secure and what not. +<h3>GnuTLS error -64</h3> +<p>Sample: <code>unexpected GnuTLS error -64 in nsd_gtls.c:517: Error while reading file.</code> +<p>This error points to an encoding error witht the pem file in question. It means "base 64 encoding error". +From my experience, it can be caused by a couple of things, some of them not obvious: +<ul> +<li>You specified a wrong file, which is not actually in .pem format +<li>The file was incorrectly generated +<li>I think I have also seen this when I accidently swapped private key files and +certificate files. So double-check the type of file you are using. +<li>It may even be a result of an access (permission) problem. In theory, that +should lead to another error, but in practice it sometimes seems to lead to +this -64 error. +</ul> +<h3>info on invalid cert</h3> +<p>Sample: +<code> +info on invalid cert: peer provided 1 certificate(s). Certificate 1 info: certificate valid from Wed Jun 18 11:45:44 2008 to Sat Jun 16 11:45:53 2018; Certificate public key: RSA; DN: C=US,O=Sample Corp,OU=Certs,L=Somehwere,ST=CA,CN=somename; Issuer DN: C=US,O=Sample Corp,OU=Certs,L=Somewhere,ST=CA,CN=somename,EMAIL=xxx@example.com; SAN:DNSname: machine.example.net; +</code> +<p>This is <b>not</b> an error message in itself. It always follows the actual error message and +tells you what is seen in the peer's certificate. This is done to give you a chance to evaluate +the certificate and better understand why the initial error message was issued. +<p>Please note that you can NOT diagnose problems based on this message alone. It follows +in a number of error cases and does not pinpoint any problems by itself. +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_machine.html b/doc/tls_cert_machine.html new file mode 100644 index 00000000..095e15c2 --- /dev/null +++ b/doc/tls_cert_machine.html @@ -0,0 +1,182 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: generating the machine certificate</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-06-18)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +</ul> + +<h3>generating the machine certificate</h3> +<p>In this step, we generate certificates for each of the machines. Please note +that both clients and servers need certificates. The certificate identifies each +machine to the remote peer. The DNSName specified inside the certificate can +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +be specified inside the $<object>PermittedPeer config statements. +<p>For now, we assume that a single person (or group) is responsible for the whole +rsyslog system and thus it is OK if that single person is in posession of all +machine's private keys. This simplification permits us to use a somewhat less +complicated way of generating the machine certificates. So, we generate both the private +and public key on the CA (which is NOT a server!) and then copy them over to the +respective machines. +<p>If the roles of machine and CA administrators are split, the private key must +be generated by the machine administrator. This is done via a certificate request. +This request is then sent to the CA admin, which in turn generates the certificate +(containing the public key). The CA admin then sends back the certificate to the +machine admin, who installs it. That way, the CA admin never get's hold of the +machine's private key. Instructions for this mode will be given in a later revision +of this document. +<p><b>In any case, it is vital that the machine's private key is protected. Anybody +able to obtain that private key can imporsonate as the machine to which it belongs, thus +breaching your security.</b> +<h3>Sample Screen Session</h3> +<p>Text in red is user input. Please note that for some questions, there is no +user input given. This means the default was accepted by simply pressing the +enter key. +<p><b>Please note:</b> you need to substitute the names specified below with values +that match your environment. Most importantly, machine.example.net must be replaced +by the actual name of the machine that will be using this certificate. For example, +if you generate a certificate for a machine named "server.example.com", you need +to use that name. If you generate a certificate for "client.example.com", you need +to use this name. Make sure that each machine certificate has a unique name. If not, +you can not apply proper access control. +<code><pre> +[root@rgf9dev sample]# <font color="red">certtool --generate-privkey --outfile key.pem --bits 2048</font> +Generating a 2048 bit RSA private key... +[root@rgf9dev sample]# <font color="red">certtool --generate-request --load-privkey key.pem --outfile request.pem</font> +Generating a PKCS #10 certificate request... +Country name (2 chars): <font color="red">US</font> +Organization name: <font color="red">SomeOrg</font> +Organizational unit name: <font color="red">SomeOU</font> +Locality name: <font color="red">Somewhere</font> +State or province name: <font color="red">CA</font> +Common name: <font color="red">machine.example.net</font> +UID: +Enter a dnsName of the subject of the certificate: +Enter the IP address of the subject of the certificate: +Enter the e-mail of the subject of the certificate: +Enter a challange password: +Does the certificate belong to an authority? (y/N): <font color="red">n</font> +Will the certificate be used for signing (DHE and RSA-EXPORT ciphersuites)? (y/N): +Will the certificate be used for encryption (RSA ciphersuites)? (y/N): +Is this a TLS web client certificate? (y/N): <font color="red">y</font> +Is this also a TLS web server certificate? (y/N): <font color="red">y</font> +[root@rgf9dev sample]# <font color="red">certtool --generate-certificate --load-request request.pem --outfile cert.pem --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem</font> +Generating a signed certificate... +Enter the certificate's serial number (decimal): + + +Activation/Expiration time. +The certificate will expire in (days): 1000 + + +Extensions. +Do you want to honour the extensions from the request? (y/N): +Does the certificate belong to an authority? (Y/N): <font color="red">n</font> +Is this a TLS web client certificate? (Y/N): <font color="red">y</font> +Is this also a TLS web server certificate? (Y/N): <font color="red">y</font> +Enter the dnsName of the subject of the certificate: <font color="red">machine.example.net</font> <i>{This is the name of the machine that will use the certificate}</i> +Enter the IP address of the subject of certificate: +Will the certificate be used for signing (DHE and RSA-EXPORT ciphersuites)? (Y/N): +Will the certificate be used for encryption (RSA ciphersuites)? (Y/N): +X.509 Certificate Information: + Version: 3 + Serial Number (hex): 485a3819 + Validity: + Not Before: Thu Jun 19 10:42:54 UTC 2008 + Not After: Wed Mar 16 10:42:57 UTC 2011 + Subject: C=US,O=SomeOrg,OU=SomeOU,L=Somewhere,ST=CA,CN=machine.example.net + Subject Public Key Algorithm: RSA + Modulus (bits 2048): + b2:4e:5b:a9:48:1e:ff:2e:73:a1:33:ee:d8:a2:af:ae + 2f:23:76:91:b8:39:94:00:23:f2:6f:25:ad:c9:6a:ab + 2d:e6:f3:62:d8:3e:6e:8a:d6:1e:3f:72:e5:d8:b9:e0 + d0:79:c2:94:21:65:0b:10:53:66:b0:36:a6:a7:cd:46 + 1e:2c:6a:9b:79:c6:ee:c6:e2:ed:b0:a9:59:e2:49:da + c7:e3:f0:1c:e0:53:98:87:0d:d5:28:db:a4:82:36:ed + 3a:1e:d1:5c:07:13:95:5d:b3:28:05:17:2a:2b:b6:8e + 8e:78:d2:cf:ac:87:13:15:fc:17:43:6b:15:c3:7d:b9 + Exponent: + 01:00:01 + Extensions: + Basic Constraints (critical): + Certificate Authority (CA): FALSE + Key Purpose (not critical): + TLS WWW Client. + TLS WWW Server. + Subject Alternative Name (not critical): + DNSname: machine.example.net + Subject Key Identifier (not critical): + 0ce1c3dbd19d31fa035b07afe2e0ef22d90b28ac + Authority Key Identifier (not critical): + fbfe968d10a73ae5b70d7b434886c8f872997b89 +Other Information: + Public Key Id: + 0ce1c3dbd19d31fa035b07afe2e0ef22d90b28ac + +Is the above information ok? (Y/N): <font color="red">y</font> + + +Signing certificate... +[root@rgf9dev sample]# <font color="red">rm -f request.pem</font> +[root@rgf9dev sample]# <font color="red">ls -l</font> +total 16 +-r-------- 1 root root 887 2008-06-19 12:33 ca-key.pem +-rw-r--r-- 1 root root 1029 2008-06-19 12:36 ca.pem +-rw-r--r-- 1 root root 1074 2008-06-19 12:43 cert.pem +-rw-r--r-- 1 root root 887 2008-06-19 12:40 key.pem +[root@rgf9dev sample]# # it may be a good idea to rename the files to indicate where they belong to +[root@rgf9dev sample]# <font color="red">mv cert.pem machine-cert.pem</font> +[root@rgf9dev sample]# <font color="red">mv key.pem machine-key.pem</font> +[root@rgf9dev sample]# +</pre></code> +<h3>Distributing Files</h3> +<p>Provide the machine with: +<ul> +<li>a copy of ca.pem +<li>cert.pem +<li>key.pem +</ul> +<p>This is how the relevant part of rsyslog.conf looks on the target machine: +<p> +<code><pre> +$DefaultNetstreamDriverCAFile /home/rger/proj/rsyslog/sample/ca.pem +$DefaultNetstreamDriverCertFile /home/rger/proj/rsyslog/sample/machine-cert.pem +$DefaultNetstreamDriverKeyFile /home/rger/proj/rsyslog/sample/machine-key.pem +</pre></code> +<p><b><font color="red">Never</font> provide anyone with ca-key.pem!</b> Also, make sure +nobody but the machine in question gets hold of key.pem. +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_scenario.html b/doc/tls_cert_scenario.html new file mode 100644 index 00000000..7973532b --- /dev/null +++ b/doc/tls_cert_scenario.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: scenario</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-06-17)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +<li><a href="tls_cert_errmsgs.html">Frequently seen Error Messages</a> +</ul> + +<h3>Sample Scenario</h3> +<p>We have a quite simple scenario. There is one central syslog server, +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +named central.example.net. These server is being reported to by two Linux +machines with name zuse.example.net and turing.example.net. Also, there is a +third client - ada.example.net - which send both its own messages to the central +server but also forwards messages receive from an UDP-only capable router. We +hav decided to use ada.example.net because it is in the same local network +segment as the router and so we enjoy TLS' security benefits for forwarding the +router messages inside the corporate network. All systems (except the router) use +<a href="http://www.rsyslog.com/">rsyslog</a> as the syslog software.</p> +<p><center><img src="tls_cert_100.jpg"></center> +<p>Please note that the CA must not necessarily be connected to the rest of the +network. Actually, it may be considered a security plus if it is not. If the CA +is reachable via the regular network, it should be sufficiently secured (firewal +rules et al). Keep in mind that if the CA's security is breached, your overall +system security is breached. +<p>In case the CA is compromised, you need to regenerate the CA's certificate as well +as all individual machines certificates. +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_server.html b/doc/tls_cert_server.html new file mode 100644 index 00000000..9c024bc9 --- /dev/null +++ b/doc/tls_cert_server.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: central server setup</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-06-18)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +</ul> + +<h3>Setting up the Central Server</h3> +<p>In this step, we configure the central server. We assume it accepts messages only +via TLS protected plain tcp based syslog from those peers that are explicitely permitted +to send to it. The picture below show our configuration. This step configures +the server central.example.net. +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +<p><center><img src="tls_cert_100.jpg"></center> +<p><i><font color="red"><b>Important:</b> Keep in mind that the order of configuration directives +is very important in rsyslog. As such, the samples given below do only work if the given +order is preserved.</font> Re-ordering the directives can break configurations and has broken them +in practice. If you intend to re-order them, please be sure that you fully understand how +the configuration language works and, most importantly, which statements form a block together. +Please also note that we understand the the current configuration file format is +ugly. However, there has been more important work in the way of enhancing it. If you would like +to contribute some time to improve the config file language, please let us know. Any help +is appreciated (be it doc or coding work!).</i> +<p>Steps to do: +<ul> +<li>make sure you have a functional CA (<a href="tls_cert_ca.html">Setting up the CA</a>) +<li>generate a machine certificate for central.example.net (follow instructions in + <a href="tls_cert_machine.html">Generating Machine Certificates</a>) +<li>make sure you copy over ca.pem, machine-key.pem ad machine-cert.pem to the central server. +Ensure that no user except root can access them (<b>even read permissions are really bad</b>). +<li>configure the server so that it accepts messages from all machines in the +example.net domain that have certificates from your CA. Alternatively, you may also +precisely define from which machine names messages are accepted. See sample rsyslog.conf +below. +</ul> +In this setup, we use wildcards to ease adding new systems. We permit the server to accept +messages from systems whos names match *.example.net. +<pre><code> +$InputTCPServerStreamDriverPermittedPeer *.example.net +</code></pre> +This will match zuse.example.net and +turing.example.net, but NOT pascal.otherdepartment.example.net. If the later would be desired, +you can (and need) to include additional permitted peer config statments: +<pre><code> +$InputTCPServerStreamDriverPermittedPeer *.example.net +$InputTCPServerStreamDriverPermittedPeer *.otherdepartment.example.net +$InputTCPServerStreamDriverPermittedPeer *.example.com +</code></pre> +<p>As can be seen with example.com, the different permitted peers need NOT to be in a single +domain tree. Also, individual machines can be configured. For example, if only zuse, turing +and ada should be able to talk to the server, you can achive this by: +<pre><code> +$InputTCPServerStreamDriverPermittedPeer zuse.example.net +$InputTCPServerStreamDriverPermittedPeer turing.example.net +$InputTCPServerStreamDriverPermittedPeer ada.example.net +</code></pre> +<p>As an extension to the (upcoming) IETF syslog/tls standard, you can specify some text +together with a domain component wildcard. So "*server.example.net", "server*.example.net" +are valid permitted peers. However "server*Fix.example.net" is NOT a valid wildcard. The +IETF standard permits no text along the wildcards. +<p>The reason we use wildcards in the default setup is that it makes it easy to add systems +without the need to change the central server's configuration. It is important to understand that +the central server will accept names <b>only</b> (no exception) if the client certificate was +signed by the CA we set up. So if someone tries to create a malicious certificate with +a name "zuse.example.net", the server will <b>not</b> accept it. So a wildcard is safe +as long as you ensure CA security is not breached. Actually, you authorize a client by issuing +the certificate to it. +<p><b>At this point, please be reminded once again that your security needs may be quite different from +what we assume in this tutorial. Evaluate your options based on your security needs.</b> +<h3>Sample syslog.conf</h3> +<p>Keep in mind that this rsyslog.conf accepts messages via TCP, only. The only other +source accepted is messages from the server itself. +<code><pre> +$ModLoad imuxsock # local messages +$ModLoad imtcp # TCP listener + +# make gtls driver the default +$DefaultNetstreamDriver gtls + +# certificate files +$DefaultNetstreamDriverCAFile /rsyslog/protected/ca.pem +$DefaultNetstreamDriverCertFile /rsyslog/protected/machine-cert.pem +$DefaultNetstreamDriverKeyFile /rsyslog/protected/machine-key.pem + +$InputTCPServerStreamDriverAuthMode x509/name +$InputTCPServerStreamDriverPermittedPeer *.example.net +$InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode +$InputTCPServerRun 10514 # start up listener at port 10514 +</pre></code> +<p><font color="red"><b>Be sure to safeguard at least the private key (machine-key.pem)!</b> +If some third party obtains it, you security is broken!</font> +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_summary.html b/doc/tls_cert_summary.html new file mode 100644 index 00000000..8e003bc8 --- /dev/null +++ b/doc/tls_cert_summary.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: Summary</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-07-03)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +</ul> + +<h3>Summary</h3> +<p>If you followed the steps outlined in this documentation set, you now have +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +a reasonable (for most needs) secure setup for the following environment: +<center><img src="tls_cert_100.jpg"></center> +<p>You have learned about the security decisions involved and which we +made in this example. <b>Be once again reminded that you must make sure yourself +that whatever you do matches your security needs!</b> There is no guarantee that +what we generally find useful actually is. It may even be totally unsuitable for +your environment. +<p>In the example, we created a rsyslog certificate authority (CA). Guard the CA's +files. You need them whenever you need to create a new machine certificate. We also saw how +to generate the machine certificates themselfs and distribute them to the individual +machines. Also, you have found some configuration samples for a sever, a client and +a syslog relay. Hopefully, this will enable you to set up a similar system in many +environments. +<p>Please be warned that you defined some expiration dates for the certificates. +After they are reached, the certificates are no longer valid and rsyslog will NOT +accept them. At that point, syslog messages will no longer be transmitted (and rsyslogd +will heavily begin to complain). So it is a good idea to make sure that you renew the +certificates before they expire. Recording a reminder somewhere is probably a good +idea. +<p>If you have any more questions, please visit the <a href="http://kb.monitorware.com/rsyslog-f40.html">rsyslog forum</a> and simply ask ;) +<h2>Copyright</h2> +<p>Copyright (c) 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/tls_cert_udp_relay.html b/doc/tls_cert_udp_relay.html new file mode 100644 index 00000000..f4740ce7 --- /dev/null +++ b/doc/tls_cert_udp_relay.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>TLS-protected syslog: UDP relay setup</title> +</head> +<body> + +<h1>Encrypting Syslog Traffic with TLS (SSL)</h1> +<p><small><i>Written by <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> (2008-07-03)</i></small></p> + +<ul> +<li><a href="rsyslog_secure_tls.html">Overview</a> +<li><a href="tls_cert_scenario.html">Sample Scenario</a> +<li><a href="tls_cert_ca.html">Setting up the CA</a> +<li><a href="tls_cert_machine.html">Generating Machine Certificates</a> +<li><a href="tls_cert_server.html">Setting up the Central Server</a> +<li><a href="tls_cert_client.html">Setting up syslog Clients</a> +<li><a href="tls_cert_udp_relay.html">Setting up the UDP syslog relay</a> +<li><a href="tls_cert_summary.html">Wrapping it all up</a> +</ul> + +<h3>Setting up the UDP syslog relay</h3> +<p>In this step, we configure the UDP relay ada.example.net. +As a reminder, that machine relays messages from a local router, which only +supports UDP syslog, to the central syslog server. The router does not talk +directly to it, because we would like to have TLS protection for its sensitve +logs. If the router and the syslog relay are on a sufficiently secure private +network, this setup can be considered reasonable secure. In any case, it is the +best alternative among the possible configuration scenarios. +<span style="float: left"> +<script type="text/javascript"><!-- +google_ad_client = "pub-3204610807458280"; +/* rsyslog doc inline */ +google_ad_slot = "5958614527"; +google_ad_width = 125; +google_ad_height = 125; +//--> +</script> +<script type="text/javascript" +src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> +</script> +</span> +<p><center><img src="tls_cert_100.jpg"></center> +<p>Steps to do: +<ul> +<li>make sure you have a functional CA (<a href="tls_cert_ca.html">Setting up the CA</a>) +<li>generate a machine certificate for ada.example.net (follow instructions in + <a href="tls_cert_machine.html">Generating Machine Certificates</a>) +<li>make sure you copy over ca.pem, machine-key.pem ad machine-cert.pem to the client. +Ensure that no user except root can access them (<b>even read permissions are really bad</b>). +<li>configure the client so that it checks the server identity and sends messages only +if the server identity is known. +</ul> +<p>These were essentially the same steps as for any +<a href="tls_cert_client.html">TLS syslog client</a>. We now need to add the +capability to forward the router logs: +<ul> +<li>make sure that the firewall rules permit message recpetion on UDP port 514 (if you use +a non-standard port for UDP syslog, make sure that port number is permitted). +<li>you may want to limit who can send syslog messages via UDP. A great place to do this +is inside the firewall, but you can also do it in rsyslog.conf via an $AllowedSender +directive. We have used one in the sample config below. Please be aware that this is +a kind of weak authentication, but definitely better than nothing... +<li>add the UDP input plugin to rsyslog's config and start a UDP listener +<li>make sure that your forwarding-filter permits to forward messages received +from the remote router to the server. In our sample scenario, we do not need to +add anything special, because all messages are forwarded. This includes messages +received from remote hosts. +</ul> +<p><b>At this point, please be reminded once again that your security needs may be quite different from +what we assume in this tutorial. Evaluate your options based on your security needs.</b> +<h3>Sample syslog.conf</h3> +<p>Keep in mind that this rsyslog.conf sends messages via TCP, only. Also, we do not +show any rules to write local files. Feel free to add them. +<code><pre> +# start a UDP listener for the remote router +$ModLoad imudp # load UDP server plugin +$AllowedSender UDP, 192.0.2.1 # permit only the router +$UDPServerRun 514 # listen on default syslog UDP port 514 + +# make gtls driver the default +$DefaultNetstreamDriver gtls + +# certificate files +$DefaultNetstreamDriverCAFile /rsyslog/protected/ca.pem +$DefaultNetstreamDriverCertFile /rsyslog/protected/machine-cert.pem +$DefaultNetstreamDriverKeyFile /rsyslog/protected/machine-key.pem + +$ActionSendStreamDriverAuthMode x509/name +$ActionSendStreamDriverPermittedPeer central.example.net +$ActionSendStreamDriverMode 1 # run driver in TLS-only mode +*.* @@central.example.net:10514 # forward everything to remote server +</pre></code> +<p><font color="red"><b>Be sure to safeguard at least the private key (machine-key.pem)!</b> +If some third party obtains it, you security is broken!</font> +<h2>Copyright</h2> +<p>Copyright © 2008 <a href="http://www.adiscon.com/en/people/rainer-gerhards.php">Rainer +Gerhards</a> and +<a href="http://www.adiscon.com/en/">Adiscon</a>.</p> +<p> Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version +1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license can be viewed at +<a href="http://www.gnu.org/copyleft/fdl.html">http://www.gnu.org/copyleft/fdl.html</a>.</p> +</body></html> diff --git a/doc/troubleshoot.html b/doc/troubleshoot.html new file mode 100644 index 00000000..0f0c7fca --- /dev/null +++ b/doc/troubleshoot.html @@ -0,0 +1,164 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>troubleshooting rsyslog</title></head> +<body> +<h2>troubleshooting rsyslog</h2> +<p><b>Having trouble with <a href="http://www.rsyslog.com">rsyslog</a>?</b> +This page provides some tips on where to look for help and what to do +if you need to ask for assistance. This page is continously being expanded. +<p>Useful troubleshooting ressources are: +<ul> +<li>The <a href="http://www.rsyslog.com/doc">rsyslog documentation</a> - note that the online version always covers +the most recent development version. However, there is a version-specific +doc set in each tarball. If you installed rsyslog from a package, there usually +is a rsyslog-doc package, that often needs to be installed separately. +<li>The <a href="http://wiki.rsyslog.com">rsyslog wiki</a> provides user tips and experiences. +<li>Check <a href="http://bugzilla.adiscon.com">the bugzilla</a> to see if your problem is a known +(and even fixed ;)) bug. +</ul> +<p><b>Malformed Messages and Message Properties</b> +<p>A common trouble source are <a href="syslog_parsing.html">ill-formed syslog messages</a>, which +lead to to all sorts of interesting problems, including malformed hostnames and dates. +Read the quoted guide to find relief. A common symptom is that the %HOSTNAME% property is +used for generating dynafile names, but some glibberish shows up. This is caused by the +malformed syslog messages, so be sure to read the +<a href="syslog_parsing.html">guide</a> if you face that problem. Just let me add that the +common work-around is to use %FROMHOST% or %FROMHOST-IP% instead. These do not take the +hostname from the message, but rather use the host that sent the message (taken from +the socket layer). Of course, this does not work over NAT or relay chains, where the +only cure is to make sure senders emit well-formed messages. +<p><b>Configuration Problems</b> +<p>Rsyslog 3.21.1 and above has been enhanced to support extended configuration checking. +It offers a special command line switch (-N1) that puts it into "config verfication mode". +In that mode, it interprets and check the configuration file, but does not startup. This +mode can be used in parallel to a running instance of rsyslogd. +<p>To enable it, run rsyslog interactively as follows: +<p><b><i>/path/to/rsyslogd -f/path/to/config-file -N1</i></b> +<p>You should also specify other options you usually give (like -c3 and whatever else). +Any problems experienced are reported to stderr [aka "your screen" (if not redirected)]. +<p><b>Configuration Graphs</b> +<p>Starting with rsyslog 4.3.1, the +"<a href="rsconf1_generateconfiggraph.html">$GenerateConfigGraph</a>" +command is supported, a very valuable troubleshooting tool. It permits to +generate a graph of how rsyslogd understood its configuration file. It is assumed that +many configuration issues can easily be detected just by looking at the configuration graph. +Full details of how to generate the graphs, and what to look for can be found in the +"<a href="rsconf1_generateconfiggraph.html">$GenerateConfigGraph</a>" +manual page. +<p><b>Asking for Help</b> +<p>If you can't find the answer yourself, you should look at these places for +community help. +<ul> +<li>The <a href="http://kb.monitorware.com/rsyslog-f40.html">rsyslog forum</a>. This is +the preferred method of obtaining support. +<li>The <a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">rsyslog mailing list</a>. +This is a low-volume list which occasional gets traffic spikes. +The mailing list is probably a good place for complex questions. +</ul> +<p><b>Debug Log</b> +<p>If you ask for help, there are chances that we need to ask for an rsyslog debug log. +The debug log is a detailled report of what rsyslog does during processing. As such, it may +even be useful for your very own troubleshooting. People have seen things inside their debug +log that enabled them to find problems they did not see before. So having a look at the +debug log, even before asking for help, may be useful. +<p>Note that the debug log contains most of those things we consider useful. This is a lot +of information, but may still be too few. So it sometimes may happen that you will be asked +to run a specific version which has additional debug output. Also, we revise from time to +time what is worth putting into the standard debug log. As such, log content may change +from version to version. We do not guarantee any specific debug log contents, so do not +rely on that. The amount of debug logging can also be controlled via some environment +options. Please see <a href="debug.html">debugging support</a> for further details. +<p>In general, it is advisable to run rsyslogd in the foreground to obtain the log. +To do so, make sure you know which options are usually used when you start rsyslogd +as a background daemon. Let's assume "-c3" is the only option used. Then, do the following: +<ul> +<li>make sure rsyslogd as a daemon is stopped (verify with ps -ef|grep rsyslogd) +<li>make sure you have a console session with root permissions +<li>run rsyslogd interactively: /sbin/rsyslogd ..your options.. -dn > logfile +<br>where "your options" is what you usually use. /sbin/rsyslogd is the full path +to the rsyslogd binary (location different depending on distro). +In our case, the command would be +<br>/sbin/rsyslogd -c3 -dn > logfile +<li>press ctrl-C when you have sufficient data (e.g. a device logged a record) +<br><b>NOTE: rsyslogd will NOT stop automatically - you need to ctrl-c out of it!</b> +<li>Once you have done all that, you can review logfile. It contains the debug output. +<li>When you are done, make sure you re-enable (and start) the background daemon! +</ul> +<p>If you need to submit the logfile, you may want to check if it contains any +passwords or other sensitive data. If it does, you can change it to some <b>consistent</b> +meaningless value. <b>Do not delete the lines</b>, as this renders the debug log +unusable (and makes Rainer quite angry for wasted time, aka significantly reduces the chance +he will remain motivated to look at your problem ;)). For the same reason, make sure +whatever you change is change consistently. Really! +<p>Debug log file can get quite large. Before submitting them, it is a good idea to zip them. +Rainer has handled files of around 1 to 2 GB. If your's is larger ask before submitting. Often, +it is sufficient to submit the first 2,000 lines of the log file and around another 1,000 around +the area where you see a problem. Also, +ask you can submit a file via private mail. Private mail is usually a good way to go for large files +or files with sensitive content. However, do NOT send anything sensitive that you do not want +the outside to be known. While Rainer so far made effort no to leak any sensitive information, +there is no guarantee that doesn't happen. If you need a guarantee, you are probably a +candidate for a <a href="professional_support.html">commercial support contract</a>. Free support +comes without any guarantees, include no guarantee on confidentiality +[aka "we don't want to be sued for work were are not even paid for ;)]. +<b>So if you submit debug logs, do so at your sole risk</b>. By submitting them, you accept +this policy. +<p><b>Segmentation Faults</b> +<p>Rsyslog has a very rapid development process, complex capabilities and now gradually gets +more and more exposure. While we are happy about this, it also has some bad effects: some +deployment scenarios have probably never been tested and it may be impossible to test +them for the development team because of resources needed. So while we try to avoid this, +you may see a serious problem during deployments in demanding, non-standard, environments +(hopefully not with a stable version, but chances are good you'll run into troubles with +the development versions). +<p>Active support from the user base is very important to help us track down those things. +Most often, serious problems are the result of some memory misadressing. During development, +we routinely use valgrind, a very well and capable memory debugger. This helps us to create +pretty clean code. But valgrind can not detect everything, most importantly not code pathes +that are never executed. So of most use for us is information about aborts and abort locations. +<p>Unforutnately, faults rooted in adressing errors typically show up only later, so the +actual abort location is in an unrelated spot. To help track down the original spot, +<a href="http://www.gnu.org/software/hello/manual/libc/Heap-Consistency-Checking.html">libc +later than 5.4.23 offers support</a> for finding, and possible temporary relief from it, +by means of the MALLOC_CHECK_ environment variable. Setting it to 2 is a useful troubleshooting +aid for us. It will make the program abort as soon as the check routines detect anything +suspicious (unfortunately, this may still not be the root cause, but hopefully closer to it). +Setting it to 0 may even make some problems disappear (but it will NOT fix them!). +With functionality comes cost, and so exporting MALLOC_CHECK_ without need comes at +a performance penalty. However, we strongly recommend adding this instrumentation to your +test environment should you see any serious problems. Chances are good it will help us +interpret a dump better, and thus be able to quicker craft a fix. +<p>In order to get useful information, we need some backtrace of the abort. First, you need +to make sure that a core file is created. Under Fedora, for example, that means you need +to have an "ulimit -c unlimited" in place. +<p>Now let's assume you got a core file (e.g. in /core.1234). So what to do next? Sending a +core file to us is most often pointless - we need to have the exact same system configuration in +order to interpret it correctly. Obviously, chances are extremely slim for this to be. So we would +appreciate if you could extract the most important information. This is done as follows: +<ul> +<li>$gdb /path/to/rsyslogd +<li>$info thread +<li>you'll see a number of threads (in the range 0 to n with n being the highest number). For + <b>each</b> of them, do the following (let's assume that i is the thread number): + <ul> + <li>$ thread i (e.g. thread 0, thread 1, ...) + <li>$bt + </ul> +<li>then you can quit gdb with "$q" +</ul> +<p>Then please send all information that gdb spit out to the development team. It is best to first +ask on the forum or mailing list on how to do that. The developers will keep in contact with you +and, I fear, will probably ask for other things as well ;) +<p>Note that we strive for highest reliability of the engine even in unusual deployment scenarios. +Unfortunately, this is hard to achieve, especially with limited resources. So we are depending on +cooperation from users. This is your chance to make a big contribution to the project without the +need to program or do anything else except get a problem solved ;) +<p>[<a href="manual.html">manual index</a>] +[<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2008-2010 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 3 or higher.</font></p> +</body> +</html> + diff --git a/doc/v3compatibility.html b/doc/v3compatibility.html new file mode 100644 index 00000000..1c153506 --- /dev/null +++ b/doc/v3compatibility.html @@ -0,0 +1,196 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Compatibility notes for rsyslog v3</title> + +<meta name="KEYWORDS" content="syslog, mysql, syslog to mysql, howto"></head> +<body> +<h1>Compatibility Notes for rsyslog v3</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2008-03-28)</i></small></p> +<p>Rsyslog aims to be a drop-in replacement for sysklogd. +However, version 3 has some considerable enhancements, which lead to +some backward compatibility issues both in regard to sysklogd and +rsyslog v1 and v2. Most of these issues are avoided by default by not +specifying the -c option on the rsyslog command line. That will enable +backwards-compatibility mode. However, please note that things may be +suboptimal in backward compatibility mode, so the advise is to work +through this document, update your rsyslog.conf, remove the no longer +supported startup options and then add -c3 as the first option to the +rsyslog command line. That will enable native mode.</p> +<p>Please note that rsyslogd helps you during that process by +logging appropriate messages about compatibility mode and +backwards-compatibility statemtents automatically generated. You may +want your syslogd log for those. They immediately follow rsyslogd's +startup message.</p> +<h2>Inputs</h2> +<p>With v2 and below, inputs were automatically started together +with rsyslog. In v3, inputs are optional! They come in the form of +plug-in modules. +<font color="#ff0000"><b>At least one input module +must be loaded to make rsyslog do any useful work.</b></font> +The config file directives doc briefly lists which config statements +are available by which modules.</p> +<p>It is suggested that input modules be loaded in the top part +of the config file. Here is an example, also highlighting the most +important modules:</p> +<p><b>$ModLoad immark # provides --MARK-- +message capability<br> +$ModLoad imudp # provides UDP syslog reception<br> +$ModLoad imtcp # provides TCP syslog reception<br> +</b><b>$ModLoad imgssapi # provides GSSAPI syslog +reception<br> +</b><b>$ModLoad imuxsock # provides support for local +system logging (e.g. +via logger command)<br> +$ModLoad imklog # provides kernel logging support (previously done +by rklogd)</b></p> +<h2>Command Line Options</h2> +<p>A number of command line options have been removed. New config +file directives have been added for them. The -h and -e option have +been removed even in compatibility mode. They are ignored but an +informative message is logged. Please note that -h was never supported +in v2, but was silently ignored. It disappeared some time ago in the +final v1 builds. It can be replaced by applying proper filtering inside +syslog.conf.</p> +<h2>-c option / Compatibility Mode</h2> +<p>The -c option is new and tells rsyslogd about the desired +backward compatibility mode. It must always be the first option on the +command line, as it influences processing of the other options. To use +the rsyslog v3 native +interface, specify -c3. To use compatibility mode , +either do not use -c at all or use -c<vers> where vers is +the +rsyslog version that it shall be compatible to. Use -c0 to be +command-line compatible to sysklogd.</p><p><span style="font-weight: bold;">Please note that rsyslogd issues warning messages if the -c3 command line option is not given.</span> +This is to alert you that your are running in compatibility mode. +Compatibility mode interfers with you rsyslog.conf commands and may +cause some undesired side-effects. It is meant to be used with a plain +old rsyslog.conf - if you use new features, things become messy. So the +best advise is to work through this document, convert your options and +config file and then use rsyslog in native mode. In order to aid you in +this process, rsyslog logs every compatibility-mode config file +directive it has generated. So you can simply copy them from your +logfile and paste them to the config.</p> +<h2>-e Option</h2> +This option is no longer supported, as the "last message repeated n +times" feature is now turned off by default. We changed this default +because this feature is causing a lot of trouble and we need to make it +either go away or change the way it works. For more information, please +see our dedicted <a href="http://www.rsyslog.com/PNphpBB2-viewtopic-p-1130.phtml">forum +thread on "last message repeated n times"</a>. This thread also +contains information on how to configure rsyslogd so that it continues +to support this feature (as long as it is not totally removed). +<h2>-m Option</h2> +<p>The -m command line option is emulated in compatibiltiy mode. +To replace it, use the following config directives (compatibility mode +auto-generates them):</p> +<p><b>$ModLoad immark<br> +$MarkMessagePeriod 1800 # 30 minutes</b></p> +<h2>-r Option</h2> +<p>Is no longer available in native mode. However, it +is +understood in compatibility mode (if no -c option is given). Use the <b>$UDPSeverRun +<port></b> config file directives. You can now also +set the local address the server should listen to via <b>$UDPServerAddress +<ip></b> config directive.</p> +<p>The following example configures an UDP syslog server at the +local address 192.0.2.1 on port 514:</p> +<p><b>$ModLoad imudp<br> +$UDPServerAddress 192.0.2.1 # this MUST be before the $UDPServerRun +directive!<br> +$UDPServerRun 514</b></p> +<p>"$UDPServerAddress *" means listen on all local interfaces. +This is the default if no directive is specified.</p> +<p>Please note that now multiple listeners are supported. For +example, you can do the following:</p> +<p><b>$ModLoad imudp<br> +$UDPServerAddress 192.0.2.1 # this MUST be before the $UDPServerRun +directive!<br> +$UDPServerRun 514<br> +$UDPServerAddress * # all local interfaces<br> +$UDPServerRun 1514</b></p> +<p>These config file settings run two listeners: one +at 192.0.2.1:514 and one on port 1514, which listens on all local +interfaces.</p> +<h2>Default port for UDP (and TCP) Servers</h2> +<p>Please note that with pre-v3 rsyslogd, a service database +lookup was made when a UDP server was started and no port was +configured. Only if that failed, the IANA default of 514 was used. For +TCP servers, this lookup was never done and 514 always used if no +specific port was configured. For consitency, both TCP and UDP now use +port 514 as default. If a lookup is desired, you need to specify it in +the "Run" directive, e.g. "<i>$UDPServerRun syslog</i>".</p> +<h2>klogd</h2> +<p>klogd has (finally) been replaced by a loadable input module. +To enable klogd functionality, do</p> +<p><b>$ModLoad imklog</b></p> +<p>Note that this can not be handled by the compatibility layer, +as klogd was a separate binary.A limited set of klogd command line +settings is now supported +via rsyslog.conf. That set of configuration directives is to be +expanded. </p> +<h2>Output File Syncing</h2> +Rsyslogd tries to keep as compatible to +stock syslogd as possible. As such, it retained stock syslogd's default +of syncing every file write if not specified otherwise (by placing a +dash in front of the output file name). While this was a useful feature +in past days where hardware was much less reliable and UPS seldom, this +no longer is useful in today's worl. Instead, the syncing is a high +performace hit. With it, rsyslogd writes files around 50 *times* slower +than without it. It also affects overall system performance due to the +high IO activity. In rsyslog v3, syncing has been turned off by +default. This is done via a specific configuration directive +"$ActionFileEnableSync on/off" which is off by default. So even if +rsyslogd finds sync selector lines, it ignores them by default. In +order to enable file syncing, the administrator must specify +"$ActionFileEnableSync on" at the top of rsyslog.conf. This ensures +that syncing only happens in some installations where the administrator +actually wanted that (performance-intense) feature. In the fast +majority of cases (if not all), this dramatically increases rsyslogd +performance without any negative effects. +<h2>Output File Format</h2> +<p>Rsyslog supports high precision RFC 3339 timestamps and puts these into +local log files by default. This is a departure from previous syslogd +behaviour. We decided to sacrify some backward-compatibility in an +effort to provide a better logging solution. Rsyslog has been +supporting the high-precision timestamps for over three years as of +this writing, but nobody used them because they were not default (one +may also assume that most people didn't even know about them). Now, we +are writing the great high-precision time stamps, which greatly aid in +getting the right sequence of logging events. If you do not like that, +you can easily turn them off by placing +</p><p style="font-weight: bold;"><code>$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat</code> +</p><p>right at the start of your rsyslog.conf. This will use the +previous format. Please note that the name is case-sensitive and must +be specificed exactly as shown above. Please also note that you can of +course use any other format of your liking. To do so, simply specify +the template to use or set a new default template via the +$ActionFileDefaultTemplate directive. Keep in mind, though, that +templates must be defined before they are used.</p><p>Keep in mind that +when receiving messages from remote hosts, the timestamp is just as +precise as the remote host provided it. In most cases, this means you +will only a receive a standard timestamp with second precision. If +rsyslog is running at the remote end, you can configure it to provide +high-precision timestamps (see below).</p><h2>Forwarding Format</h2><p>When +forwarding messages to remote syslog servers, rsyslogd by default uses +the plain old syslog format with second-level resolution inside the +timestamps. We could have made it emit high precision timestamps. +However, that would have broken almost all receivers, including earlier +versions of rsyslog. To avoid this hassle, high-precision timestamps +need to be explicitely enabled. To make this as painless as possible, +rsyslog comes with a canned template that contains everything +necessary. To enable high-precision timestamps, just use:</p><p style="font-weight: bold;"><code>$ActionForwardDefaultTemplate RSYSLOG_ForwardFormat # for plain TCP and UDP</code></p><p style="font-weight: bold;"><code>$ActionGSSForwardDefaultTemplate RSYSLOG_ForwardFormat # for GSS-API</code></p><p>And, of course, you can always set different forwarding formats by just specifying the right template.</p><p>If +you are running in a system with only rsyslog 3.12.5 and above in the +receiver roles, it is suggested to add one (or both) of the above +statements to the top of your rsyslog.conf (but after the $ModLoad's!) +- that will enable you to use the best in timestamp support availble. +Please note that when you use this format with other receivers, they +will probably become pretty confused and not detect the timestamp at +all. In earlier rsyslog versions, for example, that leads to +duplication of timestamp and hostname fields and disables the detection +of the orignal hostname in a relayed/NATed environment. So use the new +format with care. </p><h2>Queue Modes for the Main Message Queue</h2> +<p>Either "FixedArray" or "LinkedList" is recommended. "Direct" +is available, but should not be used except for a very good reason +("Direct" disables queueing and will potentially lead to message loss +on the input side).</p> +</body></html> diff --git a/doc/v4compatibility.html b/doc/v4compatibility.html new file mode 100644 index 00000000..2a51adea --- /dev/null +++ b/doc/v4compatibility.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Compatibility notes for rsyslog v4</title> +</head> +<body> +<h1>Compatibility Notes for rsyslog v4</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2009-07-15)</i></small></p> +<p>The changes introduced in rsyslog v4 are numerous, but not very intrusive. +This document describes things to keep in mind when moving from v3 to v4. It +does not list enhancements nor does it talk about compatibility concerns introduced +by v3 (for this, see the <a href="v3compatibility.html">rsyslog v3 compatibility notes</a>). +<h2>HUP processing</h2> +<p>With v3 and below, rsyslog used the traditional HUP behaviour. That meant that +all output files are closed and the configuration file is re-read and the new configuration +applied. +<p>With a program as simple and static as sysklogd, this was not much of an issue. The +most important config settings (like udp reception) of a traditional syslogd can not be +modified via the configuration file. So a config file reload only meant setting up a new set of filters. It also didn't account as problem that while doing so messages may be lost - without +any threading and queuing model, a traditional syslogd will potentially always loose +messages, so it is irrelevant if this happens, too, during the short config re-read +phase. +<p>In rsyslog, things are quite different: the program is more or less a framework into +which loadable modules are loaded as needed for a particular configuration. The software +that will acutally be running is taylored via the config file. Thus, a re-read of +the config file requires a full, very heavy restart, because the software acutally +running with the new config can be totally different from what ran with the old config. +<p>Consequently, the traditional HUP is a very heavy operation and may even cause some +data loss because queues must be shut down, listeners stopped and so on. Some of these +operations (depending on their configuration) involve intentional message loss. The operation +also takes up a lot of system resources and needs quite some time (maybe seconds) to be +completed. During this restart period, the syslog subsytem is not fully available. +<p>From the software developer's point of view, the full restart done by a HUP is rather complex, +especially if user-timeout limits set on action completion are taken into consideration (for +those in the know: at the extreme ends this means we need to cancel threads as a last resort, +but than we need to make sure that such cancellation does not happen at points where it +would be fatal for a restart). A regular restart, where the process is actually terminated, is +much less complex, because the operating system does a full cleanup after process termination, +so rsyslogd does not need to take care for exotic cleanup cases and leave that to the OS. +In the end result, restart-type HUPs clutter the code, increase complexity (read: add bugs) +and cost performance. +<p>On the contrary, a HUP is typically needed for log rotation, and the real desire is +to close files. This is a non-disruptive and very lightweigth operation. +<p>Many people have said that they are used to HUP the syslogd to apply configuration +changes. This is true, but it is questionable if that really justifies all the cost that +comes with it. After all, it is the difference between typing +<pre> +$ kill -HUP `cat /var/run/rsyslogd.pid` +</pre> +versus +<pre> +$ /etc/init.d/rsyslog restart +</pre> +Semantically, both is mostly the same thing. The only difference is that with the restart +command rsyslogd can spit config error message to stderr, so that the user is able to see +any problems and fix them. With a HUP, we do not have access to stderr and thus can log +error messages only to their configured destinations; exprience tells that most users +will never find them there. What, by the way, is another strong argument against +restarting rsyslogd by HUPing it. +<p>So a restart via HUP is not strictly necessary +and most other deamons require that a restart command is typed in if a restart is required. +<p>Rsyslog will follow this paradigm in the next versions, resulting in many benefits. In v4, +we provide some support for the old-style semantics. We introduced a setting $HUPisRestart +which may be set to "on" (tradional, heavy operation) +or "off" (new, lightweight "file close only" operation). +The initial versions had the default set to traditional behavior, but starting with 4.5.1 +we are now using the new behavior as the default. +<p>Most importantly, <b>this may break some scripts</b>, but my sincere belief is that +there are very few scripts that automatically <b>change</b> rsyslog's config and then do a +HUP to reload it. Anyhow, if you have some of these, it may be a good idea to change +them now instead of turning restart-type HUPs on. Other than that, one mainly needs +to change the habit of how to restart rsyslog after a configuration change. +<p><b>Please note that restart-type HUP is depricated and will go away in rsyslog v5.</b> +So it is a good idea to become ready for the new version now and also enjoy some of the +benefits of the "real restart", like the better error-reporting capability. +<p>Note that code complexity reduction (and thus performance improvement) needs the restart-type +HUP code to be removed, so these changes can (and will) only happen in version 5. +<h2>outchannels</h2> +Note: as always documented, outchannels are an experimental feature that may be +removed and/or changed in the future. +There is one concrete change done starting with 4.6.7: let's assume an +outchannel "mychannel" was defined. Then, this channel could be used inside an +<code> +*.* $mychannel +</code> +This is still supported and will remain to be supported in v4. However, there is +a new variant which explicitely tells this is to be handled by omfile. This new +syntax is as follows: +<code> +*.* :omfile:$mychannel +</code> +Note that future versions, specifically starting with v6, the older syntax is no +longer supported. So users are strongly advised to switch to the new syntax. As an +aid to the conversion process, rsyslog 4.7.4 and above issue a warning message +if the old-style directive is seen -- but still accept the old syntax without +any problems. +</body></html> diff --git a/doc/v5compatibility.html b/doc/v5compatibility.html new file mode 100644 index 00000000..fc4289c5 --- /dev/null +++ b/doc/v5compatibility.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Compatibility notes for rsyslog v5</title> +</head> +<body> +<h1>Compatibility Notes for rsyslog v5</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2009-07-15)</i></small></p> +<p>The changes introduced in rsyslog v5 are numerous, but not very intrusive. +This document describes things to keep in mind when moving from v4 to v5. It +does not list enhancements nor does it talk about compatibility concerns introduced +by earlier versions (for this, see their respective compatibility documents). +<h2>HUP processing</h2> +<p>The $HUPisRestart directive is supported by some early v5 versions, but has been removed +in 5.1.3 and above. That means that restart-type HUP processing is no longer +available. This processing was redundant and had a lot a drawbacks. +For details, please see the +<a href="v4compatibility.html">rsyslog v4 compatibility notes</a> which elaborate +on the reasons and the (few) things you may need to change. +<p>Please note that starting with 5.8.11 HUP will also requery the local hostname. +<h2>Queue on-disk format</h2> +<p>The queue information file format has been changed. When upgrading from v4 to +v5, make sure that the queue is emptied and no on-disk structure present. We did +not go great length in understanding the old format, as there was too little demand +for that (and it being quite some effort if done right). +<h2>Queue Worker Thread Shutdown</h2> +<p>Previous rsyslog versions had the capability to "run" on zero queue worker +if no work was required. This was done to save a very limited number of resources. However, +it came at the price of great complexity. In v5, we have decided to let a minium of one +worker run all the time. The additional resource consumption is probably not noticable at +all, however, this enabled us to do some important code cleanups, resulting in faster +and more reliable code (complex code is hard to maintain and error-prone). From the +regular user's point of view, this change should be barely noticable. I am including the +note for expert users, who will notice it in rsyslog debug output and other analysis tools. +So it is no error if each queue in non-direct mode now always runs at least one worker +thread. +</body></html> diff --git a/doc/v6compatibility.html b/doc/v6compatibility.html new file mode 100644 index 00000000..7ce8c001 --- /dev/null +++ b/doc/v6compatibility.html @@ -0,0 +1,198 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Compatibility notes for rsyslog v6</title> +</head> +<body> +<h1>Compatibility Notes for rsyslog v6</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2011-10-27)</i></small></p> +<p> +This document describes things to keep in mind when moving from v5 to v6. It +does not list enhancements nor does it talk about compatibility concerns introduced +by earlier versions (for this, see their respective compatibility documents). Its focus +is primarily on what you need to know if you used a previous version and want to use the +current one without hassle. +<p>Version 6 offers a better config language and some other improvements. +As the config system has many ties into the rsyslog engine AND all plugins, +the changes are somewhat intrusive. Note, however, that core processing has +not been changed much in v6 and will not. So once the configuration is loaded, +the stability of v6 is quite comparable to v5. +</p> +<h2>Property "pri-text"</h2> +<p>Traditionally, this property did not only return the textual form +of the pri ("local0.err"), but also appended the numerical value to it +("local0.err<133>"). This sounds odd and was left unnoticed for some years. +In October 2011, this odd behaviour was brought up on the rsyslog mailing list +by Gregory K. Ruiz-Ade. Code review showed that the behaviour was intentional, +but no trace of what the intention was when it was introduced could be found. +The documentation was also unclear, it said no numerical value was present, +but the samples had it. We agreed that the additional numerical value is +of disadvantage. We also guessed that this property is very rarely being used, +otherwise the problem should have been raised much earlier. However, we +didn't want to change behaviour in older builds. So v6 was set to clean up +the situation. In v6, text-pri will always return the textual part only +("local0.err") and the numerical value will not be contained any longer inside +the string. If you actually need that value, it can fairly easily be added +via the template system. +<p><b>If you have used this property previously and relied on the numerical +part, you need to update your rsyslog configuration files.</b> +<h2>Plugin ABI</h2> +<p>The plugin interface has considerably been changed to support the new +config language. All plugins need to be upgraded. This usually does not require +much coding. However, if the new config language shall be supported, more +changes must be made to plugin code. All project-supported plugins have been +upgraded, so this compatibility issue is only of interest for you if you have +custom plugins or use some user-contributed plugins from the rsyslog project +that are not maintained by the project itself (omoracle is an example). Please +expect some further plugin instablity during the initial v6 releases. +<h2>RainerScript based rsyslog.conf</h2> +<p>A better config format was the main release target for rsyslog v6. It comes in the +flavor of so-called RainerScript +(<a href="http://blog.gerhards.net/2008/02/introducing-rainerscript-and-some.html">why the +name RainerScript?</a>). RainerScript supports legacy syslog.conf format, much as you know it +from other syslogd's (like sysklogd or the BSD syslogd's) as well as previous versions +of rsyslog. Initial work on RainerScript began in v4, and the if-construct was already +supported in v4 and v5. Version 6 has now taken this further. After long discussions we +decided to use the legacy format as a basis, and lightly extend it by native RainerScript +constructs. The main goal was to make sure that previous knowledge and config systems +could still be used while offering a much more intuitive and powerful way of configuring +rsyslog. +<p>RainerScript has been implemented from scratch and with new tools (flex/bison, for those in the +know). Starting with 6.3.3, this new config file processor replaces the legacy one. Note that +the new processor handles all formats, extended RainerScript as well as legacy syslog.conf format. +There are some legacy construct that were especially hard to translate. You'll read about them in +other parts of this document (especially outchannels, which require a format change). + +<p>In v6, all legacy formats are supported. In the long term, we may remove some of the ugly +rsyslog-specific constructs. Good candidates are all configuration commands starting with +a dollar sign, like "$ActionFileDefaultTemplate"). However, this will not be the case before +rsyslog v7 or (much more likely) v8/9. Right now, you also need to use these commands, because +not all have already been converted to the new RainerScript format. + +<p>In 6.3.3, the new parser is used, but almost none of the extended RainerScript capabilities +are available. They will incrementally be introduced with the following releases. Note that for +some features (most importantly if-then-else nested blocks), the v6 core engine is not +capable enough. It is our aim to provide a much better config language to as many rsyslog +users as quickly as possible. As such, we refrain from doing big engine changes in v6. This +in turn means we cannot introduce some features into RainerScript that we really want to see. +These features will come up with rsyslog v7, which will have even better flow control +capabilities inside the core engine. Note that v7 will fully support v6 RainerScript. +Let us also say that the v6 version is not a low-end quick hack: it offers full-fledged +syslog message processing control, capable of doing the best you can find inside the +industry. We just say that v7 will come up with even more advanced capabilites. +<p>Please note that we tried hard to make the RainerScript parser compatible with +all legacy config files. However, we may have failed in one case or another. So if you +experience problems during config processing, chances are there may be a problem +on the rsyslog side. In that case, please let us know. + +<p>Please see the +<a href="http://blog.gerhards.net/2011/07/rsyslog-633-config-format-improvements.html">blog +post about rsyslog 6.3.3 config format</a> for details of what is currently supported. + +<h2>compatibility mode</h2> +<p>Compatibility mode (specified via -c option) has been removed. This was a migration aid from +sysklogd and very early versions of rsyslog. As all major distros now have rsyslog as their +default, and thus ship rsyslog-compliant config files, there is no longer a need for +compatibility mode. Removing it provides easier to maintain code. Also, practice has shown +that many users were confused by compatibility mode (and even some package maintainers got +it wrong). So this not only cleans up the code but rather removes a frequent source of +error. +<p>It must be noted, though, that this means rsyslog is no longer a 100% drop-in replacement +for sysklogd. If you convert an extremely old system, you need to checks its config and +probably need to apply some very mild changes to the config file. +<h2>abort on config errors</h2> +<p>Previous versions accepted some malformedness inside the config file without aborting. This +could lead to some uncertainty about which configuration was actually running. In v6 there +are some situations where config file errors can not be ignored. In these cases rsyslog +emits error messages to stderr, and then exists with a non-zero exit code. It is important +to check for those cases as this means log data is potentially lost. +Please note that +the root problem is the same for earlier versions as well. With them, it was just harder +to spot why things went wrong (and if at all). +<h2>Default Batch Sizes</h2> +<p>Due to their positive effect on performance and comparatively low overhead, +default batch sizes have been increased. Starting with 6.3.4, the action queues +have a default batch size of 128 messages. +<h2>Default action queue enqueue timeout</h2> +<p>This timeout previously was 2seconds, and has been reduced to 50ms (starting with 6.5.0). This change +was made as a long timeout will caused delays in the associated main queue, something +that was quite unexpected to users. Now, this can still happen, but the effect is much +less harsh (but still considerable on a busy system). Also, 50ms should be fairly enough +for most output sources, except when they are really broken (like network disconnect). If +they are really broken, even a 2second timeout does not help, so we hopefully get the best +of both worlds with the new timeout. A specific timeout can of course still be configured, +it is just the timeout that changed. +<h2>outchannels</h2> +<p>Outchannels are a to-be-removed feature of rsyslog, at least as far as the config +syntax is concerned. Nevertheless, v6 still supports it, but a new syntax is required +for the action. Let's assume your outchannel is named "channel". The previous syntax was +<blockquote><code> +*.* $channel +</code> </blockquote> +This was deprecated in v5 and no longer works in v6. Instead, you need to specify +<blockquote><code> +*.* :omfile:$channel +</code></blockquote> +Note that this syntax is available starting with rsyslog v4. It is important to keep on your +mind that future versions of rsyslog will require different syntax and/or drop outchannel support +completely. So if at all possible, avoid using this feature. If you must use it, be prepared for +future changes and watch announcements very carefully. +<h2>ompipe default template</h2> +<p>Starting with 6.5.0, ompipe does no longer use the omfile default template. +Instead, the default template must be set via the module load statement. +An example is +<blockquote><code> +module(load="builtin:ompipe" template="myDefaultTemplate") +</code> </blockquote> +<p>For obvious reasons, the default template must be defined somewhere in +the config file, otherwise errors will happen during the config load +phase. +<h2>omusrmsg</h2> +<p>The omusrmsg module is used to send messages to users. In legacy-legacy +config format (that is the very old sysklogd style), it was suffucient to use +just the user name to call this action, like in this example: +<blockquote><code> +*.* rgerhards +</code> </blockquote> +This format is very ambigious and causes headache (see +<a href="http://blog.gerhards.net/2011/07/why-omusrmsg-is-evil-and-how-it-is.html">blog post +on omusrmsg</a> for details). Thus the format has been superseded by this syntax +(which is legacy format ;-)): +<blockquote><code> +*.* :omusrmsg:rgerhards +</code> </blockquote> +That syntax is supported since later subversions of version 4. +<p>Rsyslog v6 still supports the legacy-legacy format, but in a very strict +sense. For example, if multiple users or templates are given, no spaces +must be included in the action line. For example, this works up to v5, but no +longer in v6: +<blockquote><code> +*.* rgerhards, bgerhards +</code> </blockquote> +To fix it in a way that is compatible with pre-v4, use (note the removed space!): +<blockquote><code> +*.* rgerhards,bgerhards +</code> </blockquote> +Of course, it probably is better to understand in native v6 format: +<blockquote><code> +*.* action(type="omusrmsg" users="rgerhards, bgerhards") +</code> </blockquote> +As you see, here you may include spaces between user names. +<p>In the long term, legacy-legacy format will most probably totally disappear, +so it is a wise decision to change config files at least to the legacy +format (with ":omusrmsg:" in front of the name). + +<h2>Escape Sequences in Script-Based Filters</h2> +<p>In v5, escape sequences were very simplistic. Inside a string, "\x" meant +"x" with x being any character. This has been changed so that the usual set of +escapes is supported, must importantly "\n", "\t", "\xhh" (with hh being hex digits) +and "\ooo" with (o being octal digits). So if one of these sequences was used +previously, results are obviously different. However, that should not create any +real problems, because it is hard to envision why someone should have done that +(why write "\n" when you can also write "n"?). +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2011 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body></html> diff --git a/doc/v7compatibility.html b/doc/v7compatibility.html new file mode 100644 index 00000000..da4772fe --- /dev/null +++ b/doc/v7compatibility.html @@ -0,0 +1,138 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Compatibility notes for rsyslog v7</title> +</head> +<body> +<h1>Compatibility Notes for rsyslog v7</h1> +This document describes things to keep in mind when moving from v6 to v7. It +does not list enhancements nor does it talk about compatibility concerns introduced +by earlier versions (for this, see their respective compatibility documents). Its focus +is primarily on what you need to know if you used v6 and want to use v7 without hassle. +<p>Version 7 builds on the new config language introduced in v6 and extends it. +Other than v6, it not just only extends the config language, but provides +considerable changes to core elements as well. The result is much more power and +ease of use as well (this time that is not contradictionary). +</p> +<h2>BSD-Style blocks</h2> +BSD style blocks are no longer supported (for good reason). See the +<a href="http://www.rsyslog.com/g/BSD">rsyslog BSD blocks info</a> +page for more information and how to upgrade your config. +<p>[<a href="manual.html">manual index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> + +<h2>CEE-Properties</h2> +In rsyslog v6, CEE properties could not be used across disk-based queues. If this was +done, there content was reset. This was a missing feature in v6. In v7, this feature +has been implemented. Consequently, situations where the previous behaviour were +desired need now to be solved differently. We do not think that this will cause any +problems to anyone, especially as in v6 this was announced as a missing feature. + +<h2>omusrmsg: using just a username or "*" is deprecated</h2> +<p>In legacy config format, the asterisk denotes writing the message to all users. +This is usually used for emergency messages and configured like this: +<pre> +*.emerg * +</pre> +<p>Unfortunately, the use of this single character conflicts with other uses, for +example with the multiplication operator. While rsyslog up to versions v7.4 preserves the meaning of +asterisk as an action, it is deprecated and will probably be removed in future versions. +Consequently, a warning message is emitted. To make this warning go away, the action must +be explicitly given, as follows: +<pre> +*.emerg :omusrmsg:* +</pre> +<p>The same holds true for user names. For example +<pre> +*.emerg john +</pre> +<p>at a minimum should be rewritten as +<pre> +*.emerg :omusrmsg:john +</pre> +<p>Of course, for even more clarity the new RainerScript style of action can +also be used: +<pre> +*.emerg action(type="omusrmsg" users="john") +</pre> +<p>In Rainer's blog, there is more +<a href="http://blog.gerhards.net/2011/07/why-omusrmsg-is-evil-and-how-it-is.html">background +information on why omusrmsg needed to be changed</a> available. + +<h2>omruleset and discard (~) action are deprecated</h2> +<p>Both continue to work, but have been replaced by better alternatives. +<p>The discard action (tilde character) has been replaced by the "stop" +RainerScript directive. It is considered more intuitive and offers slightly +better performance. +<p>The omruleset module has been replaced by the "call" RainerScript directive. +Call permits to execute a ruleset like a subroutine, and does so with much +higher performance than omruleset did. Note that omruleset could be run off +an async queue. This was more a side than a desired effect and is not supported +by the call statement. If that effect was needed, it can simply be simulated by +running the called rulesets actions asynchronously (what in any case is the right +way to handle this). +<p>Note that the deprecated modules emit warning messages when being used. +They tell that the construct is deprecated and which statement is to be used +as replacement. This does <b>not</b> affect operations: both modules are still +fully operational and will not be removed in the v7 timeframe. + +<h2>Retries of output plugins that do not do proper replies</h2> +<p>Some output plugins may not be able to detect if their target is capable of +accepting data again after an error (technically, they always return OK when +TryResume is called). Previously, the rsyslog core engine suspended such an action +after 1000 succesive failures. This lead to potentially a large amount of +errors and error messages. Starting with 7.2.1, this has been reduced to 10 +successive failures. This still gives the plugin a chance to recover. In extreme +cases, a plugin may now enter suspend mode where it previously did not do so. +In practice, we do NOT expect that. +<h1>Notes for the 7.3/7.4 branch</h1> +<h2>"last message repeated n times" Processing</h2> +<p>This processing has been optimized and moved to the input side. This results +in usually far better performance and also de-couples different sources +from the same +processing. It is now also integrated in to the more generic rate-limiting +processing. +<h3>User-Noticable Changes</h3> +The code works almost as before, with two exceptions: +<ul> +<li>The supression amount can be different, as the new algorithm + precisely check's a single source, and while that source is being + read. The previous algorithm worked on a set of mixed messages + from multiple sources. +<li>The previous algorithm wrote a "last message repeated n times" message + at least every 60 seconds. For performance reasons, we do no longer do + this but write this message only when a new message arrives or rsyslog + is shut down. +</ul> +<p>Note that the new algorithms needs support from input modules. If old +modules which do not have the necessary support are used, duplicate +messages will most probably not be detected. Upgrading the module code is +simple, and all rsyslog-provided plugins support the new method, so this +should not be a real problem (crafting a solution would result in rather +complex code - for a case that most probably would never happen). +<h3>Performance Implications</h3> +<p>In general, the new method enables far faster output procesing. However, it +needs to be noted that the "last message repeated n" processing needs parsed +messages in order to detect duplicated. Consequently, if it is enabled the +parser step cannot be deferred to the main queue processing thread and +thus must be done during input processing. The changes workload distribution +and may have (good or bad) effect on the overall performance. If you have +a very high performance installation, it is suggested to check the performance +profile before deploying the new version. Note: for high-performance +environments it is highly recommended NOT to use "last message repeated n times" +processing but rather the other (more efficient) rate-limiting methods. These +also do NOT require the parsing step to be done during input processing. + +<h2>Stricter string-template Processing</h2> +<p>Previously, no error message for invalid string template parameters +was generated. +Rather a malformed template was generated, and error information emitted +at runtime. However, this could be quite confusing. Note that the new code +changes user experience: formerly, rsyslog and the affected +actions properly started up, but the actions did not produce proper +data. Now, there are startup error messages and the actions are NOT +executed (due to missing template due to template error). + +<p><font size="2">This documentation is part of the +<a href="http://www.rsyslog.com/">rsyslog</a> project.<br> +Copyright © 2011-2013 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> and +<a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL +version 2 or higher.</font></p> +</body></html> diff --git a/doc/version_naming.html b/doc/version_naming.html new file mode 100644 index 00000000..3bfa19bb --- /dev/null +++ b/doc/version_naming.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog version naming</title></head> +<body> +<h1>Version Naming</h1> +<p style="font-weight: bold;">This is the proposal on how versions should be named in the future:</p><p>Rsyslog version naming has undergone a number of changes in +the past. Our sincere hopes is that the scheme outlined here will serve +us well for the future. In general, a three-number versioning scheme +with a potential development state indication is used. It follows this +pattern:</p> +<p>major.minor.patchlevel[-devstate]</p> +<p>where devstate has some forther structure: +-<releaseReason><releaseNumber></p> +<p>All stable builds come without the devstate part. All unstable +development version come with it.</p> +<p>The <span style="font-weight: bold;">major</span> +version is incremented whenever something really important happens. A +single new feature, even if important, does not justify an increase in +the major version. There is no hard rule when the major version needs +an increment. It mostly is a soft factor, when the developers and/or +the community think there has been sufficient change to justify that. +Major version increments are expected to happen quite infrequently, +maybe around once a year. A major version increment has important +implications from the support side: without support contracts, the +current major version's last stable release and the last stable release +of the version immediately below it are supported (Adiscon, the rsyslog +sponsor, offers <a href="professional_support.html">support contracts</a> covering all other versions).</p> +<p>The <span style="font-weight: bold;">minor</span> version is +incremented whenever a non-trivial new feature is planned to be added. +Triviality of a feature is simply determined by time estimated to +implement a feature. If that's more than a few days, it is considered a +non-trivial feature. Whenever a new minor version is begun, the desired +feature is identified and will be the primary focus of that major.minor +version. Trivial features may justify a new minor version if they +either do not look trivial from the user's point of view or change +something quite considerable (so we need to alert users). A minor +version increment may also be done for some other good reasons that the +developers have.</p> +<p>The <span style="font-weight: bold;">patchlevel</span> is incremented whenever there is a bugfix or very minor feature added to a (stable or development) release.</p><p>The <span style="font-weight: bold;">devstate</span> +is important during development of a feature. It helps the developers +to release versions with new features to the general public and in the +hope that this will result in some testing. To understand how it works, +we need to look at the release cycle: As already said, at the start of +a new minor version, a new non-trivial feature to be implemented in +that version is selected. Development on this feature begins. At the +current pace of development, getting initial support for such a +non-trivial feature typically takes between two and four weeks. During +this time, new feature requests come in. Also, we may find out that it +may be just the right time to implement some not yet targeted feature +requests. A reason for this is that the minor release's feature focus +is easier to implement if the other feature is implemented first. This +is a quite common thing to happen. So development on the primary focus +may hold for a short period while we implement something else. Even +unrelated, but very trivial feature requests (maybe an hour's worth of +time to implement), may be done in between. Once we have implemented +these things, we would like to release as quickly as possible (even +more if someone has asked for the feature). So we do not like to wait +for the original focus feature to be ready (what could take maybe three +more weeks). As a result, we release the new features. But that version +will also include partial code of the focus feature. Typically this +doesn't hurt as long as noone tries to use it (what of course would +miserably fail). But still, part of the new code is already in it. When +we release such a "minor-feature enhanced" but "focus-feature not yet +completed" version, we need a way to flag it. In current thinking, that +is using a "<span style="font-weight: bold;">-mf<version></span>" <span style="font-weight: bold;">devstate</span> +in the version number ("mf" stands for "minor feature"). Version +numbers for -mf releases start at 0 for the first release and are +monotonically incremented. Once the focus feature has been fully +implemented, a new version now actually supporting that feature will be +released. Now, the release reason is changed to the well-know "<span style="font-weight: bold;">-rc<version></span>" +where "rc" stands for release candidate. For the first release +candidate, the version starts at 0 again and is incremented +monotonically for each subsequent release. Please note that a -rc0 may +only have bare functionality but later -rc's have a richer one. If new +minor features are implemented and released once we have reached rc +stage, still a new rc version is issued. The difference between "mf" +and "rc" is simply the presence of the desired feature. No support is +provided for -mf versions once the first -rc version has been released. +And only the most current -rc version is supported.</p><p>The -rc is +removed and the version declared stable when we think it has undergone +sufficient testing and look sufficiently well. Then, it'll turn into a +stable release. Stable minor releases never receive non-trivial new +features. There may be more than one -rc releases without a stable +release present at the same time. In fact, most often we will work on +the next minor development version while the previous minor version is +still a -rc because it is not yet considered sufficiently stable.</p><p>Note: <span style="font-weight: bold;">the +absence of the -devstate part indicates that a release is stable. +Following the same logic, any release with a -devstate part is unstable.</span></p><p>A quick sample: </p><p>4.0.0 +is the stable release. We begin to implement relp, moving to +major.minor to 4.1. While we develop it, someone requests a trivial +feature, which we implement. We need to release, so we will have +4.1.0-mf0. Another new feature is requested, move to 4.1.0-mf2. A first +version of RELP is implemented: 4.1.0-rc0. A new trivial feature is +implemented: 4.1.0-rc1. Relp is being enhanced: 4.1.0-rc2. We now feel +RELP is good enough for the time being and begin to implement TLS on +plain /Tcp syslog: logical increment to 4.2. Now another new feature in +that tree: 4.2.0-mf0. Note that we now have 4.0.0 (stable) and +4.1.0-rc2 and 4.1.0-mf0 (both devel). We find a big bug in RELP coding. +Two new releases: 4.1.0-rc3, 4.2.0-mf1 (the bug fix acts like a +non-focus feature change). We release TLS: 4.2.0-rc0. Another RELP bug +fix 4.1.0-rc4, 4.2.0-rc1. After a while, RELP is matured: 4.1.0 +(stable). Now support for 4.0.x stable ends. It, however, is still +provided for 3.x.x (in the actual case 2.x.x, because v3 was under the +old naming scheme and now stable v3 was ever released).</p><p style="font-weight: bold;">This is how it is done so far:</p><p>This document briefly outlines the strategy for naming +versions. It applies to versions 1.0.0 and above. Versions below that +are all unstable and have a different naming schema.</p> +<p><b>Please note that version naming is currently being +changed. There is a +<a href="http://blog.gerhards.net/2007/08/on-rsyslog-versions.html">blog +post about future rsyslog versions</a>.</b></p> +<p>The major version is incremented whenever a considerate, major +features have been added. This is expected to happen quite infrequently.</p> +<p>The minor version number is incremented whenever there is +"sufficient need" (at the discretion of the developers). There is a +notable difference between stable and unstable branches. The <b>stable +branch</b> always has a minor version number in the range from 0 +to 9. It is expected that the stable branch will receive bug and +security fixes only. So the range of minor version numbers should be +quite sufficient.</p> +<p>For the <b>unstable branch</b>, minor version +numbers always start at 10 and are incremented as needed (again, at the +discretion of the developers). Here, new minor versions include both +fixes as well as new features (hopefully most of the time). They are +expected to be released quite often.</p> +<p>The patch level (third number) is incremented whenever a +really minor thing must be added to an existing version. This is +expected to happen quite infrequently.</p> +<p>In general, the unstable branch carries all new development. +Once it concludes with a sufficiently-enhanced, quite stable version, a +new major stable version is assigned.</p> +</body></html> diff --git a/grammar/.gitignore b/grammar/.gitignore new file mode 100644 index 00000000..8bd546bd --- /dev/null +++ b/grammar/.gitignore @@ -0,0 +1 @@ +lexer.c diff --git a/grammar/Makefile.am b/grammar/Makefile.am new file mode 100644 index 00000000..d231bb46 --- /dev/null +++ b/grammar/Makefile.am @@ -0,0 +1,19 @@ +BUILT_SOURCES = grammar.h +CLEANFILES = grammar.h grammar.c +AM_YFLAGS = -d +noinst_LTLIBRARIES = libgrammar.la +#bin_PROGRAMS = testdriver # TODO: make this conditional + +libgrammar_la_SOURCES = \ + grammar.y \ + lexer.l \ + rainerscript.c \ + rainerscript.h \ + parserif.h \ + grammar.h +libgrammar_la_CPPFLAGS = $(RSRT_CFLAGS) + +#testdriver_SOURCES = testdriver.c libgrammar.la +#testdriver_CPPFLAGS = $(RSRT_CFLAGS) +#testdriver_LDADD = libgrammar.la +#testdriver_LDFLAGS = -lestr diff --git a/grammar/conf-fmt b/grammar/conf-fmt new file mode 100644 index 00000000..e34ab784 --- /dev/null +++ b/grammar/conf-fmt @@ -0,0 +1,145 @@ +PRI filter: + +- facility and severity may be numeric (but discouraged) +- format: facility "." priority [";" next-selector] (no whitespace) +- facility: + * auth, authpriv, cron, daemon, kern, lpr, mail, mark, news, security + (same as auth), syslog, user, uucp and local0 through local7 + * multiple +- "priority" (actually severity): + * debug, info, notice, warning, warn (same as warning), + err, error (same as err), crit, alert, emerg, panic (same as + emerg). The keywords error, warn and panic are deprecated and + should not be used anymore. + * "=" in front of sev --> exactly this + * "!" in front of sev --> ignore this priority + * "=" and "!" can be combined +- * => all fac/severities +- a '\' at end of line means that the following line f is a + continuation line. If so, leading whitespace is stripped from + f and then f as appended to the end of the current line, replacing + the backslash and all whitespace following it. + This makes it somewhat easier to grab selectors from an old-style + config stream. + '\' [WHITESPACE]* LF + + +DEBIAN SAMPLE +This probably includes everything that is problematic... + +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +#$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +user.* -/var/log/user.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +mail.info -/var/log/mail.info +mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# +# Logging for INN news system. +# +news.crit /var/log/news/news.crit +news.err /var/log/news/news.err +news.notice -/var/log/news/news.notice + +# +# Some "catch-all" log files. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole diff --git a/grammar/debian.conf b/grammar/debian.conf new file mode 100644 index 00000000..ff7708c5 --- /dev/null +++ b/grammar/debian.conf @@ -0,0 +1,132 @@ +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +#$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +#$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +user.* -/var/log/user.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +mail.info -/var/log/mail.info +mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# +# Logging for INN news system. +# +news.crit /var/log/news/news.crit +news.err /var/log/news/news.err +news.notice -/var/log/news/news.notice + +# +# Some "catch-all" log files. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +!ThisTag ++host1 +-host2 ++* +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole +$cfs 21 +$cfs 22 +$cfs 23 +# samples added to get full "flavor" of what we need to support... +:msg, contains, "error" /var/log/somelog +$cfs 11 +$cfs 12 +$cfs 13 +module() +$cfs 1 +$cfs 2 +$cfs 3 diff --git a/grammar/debian.new b/grammar/debian.new new file mode 100644 index 00000000..4dbb5907 --- /dev/null +++ b/grammar/debian.new @@ -0,0 +1,165 @@ +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +module( + name="imuxsock" # provides support for local system logging + ) +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 +module(name="imudp") +input(type="imudp" port="514") + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +#$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +#$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log + +# +# Some "catch-all" log files. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole + +global (dnscache="yes" arg1="1 2" arg2 = "1 2" arg3 ="1=2\"3") +# samples added to get full "flavor" of what we need to support... +:msg, contains, "error" /var/log/somelog +action(type="omfile" target="/var/log/mail/log") +*.* /* comment */ * # test +*.info :ommysql:, tra, la , la # comment (comment to be part of old style line!) + +# from SUSE: +if ( \ + /* kernel up to warning except of firewall */ \ + ($syslogfacility-text == 'kern') and \ + ($syslogseverity <= 4 /* warning */ ) and not \ + ($msg contains 'IN=' and $msg contains 'OUT=') \ + ) or ( \ + /* up to errors except of facility authpriv */ \ + ($syslogseverity <= 3 /* errors */ ) and not \ + ($syslogfacility-text == 'authpriv') \ + ) \ +then /dev/tty10 +& |/dev/xconsole +# +# slightly modified to not use continuation lines +if ( /* kernel up to warning except of firewall */ + ($syslogfacility-text == 'kern') and + ($syslogseverity <= 4 /* warning */ ) and not + ($msg contains 'IN=' and $msg contains 'OUT=') + ) or ( + /* up to errors except of facility authpriv */ + ($syslogseverity <= 3 /* errors */ ) and not + ($syslogfacility-text == 'authpriv') + ) +then /dev/tty10 +& |/dev/xconsole + +*.* rger # write to user (ugly...) +#ruleset name + +# FEDORA, a bit more complex config +# ### begin forwarding rule ### +# The statement between the begin ... end define a SINGLE forwarding +# rule. They belong together, do NOT split them. If you create multiple +# forwarding rules, duplicate the whole block! +# Remote Logging (we use TCP for reliable delivery) +# +# An on-disk queue is created for this action. If the remote host is +# down, messages are spooled to disk and sent when it is up again. +#$WorkDirectory /var/spppl/rsyslog # where to place spool files +#$ActionQueueFileName fwdRule1 # unique name prefix for spool files +#$ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible) +#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown +#$ActionQueueType LinkedList # run asynchronously +#$ActionResumeRetryCount -1 # infinite retries if host is down +# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional +#*.* @@remote-host:514 +# ### end of the forwarding rule ### +if $msg contains "error" then { + action(type="omfwd" protocol="tcp" target="10.0.0.1:514" + action.retryCount="-1" + queue.type="linkedList" queue.fileName="fwdRule" queue.maxDiskSpace="1g" + queue.saveOnShutdown="on" + ) + action(type="omfile" target="/var/log/somelog.log") + action(type="omuser" target="all") +} diff --git a/grammar/grammar.y b/grammar/grammar.y new file mode 100644 index 00000000..c5bad689 --- /dev/null +++ b/grammar/grammar.y @@ -0,0 +1,213 @@ + /* Bison file for rsyslog config format v2 (RainerScript). + * Please note: this file introduces the new config format, but maintains + * backward compatibility. In order to do so, the grammar is not 100% clean, + * but IMHO still sufficiently easy both to understand for programmers + * maitaining the code as well as users writing the config file. Users are, + * of course, encouraged to use new constructs only. But it needs to be noted + * that some of the legacy constructs (specifically the in-front-of-action + * PRI filter) are very hard to beat in ease of use, at least for simpler + * cases. + * + * Copyright 2011-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +%{ +#include <stdio.h> +#include <libestr.h> +#include "rainerscript.h" +#include "parserif.h" +#define YYDEBUG 1 +extern int yylineno; + +/* keep compile rule clean of errors */ +extern int yylex(void); +extern int yyerror(char*); +%} + +%union { + char *s; + long long n; + es_str_t *estr; + enum cnfobjType objType; + struct cnfobj *obj; + struct cnfstmt *stmt; + struct nvlst *nvlst; + struct objlst *objlst; + struct cnfexpr *expr; + struct cnfarray *arr; + struct cnffunc *func; + struct cnffparamlst *fparams; +} + +%token <estr> NAME +%token <estr> FUNC +%token <objType> BEGINOBJ +%token ENDOBJ +%token BEGIN_ACTION +%token BEGIN_PROPERTY +%token BEGIN_CONSTANT +%token BEGIN_TPL +%token BEGIN_RULESET +%token STOP +%token SET +%token UNSET +%token CONTINUE +%token <cnfstmt> CALL +%token <s> LEGACY_ACTION +%token <s> LEGACY_RULESET +%token <s> PRIFILT +%token <s> PROPFILT +%token <s> BSD_TAG_SELECTOR +%token <s> BSD_HOST_SELECTOR +%token IF +%token THEN +%token ELSE +%token OR +%token AND +%token NOT +%token <s> VAR +%token <estr> STRING +%token <n> NUMBER +%token CMP_EQ +%token CMP_NE +%token CMP_LE +%token CMP_GE +%token CMP_LT +%token CMP_GT +%token CMP_CONTAINS +%token CMP_CONTAINSI +%token CMP_STARTSWITH +%token CMP_STARTSWITHI + +%type <nvlst> nv nvlst value +%type <obj> obj property constant +%type <objlst> propconst +%type <expr> expr +%type <stmt> stmt s_act actlst block script +%type <fparams> fparams +%type <arr> array arrayelt + +%left AND OR +%left CMP_EQ CMP_NE CMP_LE CMP_GE CMP_LT CMP_GT CMP_CONTAINS CMP_CONTAINSI CMP_STARTSWITH CMP_STARTSWITHI +%left '+' '-' '&' +%left '*' '/' '%' +%nonassoc UMINUS NOT + +%expect 1 /* dangling else */ +/* If more erors show up, Use "bison -v grammar.y" if more conflicts arise and + * check grammar.output for were exactly these conflicts exits. + */ +%% +/* note: we use left recursion below, because that saves stack space AND + * offers the right sequence so that we can submit the top-layer objects + * one by one. + */ +conf: /* empty (to end recursion) */ + | conf obj { cnfDoObj($2); } + | conf stmt { cnfDoScript($2); } + | conf LEGACY_RULESET { cnfDoCfsysline($2); } + | conf BSD_TAG_SELECTOR { cnfDoBSDTag($2); } + | conf BSD_HOST_SELECTOR { cnfDoBSDHost($2); } +obj: BEGINOBJ nvlst ENDOBJ { $$ = cnfobjNew($1, $2); } + | BEGIN_TPL nvlst ENDOBJ { $$ = cnfobjNew(CNFOBJ_TPL, $2); } + | BEGIN_TPL nvlst ENDOBJ '{' propconst '}' + { $$ = cnfobjNew(CNFOBJ_TPL, $2); + $$->subobjs = $5; + } + | BEGIN_RULESET nvlst ENDOBJ '{' script '}' + { $$ = cnfobjNew(CNFOBJ_RULESET, $2); + $$->script = $5; + } +propconst: { $$ = NULL; } + | propconst property { $$ = objlstAdd($1, $2); } + | propconst constant { $$ = objlstAdd($1, $2); } +property: BEGIN_PROPERTY nvlst ENDOBJ { $$ = cnfobjNew(CNFOBJ_PROPERTY, $2); } +constant: BEGIN_CONSTANT nvlst ENDOBJ { $$ = cnfobjNew(CNFOBJ_CONSTANT, $2); } +nvlst: { $$ = NULL; } + | nvlst nv { $2->next = $1; $$ = $2; } +nv: NAME '=' value { $$ = nvlstSetName($3, $1); } +value: STRING { $$ = nvlstNewStr($1); } + | array { $$ = nvlstNewArray($1); } +script: stmt { $$ = $1; } + | script stmt { $$ = scriptAddStmt($1, $2); } +stmt: actlst { $$ = $1; } + | IF expr THEN block { $$ = cnfstmtNew(S_IF); + $$->d.s_if.expr = $2; + $$->d.s_if.t_then = $4; + $$->d.s_if.t_else = NULL; } + | IF expr THEN block ELSE block { $$ = cnfstmtNew(S_IF); + $$->d.s_if.expr = $2; + $$->d.s_if.t_then = $4; + $$->d.s_if.t_else = $6; } + | SET VAR '=' expr ';' { $$ = cnfstmtNewSet($2, $4); } + | UNSET VAR ';' { $$ = cnfstmtNewUnset($2); } + | PRIFILT block { $$ = cnfstmtNewPRIFILT($1, $2); } + | PROPFILT block { $$ = cnfstmtNewPROPFILT($1, $2); } +block: stmt { $$ = $1; } + | '{' script '}' { $$ = $2; } +actlst: s_act { $$ = $1; } + | actlst '&' s_act { $$ = scriptAddStmt($1, $3); } +/* s_act are actions and action-like statements */ +s_act: BEGIN_ACTION nvlst ENDOBJ { $$ = cnfstmtNewAct($2); } + | LEGACY_ACTION { $$ = cnfstmtNewLegaAct($1); } + | STOP { $$ = cnfstmtNew(S_STOP); } + | CALL NAME { $$ = cnfstmtNewCall($2); } + | CONTINUE { $$ = cnfstmtNewContinue(); } +expr: expr AND expr { $$ = cnfexprNew(AND, $1, $3); } + | expr OR expr { $$ = cnfexprNew(OR, $1, $3); } + | NOT expr { $$ = cnfexprNew(NOT, NULL, $2); } + | expr CMP_EQ expr { $$ = cnfexprNew(CMP_EQ, $1, $3); } + | expr CMP_NE expr { $$ = cnfexprNew(CMP_NE, $1, $3); } + | expr CMP_LE expr { $$ = cnfexprNew(CMP_LE, $1, $3); } + | expr CMP_GE expr { $$ = cnfexprNew(CMP_GE, $1, $3); } + | expr CMP_LT expr { $$ = cnfexprNew(CMP_LT, $1, $3); } + | expr CMP_GT expr { $$ = cnfexprNew(CMP_GT, $1, $3); } + | expr CMP_CONTAINS expr { $$ = cnfexprNew(CMP_CONTAINS, $1, $3); } + | expr CMP_CONTAINSI expr { $$ = cnfexprNew(CMP_CONTAINSI, $1, $3); } + | expr CMP_STARTSWITH expr { $$ = cnfexprNew(CMP_STARTSWITH, $1, $3); } + | expr CMP_STARTSWITHI expr { $$ = cnfexprNew(CMP_STARTSWITHI, $1, $3); } + | expr '&' expr { $$ = cnfexprNew('&', $1, $3); } + | expr '+' expr { $$ = cnfexprNew('+', $1, $3); } + | expr '-' expr { $$ = cnfexprNew('-', $1, $3); } + | expr '*' expr { $$ = cnfexprNew('*', $1, $3); } + | expr '/' expr { $$ = cnfexprNew('/', $1, $3); } + | expr '%' expr { $$ = cnfexprNew('%', $1, $3); } + | '(' expr ')' { $$ = $2; } + | '-' expr %prec UMINUS { $$ = cnfexprNew('M', NULL, $2); } + | FUNC '(' ')' { $$ = (struct cnfexpr*) cnffuncNew($1, NULL); } + | FUNC '(' fparams ')' { $$ = (struct cnfexpr*) cnffuncNew($1, $3); } + | NUMBER { $$ = (struct cnfexpr*) cnfnumvalNew($1); } + | STRING { $$ = (struct cnfexpr*) cnfstringvalNew($1); } + | VAR { $$ = (struct cnfexpr*) cnfvarNew($1); } + | array { $$ = (struct cnfexpr*) $1; } +fparams: expr { $$ = cnffparamlstNew($1, NULL); } + | expr ',' fparams { $$ = cnffparamlstNew($1, $3); } +array: '[' arrayelt ']' { $$ = $2; } +arrayelt: STRING { $$ = cnfarrayNew($1); } + | arrayelt ',' STRING { $$ = cnfarrayAdd($1, $3); } + +%% +/* +int yyerror(char *s) +{ + printf("parse failure on or before line %d: %s\n", yylineno, s); + return 0; +} +*/ diff --git a/grammar/lexer.l b/grammar/lexer.l new file mode 100644 index 00000000..237eb2a6 --- /dev/null +++ b/grammar/lexer.l @@ -0,0 +1,369 @@ + /* Lex file for rsyslog config format v2 (RainerScript). + * Please note: this file introduces the new config format, but maintains + * backward compatibility. In order to do so, the grammar is not 100% clean, + * but IMHO still sufficiently easy both to understand for programmers + * maitaining the code as well as users writing the config file. Users are, + * of course, encouraged to use new constructs only. But it needs to be noted + * that some of the legacy constructs (specifically the in-front-of-action + * PRI filter) are very hard to beat in ease of use, at least for simpler + * cases. So while we hope that cfsysline support can be dropped some time in + * the future, we will probably keep these useful constructs. + * + * Copyright 2011-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +%option noyywrap nodefault case-insensitive yylineno + /*%option noyywrap nodefault case-insensitive */ + +/* avoid compiler warning: `yyunput' defined but not used */ +%option nounput noinput + + +%x INOBJ + /* INOBJ is selected if we are inside an object (name/value pairs!) */ +%x COMMENT + /* COMMENT is "the usual trick" to handle C-style comments */ +%x INCL + /* INCL is in $IncludeConfig processing (skip to include file) */ +%x LINENO + /* LINENO: support for setting the linenumber */ +%x INCALL + /* INCALL: support for the call statement */ +%x EXPR + /* EXPR is a bit ugly, but we need it to support pre v6-syntax. The problem + * is that cfsysline statement start with $..., the same like variables in + * an expression. However, cfsysline statements can never appear inside an + * expression. So we create a specific expr mode, which is turned on after + * we lexed a keyword that needs to be followed by an expression (using + * knowledge from the upper layer...). In expr mode, we strictly do + * expression-based parsing. Expr mode is stopped when we reach a token + * that can not be part of an expression (currently only "then"). As I + * wrote this ugly, but the price needed to pay in order to remain + * compatible to the previous format. + */ +%{ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <libestr.h> +#include "rainerscript.h" +#include "parserif.h" +#include "grammar.h" +static int preCommentState; /* save for lex state before a comment */ + +struct bufstack { + struct bufstack *prev; + YY_BUFFER_STATE bs; + int lineno; + char *fn; + es_str_t *estr; +} *currbs = NULL; + +char *cnfcurrfn; /* name of currently processed file */ + +int popfile(void); +int cnfSetLexFile(char *fname); + +extern int yydebug; + +/* somehow, I need these prototype even though the headers are + * included. I guess that's some autotools magic I don't understand... + */ +int fileno(FILE *stream); + +%} + +%% + + /* keywords */ +"if" { BEGIN EXPR; return IF; } +<EXPR>"then" { BEGIN INITIAL; return THEN; } +<EXPR>";" { BEGIN INITIAL; return ';'; } +<EXPR>"or" { return OR; } +<EXPR>"and" { return AND; } +<EXPR>"not" { return NOT; } +<EXPR>"=" | +<EXPR>"," | +<EXPR>"*" | +<EXPR>"/" | +<EXPR>"%" | +<EXPR>"+" | +<EXPR>"&" | +<EXPR>"-" | +<EXPR>"[" | +<EXPR>"]" | +<EXPR>"(" | +<EXPR>")" { return yytext[0]; } +<EXPR>"==" { return CMP_EQ; } +<EXPR>"<=" { return CMP_LE; } +<EXPR>">=" { return CMP_GE; } +<EXPR>"!=" | +<EXPR>"<>" { return CMP_NE; } +<EXPR>"<" { return CMP_LT; } +<EXPR>">" { return CMP_GT; } +<EXPR>"contains" { return CMP_CONTAINS; } +<EXPR>"contains_i" { return CMP_CONTAINSI; } +<EXPR>"startswith" { return CMP_STARTSWITH; } +<EXPR>"startswith_i" { return CMP_STARTSWITHI; } +<EXPR>0[0-7]+ | /* octal number */ +<EXPR>0x[0-7a-f] | /* hex number, following rule is dec; strtoll handles all! */ +<EXPR>([1-9][0-9]*|0) { yylval.n = strtoll(yytext, NULL, 0); return NUMBER; } +<EXPR>\$[$!]{0,1}[a-z][!a-z0-9\-_\.]* { yylval.s = strdup(yytext); return VAR; } +<EXPR>\'([^'\\]|\\['"\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\' { + yytext[yyleng-1] = '\0'; + unescapeStr((uchar*)yytext+1, yyleng-2); + yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); + return STRING; } +<EXPR>\"([^"\\$]|\\["'\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\" { + yytext[yyleng-1] = '\0'; + unescapeStr((uchar*)yytext+1, yyleng-2); + yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); + return STRING; } +<EXPR>[ \t\n] +<EXPR>[a-z][a-z0-9_]* { yylval.estr = es_newStrFromCStr(yytext, yyleng); + return FUNC; } +<EXPR>. { parser_errmsg("invalid character '%s' in expression " + "- is there an invalid escape sequence somewhere?", + yytext); } +<INCALL>[ \t\n] +<INCALL>. { parser_errmsg("invalid character '%s' in 'call' statement" + "- is there an invalid escape sequence somewhere?", + yytext); } +<INCALL>[a-zA-Z][a-zA-Z0-9_\.]* { yylval.estr = es_newStrFromCStr(yytext, yyleng); + BEGIN INITIAL; + return NAME; } +"&" { return '&'; } +"{" { return '{'; } +"}" { return '}'; } +"stop" { return STOP; } +"else" { return ELSE; } +"call" { BEGIN INCALL; return CALL; } +"set" { BEGIN EXPR; return SET; } +"unset" { BEGIN EXPR; return UNSET; } +"continue" { return CONTINUE; } + /* line number support because the "preprocessor" combines lines and so needs + * to tell us the real source line. + */ +"preprocfilelinenumber(" { BEGIN LINENO; } +<LINENO>[0-9]+ { yylineno = atoi(yytext) - 1; } +<LINENO>")" { BEGIN INITIAL; } +<LINENO>.|\n + /* $IncludeConfig must be detected as part of CFSYSLINE, because this is + * always the longest match :-( + */ +<INCL>.|\n +<INCL>[^ \t\n]+ { if(cnfDoInclude(yytext) != 0) + yyterminate(); + BEGIN INITIAL; } +"global"[ \n\t]*"(" { yylval.objType = CNFOBJ_GLOBAL; + BEGIN INOBJ; return BEGINOBJ; } +"template"[ \n\t]*"(" { yylval.objType = CNFOBJ_TPL; + BEGIN INOBJ; return BEGIN_TPL; } +"ruleset"[ \n\t]*"(" { yylval.objType = CNFOBJ_RULESET; + BEGIN INOBJ; return BEGIN_RULESET; } +"property"[ \n\t]*"(" { yylval.objType = CNFOBJ_PROPERTY; + BEGIN INOBJ; return BEGIN_PROPERTY; } +"constant"[ \n\t]*"(" { yylval.objType = CNFOBJ_CONSTANT; + BEGIN INOBJ; return BEGIN_CONSTANT; } +"input"[ \n\t]*"(" { yylval.objType = CNFOBJ_INPUT; + BEGIN INOBJ; return BEGINOBJ; } +"module"[ \n\t]*"(" { yylval.objType = CNFOBJ_MODULE; + BEGIN INOBJ; return BEGINOBJ; } +"action"[ \n\t]*"(" { BEGIN INOBJ; return BEGIN_ACTION; } +^[ \t]*:\$?[a-z\-]+[ ]*,[ ]*!?[a-z]+[ ]*,[ ]*\"(\\\"|[^\"])*\" { + yylval.s = strdup(rmLeadingSpace(yytext)); + dbgprintf("lexer: propfilt is '%s'\n", yylval.s); + return PROPFILT; + } +^[ \t]*[\*a-z][\*a-z]*[0-7]*[\.,][,!=;\.\*a-z0-7]+ { yylval.s = strdup(rmLeadingSpace(yytext)); return PRIFILT; } +"~" | +"*" | +\-\/[^*][^\n]* | +\/[^*][^\n]* | +:[a-z0-9]+:[^\n]* | +[\|\.\-\@\^?~>][^\n]+ | +[a-z0-9_][a-z0-9_\-\+,;]* { yylval.s = yytext; return LEGACY_ACTION; } +<INOBJ>")" { BEGIN INITIAL; return ENDOBJ; } +<INOBJ>[a-z][a-z0-9_\.]* { yylval.estr = es_newStrFromCStr(yytext, yyleng); + return NAME; } +<INOBJ>"," | +<INOBJ>"[" | +<INOBJ>"]" | +<INOBJ>"=" { return(yytext[0]); } +<INOBJ>\"([^"\\]|\\['"?\\abfnrtv]|\\[0-7]{1,3})*\" { + yytext[yyleng-1] = '\0'; + unescapeStr((uchar*)yytext+1, yyleng-2); + yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); + return STRING; } + /*yylval.estr = es_newStrFromBuf(yytext+1, yyleng-2); + return VALUE; }*/ +"/*" { preCommentState = YY_START; BEGIN COMMENT; } +<INOBJ>"/*" { preCommentState = YY_START; BEGIN COMMENT; } +<EXPR>"/*" { preCommentState = YY_START; BEGIN COMMENT; } +<COMMENT>"*/" { BEGIN preCommentState; } +<COMMENT>([^*]|\n)+|. +<INOBJ>#.*$ /* skip comments in input */ +<INOBJ>[ \n\t] +<INOBJ>. { parser_errmsg("invalid character '%s' in object definition " + "- is there an invalid escape sequence somewhere?", + yytext); } +\$[a-z]+.*$ { /* see comment on $IncludeConfig above */ + if(!strncasecmp(yytext, "$includeconfig ", 14)) { + yyless(14); + BEGIN INCL; + } else if(!strncasecmp(yytext, "$ruleset ", 9)) { + yylval.s = strdup(yytext); + return LEGACY_RULESET; + } else { + cnfDoCfsysline(strdup(yytext)); + } + } +![^ \t\n]+[ \t]*$ { yylval.s = strdup(yytext); return BSD_TAG_SELECTOR; } +[+-]\*[ \t\n]*#.*$ { yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } +[+-]\*[ \t\n]*$ { yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } +^[ \t]*[+-][a-z0-9.:-]+[ \t]*$ { yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } +\#.*\n /* skip comments in input */ +[\n\t ] /* drop whitespace */ +. { parser_errmsg("invalid character '%s' " + "- is there an invalid escape sequence somewhere?", + yytext); } +<<EOF>> { if(popfile() != 0) yyterminate(); } + +%% +int +cnfParseBuffer(char *buf, unsigned lenBuf) +{ + struct bufstack *bs; + int r = 0; + yydebug = 1; + BEGIN INITIAL; + /* maintain stack */ + if((bs = malloc(sizeof(struct bufstack))) == NULL) { + r = 1; + goto done; + } + + if(currbs != NULL) + currbs->lineno = yylineno; + bs->prev = currbs; + bs->fn = strdup("*buffer*"); + bs->bs = yy_scan_buffer(buf, lenBuf); + bs->estr = NULL; + currbs = bs; + cnfcurrfn = bs->fn; + yylineno = 1; +done: return r; +} + +/* set a new buffers. Returns 0 on success, something else otherwise. */ +int +cnfSetLexFile(char *fname) +{ + es_str_t *str = NULL; + FILE *fp; + int r = 0; + struct bufstack *bs; + + if(fname == NULL) { + fp = stdin; + } else { + if((fp = fopen(fname, "r")) == NULL) { + r = 1; + goto done; + } + } + readConfFile(fp, &str); + if(fp != stdin) + fclose(fp); + + /* maintain stack */ + if((bs = malloc(sizeof(struct bufstack))) == NULL) { + r = 1; + goto done; + } + + if(currbs != NULL) + currbs->lineno = yylineno; + bs->prev = currbs; + bs->fn = strdup(fname == NULL ? "stdin" : fname); + bs->bs = yy_scan_buffer((char*)es_getBufAddr(str), es_strlen(str)); + bs->estr = str; /* needed so we can free it later */ + currbs = bs; + cnfcurrfn = bs->fn; + yylineno = 1; + dbgprintf("config parser: pushed file %s on top of stack\n", fname); + +done: + if(r != 0) { + if(str != NULL) + es_deleteStr(str); + } + return r; +} + + +/* returns 0 on success, something else otherwise */ +int +popfile(void) +{ + struct bufstack *bs = currbs; + + if(bs == NULL) + return 1; + + /* delete current entry. But we must not free the file name if + * this is the top-level file, because then it may still be used + * in error messages for other processing steps. + * TODO: change this to another method which stores the file + * name inside the config objects. In the longer term, this is + * necessary, as otherwise we may provide wrong file name information + * at the end of include files as well. -- rgerhards, 2011-07-22 + */ + dbgprintf("config parser: reached end of file %s\n", bs->fn); + yy_delete_buffer(bs->bs); + if(bs->prev != NULL) + free(bs->fn); + free(bs->estr); + + /* switch back to previous */ + currbs = bs->prev; + free(bs); + + if(currbs == NULL) { + dbgprintf("config parser: parsing completed\n"); + return 1; /* all processed */ + } + + yy_switch_to_buffer(currbs->bs); + yylineno = currbs->lineno; + cnfcurrfn = currbs->fn; + dbgprintf("config parser: resume parsing of file %s at line %d\n", + cnfcurrfn, yylineno); + return 0; +} + +void +tellLexEndParsing(void) +{ + free(cnfcurrfn); + cnfcurrfn= NULL; +} diff --git a/grammar/makefile.stand-alone b/grammar/makefile.stand-alone new file mode 100644 index 00000000..b998a39d --- /dev/null +++ b/grammar/makefile.stand-alone @@ -0,0 +1,14 @@ +rscript: lex.yy.c utils.o rscript.tab.h utils.h + gcc -DSTAND_ALONE -g -o rscript lex.yy.c rscript.tab.c utils.o -lestr + +lex.yy.c: rscript.l rscript.tab.h + flex rscript.l + +rscript.tab.h: rscript.y + bison -d rscript.y + +utils.o: utils.c utils.h + gcc -g -DSTAND_ALONE -Wall -c utils.c + +clean: + rm *.o diff --git a/grammar/mini.samp b/grammar/mini.samp new file mode 100644 index 00000000..3bb0de44 --- /dev/null +++ b/grammar/mini.samp @@ -0,0 +1,33 @@ +#global (dnscache="yes" arg1="1 2" arg2 = "1 2" arg3 ="1=2\"3") +action(type="omuser" target="all" target="all2") +global (dnscache="no" b="2") +$FileOwner root +*.* * +$action somelog 1 +& /var/log/somelog +$action log2 1 +$action log2 2 +$action log2 3 +& action(type="fwd" target="10.1.1.2") +& /var/log/log2 + +if 1 then /var/log/log3 +/* sample bwlow is v7 +if 1 then { /var/log/log3 + if 2 then /var/log/log4 + *.* /var/log/log4b +} +*/ +*.* { /var/log/log5 + /var/log/log6 + $port 514 + @@fwd + rger + } +if not (1==0) and 2*4/-5--(10-3)>7/*pri("*.*")*/ then { + action(type="omfile" taget="/var/log/log5") + action(type="omfile" taget="/var/log/log6") + action(type="omfwd" taget="10.0.0.1" port="514") + action(type="omwusr" taget="rger" taget="rger2") +} +if getenv("user") == "test" then /var/log/testlog diff --git a/grammar/parserif.h b/grammar/parserif.h new file mode 100644 index 00000000..aa271ec4 --- /dev/null +++ b/grammar/parserif.h @@ -0,0 +1,23 @@ +#ifndef PARSERIF_H_DEFINED +#define PARSERIF_H_DEFINED +#include "rainerscript.h" +int cnfSetLexFile(char*); +int yyparse(); +char *cnfcurrfn; +void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2))); +void parser_errmsg(char *fmt, ...) __attribute__((format(printf, 1, 2))); +void tellLexEndParsing(void); +extern int yydebug; +extern int yylineno; + +/* entry points to be called after the parser has processed the + * element in question. Actual processing must than be done inside + * these functions. + */ +void cnfDoObj(struct cnfobj *o); +void cnfDoScript(struct cnfstmt *script); +void cnfDoCfsysline(char *ln); +void cnfDoBSDTag(char *ln); +void cnfDoBSDHost(char *ln); +es_str_t *cnfGetVar(char *name, void *usrptr); +#endif diff --git a/grammar/rainerscript.c b/grammar/rainerscript.c new file mode 100644 index 00000000..89cf946c --- /dev/null +++ b/grammar/rainerscript.c @@ -0,0 +1,3687 @@ +/* rainerscript.c - routines to support RainerScript config language + * + * Module begun 2011-07-01 by Rainer Gerhards + * + * Copyright 2011-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <glob.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <libestr.h> +#include "rsyslog.h" +#include "rainerscript.h" +#include "conf.h" +#include "parserif.h" +#include "rsconf.h" +#include "grammar.h" +#include "queue.h" +#include "srUtils.h" +#include "regexp.h" +#include "obj.h" +#include "modules.h" +#include "ruleset.h" + +DEFobjCurrIf(obj) +DEFobjCurrIf(regexp) + +struct cnfexpr* cnfexprOptimize(struct cnfexpr *expr); +static void cnfstmtOptimizePRIFilt(struct cnfstmt *stmt); +static void cnfarrayPrint(struct cnfarray *ar, int indent); +struct cnffunc * cnffuncNew_prifilt(int fac); + +/* debug support: convert token to a human-readable string. Note that + * this function only supports a single thread due to a static buffer. + * This is deemed a solid solution, as it is intended to be used during + * startup, only. + * NOTE: This function MUST be updated if new tokens are defined in the + * grammar. + */ +char * +tokenToString(int token) +{ + char *tokstr; + static char tokbuf[512]; + + switch(token) { + case NAME: tokstr = "NAME"; break; + case FUNC: tokstr = "FUNC"; break; + case BEGINOBJ: tokstr ="BEGINOBJ"; break; + case ENDOBJ: tokstr ="ENDOBJ"; break; + case BEGIN_ACTION: tokstr ="BEGIN_ACTION"; break; + case BEGIN_PROPERTY: tokstr ="BEGIN_PROPERTY"; break; + case BEGIN_CONSTANT: tokstr ="BEGIN_CONSTANT"; break; + case BEGIN_TPL: tokstr ="BEGIN_TPL"; break; + case BEGIN_RULESET: tokstr ="BEGIN_RULESET"; break; + case STOP: tokstr ="STOP"; break; + case SET: tokstr ="SET"; break; + case UNSET: tokstr ="UNSET"; break; + case CONTINUE: tokstr ="CONTINUE"; break; + case CALL: tokstr ="CALL"; break; + case LEGACY_ACTION: tokstr ="LEGACY_ACTION"; break; + case LEGACY_RULESET: tokstr ="LEGACY_RULESET"; break; + case PRIFILT: tokstr ="PRIFILT"; break; + case PROPFILT: tokstr ="PROPFILT"; break; + case IF: tokstr ="IF"; break; + case THEN: tokstr ="THEN"; break; + case ELSE: tokstr ="ELSE"; break; + case OR: tokstr ="OR"; break; + case AND: tokstr ="AND"; break; + case NOT: tokstr ="NOT"; break; + case VAR: tokstr ="VAR"; break; + case STRING: tokstr ="STRING"; break; + case NUMBER: tokstr ="NUMBER"; break; + case CMP_EQ: tokstr ="CMP_EQ"; break; + case CMP_NE: tokstr ="CMP_NE"; break; + case CMP_LE: tokstr ="CMP_LE"; break; + case CMP_GE: tokstr ="CMP_GE"; break; + case CMP_LT: tokstr ="CMP_LT"; break; + case CMP_GT: tokstr ="CMP_GT"; break; + case CMP_CONTAINS: tokstr ="CMP_CONTAINS"; break; + case CMP_CONTAINSI: tokstr ="CMP_CONTAINSI"; break; + case CMP_STARTSWITH: tokstr ="CMP_STARTSWITH"; break; + case CMP_STARTSWITHI: tokstr ="CMP_STARTSWITHI"; break; + case UMINUS: tokstr ="UMINUS"; break; + default: snprintf(tokbuf, sizeof(tokbuf), "%c[%d]", token, token); + tokstr = tokbuf; break; + } + return tokstr; +} + + +char* +getFIOPName(unsigned iFIOP) +{ + char *pRet; + switch(iFIOP) { + case FIOP_CONTAINS: + pRet = "contains"; + break; + case FIOP_ISEQUAL: + pRet = "isequal"; + break; + case FIOP_STARTSWITH: + pRet = "startswith"; + break; + case FIOP_REGEX: + pRet = "regex"; + break; + case FIOP_EREREGEX: + pRet = "ereregex"; + break; + case FIOP_ISEMPTY: + pRet = "isempty"; + break; + default: + pRet = "NOP"; + break; + } + return pRet; +} + +static void +prifiltInvert(struct funcData_prifilt *prifilt) +{ + int i; + for(i = 0 ; i < LOG_NFACILITIES+1 ; ++i) { + prifilt->pmask[i] = ~prifilt->pmask[i]; + } +} + +/* set prifilt so that it matches for some severities, sev is its numerical + * value. Mode is one of the compop tokens CMP_EQ, CMP_LT, CMP_LE, CMP_GT, + * CMP_GE, CMP_NE. + */ +static void +prifiltSetSeverity(struct funcData_prifilt *prifilt, int sev, int mode) +{ + static int lessthanmasks[] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + int i; + for(i = 0 ; i < LOG_NFACILITIES+1 ; ++i) { + if(mode == CMP_EQ || mode == CMP_NE) + prifilt->pmask[i] = 1 << sev; + else if(mode == CMP_LT) + prifilt->pmask[i] = lessthanmasks[sev]; + else if(mode == CMP_LE) + prifilt->pmask[i] = lessthanmasks[sev+1]; + else if(mode == CMP_GT) + prifilt->pmask[i] = ~lessthanmasks[sev+1]; + else if(mode == CMP_GE) + prifilt->pmask[i] = ~lessthanmasks[sev]; + else + DBGPRINTF("prifiltSetSeverity: program error, invalid mode %s\n", + tokenToString(mode)); + } + if(mode == CMP_NE) + prifiltInvert(prifilt); +} + +/* set prifilt so that it matches for some facilities, fac is its numerical + * value. Mode is one of the compop tokens CMP_EQ, CMP_LT, CMP_LE, CMP_GT, + * CMP_GE, CMP_NE. For the given facilities, all severities are enabled. + * NOTE: fac MUST be in the range 0..24 (not multiplied by 8)! + */ +static void +prifiltSetFacility(struct funcData_prifilt *prifilt, int fac, int mode) +{ + int i; + + memset(prifilt->pmask, 0, sizeof(prifilt->pmask)); + switch(mode) { + case CMP_EQ: + prifilt->pmask[fac] = TABLE_ALLPRI; + break; + case CMP_NE: + prifilt->pmask[fac] = TABLE_ALLPRI; + prifiltInvert(prifilt); + break; + case CMP_LT: + for(i = 0 ; i < fac ; ++i) + prifilt->pmask[i] = TABLE_ALLPRI; + break; + case CMP_LE: + for(i = 0 ; i < fac+1 ; ++i) + prifilt->pmask[i] = TABLE_ALLPRI; + break; + case CMP_GE: + for(i = fac ; i < LOG_NFACILITIES+1 ; ++i) + prifilt->pmask[i] = TABLE_ALLPRI; + break; + case CMP_GT: + for(i = fac+1 ; i < LOG_NFACILITIES+1 ; ++i) + prifilt->pmask[i] = TABLE_ALLPRI; + break; + default:break; + } +} + +/* combine a prifilt with AND/OR (the respective token values are + * used to keep things simple). + */ +static void +prifiltCombine(struct funcData_prifilt *prifilt, struct funcData_prifilt *prifilt2, int mode) +{ + int i; + for(i = 0 ; i < LOG_NFACILITIES+1 ; ++i) { + if(mode == AND) + prifilt->pmask[i] = prifilt->pmask[i] & prifilt2->pmask[i]; + else + prifilt->pmask[i] = prifilt->pmask[i] | prifilt2->pmask[i]; + } +} + + +void +readConfFile(FILE *fp, es_str_t **str) +{ + char ln[10240]; + char buf[512]; + int lenBuf; + int bWriteLineno = 0; + int len, i; + int start; /* start index of to be submitted text */ + int bContLine = 0; + int lineno = 0; + + *str = es_newStr(4096); + + while(fgets(ln, sizeof(ln), fp) != NULL) { + ++lineno; + if(bWriteLineno) { + bWriteLineno = 0; + lenBuf = sprintf(buf, "PreprocFileLineNumber(%d)\n", lineno); + es_addBuf(str, buf, lenBuf); + } + len = strlen(ln); + /* if we are continuation line, we need to drop leading WS */ + if(bContLine) { + for(start = 0 ; start < len && isspace(ln[start]) ; ++start) + /* JUST SCAN */; + } else { + start = 0; + } + for(i = len - 1 ; i >= start && isspace(ln[i]) ; --i) + /* JUST SCAN */; + if(i >= 0) { + if(ln[i] == '\\') { + --i; + bContLine = 1; + } else { + if(bContLine) /* write line number if we had cont line */ + bWriteLineno = 1; + bContLine = 0; + } + /* add relevant data to buffer */ + es_addBuf(str, ln+start, i+1 - start); + } + if(!bContLine) + es_addChar(str, '\n'); + } + /* indicate end of buffer to flex */ + es_addChar(str, '\0'); + es_addChar(str, '\0'); +} + +/* comparison function for qsort() and bsearch() string array compare */ +static int +qs_arrcmp(const void *s1, const void *s2) +{ + return es_strcmp(*((es_str_t**)s1), *((es_str_t**)s2)); +} + + +struct objlst* +objlstNew(struct cnfobj *o) +{ + struct objlst *lst; + + if((lst = malloc(sizeof(struct objlst))) != NULL) { + lst->next = NULL; + lst->obj = o; + } +cnfobjPrint(o); + + return lst; +} + +/* add object to end of object list, always returns pointer to root object */ +struct objlst* +objlstAdd(struct objlst *root, struct cnfobj *o) +{ + struct objlst *l; + struct objlst *newl; + + newl = objlstNew(o); + if(root == 0) { + root = newl; + } else { /* find last, linear search ok, as only during config phase */ + for(l = root ; l->next != NULL ; l = l->next) + ; + l->next = newl; + } + return root; +} + +/* add stmt to current script, always return root stmt pointer */ +struct cnfstmt* +scriptAddStmt(struct cnfstmt *root, struct cnfstmt *s) +{ + struct cnfstmt *l; + + if(root == NULL) { + root = s; + } else { /* find last, linear search ok, as only during config phase */ + for(l = root ; l->next != NULL ; l = l->next) + ; + l->next = s; + } + return root; +} + +void +objlstDestruct(struct objlst *lst) +{ + struct objlst *toDel; + + while(lst != NULL) { + toDel = lst; + lst = lst->next; + cnfobjDestruct(toDel->obj); + free(toDel); + } +} + +void +objlstPrint(struct objlst *lst) +{ + dbgprintf("objlst %p:\n", lst); + while(lst != NULL) { + cnfobjPrint(lst->obj); + lst = lst->next; + } +} + +struct nvlst* +nvlstNewStr(es_str_t *value) +{ + struct nvlst *lst; + + if((lst = malloc(sizeof(struct nvlst))) != NULL) { + lst->next = NULL; + lst->val.datatype = 'S'; + lst->val.d.estr = value; + lst->bUsed = 0; + } + + return lst; +} + +struct nvlst* +nvlstNewArray(struct cnfarray *ar) +{ + struct nvlst *lst; + + if((lst = malloc(sizeof(struct nvlst))) != NULL) { + lst->next = NULL; + lst->val.datatype = 'A'; + lst->val.d.ar = ar; + lst->bUsed = 0; + } + + return lst; +} + +struct nvlst* +nvlstSetName(struct nvlst *lst, es_str_t *name) +{ + lst->name = name; + return lst; +} + +void +nvlstDestruct(struct nvlst *lst) +{ + struct nvlst *toDel; + + while(lst != NULL) { + toDel = lst; + lst = lst->next; + es_deleteStr(toDel->name); + varDelete(&toDel->val); + free(toDel); + } +} + +void +nvlstPrint(struct nvlst *lst) +{ + char *name, *value; + dbgprintf("nvlst %p:\n", lst); + while(lst != NULL) { + name = es_str2cstr(lst->name, NULL); + switch(lst->val.datatype) { + case 'A': + dbgprintf("\tname: '%s':\n", name); + cnfarrayPrint(lst->val.d.ar, 5); + break; + case 'S': + value = es_str2cstr(lst->val.d.estr, NULL); + dbgprintf("\tname: '%s', value '%s'\n", name, value); + free(value); + break; + default:dbgprintf("nvlstPrint: unknown type '%s'\n", + tokenToString(lst->val.datatype)); + break; + } + free(name); + lst = lst->next; + } +} + +/* find a name starting at node lst. Returns node with this + * name or NULL, if none found. + */ +struct nvlst* +nvlstFindName(struct nvlst *lst, es_str_t *name) +{ + while(lst != NULL && es_strcmp(lst->name, name)) + lst = lst->next; + return lst; +} + + +/* find a name starting at node lst. Same as nvlstFindName, but + * for classical C strings. This is useful because the config system + * uses C string constants. + */ +static inline struct nvlst* +nvlstFindNameCStr(struct nvlst *lst, char *name) +{ + es_size_t lenName = strlen(name); + while(lst != NULL && es_strcasebufcmp(lst->name, (uchar*)name, lenName)) + lst = lst->next; + return lst; +} + + +/* check if there are duplicate names inside a nvlst and emit + * an error message, if so. + */ +static inline void +nvlstChkDupes(struct nvlst *lst) +{ + char *cstr; + + while(lst != NULL) { + if(nvlstFindName(lst->next, lst->name) != NULL) { + cstr = es_str2cstr(lst->name, NULL); + parser_errmsg("duplicate parameter '%s' -- " + "interpretation is ambigious, one value " + "will be randomly selected. Fix this problem.", + cstr); + free(cstr); + } + lst = lst->next; + } +} + + +/* check for unused params and emit error message is found. This must + * be called after all config params have been pulled from the object + * (otherwise the flags are not correctly set). + */ +void +nvlstChkUnused(struct nvlst *lst) +{ + char *cstr; + + while(lst != NULL) { + if(!lst->bUsed) { + cstr = es_str2cstr(lst->name, NULL); + parser_errmsg("parameter '%s' not known -- " + "typo in config file?", + cstr); + free(cstr); + } + lst = lst->next; + } +} + + +static inline int +doGetSize(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + unsigned char *c; + es_size_t i; + long long n; + int r; + c = es_getBufAddr(valnode->val.d.estr); + n = 0; + i = 0; + while(i < es_strlen(valnode->val.d.estr) && isdigit(*c)) { + n = 10 * n + *c - '0'; + ++i; + ++c; + } + if(i < es_strlen(valnode->val.d.estr)) { + ++i; + switch(*c) { + /* traditional binary-based definitions */ + case 'k': n *= 1024; break; + case 'm': n *= 1024 * 1024; break; + case 'g': n *= 1024 * 1024 * 1024; break; + case 't': n *= (int64) 1024 * 1024 * 1024 * 1024; break; /* tera */ + case 'p': n *= (int64) 1024 * 1024 * 1024 * 1024 * 1024; break; /* peta */ + case 'e': n *= (int64) 1024 * 1024 * 1024 * 1024 * 1024 * 1024; break; /* exa */ + /* and now the "new" 1000-based definitions */ + case 'K': n *= 1000; break; + case 'M': n *= 1000000; break; + case 'G': n *= 1000000000; break; + /* we need to use the multiplication below because otherwise + * the compiler gets an error during constant parsing */ + case 'T': n *= (int64) 1000 * 1000000000; break; /* tera */ + case 'P': n *= (int64) 1000000 * 1000000000; break; /* peta */ + case 'E': n *= (int64) 1000000000 * 1000000000; break; /* exa */ + default: --i; break; /* indicates error */ + } + } + if(i == es_strlen(valnode->val.d.estr)) { + val->val.datatype = 'N'; + val->val.d.n = n; + r = 1; + } else { + parser_errmsg("parameter '%s' does not contain a valid size", + param->name); + r = 0; + } + return r; +} + + +static inline int +doGetBinary(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int r = 1; + val->val.datatype = 'N'; + if(!es_strbufcmp(valnode->val.d.estr, (unsigned char*) "on", 2)) { + val->val.d.n = 1; + } else if(!es_strbufcmp(valnode->val.d.estr, (unsigned char*) "off", 3)) { + val->val.d.n = 0; + } else { + parser_errmsg("parameter '%s' must be \"on\" or \"off\" but " + "is neither. Results unpredictable.", param->name); + val->val.d.n = 0; + r = 0; + } + return r; +} + +static inline int +doGetQueueType(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + char *cstr; + int r = 1; + if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"fixedarray", 10)) { + val->val.d.n = QUEUETYPE_FIXED_ARRAY; + } else if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"linkedlist", 10)) { + val->val.d.n = QUEUETYPE_LINKEDLIST; + } else if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"disk", 4)) { + val->val.d.n = QUEUETYPE_DISK; + } else if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"direct", 6)) { + val->val.d.n = QUEUETYPE_DIRECT; + } else { + cstr = es_str2cstr(valnode->val.d.estr, NULL); + parser_errmsg("param '%s': unknown queue type: '%s'", + param->name, cstr); + free(cstr); + r = 0; + } + val->val.datatype = 'N'; + return r; +} + + +/* A file create-mode must be a four-digit octal number + * starting with '0'. + */ +static inline int +doGetFileCreateMode(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int fmtOK = 0; + char *cstr; + uchar *c; + + if(es_strlen(valnode->val.d.estr) == 4) { + c = es_getBufAddr(valnode->val.d.estr); + if( (c[0] == '0') + && (c[1] >= '0' && c[1] <= '7') + && (c[2] >= '0' && c[2] <= '7') + && (c[3] >= '0' && c[3] <= '7') ) { + fmtOK = 1; + } + } + + if(fmtOK) { + val->val.datatype = 'N'; + val->val.d.n = (c[1]-'0') * 64 + (c[2]-'0') * 8 + (c[3]-'0'); + } else { + cstr = es_str2cstr(valnode->val.d.estr, NULL); + parser_errmsg("file modes need to be specified as " + "4-digit octal numbers starting with '0' -" + "parameter '%s=\"%s\"' is not a file mode", + param->name, cstr); + free(cstr); + } + return fmtOK; +} + +static inline int +doGetGID(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + char *cstr; + int r; + struct group *resultBuf; + struct group wrkBuf; + char stringBuf[2048]; /* 2048 has been proven to be large enough */ + + cstr = es_str2cstr(valnode->val.d.estr, NULL); + getgrnam_r(cstr, &wrkBuf, stringBuf, sizeof(stringBuf), &resultBuf); + if(resultBuf == NULL) { + parser_errmsg("parameter '%s': ID for group %s could not " + "be found", param->name, cstr); + r = 0; + } else { + val->val.datatype = 'N'; + val->val.d.n = resultBuf->gr_gid; + dbgprintf("param '%s': uid %d obtained for group '%s'\n", + param->name, (int) resultBuf->gr_gid, cstr); + r = 1; + } + free(cstr); + return r; +} + +static inline int +doGetUID(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + char *cstr; + int r; + struct passwd *resultBuf; + struct passwd wrkBuf; + char stringBuf[2048]; /* 2048 has been proven to be large enough */ + + cstr = es_str2cstr(valnode->val.d.estr, NULL); + getpwnam_r(cstr, &wrkBuf, stringBuf, sizeof(stringBuf), &resultBuf); + if(resultBuf == NULL) { + parser_errmsg("parameter '%s': ID for user %s could not " + "be found", param->name, cstr); + r = 0; + } else { + val->val.datatype = 'N'; + val->val.d.n = resultBuf->pw_uid; + dbgprintf("param '%s': uid %d obtained for user '%s'\n", + param->name, (int) resultBuf->pw_uid, cstr); + r = 1; + } + free(cstr); + return r; +} + +/* note: we support all integer formats that es_str2num support, + * so hex and octal representations are also valid. + */ +static inline int +doGetInt(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + long long n; + int bSuccess; + + n = es_str2num(valnode->val.d.estr, &bSuccess); + if(!bSuccess) { + parser_errmsg("parameter '%s' is not a proper number", + param->name); + } + val->val.datatype = 'N'; + val->val.d.n = n; + return bSuccess; +} + +static inline int +doGetNonNegInt(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int bSuccess; + + if((bSuccess = doGetInt(valnode, param, val))) { + if(val->val.d.n < 0) { + parser_errmsg("parameter '%s' cannot be less than zero (was %lld)", + param->name, val->val.d.n); + bSuccess = 0; + } + } + return bSuccess; +} + +static inline int +doGetPositiveInt(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int bSuccess; + + if((bSuccess = doGetInt(valnode, param, val))) { + if(val->val.d.n < 1) { + parser_errmsg("parameter '%s' cannot be less than one (was %lld)", + param->name, val->val.d.n); + bSuccess = 0; + } + } + return bSuccess; +} + +static inline int +doGetWord(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + es_size_t i; + int r = 1; + unsigned char *c; + + val->val.datatype = 'S'; + val->val.d.estr = es_newStr(32); + c = es_getBufAddr(valnode->val.d.estr); + for(i = 0 ; i < es_strlen(valnode->val.d.estr) && !isspace(c[i]) ; ++i) { + es_addChar(&val->val.d.estr, c[i]); + } + if(i != es_strlen(valnode->val.d.estr)) { + parser_errmsg("parameter '%s' contains whitespace, which is not " + "permitted", + param->name); + r = 0; + } + return r; +} + +static inline int +doGetArray(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int r = 1; + + switch(valnode->val.datatype) { + case 'S': + /* a constant string is assumed to be a single-element array */ + val->val.datatype = 'A'; + val->val.d.ar = cnfarrayNew(es_strdup(valnode->val.d.estr)); + break; + case 'A': + val->val.datatype = 'A'; + val->val.d.ar = cnfarrayDup(valnode->val.d.ar); + break; + default:parser_errmsg("parameter '%s' must be an array, but is a " + "different datatype", param->name); + r = 0; + break; + } + return r; +} + +static inline int +doGetChar(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int r = 1; + if(es_strlen(valnode->val.d.estr) != 1) { + parser_errmsg("parameter '%s' must contain exactly one character " + "but contains %d - cannot be processed", + param->name, es_strlen(valnode->val.d.estr)); + r = 0; + } + val->val.datatype = 'S'; + val->val.d.estr = es_strdup(valnode->val.d.estr); + return r; +} + +/* get a single parameter according to its definition. Helper to + * nvlstGetParams. returns 1 if success, 0 otherwise + */ +static inline int +nvlstGetParam(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + uchar *cstr; + int r; + + DBGPRINTF("nvlstGetParam: name '%s', type %d, valnode->bUsed %d\n", + param->name, (int) param->type, valnode->bUsed); + if(valnode->val.datatype != 'S' && param->type != eCmdHdlrArray) { + parser_errmsg("parameter '%s' is not a string, which is not " + "permitted", + param->name); + r = 0; + goto done; + } + valnode->bUsed = 1; + val->bUsed = 1; + switch(param->type) { + case eCmdHdlrQueueType: + r = doGetQueueType(valnode, param, val); + break; + case eCmdHdlrUID: + r = doGetUID(valnode, param, val); + break; + case eCmdHdlrGID: + r = doGetGID(valnode, param, val); + break; + case eCmdHdlrBinary: + r = doGetBinary(valnode, param, val); + break; + case eCmdHdlrFileCreateMode: + r = doGetFileCreateMode(valnode, param, val); + break; + case eCmdHdlrInt: + r = doGetInt(valnode, param, val); + break; + case eCmdHdlrNonNegInt: + r = doGetPositiveInt(valnode, param, val); + break; + case eCmdHdlrPositiveInt: + r = doGetPositiveInt(valnode, param, val); + break; + case eCmdHdlrSize: + r = doGetSize(valnode, param, val); + break; + case eCmdHdlrGetChar: + r = doGetChar(valnode, param, val); + break; + case eCmdHdlrFacility: + cstr = (uchar*) es_str2cstr(valnode->val.d.estr, NULL); + val->val.datatype = 'N'; + val->val.d.n = decodeSyslogName(cstr, syslogFacNames); + free(cstr); + r = 1; + break; + case eCmdHdlrSeverity: + cstr = (uchar*) es_str2cstr(valnode->val.d.estr, NULL); + val->val.datatype = 'N'; + val->val.d.n = decodeSyslogName(cstr, syslogPriNames); + free(cstr); + r = 1; + break; + case eCmdHdlrGetWord: + r = doGetWord(valnode, param, val); + break; + case eCmdHdlrString: + val->val.datatype = 'S'; + val->val.d.estr = es_strdup(valnode->val.d.estr); + r = 1; + break; + case eCmdHdlrArray: + r = doGetArray(valnode, param, val); + break; + case eCmdHdlrGoneAway: + parser_errmsg("parameter '%s' is no longer supported", + param->name); + r = 1; /* this *is* valid! */ + break; + default: + dbgprintf("error: invalid param type\n"); + r = 0; + break; + } +done: return r; +} + + +/* obtain conf params from an nvlst and emit error messages if + * necessary. If an already-existing param value is passed, that is + * used. If NULL is passed instead, a new one is allocated. In that case, + * it is the caller's duty to free it when no longer needed. + * NULL is returned on error, otherwise a pointer to the vals array. + */ +struct cnfparamvals* +nvlstGetParams(struct nvlst *lst, struct cnfparamblk *params, + struct cnfparamvals *vals) +{ + int i; + int bValsWasNULL; + int bInError = 0; + struct nvlst *valnode; + struct cnfparamdescr *param; + + if(params->version != CNFPARAMBLK_VERSION) { + dbgprintf("nvlstGetParams: invalid param block version " + "%d, expected %d\n", + params->version, CNFPARAMBLK_VERSION); + return NULL; + } + + if(vals == NULL) { + bValsWasNULL = 1; + if((vals = calloc(params->nParams, + sizeof(struct cnfparamvals))) == NULL) + return NULL; + } else { + bValsWasNULL = 0; + } + + for(i = 0 ; i < params->nParams ; ++i) { + param = params->descr + i; + if((valnode = nvlstFindNameCStr(lst, param->name)) == NULL) + continue; + if(vals[i].bUsed) { + parser_errmsg("parameter '%s' specified more than once - " + "one instance is ignored. Fix config", param->name); + continue; + } + if(!nvlstGetParam(valnode, param, vals + i)) { + bInError = 1; + } + } + + + if(bInError) { + if(bValsWasNULL) + cnfparamvalsDestruct(vals, params); + vals = NULL; + } + + return vals; +} + + +/* check if at least one cnfparamval is actually set + * returns 1 if so, 0 otherwise + */ +int +cnfparamvalsIsSet(struct cnfparamblk *params, struct cnfparamvals *vals) +{ + int i; + + if(vals == NULL) + return 0; + if(params->version != CNFPARAMBLK_VERSION) { + dbgprintf("nvlstGetParams: invalid param block version " + "%d, expected %d\n", + params->version, CNFPARAMBLK_VERSION); + return 0; + } + for(i = 0 ; i < params->nParams ; ++i) { + if(vals[i].bUsed) + return 1; + } + return 0; +} + + +void +cnfparamsPrint(struct cnfparamblk *params, struct cnfparamvals *vals) +{ + int i; + char *cstr; + + for(i = 0 ; i < params->nParams ; ++i) { + dbgprintf("%s: ", params->descr[i].name); + if(vals[i].bUsed) { + // TODO: other types! + switch(vals[i].val.datatype) { + case 'S': + cstr = es_str2cstr(vals[i].val.d.estr, NULL); + dbgprintf(" '%s'", cstr); + free(cstr); + break; + case 'A': + cnfarrayPrint(vals[i].val.d.ar, 0); + break; + case 'N': + dbgprintf("%lld", vals[i].val.d.n); + break; + default: + dbgprintf("(unsupported datatype %c)", + vals[i].val.datatype); + } + } else { + dbgprintf("(unset)"); + } + dbgprintf("\n"); + } +} + +struct cnfobj* +cnfobjNew(enum cnfobjType objType, struct nvlst *lst) +{ + struct cnfobj *o; + + if((o = malloc(sizeof(struct nvlst))) != NULL) { + nvlstChkDupes(lst); + o->objType = objType; + o->nvlst = lst; + o->subobjs = NULL; + o->script = NULL; + } + + return o; +} + +void +cnfobjDestruct(struct cnfobj *o) +{ + if(o != NULL) { + nvlstDestruct(o->nvlst); + objlstDestruct(o->subobjs); + free(o); + } +} + +void +cnfobjPrint(struct cnfobj *o) +{ + dbgprintf("obj: '%s'\n", cnfobjType2str(o->objType)); + nvlstPrint(o->nvlst); +} + + +struct cnfexpr* +cnfexprNew(unsigned nodetype, struct cnfexpr *l, struct cnfexpr *r) +{ + struct cnfexpr *expr; + + /* optimize some constructs during parsing */ + if(nodetype == 'M' && r->nodetype == 'N') { + ((struct cnfnumval*)r)->val *= -1; + expr = r; + goto done; + } + + if((expr = malloc(sizeof(struct cnfexpr))) != NULL) { + expr->nodetype = nodetype; + expr->l = l; + expr->r = r; + } +done: + return expr; +} + + +/* ensure that retval is a number; if string is no number, + * try to convert it to one. The semantics from es_str2num() + * are used (bSuccess tells if the conversion went well or not). + */ +static long long +var2Number(struct var *r, int *bSuccess) +{ + long long n; + if(r->datatype == 'S') { + n = es_str2num(r->d.estr, bSuccess); + } else { + if(r->datatype == 'J') { + n = (r->d.json == NULL) ? 0 : json_object_get_int(r->d.json); + } else { + n = r->d.n; + } + if(bSuccess != NULL) + *bSuccess = 1; + } + return n; +} + +/* ensure that retval is a string + */ +static inline es_str_t * +var2String(struct var *r, int *bMustFree) +{ + es_str_t *estr; + char *cstr; + rs_size_t lenstr; + if(r->datatype == 'N') { + *bMustFree = 1; + estr = es_newStrFromNumber(r->d.n); + } else if(r->datatype == 'J') { + *bMustFree = 1; + if(r->d.json == NULL) { + cstr = "", + lenstr = 0; + } else { + cstr = (char*)json_object_get_string(r->d.json); + lenstr = strlen(cstr); + } + estr = es_newStrFromCStr(cstr, lenstr); + } else { + *bMustFree = 0; + estr = r->d.estr; + } + return estr; +} + +static uchar* +var2CString(struct var *r, int *bMustFree) +{ + uchar *cstr; + es_str_t *estr; + estr = var2String(r, bMustFree); + cstr = (uchar*) es_str2cstr(estr, NULL); + if(*bMustFree) + es_deleteStr(estr); + *bMustFree = 1; + return cstr; +} + +static rsRetVal +doExtractFieldByChar(uchar *str, uchar delim, int matchnbr, uchar **resstr) +{ + int iCurrFld; + int iLen; + uchar *pBuf; + uchar *pFld; + uchar *pFldEnd; + DEFiRet; + + /* first, skip to the field in question */ + iCurrFld = 1; + pFld = str; + while(*pFld && iCurrFld < matchnbr) { + /* skip fields until the requested field or end of string is found */ + while(*pFld && (uchar) *pFld != delim) + ++pFld; /* skip to field terminator */ + if(*pFld == delim) { + ++pFld; /* eat it */ + ++iCurrFld; + } + } + dbgprintf("field() field requested %d, field found %d\n", matchnbr, iCurrFld); + + if(iCurrFld == matchnbr) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = pFld; + while(*pFldEnd && *pFldEnd != delim) + ++pFldEnd; + --pFldEnd; /* we are already at the delimiter - so we need to + * step back a little not to copy it as part of the field. */ + /* we got our end pointer, now do the copy */ + iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ + CHKmalloc(pBuf = MALLOC((iLen + 1) * sizeof(char))); + /* now copy */ + memcpy(pBuf, pFld, iLen); + pBuf[iLen] = '\0'; /* terminate it */ + *resstr = pBuf; + } else { + ABORT_FINALIZE(RS_RET_FIELD_NOT_FOUND); + } +finalize_it: + RETiRet; +} + + +static rsRetVal +doExtractFieldByStr(uchar *str, char *delim, rs_size_t lenDelim, int matchnbr, uchar **resstr) +{ + int iCurrFld; + int iLen; + uchar *pBuf; + uchar *pFld; + uchar *pFldEnd; + DEFiRet; + + /* first, skip to the field in question */ + iCurrFld = 1; + pFld = str; + while(pFld != NULL && iCurrFld < matchnbr) { + if((pFld = (uchar*) strstr((char*)pFld, delim)) != NULL) { + pFld += lenDelim; + ++iCurrFld; + } + } + dbgprintf("field() field requested %d, field found %d\n", matchnbr, iCurrFld); + + if(iCurrFld == matchnbr) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = (uchar*) strstr((char*)pFld, delim); + if(pFldEnd == NULL) { + iLen = strlen((char*) pFld); + } else { /* found delmiter! Note that pFldEnd *is* already on + * the first delmi char, we don't need that. */ + iLen = pFldEnd - pFld; + } + /* we got our end pointer, now do the copy */ + CHKmalloc(pBuf = MALLOC((iLen + 1) * sizeof(char))); + /* now copy */ + memcpy(pBuf, pFld, iLen); + pBuf[iLen] = '\0'; /* terminate it */ + *resstr = pBuf; + } else { + ABORT_FINALIZE(RS_RET_FIELD_NOT_FOUND); + } +finalize_it: + RETiRet; +} + +static inline void +doFunc_re_extract(struct cnffunc *func, struct var *ret, void* usrptr) +{ + size_t submatchnbr; + short matchnbr; + regmatch_t pmatch[50]; + int bMustFree; + es_str_t *estr; + char *str; + struct var r[CNFFUNC_MAX_ARGS]; + int iLenBuf; + unsigned iOffs; + short iTry = 0; + uchar bFound = 0; + iOffs = 0; + sbool bHadNoMatch = 0; + + cnfexprEval(func->expr[0], &r[0], usrptr); + /* search string is already part of the compiled regex, so we don't + * need it here! + */ + cnfexprEval(func->expr[2], &r[2], usrptr); + cnfexprEval(func->expr[3], &r[3], usrptr); + str = (char*) var2CString(&r[0], &bMustFree); + matchnbr = (short) var2Number(&r[2], NULL); + submatchnbr = (size_t) var2Number(&r[3], NULL); + if(submatchnbr > sizeof(pmatch)/sizeof(regmatch_t)) { + DBGPRINTF("re_extract() submatch %d is too large\n", submatchnbr); + bHadNoMatch = 1; + goto finalize_it; + } + + /* first see if we find a match, iterating through the series of + * potential matches over the string. + */ + while(!bFound) { + int iREstat; + iREstat = regexp.regexec(func->funcdata, (char*)(str + iOffs), + submatchnbr+1, pmatch, 0); + dbgprintf("re_extract: regexec return is %d\n", iREstat); + if(iREstat == 0) { + if(pmatch[0].rm_so == -1) { + dbgprintf("oops ... start offset of successful regexec is -1\n"); + break; + } + if(iTry == matchnbr) { + bFound = 1; + } else { + dbgprintf("re_extract: regex found at offset %d, new offset %d, tries %d\n", + iOffs, (int) (iOffs + pmatch[0].rm_eo), iTry); + iOffs += pmatch[0].rm_eo; + ++iTry; + } + } else { + break; + } + } + dbgprintf("re_extract: regex: end search, found %d\n", bFound); + if(!bFound) { + bHadNoMatch = 1; + goto finalize_it; + } else { + /* Match- but did it match the one we wanted? */ + /* we got no match! */ + if(pmatch[submatchnbr].rm_so == -1) { + bHadNoMatch = 1; + goto finalize_it; + } + /* OK, we have a usable match - we now need to malloc pB */ + iLenBuf = pmatch[submatchnbr].rm_eo - pmatch[submatchnbr].rm_so; + estr = es_newStrFromBuf(str + iOffs + pmatch[submatchnbr].rm_so, + iLenBuf); + } + + if(bMustFree) free(str); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + if(r[2].datatype == 'S') es_deleteStr(r[2].d.estr); + if(r[3].datatype == 'S') es_deleteStr(r[3].d.estr); +finalize_it: + if(bHadNoMatch) { + cnfexprEval(func->expr[4], &r[4], usrptr); + estr = var2String(&r[4], &bMustFree); + if(r[4].datatype == 'S') es_deleteStr(r[4].d.estr); + } + ret->datatype = 'S'; + ret->d.estr = estr; + return; +} + + +/* Perform a function call. This has been moved out of cnfExprEval in order + * to keep the code small and easier to maintain. + */ +static inline void +doFuncCall(struct cnffunc *func, struct var *ret, void* usrptr) +{ + char *fname; + char *envvar; + int bMustFree; + es_str_t *estr; + char *str; + uchar *resStr; + int retval; + struct var r[CNFFUNC_MAX_ARGS]; + int delim; + int matchnbr; + struct funcData_prifilt *pPrifilt; + rsRetVal localRet; + + dbgprintf("rainerscript: executing function id %d\n", func->fID); + switch(func->fID) { + case CNFFUNC_STRLEN: + if(func->expr[0]->nodetype == 'S') { + /* if we already have a string, we do not need to + * do one more recursive call. + */ + ret->d.n = es_strlen(((struct cnfstringval*) func->expr[0])->estr); + } else { + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + ret->d.n = es_strlen(estr); + if(bMustFree) es_deleteStr(estr); + } + ret->datatype = 'N'; + break; + case CNFFUNC_GETENV: + /* note: the optimizer shall have replaced calls to getenv() + * with a constant argument to a single string (once obtained via + * getenv()). So we do NOT need to check if there is just a + * string following. + */ + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + str = (char*) es_str2cstr(estr, NULL); + envvar = getenv(str); + if(envvar == NULL) { + ret->d.estr = es_newStr(0); + } else { + ret->d.estr = es_newStrFromCStr(envvar, strlen(envvar)); + } + ret->datatype = 'S'; + if(bMustFree) es_deleteStr(estr); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + free(str); + break; + case CNFFUNC_TOLOWER: + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + if(!bMustFree) /* let caller handle that M) */ + estr = es_strdup(estr); + es_tolower(estr); + ret->datatype = 'S'; + ret->d.estr = estr; + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + break; + case CNFFUNC_CSTR: + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + if(!bMustFree) /* let caller handle that M) */ + estr = es_strdup(estr); + ret->datatype = 'S'; + ret->d.estr = estr; + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + break; + case CNFFUNC_CNUM: + if(func->expr[0]->nodetype == 'N') { + ret->d.n = ((struct cnfnumval*)func->expr[0])->val; + } else if(func->expr[0]->nodetype == 'S') { + ret->d.n = es_str2num(((struct cnfstringval*) func->expr[0])->estr, + NULL); + } else { + cnfexprEval(func->expr[0], &r[0], usrptr); + ret->d.n = var2Number(&r[0], NULL); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + } + ret->datatype = 'N'; + break; + case CNFFUNC_RE_MATCH: + cnfexprEval(func->expr[0], &r[0], usrptr); + str = (char*) var2CString(&r[0], &bMustFree); + retval = regexp.regexec(func->funcdata, str, 0, NULL, 0); + if(retval == 0) + ret->d.n = 1; + else { + ret->d.n = 0; + if(retval != REG_NOMATCH) { + DBGPRINTF("re_match: regexec returned error %d\n", retval); + } + } + ret->datatype = 'N'; + if(bMustFree) free(str); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + break; + case CNFFUNC_RE_EXTRACT: + doFunc_re_extract(func, ret, usrptr); + break; + case CNFFUNC_FIELD: + cnfexprEval(func->expr[0], &r[0], usrptr); + cnfexprEval(func->expr[1], &r[1], usrptr); + cnfexprEval(func->expr[2], &r[2], usrptr); + str = (char*) var2CString(&r[0], &bMustFree); + matchnbr = var2Number(&r[2], NULL); + if(r[1].datatype == 'S') { + char *delimstr; + delimstr = (char*) es_str2cstr(r[1].d.estr, NULL); + localRet = doExtractFieldByStr((uchar*)str, delimstr, es_strlen(r[1].d.estr), + matchnbr, &resStr); + free(delimstr); + } else { + delim = var2Number(&r[1], NULL); + localRet = doExtractFieldByChar((uchar*)str, (char) delim, matchnbr, &resStr); + } + if(localRet == RS_RET_OK) { + ret->d.estr = es_newStrFromCStr((char*)resStr, strlen((char*)resStr)); + free(resStr); + } else if(localRet == RS_RET_FIELD_NOT_FOUND) { + ret->d.estr = es_newStrFromCStr("***FIELD NOT FOUND***", + sizeof("***FIELD NOT FOUND***")-1); + } else { + ret->d.estr = es_newStrFromCStr("***ERROR in field() FUNCTION***", + sizeof("***ERROR in field() FUNCTION***")-1); + } + ret->datatype = 'S'; + if(bMustFree) free(str); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + if(r[1].datatype == 'S') es_deleteStr(r[1].d.estr); + if(r[2].datatype == 'S') es_deleteStr(r[2].d.estr); + break; + case CNFFUNC_PRIFILT: + pPrifilt = (struct funcData_prifilt*) func->funcdata; + if( (pPrifilt->pmask[((msg_t*)usrptr)->iFacility] == TABLE_NOPRI) || + ((pPrifilt->pmask[((msg_t*)usrptr)->iFacility] + & (1<<((msg_t*)usrptr)->iSeverity)) == 0) ) + ret->d.n = 0; + else + ret->d.n = 1; + ret->datatype = 'N'; + break; + default: + if(Debug) { + fname = es_str2cstr(func->fname, NULL); + dbgprintf("rainerscript: invalid function id %u (name '%s')\n", + (unsigned) func->fID, fname); + free(fname); + } + ret->datatype = 'N'; + ret->d.n = 0; + } +} + +static inline void +evalVar(struct cnfvar *var, void *usrptr, struct var *ret) +{ + rsRetVal localRet; + es_str_t *estr; + struct json_object *json; + + if(var->name[0] == '$' && var->name[1] == '!') { + /* TODO: unify string libs */ + estr = es_newStrFromBuf(var->name+1, strlen(var->name)-1); + localRet = msgGetCEEPropJSON((msg_t*)usrptr, estr, &json); + es_deleteStr(estr); + ret->datatype = 'J'; + ret->d.json = (localRet == RS_RET_OK) ? json : NULL; + } else { + ret->datatype = 'S'; + ret->d.estr = cnfGetVar(var->name, usrptr); + } + +} + +/* perform a string comparision operation against a while array. Semantic is + * that one one comparison is true, the whole construct is true. + * TODO: we can obviously optimize this process. One idea is to + * compile a regex, which should work faster than serial comparison. + * Note: compiling a regex does NOT work at all. I experimented with that + * and it was generally 5 to 10 times SLOWER than what we do here... + */ +static int +evalStrArrayCmp(es_str_t *estr_l, struct cnfarray* ar, int cmpop) +{ + int i; + int r = 0; + es_str_t **res; + if(cmpop == CMP_EQ) { + res = bsearch(&estr_l, ar->arr, ar->nmemb, sizeof(es_str_t*), qs_arrcmp); + r = res != NULL; + } else if(cmpop == CMP_NE) { + res = bsearch(&estr_l, ar->arr, ar->nmemb, sizeof(es_str_t*), qs_arrcmp); + r = res == NULL; + } else { + for(i = 0 ; (r == 0) && (i < ar->nmemb) ; ++i) { + switch(cmpop) { + case CMP_STARTSWITH: + r = es_strncmp(estr_l, ar->arr[i], es_strlen(ar->arr[i])) == 0; + break; + case CMP_STARTSWITHI: + r = es_strncasecmp(estr_l, ar->arr[i], es_strlen(ar->arr[i])) == 0; + break; + case CMP_CONTAINS: + r = es_strContains(estr_l, ar->arr[i]) != -1; + break; + case CMP_CONTAINSI: + r = es_strCaseContains(estr_l, ar->arr[i]) != -1; + break; + } + } + } + return r; +} + +#define FREE_BOTH_RET \ + if(r.datatype == 'S') es_deleteStr(r.d.estr); \ + if(l.datatype == 'S') es_deleteStr(l.d.estr) + +#define COMP_NUM_BINOP(x) \ + cnfexprEval(expr->l, &l, usrptr); \ + cnfexprEval(expr->r, &r, usrptr); \ + ret->datatype = 'N'; \ + ret->d.n = var2Number(&l, &convok_l) x var2Number(&r, &convok_r); \ + FREE_BOTH_RET + +/* NOTE: array as right-hand argument MUST be handled by user */ +#define PREP_TWO_STRINGS \ + cnfexprEval(expr->l, &l, usrptr); \ + estr_l = var2String(&l, &bMustFree2); \ + if(expr->r->nodetype == 'S') { \ + estr_r = ((struct cnfstringval*)expr->r)->estr;\ + bMustFree = 0; \ + } else if(expr->r->nodetype != 'A') { \ + cnfexprEval(expr->r, &r, usrptr); \ + estr_r = var2String(&r, &bMustFree); \ + } else { \ + /* Note: this is not really necessary, but if we do not */ \ + /* do it, we get a very irritating compiler warning... */ \ + estr_r = NULL; \ + } + +#define FREE_TWO_STRINGS \ + if(bMustFree) es_deleteStr(estr_r); \ + if(expr->r->nodetype != 'S' && expr->r->nodetype != 'A' && r.datatype == 'S') es_deleteStr(r.d.estr); \ + if(bMustFree2) es_deleteStr(estr_l); \ + if(l.datatype == 'S') es_deleteStr(l.d.estr) + +/* evaluate an expression. + * Note that we try to avoid malloc whenever possible (because of + * the large overhead it has, especially on highly threaded programs). + * As such, the each caller level must provide buffer space for the + * result on its stack during recursion. This permits the callee to store + * the return value without malloc. As the value is a somewhat larger + * struct, we could otherwise not return it without malloc. + * Note that we implement boolean shortcut operations. For our needs, there + * simply is no case where full evaluation would make any sense at all. + */ +void +cnfexprEval(struct cnfexpr *expr, struct var *ret, void* usrptr) +{ + struct var r, l; /* memory for subexpression results */ + es_str_t *estr_r, *estr_l; + int convok_r, convok_l; + int bMustFree, bMustFree2; + long long n_r, n_l; + + dbgprintf("eval expr %p, type '%s'\n", expr, tokenToString(expr->nodetype)); + switch(expr->nodetype) { + /* note: comparison operations are extremely similar. The code can be copyied, only + * places flagged with "CMP" need to be changed. + */ + case CMP_EQ: + /* this is optimized in regard to right param as a PoC for all compOps + * So this is a NOT yet the copy template! + */ + cnfexprEval(expr->l, &l, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(expr->r->nodetype == 'S') { + ret->d.n = !es_strcmp(l.d.estr, ((struct cnfstringval*)expr->r)->estr); /*CMP*/ + } else if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(l.d.estr, (struct cnfarray*) expr->r, CMP_EQ); + } else { + cnfexprEval(expr->r, &r, usrptr); + if(r.datatype == 'S') { + ret->d.n = !es_strcmp(l.d.estr, r.d.estr); /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l == r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = !es_strcmp(l.d.estr, estr_r); /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + if(r.datatype == 'S') es_deleteStr(r.d.estr); + } + } else if(l.datatype == 'J') { + estr_l = var2String(&l, &bMustFree); + if(expr->r->nodetype == 'S') { + ret->d.n = !es_strcmp(estr_l, ((struct cnfstringval*)expr->r)->estr); /*CMP*/ + } else if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray*) expr->r, CMP_EQ); + } else { + cnfexprEval(expr->r, &r, usrptr); + if(r.datatype == 'S') { + ret->d.n = !es_strcmp(estr_l, r.d.estr); /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l == r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = !es_strcmp(estr_l, estr_r); /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + if(r.datatype == 'S') es_deleteStr(r.d.estr); + } + if(bMustFree) es_deleteStr(estr_l); + } else { + cnfexprEval(expr->r, &r, usrptr); + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n == n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = !es_strcmp(r.d.estr, estr_l); /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n == r.d.n); /*CMP*/ + } + if(r.datatype == 'S') es_deleteStr(r.d.estr); + } + if(l.datatype == 'S') es_deleteStr(l.d.estr); + break; + case CMP_NE: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(expr->r->nodetype == 'S') { + ret->d.n = es_strcmp(l.d.estr, ((struct cnfstringval*)expr->r)->estr); /*CMP*/ + } else if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(l.d.estr, (struct cnfarray*) expr->r, CMP_NE); + } else { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr); /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l != r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r); /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n != n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l); /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n != r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_LE: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) <= 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l <= r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) <= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n <= n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) <= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n <= r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_GE: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) >= 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l >= r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) >= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n >= n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) >= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n >= r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_LT: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) < 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l < r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) < 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n < n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) < 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n < r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_GT: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) > 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l > r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) > 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n > n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) > 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n > r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_STARTSWITH: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray*) expr->r, CMP_STARTSWITH); + bMustFree = 0; + } else { + ret->d.n = es_strncmp(estr_l, estr_r, estr_r->lenStr) == 0; + } + FREE_TWO_STRINGS; + break; + case CMP_STARTSWITHI: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray*) expr->r, CMP_STARTSWITHI); + bMustFree = 0; + } else { + ret->d.n = es_strncasecmp(estr_l, estr_r, estr_r->lenStr) == 0; + } + FREE_TWO_STRINGS; + break; + case CMP_CONTAINS: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray*) expr->r, CMP_CONTAINS); + bMustFree = 0; + } else { + ret->d.n = es_strContains(estr_l, estr_r) != -1; + } + FREE_TWO_STRINGS; + break; + case CMP_CONTAINSI: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + if(expr->r->nodetype == 'A') { + ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray*) expr->r, CMP_CONTAINSI); + bMustFree = 0; + } else { + ret->d.n = es_strCaseContains(estr_l, estr_r) != -1; + } + FREE_TWO_STRINGS; + break; + case OR: + cnfexprEval(expr->l, &l, usrptr); + ret->datatype = 'N'; + if(var2Number(&l, &convok_l)) { + ret->d.n = 1ll; + } else { + cnfexprEval(expr->r, &r, usrptr); + if(var2Number(&r, &convok_r)) + ret->d.n = 1ll; + else + ret->d.n = 0ll; + if(r.datatype == 'S') es_deleteStr(r.d.estr); + } + if(l.datatype == 'S') es_deleteStr(l.d.estr); + break; + case AND: + cnfexprEval(expr->l, &l, usrptr); + ret->datatype = 'N'; + if(var2Number(&l, &convok_l)) { + cnfexprEval(expr->r, &r, usrptr); + if(var2Number(&r, &convok_r)) + ret->d.n = 1ll; + else + ret->d.n = 0ll; + if(r.datatype == 'S') es_deleteStr(r.d.estr); + } else { + ret->d.n = 0ll; + } + if(l.datatype == 'S') es_deleteStr(l.d.estr); + break; + case NOT: + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + ret->d.n = !var2Number(&r, &convok_r); + if(r.datatype == 'S') es_deleteStr(r.d.estr); + break; + case 'N': + ret->datatype = 'N'; + ret->d.n = ((struct cnfnumval*)expr)->val; + break; + case 'S': + ret->datatype = 'S'; + ret->d.estr = es_strdup(((struct cnfstringval*)expr)->estr); + break; + case 'A': + /* if an array is used with "normal" operations, it just evaluates + * to its first element. + */ + ret->datatype = 'S'; + ret->d.estr = es_strdup(((struct cnfarray*)expr)->arr[0]); + break; + case 'V': + evalVar((struct cnfvar*)expr, usrptr, ret); + break; + case '&': + /* TODO: think about optimization, should be possible ;) */ + PREP_TWO_STRINGS; + if(expr->r->nodetype == 'A') { + estr_r = ((struct cnfarray*)expr->r)->arr[0]; + bMustFree = 0; + } + ret->datatype = 'S'; + ret->d.estr = es_strdup(estr_l); + es_addStr(&ret->d.estr, estr_r); + FREE_TWO_STRINGS; + break; + case '+': + COMP_NUM_BINOP(+); + break; + case '-': + COMP_NUM_BINOP(-); + break; + case '*': + COMP_NUM_BINOP(*); + break; + case '/': + COMP_NUM_BINOP(/); + break; + case '%': + COMP_NUM_BINOP(%); + break; + case 'M': + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + ret->d.n = -var2Number(&r, &convok_r); + if(r.datatype == 'S') es_deleteStr(r.d.estr); + break; + case 'F': + doFuncCall((struct cnffunc*) expr, ret, usrptr); + break; + default: + ret->datatype = 'N'; + ret->d.n = 0ll; + dbgprintf("eval error: unknown nodetype %u['%c']\n", + (unsigned) expr->nodetype, (char) expr->nodetype); + break; + } +} + +//--------------------------------------------------------- + +void +cnfarrayContentDestruct(struct cnfarray *ar) +{ + unsigned short i; + for(i = 0 ; i < ar->nmemb ; ++i) { + es_deleteStr(ar->arr[i]); + } + free(ar->arr); +} + +static inline void +cnffuncDestruct(struct cnffunc *func) +{ + unsigned short i; + + for(i = 0 ; i < func->nParams ; ++i) { + cnfexprDestruct(func->expr[i]); + } + /* some functions require special destruction */ + switch(func->fID) { + case CNFFUNC_RE_MATCH: + case CNFFUNC_RE_EXTRACT: + if(func->funcdata != NULL) + regexp.regfree(func->funcdata); + break; + default:break; + } + free(func->funcdata); + free(func->fname); +} + +/* Destruct an expression and all sub-expressions contained in it. + */ +void +cnfexprDestruct(struct cnfexpr *expr) +{ + + if(expr == NULL) { + /* this is valid and can happen during optimizer run! */ + DBGPRINTF("cnfexprDestruct got NULL ptr - valid, so doing nothing\n"); + return; + } + + DBGPRINTF("cnfexprDestruct expr %p, type '%s'\n", expr, tokenToString(expr->nodetype)); + switch(expr->nodetype) { + case CMP_NE: + case CMP_EQ: + case CMP_LE: + case CMP_GE: + case CMP_LT: + case CMP_GT: + case CMP_STARTSWITH: + case CMP_STARTSWITHI: + case CMP_CONTAINS: + case CMP_CONTAINSI: + case OR: + case AND: + case '&': + case '+': + case '-': + case '*': + case '/': + case '%': /* binary */ + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + break; + case NOT: + case 'M': /* unary */ + cnfexprDestruct(expr->r); + break; + case 'N': + break; + case 'S': + es_deleteStr(((struct cnfstringval*)expr)->estr); + break; + case 'V': + free(((struct cnfvar*)expr)->name); + break; + case 'F': + cnffuncDestruct((struct cnffunc*)expr); + break; + case 'A': + cnfarrayContentDestruct((struct cnfarray*)expr); + break; + default:break; + } + free(expr); +} + +//---- END + + +/* Evaluate an expression as a bool. This is added because expressions are + * mostly used inside filters, and so this function is quite common and + * important. + */ +int +cnfexprEvalBool(struct cnfexpr *expr, void *usrptr) +{ + int convok; + struct var ret; + cnfexprEval(expr, &ret, usrptr); + return var2Number(&ret, &convok); +} + +inline static void +doIndent(int indent) +{ + int i; + for(i = 0 ; i < indent ; ++i) + dbgprintf(" "); +} + +static void +pmaskPrint(uchar *pmask, int indent) +{ + int i; + doIndent(indent); + dbgprintf("pmask: "); + for (i = 0; i <= LOG_NFACILITIES; i++) + if (pmask[i] == TABLE_NOPRI) + dbgprintf(" X "); + else + dbgprintf("%2X ", pmask[i]); + dbgprintf("\n"); +} + +static void +cnfarrayPrint(struct cnfarray *ar, int indent) +{ + int i; + doIndent(indent); dbgprintf("ARRAY:\n"); + for(i = 0 ; i < ar->nmemb ; ++i) { + doIndent(indent+1); + cstrPrint("string '", ar->arr[i]); + dbgprintf("'\n"); + } +} + +void +cnfexprPrint(struct cnfexpr *expr, int indent) +{ + struct cnffunc *func; + int i; + + switch(expr->nodetype) { + case CMP_EQ: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("==\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_NE: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("!=\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_LE: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("<=\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_GE: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf(">=\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_LT: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("<\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_GT: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf(">\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_CONTAINS: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("CONTAINS\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_CONTAINSI: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("CONTAINS_I\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_STARTSWITH: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("STARTSWITH\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_STARTSWITHI: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("STARTSWITH_I\n"); + cnfexprPrint(expr->r, indent+1); + break; + case OR: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("OR\n"); + cnfexprPrint(expr->r, indent+1); + break; + case AND: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("AND\n"); + cnfexprPrint(expr->r, indent+1); + break; + case NOT: + doIndent(indent); + dbgprintf("NOT\n"); + cnfexprPrint(expr->r, indent+1); + break; + case 'S': + doIndent(indent); + cstrPrint("string '", ((struct cnfstringval*)expr)->estr); + dbgprintf("'\n"); + break; + case 'A': + cnfarrayPrint((struct cnfarray*)expr, indent); + break; + case 'N': + doIndent(indent); + dbgprintf("%lld\n", ((struct cnfnumval*)expr)->val); + break; + case 'V': + doIndent(indent); + dbgprintf("var '%s'\n", ((struct cnfvar*)expr)->name); + break; + case 'F': + doIndent(indent); + func = (struct cnffunc*) expr; + cstrPrint("function '", func->fname); + dbgprintf("' (id:%d, params:%hu)\n", func->fID, func->nParams); + if(func->fID == CNFFUNC_PRIFILT) { + struct funcData_prifilt *pD; + pD = (struct funcData_prifilt*) func->funcdata; + pmaskPrint(pD->pmask, indent+1); + } + for(i = 0 ; i < func->nParams ; ++i) { + cnfexprPrint(func->expr[i], indent+1); + } + break; + case '&': + case '+': + case '-': + case '*': + case '/': + case '%': + case 'M': + if(expr->l != NULL) + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("%c\n", (char) expr->nodetype); + cnfexprPrint(expr->r, indent+1); + break; + default: + dbgprintf("error: unknown nodetype %u['%c']\n", + (unsigned) expr->nodetype, (char) expr->nodetype); + break; + } +} +/* print only the given stmt + * if "subtree" equals 1, the full statement subtree is printed, else + * really only the statement. + */ +void +cnfstmtPrintOnly(struct cnfstmt *stmt, int indent, sbool subtree) +{ + char *cstr; + switch(stmt->nodetype) { + case S_NOP: + doIndent(indent); dbgprintf("NOP\n"); + break; + case S_STOP: + doIndent(indent); dbgprintf("STOP\n"); + break; + case S_CALL: + cstr = es_str2cstr(stmt->d.s_call.name, NULL); + doIndent(indent); dbgprintf("CALL [%s]\n", cstr); + free(cstr); + break; + case S_ACT: + doIndent(indent); dbgprintf("ACTION %p [%s]\n", stmt->d.act, stmt->printable); + break; + case S_IF: + doIndent(indent); dbgprintf("IF\n"); + cnfexprPrint(stmt->d.s_if.expr, indent+1); + if(subtree) { + doIndent(indent); dbgprintf("THEN\n"); + cnfstmtPrint(stmt->d.s_if.t_then, indent+1); + if(stmt->d.s_if.t_else != NULL) { + doIndent(indent); dbgprintf("ELSE\n"); + cnfstmtPrint(stmt->d.s_if.t_else, indent+1); + } + doIndent(indent); dbgprintf("END IF\n"); + } + break; + case S_SET: + doIndent(indent); dbgprintf("SET %s =\n", + stmt->d.s_set.varname); + cnfexprPrint(stmt->d.s_set.expr, indent+1); + doIndent(indent); dbgprintf("END SET\n"); + break; + case S_UNSET: + doIndent(indent); dbgprintf("UNSET %s\n", + stmt->d.s_unset.varname); + break; + case S_PRIFILT: + doIndent(indent); dbgprintf("PRIFILT '%s'\n", stmt->printable); + pmaskPrint(stmt->d.s_prifilt.pmask, indent); + if(subtree) { + cnfstmtPrint(stmt->d.s_prifilt.t_then, indent+1); + if(stmt->d.s_prifilt.t_else != NULL) { + doIndent(indent); dbgprintf("ELSE\n"); + cnfstmtPrint(stmt->d.s_prifilt.t_else, indent+1); + } + doIndent(indent); dbgprintf("END PRIFILT\n"); + } + break; + case S_PROPFILT: + doIndent(indent); dbgprintf("PROPFILT\n"); + doIndent(indent); dbgprintf("\tProperty.: '%s'\n", + propIDToName(stmt->d.s_propfilt.propID)); + if(stmt->d.s_propfilt.propName != NULL) { + cstr = es_str2cstr(stmt->d.s_propfilt.propName, NULL); + doIndent(indent); + dbgprintf("\tCEE-Prop.: '%s'\n", cstr); + free(cstr); + } + doIndent(indent); dbgprintf("\tOperation: "); + if(stmt->d.s_propfilt.isNegated) + dbgprintf("NOT "); + dbgprintf("'%s'\n", getFIOPName(stmt->d.s_propfilt.operation)); + if(stmt->d.s_propfilt.pCSCompValue != NULL) { + doIndent(indent); dbgprintf("\tValue....: '%s'\n", + rsCStrGetSzStrNoNULL(stmt->d.s_propfilt.pCSCompValue)); + } + if(subtree) { + doIndent(indent); dbgprintf("THEN\n"); + cnfstmtPrint(stmt->d.s_propfilt.t_then, indent+1); + doIndent(indent); dbgprintf("END PROPFILT\n"); + } + break; + default: + dbgprintf("error: unknown stmt type %u\n", + (unsigned) stmt->nodetype); + break; + } +} +void +cnfstmtPrint(struct cnfstmt *root, int indent) +{ + struct cnfstmt *stmt; + //dbgprintf("stmt %p, indent %d, type '%c'\n", expr, indent, expr->nodetype); + for(stmt = root ; stmt != NULL ; stmt = stmt->next) { + cnfstmtPrintOnly(stmt, indent, 1); + } +} + +struct cnfnumval* +cnfnumvalNew(long long val) +{ + struct cnfnumval *numval; + if((numval = malloc(sizeof(struct cnfnumval))) != NULL) { + numval->nodetype = 'N'; + numval->val = val; + } + return numval; +} + +struct cnfstringval* +cnfstringvalNew(es_str_t *estr) +{ + struct cnfstringval *strval; + if((strval = malloc(sizeof(struct cnfstringval))) != NULL) { + strval->nodetype = 'S'; + strval->estr = estr; + } + return strval; +} + +/* creates array AND adds first element to it */ +struct cnfarray* +cnfarrayNew(es_str_t *val) +{ + struct cnfarray *ar; + if((ar = malloc(sizeof(struct cnfarray))) != NULL) { + ar->nodetype = 'A'; + ar->nmemb = 1; + if((ar->arr = malloc(sizeof(es_str_t*))) == NULL) { + free(ar); + ar = NULL; + goto done; + } + ar->arr[0] = val; + } +done: return ar; +} + +struct cnfarray* +cnfarrayAdd(struct cnfarray *ar, es_str_t *val) +{ + es_str_t **newptr; + if((newptr = realloc(ar->arr, (ar->nmemb+1)*sizeof(es_str_t*))) == NULL) { + DBGPRINTF("cnfarrayAdd: realloc failed, item ignored, ar->arr=%p\n", ar->arr); + goto done; + } else { + ar->arr = newptr; + ar->arr[ar->nmemb] = val; + ar->nmemb++; + } +done: return ar; +} + +/* duplicate an array (deep copy) */ +struct cnfarray* +cnfarrayDup(struct cnfarray *old) +{ + int i; + struct cnfarray *ar; + ar = cnfarrayNew(es_strdup(old->arr[0])); + for(i = 1 ; i < old->nmemb ; ++i) { + cnfarrayAdd(ar, es_strdup(old->arr[i])); + } + return ar; +} + +struct cnfvar* +cnfvarNew(char *name) +{ + struct cnfvar *var; + if((var = malloc(sizeof(struct cnfvar))) != NULL) { + var->nodetype = 'V'; + var->name = name; + } + return var; +} + +struct cnfstmt * +cnfstmtNew(unsigned s_type) +{ + struct cnfstmt* cnfstmt; + if((cnfstmt = malloc(sizeof(struct cnfstmt))) != NULL) { + cnfstmt->nodetype = s_type; + cnfstmt->printable = NULL; + cnfstmt->next = NULL; + } + return cnfstmt; +} + +void +cnfstmtDestruct(struct cnfstmt *root) +{ + struct cnfstmt *stmt, *todel; + for(stmt = root ; stmt != NULL ; ) { + switch(stmt->nodetype) { + case S_NOP: + case S_STOP: + break; + case S_CALL: + es_deleteStr(stmt->d.s_call.name); + break; + case S_ACT: + actionDestruct(stmt->d.act); + break; + case S_IF: + cnfexprDestruct(stmt->d.s_if.expr); + if(stmt->d.s_if.t_then != NULL) { + cnfstmtDestruct(stmt->d.s_if.t_then); + } + if(stmt->d.s_if.t_else != NULL) { + cnfstmtDestruct(stmt->d.s_if.t_else); + } + break; + case S_SET: + free(stmt->d.s_set.varname); + cnfexprDestruct(stmt->d.s_set.expr); + break; + case S_UNSET: + free(stmt->d.s_set.varname); + break; + case S_PRIFILT: + cnfstmtDestruct(stmt->d.s_prifilt.t_then); + cnfstmtDestruct(stmt->d.s_prifilt.t_else); + break; + case S_PROPFILT: + if(stmt->d.s_propfilt.propName != NULL) + es_deleteStr(stmt->d.s_propfilt.propName); + if(stmt->d.s_propfilt.regex_cache != NULL) + rsCStrRegexDestruct(&stmt->d.s_propfilt.regex_cache); + if(stmt->d.s_propfilt.pCSCompValue != NULL) + cstrDestruct(&stmt->d.s_propfilt.pCSCompValue); + cnfstmtDestruct(stmt->d.s_propfilt.t_then); + break; + default: + dbgprintf("error: unknown stmt type during destruct %u\n", + (unsigned) stmt->nodetype); + break; + } + free(stmt->printable); + todel = stmt; + stmt = stmt->next; + free(todel); + } +} + +struct cnfstmt * +cnfstmtNewSet(char *var, struct cnfexpr *expr) +{ + struct cnfstmt* cnfstmt; + if((cnfstmt = cnfstmtNew(S_SET)) != NULL) { + cnfstmt->d.s_set.varname = (uchar*) var; + cnfstmt->d.s_set.expr = expr; + } + return cnfstmt; +} + +struct cnfstmt * +cnfstmtNewCall(es_str_t *name) +{ + struct cnfstmt* cnfstmt; + if((cnfstmt = cnfstmtNew(S_CALL)) != NULL) { + cnfstmt->d.s_call.name = name; + } + return cnfstmt; +} + +struct cnfstmt * +cnfstmtNewUnset(char *var) +{ + struct cnfstmt* cnfstmt; + if((cnfstmt = cnfstmtNew(S_UNSET)) != NULL) { + cnfstmt->d.s_unset.varname = (uchar*) var; + } + return cnfstmt; +} + +struct cnfstmt * +cnfstmtNewContinue(void) +{ + return cnfstmtNew(S_NOP); +} + +struct cnfstmt * +cnfstmtNewPRIFILT(char *prifilt, struct cnfstmt *t_then) +{ + struct cnfstmt* cnfstmt; + if((cnfstmt = cnfstmtNew(S_PRIFILT)) != NULL) { + cnfstmt->printable = (uchar*)prifilt; + cnfstmt->d.s_prifilt.t_then = t_then; + cnfstmt->d.s_prifilt.t_else = NULL; + DecodePRIFilter((uchar*)prifilt, cnfstmt->d.s_prifilt.pmask); + } + return cnfstmt; +} + +struct cnfstmt * +cnfstmtNewPROPFILT(char *propfilt, struct cnfstmt *t_then) +{ + struct cnfstmt* cnfstmt; + rsRetVal lRet; + if((cnfstmt = cnfstmtNew(S_PROPFILT)) != NULL) { + cnfstmt->printable = (uchar*)propfilt; + cnfstmt->d.s_propfilt.t_then = t_then; + cnfstmt->d.s_propfilt.propName = NULL; + cnfstmt->d.s_propfilt.regex_cache = NULL; + cnfstmt->d.s_propfilt.pCSCompValue = NULL; + lRet = DecodePropFilter((uchar*)propfilt, cnfstmt); + } + return cnfstmt; +} + +struct cnfstmt * +cnfstmtNewAct(struct nvlst *lst) +{ + struct cnfstmt* cnfstmt; + char namebuf[256]; + rsRetVal localRet; + if((cnfstmt = cnfstmtNew(S_ACT)) == NULL) + goto done; + localRet = actionNewInst(lst, &cnfstmt->d.act); + if(localRet == RS_RET_OK_WARN) { + parser_errmsg("warnings occured in file '%s' around line %d", + cnfcurrfn, yylineno); + } else if(localRet != RS_RET_OK) { + parser_errmsg("errors occured in file '%s' around line %d", + cnfcurrfn, yylineno); + cnfstmt->nodetype = S_NOP; /* disable action! */ + goto done; + } + snprintf(namebuf, sizeof(namebuf)-1, "action(type=\"%s\" ...)", + modGetName(cnfstmt->d.act->pMod)); + namebuf[255] = '\0'; /* be on safe side */ + cnfstmt->printable = (uchar*)strdup(namebuf); + nvlstChkUnused(lst); + nvlstDestruct(lst); +done: return cnfstmt; +} + +struct cnfstmt * +cnfstmtNewLegaAct(char *actline) +{ + struct cnfstmt* cnfstmt; + rsRetVal localRet; + if((cnfstmt = cnfstmtNew(S_ACT)) == NULL) + goto done; + cnfstmt->printable = (uchar*)strdup((char*)actline); + localRet = cflineDoAction(loadConf, (uchar**)&actline, &cnfstmt->d.act); + if(localRet != RS_RET_OK && localRet != RS_RET_OK_WARN) { + parser_errmsg("%s occured in file '%s' around line %d", + (localRet == RS_RET_OK_WARN) ? "warnings" : "errors", + cnfcurrfn, yylineno); + if(localRet != RS_RET_OK_WARN) { + cnfstmt->nodetype = S_NOP; /* disable action! */ + goto done; + } + } +done: return cnfstmt; +} + + +/* returns 1 if the two expressions are constants, 0 otherwise + * if both are constants, the expression subtrees are destructed + * (this is an aid for constant folding optimizing) + */ +static int +getConstNumber(struct cnfexpr *expr, long long *l, long long *r) +{ + int ret = 0; + cnfexprOptimize(expr->l); + cnfexprOptimize(expr->r); + if(expr->l->nodetype == 'N') { + if(expr->r->nodetype == 'N') { + ret = 1; + *l = ((struct cnfnumval*)expr->l)->val; + *r = ((struct cnfnumval*)expr->r)->val; + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + } else if(expr->r->nodetype == 'S') { + ret = 1; + *l = ((struct cnfnumval*)expr->l)->val; + *r = es_str2num(((struct cnfstringval*)expr->r)->estr, NULL); + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + } + } else if(expr->l->nodetype == 'S') { + if(expr->r->nodetype == 'N') { + ret = 1; + *l = es_str2num(((struct cnfstringval*)expr->l)->estr, NULL); + *r = ((struct cnfnumval*)expr->r)->val; + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + } else if(expr->r->nodetype == 'S') { + ret = 1; + *l = es_str2num(((struct cnfstringval*)expr->l)->estr, NULL); + *r = es_str2num(((struct cnfstringval*)expr->r)->estr, NULL); + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + } + } + return ret; +} + + +/* constant folding for string concatenation */ +static inline void +constFoldConcat(struct cnfexpr *expr) +{ + es_str_t *estr; + cnfexprOptimize(expr->l); + cnfexprOptimize(expr->r); + if(expr->l->nodetype == 'S') { + if(expr->r->nodetype == 'S') { + estr = ((struct cnfstringval*)expr->l)->estr; + ((struct cnfstringval*)expr->l)->estr = NULL; + es_addStr(&estr, ((struct cnfstringval*)expr->r)->estr); + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + expr->nodetype = 'S'; + ((struct cnfstringval*)expr)->estr = estr; + } else if(expr->r->nodetype == 'N') { + es_str_t *numstr; + estr = ((struct cnfstringval*)expr->l)->estr; + ((struct cnfstringval*)expr->l)->estr = NULL; + numstr = es_newStrFromNumber(((struct cnfnumval*)expr->r)->val); + es_addStr(&estr, numstr); + es_deleteStr(numstr); + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + expr->nodetype = 'S'; + ((struct cnfstringval*)expr)->estr = estr; + } + } else if(expr->l->nodetype == 'N') { + if(expr->r->nodetype == 'S') { + estr = es_newStrFromNumber(((struct cnfnumval*)expr->l)->val); + es_addStr(&estr, ((struct cnfstringval*)expr->r)->estr); + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + expr->nodetype = 'S'; + ((struct cnfstringval*)expr)->estr = estr; + } else if(expr->r->nodetype == 'S') { + es_str_t *numstr; + estr = es_newStrFromNumber(((struct cnfnumval*)expr->l)->val); + numstr = es_newStrFromNumber(((struct cnfnumval*)expr->r)->val); + es_addStr(&estr, numstr); + es_deleteStr(numstr); + cnfexprDestruct(expr->l); + cnfexprDestruct(expr->r); + expr->nodetype = 'S'; + ((struct cnfstringval*)expr)->estr = estr; + } + } +} + + +/* optimize comparisons with syslog severity/facility. This is a special + * handler as the numerical values also support GT, LT, etc ops. + */ +static inline struct cnfexpr* +cnfexprOptimize_CMP_severity_facility(struct cnfexpr *expr) +{ + struct cnffunc *func; + + if(!strcmp("$syslogseverity", ((struct cnfvar*)expr->l)->name)) { + if(expr->r->nodetype == 'N') { + int sev = (int) ((struct cnfnumval*)expr->r)->val; + if(sev >= 0 && sev <= 7) { + DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n"); + func = cnffuncNew_prifilt(0); /* fac is irrelevant, set below... */ + prifiltSetSeverity(func->funcdata, sev, expr->nodetype); + cnfexprDestruct(expr); + expr = (struct cnfexpr*) func; + } else { + parser_errmsg("invalid syslogseverity %d, expression will always " + "evaluate to FALSE", sev); + } + } + } else if(!strcmp("$syslogfacility", ((struct cnfvar*)expr->l)->name)) { + if(expr->r->nodetype == 'N') { + int fac = (int) ((struct cnfnumval*)expr->r)->val; + if(fac >= 0 && fac <= 24) { + DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n"); + func = cnffuncNew_prifilt(0); /* fac is irrelevant, set below... */ + prifiltSetFacility(func->funcdata, fac, expr->nodetype); + cnfexprDestruct(expr); + expr = (struct cnfexpr*) func; + } else { + parser_errmsg("invalid syslogfacility %d, expression will always " + "evaluate to FALSE", fac); + } + } + } + return expr; +} + +/* optimize a comparison with a variable as left-hand operand + * NOTE: Currently support CMP_EQ, CMP_NE only and code NEEDS + * TO BE CHANGED fgr other comparisons! + */ +static inline struct cnfexpr* +cnfexprOptimize_CMP_var(struct cnfexpr *expr) +{ + struct cnffunc *func; + + if(!strcmp("$syslogfacility-text", ((struct cnfvar*)expr->l)->name)) { + if(expr->r->nodetype == 'S') { + char *cstr = es_str2cstr(((struct cnfstringval*)expr->r)->estr, NULL); + int fac = decodeSyslogName((uchar*)cstr, syslogFacNames); + if(fac == -1) { + parser_errmsg("invalid facility '%s', expression will always " + "evaluate to FALSE", cstr); + } else { + /* we can acutally optimize! */ + DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n"); + func = cnffuncNew_prifilt(fac); + if(expr->nodetype == CMP_NE) + prifiltInvert(func->funcdata); + cnfexprDestruct(expr); + expr = (struct cnfexpr*) func; + } + free(cstr); + } + } else if(!strcmp("$syslogseverity-text", ((struct cnfvar*)expr->l)->name)) { + if(expr->r->nodetype == 'S') { + char *cstr = es_str2cstr(((struct cnfstringval*)expr->r)->estr, NULL); + int sev = decodeSyslogName((uchar*)cstr, syslogPriNames); + if(sev == -1) { + parser_errmsg("invalid syslogseverity '%s', expression will always " + "evaluate to FALSE", cstr); + } else { + /* we can acutally optimize! */ + DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n"); + func = cnffuncNew_prifilt(0); + prifiltSetSeverity(func->funcdata, sev, expr->nodetype); + cnfexprDestruct(expr); + expr = (struct cnfexpr*) func; + } + free(cstr); + } + } else { + expr = cnfexprOptimize_CMP_severity_facility(expr); + } + return expr; +} + +static inline struct cnfexpr* +cnfexprOptimize_NOT(struct cnfexpr *expr) +{ + struct cnffunc *func; + + if(expr->r->nodetype == 'F') { + func = (struct cnffunc *)expr->r; + if(func->fID == CNFFUNC_PRIFILT) { + DBGPRINTF("optimize NOT prifilt() to inverted prifilt()\n"); + expr->r = NULL; + cnfexprDestruct(expr); + prifiltInvert(func->funcdata); + expr = (struct cnfexpr*) func; + } + } + return expr; +} + +static inline struct cnfexpr* +cnfexprOptimize_AND_OR(struct cnfexpr *expr) +{ + struct cnffunc *funcl, *funcr; + + if(expr->l->nodetype == 'F') { + if(expr->r->nodetype == 'F') { + funcl = (struct cnffunc *)expr->l; + funcr = (struct cnffunc *)expr->r; + if(funcl->fID == CNFFUNC_PRIFILT && funcr->fID == CNFFUNC_PRIFILT) { + DBGPRINTF("optimize combine AND/OR prifilt()\n"); + expr->l = NULL; + prifiltCombine(funcl->funcdata, funcr->funcdata, expr->nodetype); + cnfexprDestruct(expr); + expr = (struct cnfexpr*) funcl; + } + } + } + return expr; +} + + +/* optimize array for EQ/NEQ comparisons. We sort the array in + * this case so that we can apply binary search later on. + */ +static inline void +cnfexprOptimize_CMPEQ_arr(struct cnfarray *arr) +{ + DBGPRINTF("optimizer: sorting array for CMP_EQ/NEQ comparison\n"); + qsort(arr->arr, arr->nmemb, sizeof(es_str_t*), qs_arrcmp); +} + + +/* (recursively) optimize an expression */ +struct cnfexpr* +cnfexprOptimize(struct cnfexpr *expr) +{ + long long ln, rn; + struct cnfexpr *exprswap; + + dbgprintf("optimize expr %p, type '%s'\n", expr, tokenToString(expr->nodetype)); + switch(expr->nodetype) { + case '&': + constFoldConcat(expr); + break; + case '+': + if(getConstNumber(expr, &ln, &rn)) { + expr->nodetype = 'N'; + ((struct cnfnumval*)expr)->val = ln + rn; + } + break; + case '-': + if(getConstNumber(expr, &ln, &rn)) { + expr->nodetype = 'N'; + ((struct cnfnumval*)expr)->val = ln - rn; + } + break; + case '*': + if(getConstNumber(expr, &ln, &rn)) { + expr->nodetype = 'N'; + ((struct cnfnumval*)expr)->val = ln * rn; + } + break; + case '/': + if(getConstNumber(expr, &ln, &rn)) { + expr->nodetype = 'N'; + ((struct cnfnumval*)expr)->val = ln / rn; + } + break; + case '%': + if(getConstNumber(expr, &ln, &rn)) { + expr->nodetype = 'N'; + ((struct cnfnumval*)expr)->val = ln % rn; + } + break; + case CMP_NE: + case CMP_EQ: + expr->l = cnfexprOptimize(expr->l); + expr->r = cnfexprOptimize(expr->r); + if(expr->l->nodetype == 'A') { + if(expr->r->nodetype == 'A') { + parser_errmsg("warning: '==' or '<>' " + "comparison of two constant string " + "arrays makes no sense"); + } else { /* swap for simpler execution step */ + exprswap = expr->l; + expr->l = expr->r; + expr->r = exprswap; + } + } + if(expr->l->nodetype == 'V') { + expr = cnfexprOptimize_CMP_var(expr); + } else if(expr->r->nodetype == 'A') { + cnfexprOptimize_CMPEQ_arr((struct cnfarray *)expr->r); + } + break; + case CMP_LE: + case CMP_GE: + case CMP_LT: + case CMP_GT: + expr->l = cnfexprOptimize(expr->l); + expr->r = cnfexprOptimize(expr->r); + expr = cnfexprOptimize_CMP_severity_facility(expr); + break; + case CMP_CONTAINS: + case CMP_CONTAINSI: + case CMP_STARTSWITH: + case CMP_STARTSWITHI: + expr->l = cnfexprOptimize(expr->l); + expr->r = cnfexprOptimize(expr->r); + break; + case AND: + case OR: + expr->l = cnfexprOptimize(expr->l); + expr->r = cnfexprOptimize(expr->r); + expr = cnfexprOptimize_AND_OR(expr); + break; + case NOT: + expr->r = cnfexprOptimize(expr->r); + expr = cnfexprOptimize_NOT(expr); + break; + default:/* nodetypes we cannot optimize */ + break; + } + return expr; +} + +/* removes NOPs from a statement list and returns the + * first non-NOP entry. + */ +static inline struct cnfstmt * +removeNOPs(struct cnfstmt *root) +{ + struct cnfstmt *stmt, *toDel, *prevstmt = NULL; + struct cnfstmt *newRoot = NULL; + + if(root == NULL) goto done; + stmt = root; + while(stmt != NULL) { + if(stmt->nodetype == S_NOP) { + if(prevstmt != NULL) + /* end chain, is rebuild if more non-NOPs follow */ + prevstmt->next = NULL; + toDel = stmt; + stmt = stmt->next; + cnfstmtDestruct(toDel); + } else { + if(newRoot == NULL) + newRoot = stmt; + if(prevstmt != NULL) + prevstmt->next = stmt; + prevstmt = stmt; + stmt = stmt->next; + } + } +done: return newRoot; +} + + +static inline void +cnfstmtOptimizeIf(struct cnfstmt *stmt) +{ + struct cnfstmt *t_then, *t_else; + struct cnfexpr *expr; + struct cnffunc *func; + struct funcData_prifilt *prifilt; + + expr = stmt->d.s_if.expr = cnfexprOptimize(stmt->d.s_if.expr); + stmt->d.s_if.t_then = removeNOPs(stmt->d.s_if.t_then); + stmt->d.s_if.t_else = removeNOPs(stmt->d.s_if.t_else); + cnfstmtOptimize(stmt->d.s_if.t_then); + cnfstmtOptimize(stmt->d.s_if.t_else); + + if(stmt->d.s_if.expr->nodetype == 'F') { + func = (struct cnffunc*)expr; + if(func->fID == CNFFUNC_PRIFILT) { + DBGPRINTF("optimizer: change IF to PRIFILT\n"); + t_then = stmt->d.s_if.t_then; + t_else = stmt->d.s_if.t_else; + stmt->nodetype = S_PRIFILT; + prifilt = (struct funcData_prifilt*) func->funcdata; + memcpy(stmt->d.s_prifilt.pmask, prifilt->pmask, + sizeof(prifilt->pmask)); + stmt->d.s_prifilt.t_then = t_then; + stmt->d.s_prifilt.t_else = t_else; + if(func->nParams == 0) + stmt->printable = (uchar*)strdup("[Optimizer Result]"); + else + stmt->printable = (uchar*) + es_str2cstr(((struct cnfstringval*)func->expr[0])->estr, NULL); + cnfexprDestruct(expr); + cnfstmtOptimizePRIFilt(stmt); + } + } +} + +static inline void +cnfstmtOptimizeAct(struct cnfstmt *stmt) +{ + action_t *pAct; + + pAct = stmt->d.act; + if(!strcmp((char*)modGetName(pAct->pMod), "builtin:omdiscard")) { + DBGPRINTF("optimizer: replacing omdiscard by STOP\n"); + actionDestruct(stmt->d.act); + stmt->nodetype = S_STOP; + } +} + +static void +cnfstmtOptimizePRIFilt(struct cnfstmt *stmt) +{ + int i; + int isAlways = 1; + struct cnfstmt *subroot, *last; + + stmt->d.s_prifilt.t_then = removeNOPs(stmt->d.s_prifilt.t_then); + cnfstmtOptimize(stmt->d.s_prifilt.t_then); + + for(i = 0; i <= LOG_NFACILITIES; i++) + if(stmt->d.s_prifilt.pmask[i] != 0xff) { + isAlways = 0; + break; + } + if(!isAlways) + goto done; + + DBGPRINTF("optimizer: removing always-true PRIFILT %p\n", stmt); + if(stmt->d.s_prifilt.t_else != NULL) { + parser_errmsg("error: always-true PRI filter has else part!\n"); + cnfstmtDestruct(stmt->d.s_prifilt.t_else); + } + free(stmt->printable); + stmt->printable = NULL; + subroot = stmt->d.s_prifilt.t_then; + if(subroot == NULL) { + /* very strange, we set it to NOP, best we can do + * This case is NOT expected in practice + */ + stmt->nodetype = S_NOP; + goto done; + } + for(last = subroot ; last->next != NULL ; last = last->next) + /* find last node in subtree */; + last->next = stmt->next; + memcpy(stmt, subroot, sizeof(struct cnfstmt)); + free(subroot); + +done: return; +} + +/* we abuse "optimize" a bit. Actually, we obtain a ruleset pointer, as + * all rulesets are only known later in the process (now!). + */ +static void +cnfstmtOptimizeCall(struct cnfstmt *stmt) +{ + ruleset_t *pRuleset; + rsRetVal localRet; + uchar *rsName; + + rsName = (uchar*) es_str2cstr(stmt->d.s_call.name, NULL); + localRet = rulesetGetRuleset(loadConf, &pRuleset, rsName); + if(localRet != RS_RET_OK) { + /* in that case, we accept that a NOP will "survive" */ + parser_errmsg("ruleset '%s' cannot be found\n", rsName); + es_deleteStr(stmt->d.s_call.name); + stmt->nodetype = S_NOP; + goto done; + } + DBGPRINTF("CALL obtained ruleset ptr %p for ruleset %s\n", pRuleset, rsName); + stmt->d.s_call.stmt = pRuleset->root; +done: + free(rsName); + return; +} +/* (recursively) optimize a statement */ +void +cnfstmtOptimize(struct cnfstmt *root) +{ + struct cnfstmt *stmt; + if(root == NULL) goto done; + for(stmt = root ; stmt != NULL ; stmt = stmt->next) { + switch(stmt->nodetype) { + case S_IF: + cnfstmtOptimizeIf(stmt); + break; + case S_PRIFILT: + cnfstmtOptimizePRIFilt(stmt); + break; + case S_PROPFILT: + stmt->d.s_propfilt.t_then = removeNOPs(stmt->d.s_propfilt.t_then); + cnfstmtOptimize(stmt->d.s_propfilt.t_then); + break; + case S_SET: + stmt->d.s_set.expr = cnfexprOptimize(stmt->d.s_set.expr); + break; + case S_ACT: + cnfstmtOptimizeAct(stmt); + break; + case S_CALL: + cnfstmtOptimizeCall(stmt); + break; + case S_STOP: + if(stmt->next != NULL) + parser_errmsg("STOP is followed by unreachable statements!\n"); + break; + case S_UNSET: /* nothing to do */ + break; + case S_NOP: + DBGPRINTF("optimizer error: we see a NOP, how come?\n"); + break; + default: + dbgprintf("error: unknown stmt type %u during optimizer run\n", + (unsigned) stmt->nodetype); + break; + } + } +done: return; +} + + +struct cnffparamlst * +cnffparamlstNew(struct cnfexpr *expr, struct cnffparamlst *next) +{ + struct cnffparamlst* lst; + if((lst = malloc(sizeof(struct cnffparamlst))) != NULL) { + lst->nodetype = 'P'; + lst->expr = expr; + lst->next = next; + } + return lst; +} + +/* Obtain function id from name AND number of params. Issues the + * relevant error messages if errors are detected. + */ +static inline enum cnffuncid +funcName2ID(es_str_t *fname, unsigned short nParams) +{ + if(!es_strbufcmp(fname, (unsigned char*)"strlen", sizeof("strlen") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for strlen() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_STRLEN; + } else if(!es_strbufcmp(fname, (unsigned char*)"getenv", sizeof("getenv") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for getenv() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_GETENV; + } else if(!es_strbufcmp(fname, (unsigned char*)"tolower", sizeof("tolower") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for tolower() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_TOLOWER; + } else if(!es_strbufcmp(fname, (unsigned char*)"cstr", sizeof("cstr") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for cstr() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_CSTR; + } else if(!es_strbufcmp(fname, (unsigned char*)"cnum", sizeof("cnum") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for cnum() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_CNUM; + } else if(!es_strbufcmp(fname, (unsigned char*)"re_match", sizeof("re_match") - 1)) { + if(nParams != 2) { + parser_errmsg("number of parameters for re_match() must be two " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_RE_MATCH; + } else if(!es_strbufcmp(fname, (unsigned char*)"re_extract", sizeof("re_extract") - 1)) { + if(nParams != 5) { + parser_errmsg("number of parameters for re_extract() must be five " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_RE_EXTRACT; + } else if(!es_strbufcmp(fname, (unsigned char*)"field", sizeof("field") - 1)) { + if(nParams != 3) { + parser_errmsg("number of parameters for field() must be three " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_FIELD; + } else if(!es_strbufcmp(fname, (unsigned char*)"prifilt", sizeof("prifilt") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for prifilt() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_PRIFILT; + } else { + return CNFFUNC_INVALID; + } +} + + +static inline rsRetVal +initFunc_re_match(struct cnffunc *func) +{ + rsRetVal localRet; + char *regex = NULL; + regex_t *re; + DEFiRet; + + func->funcdata = NULL; + if(func->expr[1]->nodetype != 'S') { + parser_errmsg("param 2 of re_match/extract() must be a constant string"); + FINALIZE; + } + + CHKmalloc(re = malloc(sizeof(regex_t))); + func->funcdata = re; + + regex = es_str2cstr(((struct cnfstringval*) func->expr[1])->estr, NULL); + + if((localRet = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { + if(regexp.regcomp(re, (char*) regex, REG_EXTENDED) != 0) { + parser_errmsg("cannot compile regex '%s'", regex); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { /* regexp object could not be loaded */ + parser_errmsg("could not load regex support - regex ignored"); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + free(regex); + RETiRet; +} + + +static inline rsRetVal +initFunc_prifilt(struct cnffunc *func) +{ + struct funcData_prifilt *pData; + uchar *cstr; + DEFiRet; + + func->funcdata = NULL; + if(func->expr[0]->nodetype != 'S') { + parser_errmsg("param 1 of prifilt() must be a constant string"); + FINALIZE; + } + + CHKmalloc(pData = calloc(1, sizeof(struct funcData_prifilt))); + func->funcdata = pData; + cstr = (uchar*)es_str2cstr(((struct cnfstringval*) func->expr[0])->estr, NULL); + CHKiRet(DecodePRIFilter(cstr, pData->pmask)); + free(cstr); +finalize_it: + RETiRet; +} + + +struct cnffunc * +cnffuncNew(es_str_t *fname, struct cnffparamlst* paramlst) +{ + struct cnffunc* func; + struct cnffparamlst *param, *toDel; + unsigned short i; + unsigned short nParams; + + /* we first need to find out how many params we have */ + nParams = 0; + for(param = paramlst ; param != NULL ; param = param->next) + ++nParams; + if((func = malloc(sizeof(struct cnffunc) + (nParams * sizeof(struct cnfexp*)))) + != NULL) { + func->nodetype = 'F'; + func->fname = fname; + func->nParams = nParams; + func->funcdata = NULL; + func->fID = funcName2ID(fname, nParams); + /* shuffle params over to array (access speed!) */ + param = paramlst; + for(i = 0 ; i < nParams ; ++i) { + func->expr[i] = param->expr; + toDel = param; + param = param->next; + free(toDel); + } + /* some functions require special initialization */ + switch(func->fID) { + case CNFFUNC_RE_MATCH: + case CNFFUNC_RE_EXTRACT: + /* need to compile the regexp in param 2, so this MUST be a constant */ + initFunc_re_match(func); + break; + case CNFFUNC_PRIFILT: + initFunc_prifilt(func); + break; + default:break; + } + } + return func; +} + + +/* A special function to create a prifilt() expression during optimization + * phase. + */ +struct cnffunc * +cnffuncNew_prifilt(int fac) +{ + struct cnffunc* func; + + if((func = malloc(sizeof(struct cnffunc))) != NULL) { + func->nodetype = 'F'; + func->fname = es_newStrFromCStr("prifilt", sizeof("prifilt")-1); + func->nParams = 0; + func->fID = CNFFUNC_PRIFILT; + func->funcdata = calloc(1, sizeof(struct funcData_prifilt)); + ((struct funcData_prifilt *)func->funcdata)->pmask[fac >> 3] = TABLE_ALLPRI; + } + return func; +} + + +/* returns 0 if everything is OK and config parsing shall continue, + * and 1 if things are so wrong that config parsing shall be aborted. + */ +int +cnfDoInclude(char *name) +{ + char *cfgFile; + char *finalName; + int i; + int result; + glob_t cfgFiles; + struct stat fileInfo; + char nameBuf[MAXFNAME+1]; + char cwdBuf[MAXFNAME+1]; + + finalName = name; + if(stat(name, &fileInfo) == 0) { + /* stat usually fails if we have a wildcard - so this does NOT indicate error! */ + if(S_ISDIR(fileInfo.st_mode)) { + /* if we have a directory, we need to add "*" to get its files */ + snprintf(nameBuf, sizeof(nameBuf), "%s*", name); + finalName = nameBuf; + } + } + + /* Use GLOB_MARK to append a trailing slash for directories. */ + /* Use GLOB_NOMAGIC to detect wildcards that match nothing. */ +#ifdef HAVE_GLOB_NOMAGIC + /* Silently ignore wildcards that match nothing */ + result = glob(finalName, GLOB_MARK | GLOB_NOMAGIC, NULL, &cfgFiles); + if(result == GLOB_NOMATCH) { +#else + result = glob(finalName, GLOB_MARK, NULL, &cfgFiles); + if(result == GLOB_NOMATCH && containsGlobWildcard(finalName)) { +#endif /* HAVE_GLOB_NOMAGIC */ + return 0; + } + + if(result == GLOB_NOSPACE || result == GLOB_ABORTED) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + if(getcwd(cwdBuf, sizeof(cwdBuf)) == NULL) + strcpy(cwdBuf, "??getcwd() failed??"); + parser_errmsg("error accessing config file or directory '%s' [cwd:%s]: %s", + finalName, cwdBuf, errStr); + return 1; + } + + /* note: bison "stacks" the files, so we need to submit them + * in reverse order to the *stack* in order to get the proper + * parsing order. Also see + * http://bugzilla.adiscon.com/show_bug.cgi?id=411 + */ + for(i = cfgFiles.gl_pathc - 1; i >= 0 ; i--) { + cfgFile = cfgFiles.gl_pathv[i]; + if(stat(cfgFile, &fileInfo) != 0) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + if(getcwd(cwdBuf, sizeof(cwdBuf)) == NULL) + strcpy(cwdBuf, "??getcwd() failed??"); + parser_errmsg("error accessing config file or directory '%s' " + "[cwd: %s]: %s", cfgFile, cwdBuf, errStr); + return 1; + } + + if(S_ISREG(fileInfo.st_mode)) { /* config file */ + dbgprintf("requested to include config file '%s'\n", cfgFile); + cnfSetLexFile(cfgFile); + } else if(S_ISDIR(fileInfo.st_mode)) { /* config directory */ + dbgprintf("requested to include directory '%s'\n", cfgFile); + cnfDoInclude(cfgFile); + } else { + dbgprintf("warning: unable to process IncludeConfig directive '%s'\n", cfgFile); + } + } + + globfree(&cfgFiles); + return 0; +} + +void +varDelete(struct var *v) +{ + switch(v->datatype) { + case 'S': + es_deleteStr(v->d.estr); + break; + case 'A': + cnfarrayContentDestruct(v->d.ar); + free(v->d.ar); + break; + default:break; + } +} + +void +cnfparamvalsDestruct(struct cnfparamvals *paramvals, struct cnfparamblk *blk) +{ + int i; + for(i = 0 ; i < blk->nParams ; ++i) { + if(paramvals[i].bUsed) { + varDelete(¶mvals[i].val); + } + } + free(paramvals); +} + +/* find the index (or -1!) for a config param by name. This is used to + * address the parameter array. Of course, we could use with static + * indices, but that would create some extra bug potential. So we + * resort to names. As we do this only during the initial config parsing + * stage the (considerable!) extra overhead is OK. -- rgerhards, 2011-07-19 + */ +int +cnfparamGetIdx(struct cnfparamblk *params, char *name) +{ + int i; + for(i = 0 ; i < params->nParams ; ++i) + if(!strcmp(params->descr[i].name, name)) + break; + if(i == params->nParams) + i = -1; /* not found */ + return i; +} + + +void +cstrPrint(char *text, es_str_t *estr) +{ + char *str; + str = es_str2cstr(estr, NULL); + dbgprintf("%s%s", text, str); + free(str); +} + +char * +rmLeadingSpace(char *s) +{ + char *p; + for(p = s ; *p && isspace(*p) ; ++p) + ; + return(p); +} + +/* init must be called once before any parsing of the script files start */ +rsRetVal +initRainerscript(void) +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); +finalize_it: + RETiRet; +} + +/* we need a function to check for octal digits */ +static inline int +isodigit(uchar c) +{ + return(c >= '0' && c <= '7'); +} + +/** + * Get numerical value of a hex digit. This is a helper function. + * @param[in] c a character containing 0..9, A..Z, a..z anything else + * is an (undetected) error. + */ +static inline int +hexDigitVal(char c) +{ + int r; + if(c < 'A') + r = c - '0'; + else if(c < 'a') + r = c - 'A' + 10; + else + r = c - 'a' + 10; + return r; +} + +/* Handle the actual unescaping. + * a helper to unescapeStr(), to help make the function easier to read. + */ +static inline void +doUnescape(unsigned char *c, int len, int *iSrc, int iDst) +{ + if(c[*iSrc] == '\\') { + if(++(*iSrc) == len) { + /* error, incomplete escape, treat as single char */ + c[iDst] = '\\'; + } + /* regular case, unescape */ + switch(c[*iSrc]) { + case 'a': + c[iDst] = '\007'; + break; + case 'b': + c[iDst] = '\b'; + break; + case 'f': + c[iDst] = '\014'; + break; + case 'n': + c[iDst] = '\n'; + break; + case 'r': + c[iDst] = '\r'; + break; + case 't': + c[iDst] = '\t'; + break; + case '\'': + c[iDst] = '\''; + break; + case '"': + c[iDst] = '"'; + break; + case '?': + c[iDst] = '?'; + break; + case '$': + c[iDst] = '$'; + break; + case '\\': + c[iDst] = '\\'; + break; + case 'x': + if( (*iSrc)+2 >= len + || !isxdigit(c[(*iSrc)+1]) + || !isxdigit(c[(*iSrc)+2])) { + /* error, incomplete escape, use as is */ + c[iDst] = '\\'; + --(*iSrc); + } + c[iDst] = (hexDigitVal(c[(*iSrc)+1]) << 4) + + hexDigitVal(c[(*iSrc)+2]); + *iSrc += 2; + break; + case '0': /* octal escape */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + if( (*iSrc)+2 >= len + || !isodigit(c[(*iSrc)+1]) + || !isodigit(c[(*iSrc)+2])) { + /* error, incomplete escape, use as is */ + c[iDst] = '\\'; + --(*iSrc); + } + c[iDst] = ((c[(*iSrc) ] - '0') << 6) + + ((c[(*iSrc)+1] - '0') << 3) + + ( c[(*iSrc)+2] - '0'); + *iSrc += 2; + break; + default: + /* error, incomplete escape, indicate by '?' */ + c[iDst] = '?'; + break; + } + } else { + /* regular character */ + c[iDst] = c[*iSrc]; + } +} + +void +unescapeStr(uchar *s, int len) +{ + int iSrc, iDst; + assert(s != NULL); + + /* scan for first escape sequence (if we are luky, there is none!) */ + iSrc = 0; + while(iSrc < len && s[iSrc] != '\\') + ++iSrc; + /* now we have a sequence or end of string. In any case, we process + * all remaining characters (maybe 0!) and unescape. + */ + if(iSrc != len) { + iDst = iSrc; + while(iSrc < len) { + doUnescape(s, len, &iSrc, iDst); + ++iSrc; + ++iDst; + } + s[iDst] = '\0'; + } +} + +char * +tokenval2str(int tok) +{ + if(tok < 256) return ""; + switch(tok) { + case NAME: return "NAME"; + case FUNC: return "FUNC"; + case BEGINOBJ: return "BEGINOBJ"; + case ENDOBJ: return "ENDOBJ"; + case BEGIN_ACTION: return "BEGIN_ACTION"; + case BEGIN_PROPERTY: return "BEGIN_PROPERTY"; + case BEGIN_CONSTANT: return "BEGIN_CONSTANT"; + case BEGIN_TPL: return "BEGIN_TPL"; + case BEGIN_RULESET: return "BEGIN_RULESET"; + case STOP: return "STOP"; + case SET: return "SET"; + case UNSET: return "UNSET"; + case CONTINUE: return "CONTINUE"; + case CALL: return "CALL"; + case LEGACY_ACTION: return "LEGACY_ACTION"; + case LEGACY_RULESET: return "LEGACY_RULESET"; + case PRIFILT: return "PRIFILT"; + case PROPFILT: return "PROPFILT"; + case BSD_TAG_SELECTOR: return "BSD_TAG_SELECTOR"; + case BSD_HOST_SELECTOR: return "BSD_HOST_SELECTOR"; + case IF: return "IF"; + case THEN: return "THEN"; + case ELSE: return "ELSE"; + case OR: return "OR"; + case AND: return "AND"; + case NOT: return "NOT"; + case VAR: return "VAR"; + case STRING: return "STRING"; + case NUMBER: return "NUMBER"; + case CMP_EQ: return "CMP_EQ"; + case CMP_NE: return "CMP_NE"; + case CMP_LE: return "CMP_LE"; + case CMP_GE: return "CMP_GE"; + case CMP_LT: return "CMP_LT"; + case CMP_GT: return "CMP_GT"; + case CMP_CONTAINS: return "CMP_CONTAINS"; + case CMP_CONTAINSI: return "CMP_CONTAINSI"; + case CMP_STARTSWITH: return "CMP_STARTSWITH"; + case CMP_STARTSWITHI: return "CMP_STARTSWITHI"; + case UMINUS: return "UMINUS"; + default: return "UNKNOWN TOKEN"; + } +} diff --git a/grammar/rainerscript.h b/grammar/rainerscript.h new file mode 100644 index 00000000..31b2eb93 --- /dev/null +++ b/grammar/rainerscript.h @@ -0,0 +1,347 @@ +#ifndef INC_UTILS_H +#define INC_UTILS_H +#include <stdio.h> +#include <libestr.h> +#include <typedefs.h> +#include <sys/types.h> +#include <regex.h> + + +#define LOG_NFACILITIES 24 /* current number of syslog facilities */ +#define CNFFUNC_MAX_ARGS 32 + /**< maximum number of arguments that any function can have (among + * others, this is used to size data structures). + */ + +extern int Debug; /* 1 if in debug mode, 0 otherwise -- to be enhanced */ + +enum cnfobjType { + CNFOBJ_ACTION, + CNFOBJ_RULESET, + CNFOBJ_GLOBAL, + CNFOBJ_INPUT, + CNFOBJ_MODULE, + CNFOBJ_TPL, + CNFOBJ_PROPERTY, + CNFOBJ_CONSTANT, + CNFOBJ_INVALID = 0 +}; + +static inline char* +cnfobjType2str(enum cnfobjType ot) +{ + switch(ot) { + case CNFOBJ_ACTION: + return "action"; + break; + case CNFOBJ_RULESET: + return "ruleset"; + break; + case CNFOBJ_GLOBAL: + return "global"; + break; + case CNFOBJ_INPUT: + return "input"; + break; + case CNFOBJ_MODULE: + return "module"; + break; + case CNFOBJ_TPL: + return "template"; + break; + case CNFOBJ_PROPERTY: + return "property"; + break; + case CNFOBJ_CONSTANT: + return "constant"; + break; + default:return "error: invalid cnfobjType"; + } +} + +enum cnfactType { CNFACT_V2, CNFACT_LEGACY }; + +/* a variant type, for example used for expression evaluation + * 2011-07-15/rger: note that there exists a "legacy" object var_t, + * which implements the same idea, but in a suboptimal manner. I have + * stipped this down as much as possible, but will keep it for a while + * to avoid unnecessary complexity during development. TODO: in the long + * term, var_t shall be replaced by struct var. + */ +struct var { + union { + es_str_t *estr; + struct cnfarray *ar; + long long n; + struct json_object *json; + } d; + char datatype; /* 'N' number, 'S' string, 'J' JSON, 'A' array + * Note: 'A' is only supported during config phase + */ +}; + +struct cnfobj { + enum cnfobjType objType; + struct nvlst *nvlst; + struct objlst *subobjs; + struct cnfstmt *script; +}; + +struct objlst { + struct objlst *next; + struct cnfobj *obj; +}; + +struct nvlst { + struct nvlst *next; + es_str_t *name; + struct var val; + unsigned char bUsed; + /**< was this node used during config processing? If not, this + * indicates an error. After all, the user specified a setting + * that the software does not know. + */ +}; + +/* the following structures support expressions, and may (very much later + * be the sole foundation for the AST. + * + * nodetypes (list not yet complete) + * F - function + * N - number + * P - fparamlst + * R - rule + * S - string + * V - var + * A - (string) array + * ... plus the S_* #define's below: + */ +#define S_STOP 4000 +#define S_PRIFILT 4001 +#define S_PROPFILT 4002 +#define S_IF 4003 +#define S_ACT 4004 +#define S_NOP 4005 /* usually used to disable some statement */ +#define S_SET 4006 +#define S_UNSET 4007 +#define S_CALL 4008 + +enum cnfFiltType { CNFFILT_NONE, CNFFILT_PRI, CNFFILT_PROP, CNFFILT_SCRIPT }; +static inline char* +cnfFiltType2str(enum cnfFiltType filttype) +{ + switch(filttype) { + case CNFFILT_NONE: + return("filter:none"); + case CNFFILT_PRI: + return("filter:pri"); + case CNFFILT_PROP: + return("filter:prop"); + case CNFFILT_SCRIPT: + return("filter:script"); + } + return("error:invalid_filter_type"); /* should never be reached */ +} + + +struct cnfstmt { + unsigned nodetype; + struct cnfstmt *next; + uchar *printable; /* printable text for debugging */ + union { + struct { + struct cnfexpr *expr; + struct cnfstmt *t_then; + struct cnfstmt *t_else; + } s_if; + struct { + uchar *varname; + struct cnfexpr *expr; + } s_set; + struct { + uchar *varname; + } s_unset; + struct { + es_str_t *name; + struct cnfstmt *stmt; + } s_call; + struct { + uchar pmask[LOG_NFACILITIES+1]; /* priority mask */ + struct cnfstmt *t_then; + struct cnfstmt *t_else; + } s_prifilt; + struct { + fiop_t operation; + regex_t *regex_cache;/* cache for compiled REs, if used */ + struct cstr_s *pCSCompValue;/* value to "compare" against */ + sbool isNegated; + uintTiny propID;/* ID of the requested property */ + es_str_t *propName;/* name of property for CEE-based filters */ + struct cnfstmt *t_then; + struct cnfstmt *t_else; + } s_propfilt; + struct action_s *act; + } d; +}; + +struct cnfexpr { + unsigned nodetype; + struct cnfexpr *l; + struct cnfexpr *r; +}; + +struct cnfnumval { + unsigned nodetype; + long long val; +}; + +struct cnfstringval { + unsigned nodetype; + es_str_t *estr; +}; + +struct cnfvar { + unsigned nodetype; + char *name; +}; + +struct cnfarray { + unsigned nodetype; + int nmemb; + es_str_t **arr; +}; + +struct cnffparamlst { + unsigned nodetype; /* P */ + struct cnffparamlst *next; + struct cnfexpr *expr; +}; + +enum cnffuncid { + CNFFUNC_INVALID = 0, /**< defunct entry, do not use (should normally not be present) */ + CNFFUNC_NAME = 1, /**< use name to call function (for future use) */ + CNFFUNC_STRLEN, + CNFFUNC_GETENV, + CNFFUNC_TOLOWER, + CNFFUNC_CSTR, + CNFFUNC_CNUM, + CNFFUNC_RE_MATCH, + CNFFUNC_RE_EXTRACT, + CNFFUNC_FIELD, + CNFFUNC_PRIFILT +}; + +struct cnffunc { + unsigned nodetype; + es_str_t *fname; + unsigned short nParams; + enum cnffuncid fID; /* function ID for built-ins, 0 means use name */ + void *funcdata; /* global data for function-specific use (e.g. compiled regex) */ + struct cnfexpr *expr[]; +}; + +/* future extensions +struct x { + int nodetype; +}; +*/ + + +/* the following defines describe the parameter block for puling + * config parameters. Note that the focus is on ease and saveness of + * use, not performance. For example, we address parameters by name + * instead of index, because the former is less error-prone. The (severe) + * performance hit does not matter, as it is a one-time hit during config + * load but never during actual processing. So there is really no reason + * to care. + */ +struct cnfparamdescr { /* first the param description */ + char *name; /**< not a es_str_t to ease definition in code */ + ecslCmdHdrlType type; + unsigned flags; +}; +/* flags for cnfparamdescr: */ +#define CNFPARAM_REQUIRED 0x0001 + +struct cnfparamblk { /* now the actual param block use in API calls */ + unsigned short version; + unsigned short nParams; + struct cnfparamdescr *descr; +}; +#define CNFPARAMBLK_VERSION 1 + /**< caller must have same version as engine -- else things may + * be messed up. But note that we may support multiple versions + * inside the engine, if at some later stage we want to do + * that. -- rgerhards, 2011-07-15 + */ +struct cnfparamvals { /* the values we obtained for param descr. */ + struct var val; + unsigned char bUsed; +}; + +struct funcData_prifilt { + uchar pmask[LOG_NFACILITIES+1]; /* priority mask */ +}; + + +int cnfParseBuffer(char *buf, unsigned lenBuf); +void readConfFile(FILE *fp, es_str_t **str); +struct objlst* objlstNew(struct cnfobj *obj); +void objlstDestruct(struct objlst *lst); +void objlstPrint(struct objlst *lst); +struct nvlst* nvlstNewArray(struct cnfarray *ar); +struct nvlst* nvlstNewStr(es_str_t *value); +struct nvlst* nvlstSetName(struct nvlst *lst, es_str_t *name); +void nvlstDestruct(struct nvlst *lst); +void nvlstPrint(struct nvlst *lst); +void nvlstChkUnused(struct nvlst *lst); +struct nvlst* nvlstFindName(struct nvlst *lst, es_str_t *name); +struct cnfobj* cnfobjNew(enum cnfobjType objType, struct nvlst *lst); +void cnfobjDestruct(struct cnfobj *o); +void cnfobjPrint(struct cnfobj *o); +struct cnfexpr* cnfexprNew(unsigned nodetype, struct cnfexpr *l, struct cnfexpr *r); +void cnfexprPrint(struct cnfexpr *expr, int indent); +void cnfexprEval(struct cnfexpr *expr, struct var *ret, void *pusr); +int cnfexprEvalBool(struct cnfexpr *expr, void *usrptr); +void cnfexprDestruct(struct cnfexpr *expr); +struct cnfnumval* cnfnumvalNew(long long val); +struct cnfstringval* cnfstringvalNew(es_str_t *estr); +struct cnfvar* cnfvarNew(char *name); +struct cnffunc * cnffuncNew(es_str_t *fname, struct cnffparamlst* paramlst); +struct cnffparamlst * cnffparamlstNew(struct cnfexpr *expr, struct cnffparamlst *next); +int cnfDoInclude(char *name); +int cnfparamGetIdx(struct cnfparamblk *params, char *name); +struct cnfparamvals* nvlstGetParams(struct nvlst *lst, struct cnfparamblk *params, + struct cnfparamvals *vals); +void cnfparamsPrint(struct cnfparamblk *params, struct cnfparamvals *vals); +int cnfparamvalsIsSet(struct cnfparamblk *params, struct cnfparamvals *vals); +void varDelete(struct var *v); +void cnfparamvalsDestruct(struct cnfparamvals *paramvals, struct cnfparamblk *blk); +struct cnfstmt * cnfstmtNew(unsigned s_type); +void cnfstmtPrintOnly(struct cnfstmt *stmt, int indent, sbool subtree); +void cnfstmtPrint(struct cnfstmt *stmt, int indent); +struct cnfstmt* scriptAddStmt(struct cnfstmt *root, struct cnfstmt *s); +struct objlst* objlstAdd(struct objlst *root, struct cnfobj *o); +char *rmLeadingSpace(char *s); +struct cnfstmt * cnfstmtNewPRIFILT(char *prifilt, struct cnfstmt *t_then); +struct cnfstmt * cnfstmtNewPROPFILT(char *propfilt, struct cnfstmt *t_then); +struct cnfstmt * cnfstmtNewAct(struct nvlst *lst); +struct cnfstmt * cnfstmtNewLegaAct(char *actline); +struct cnfstmt * cnfstmtNewSet(char *var, struct cnfexpr *expr); +struct cnfstmt * cnfstmtNewUnset(char *var); +struct cnfstmt * cnfstmtNewCall(es_str_t *name); +struct cnfstmt * cnfstmtNewContinue(void); +void cnfstmtDestruct(struct cnfstmt *root); +void cnfstmtOptimize(struct cnfstmt *root); +struct cnfarray* cnfarrayNew(es_str_t *val); +struct cnfarray* cnfarrayDup(struct cnfarray *old); +struct cnfarray* cnfarrayAdd(struct cnfarray *ar, es_str_t *val); +void cnfarrayContentDestruct(struct cnfarray *ar); +char* getFIOPName(unsigned iFIOP); +rsRetVal initRainerscript(void); +void unescapeStr(uchar *s, int len); +char * tokenval2str(int tok); + +/* debug helper */ +void cstrPrint(char *text, es_str_t *estr); +#endif diff --git a/grammar/samp b/grammar/samp new file mode 100644 index 00000000..91d475b0 --- /dev/null +++ b/grammar/samp @@ -0,0 +1,11 @@ +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +mail.info -/var/log/mail.info + diff --git a/grammar/testdriver.c b/grammar/testdriver.c new file mode 100644 index 00000000..b29626d4 --- /dev/null +++ b/grammar/testdriver.c @@ -0,0 +1,109 @@ +/* This is a stand-alone test driver for grammar processing. We try to + * keep this separate as it simplyfies grammer development. + * + * Copyright 2011 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <libestr.h> +#include "rainerscript.h" +#include "parserif.h" + +extern int yylineno; +int Debug = 1; + +void +parser_errmsg(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + printf("error on or before line %d: ", yylineno); + vprintf(fmt, ap); + printf("\n"); + va_end(ap); +} + +int +yyerror(char *s) +{ + parser_errmsg("%s", s); + return 0; +} + +void +dbgprintf(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); +} + +void cnfDoObj(struct cnfobj *o) +{ + dbgprintf("global:obj: "); + cnfobjPrint(o); + cnfobjDestruct(o); +} + +void cnfDoRule(struct cnfrule *rule) +{ + dbgprintf("global:rule processed\n"); + cnfrulePrint(rule); +} + +void cnfDoCfsysline(char *ln) +{ + dbgprintf("global:cfsysline: %s\n", ln); +} + +void cnfDoBSDTag(char *ln) +{ + dbgprintf("global:BSD tag: %s\n", ln); +} + +void cnfDoBSDHost(char *ln) +{ + dbgprintf("global:BSD host: %s\n", ln); +} + +es_str_t* +cnfGetVar(char __attribute__((unused)) *name, + void __attribute__((unused)) *usrptr) +{ + es_str_t *estr; + estr = es_newStrFromCStr("", 1); + return estr; +} + +int +main(int argc, char *argv[]) +{ + int r; + + cnfSetLexFile(argc == 1 ? NULL : argv[1]); + yydebug = 0; + r = yyparse(); + printf("yyparse() returned %d\n", r); + return r; +} diff --git a/gss-misc.c b/gss-misc.c new file mode 100644 index 00000000..d30eda02 --- /dev/null +++ b/gss-misc.c @@ -0,0 +1,318 @@ +/* gss-misc.c + * This is a miscellaneous helper class for gss-api features. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <fnmatch.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#ifdef USE_PTHREADS +#include <pthread.h> +#else +#include <fcntl.h> +#endif +#include <gssapi/gssapi.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "net.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "gss-misc.h" +#include "debug.h" +#include "glbl.h" +#include "unlimited_select.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(errmsg) + +static void display_status_(char *m, OM_uint32 code, int type) +{ + OM_uint32 maj_stat, min_stat, msg_ctx = 0; + gss_buffer_desc msg; + + do { + maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NO_OID, &msg_ctx, &msg); + if (maj_stat != GSS_S_COMPLETE) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error in gss_display_status called from <%s>\n", m); + break; + } else { + char buf[1024]; + snprintf(buf, sizeof(buf), "GSS-API error %s: %s\n", m, (char *) msg.value); + buf[sizeof(buf)/sizeof(char) - 1] = '\0'; + errmsg.LogError(0, NO_ERRCODE, "%s", buf); + } + if (msg.length != 0) + gss_release_buffer(&min_stat, &msg); + } while (msg_ctx); +} + + +static void display_status(char *m, OM_uint32 maj_stat, OM_uint32 min_stat) +{ + display_status_(m, maj_stat, GSS_C_GSS_CODE); + display_status_(m, min_stat, GSS_C_MECH_CODE); +} + + +static void display_ctx_flags(OM_uint32 flags) +{ + if (flags & GSS_C_DELEG_FLAG) + dbgprintf("GSS_C_DELEG_FLAG\n"); + if (flags & GSS_C_MUTUAL_FLAG) + dbgprintf("GSS_C_MUTUAL_FLAG\n"); + if (flags & GSS_C_REPLAY_FLAG) + dbgprintf("GSS_C_REPLAY_FLAG\n"); + if (flags & GSS_C_SEQUENCE_FLAG) + dbgprintf("GSS_C_SEQUENCE_FLAG\n"); + if (flags & GSS_C_CONF_FLAG) + dbgprintf("GSS_C_CONF_FLAG\n"); + if (flags & GSS_C_INTEG_FLAG) + dbgprintf("GSS_C_INTEG_FLAG\n"); +} + + +static int read_all(int fd, char *buf, unsigned int nbyte) +{ + int ret; + char *ptr; + struct timeval tv; +#ifdef USE_UNLIMITED_SELECT + fd_set *pRfds = malloc(glbl.GetFdSetSize()); +#else + fd_set rfds; + fd_set *pRfds = &rfds; +#endif + + for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) { + FD_ZERO(pRfds); + FD_SET(fd, pRfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + if ((ret = select(FD_SETSIZE, pRfds, NULL, NULL, &tv)) <= 0 + || !FD_ISSET(fd, pRfds)) { + freeFdSet(pRfds); + return ret; + } + ret = recv(fd, ptr, nbyte, 0); + if (ret < 0) { + if (errno == EINTR) + continue; + freeFdSet(pRfds); + return (ret); + } else if (ret == 0) { + freeFdSet(pRfds); + return (ptr - buf); + } + } + + freeFdSet(pRfds); + return (ptr - buf); +} + + +static int write_all(int fd, char *buf, unsigned int nbyte) +{ + int ret; + char *ptr; + + for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) { + ret = send(fd, ptr, nbyte, 0); + if (ret < 0) { + if (errno == EINTR) + continue; + return (ret); + } else if (ret == 0) { + return (ptr - buf); + } + } + + return (ptr - buf); +} + + +static int recv_token(int s, gss_buffer_t tok) +{ + int ret; + unsigned char lenbuf[4]; + unsigned int len; + + ret = read_all(s, (char *) lenbuf, 4); + if (ret < 0) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error reading token length"); + return -1; + } else if (!ret) { + return 0; + } else if (ret != 4) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error reading token length"); + return -1; + } + + len = ((lenbuf[0] << 24) + | (lenbuf[1] << 16) + | (lenbuf[2] << 8) + | lenbuf[3]); + tok->length = ntohl(len); + + tok->value = (char *) MALLOC(tok->length ? tok->length : 1); + if (tok->length && tok->value == NULL) { + errmsg.LogError(0, NO_ERRCODE, "Out of memory allocating token data\n"); + return -1; + } + + ret = read_all(s, (char *) tok->value, tok->length); + if (ret < 0) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error reading token data"); + free(tok->value); + return -1; + } else if (ret != (int) tok->length) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error reading token data"); + free(tok->value); + return -1; + } + + return 1; +} + + +static int send_token(int s, gss_buffer_t tok) +{ + int ret; + unsigned char lenbuf[4]; + unsigned int len; + + if (tok->length > 0xffffffffUL) + abort(); /* TODO: we need to reconsider this, abort() is not really a solution - degrade, but keep running */ + len = htonl(tok->length); + lenbuf[0] = (len >> 24) & 0xff; + lenbuf[1] = (len >> 16) & 0xff; + lenbuf[2] = (len >> 8) & 0xff; + lenbuf[3] = len & 0xff; + + ret = write_all(s, (char *) lenbuf, 4); + if (ret < 0) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error sending token length"); + return -1; + } else if (ret != 4) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error sending token length"); + return -1; + } + + ret = write_all(s, tok->value, tok->length); + if (ret < 0) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error sending token data"); + return -1; + } else if (ret != (int) tok->length) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API error sending token data"); + return -1; + } + + return 0; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(gssutil) +CODESTARTobjQueryInterface(gssutil) + if(pIf->ifVersion != gssutilCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->recv_token = recv_token; + pIf->send_token = send_token; + pIf->display_status = display_status; + pIf->display_ctx_flags = display_ctx_flags; + +finalize_it: +ENDobjQueryInterface(gssutil) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(gssutil, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(gssutil) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); +ENDObjClassExit(gssutil) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINAbstractObjClassInit(gssutil, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); +ENDObjClassInit(gssutil) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + gssutilClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(gssutilClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit diff --git a/gss-misc.h b/gss-misc.h new file mode 100644 index 00000000..3e94bf41 --- /dev/null +++ b/gss-misc.h @@ -0,0 +1,45 @@ +/* Definitions for gssutil class. This implements a session of the + * plain TCP server. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef GSS_MISC_H_INCLUDED +#define GSS_MISC_H_INCLUDED 1 + +#include <gssapi/gssapi.h> +#include "obj.h" + +/* interfaces */ +BEGINinterface(gssutil) /* name must also be changed in ENDinterface macro! */ + int (*recv_token)(int s, gss_buffer_t tok); + int (*send_token)(int s, gss_buffer_t tok); + void (*display_status)(char *m, OM_uint32 maj_stat, OM_uint32 min_stat); + void (*display_ctx_flags)(OM_uint32 flags); +ENDinterface(gssutil) +#define gssutilCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(gssutil); + +/* the name of our library binary */ +#define LM_GSSUTIL_FILENAME "lmgssutil" + +#endif /* #ifndef GSS_MISC_H_INCLUDED */ diff --git a/java/Makefile.am b/java/Makefile.am new file mode 100644 index 00000000..67f5eb43 --- /dev/null +++ b/java/Makefile.am @@ -0,0 +1,35 @@ +# very rough support for compiling the java components of rsyslog +# Some usage notes: you need to use the Sun JDK compiler (jdk-devel) +# with this. At least it didn't work for me with the eclipse compiler. +# There is no real installation support. If you intend to run a program, +# change to the ./java subdirectory and issue +# java -cp . <class> +# e.g.: java -cp . com.rsyslog.gui.diaggui.DiagGUI +# or any equivalent command. +# +# I am very glad to hear suggestions about how to improve this part +# of the build system. -- rgerhards, 2009-08-27 + +javadir = $(top_builddir)/java +JAVAROOT = $(javadir) +# I don't know why CLASSPATH_ENV works this way, but at least it works... +CLASSPATH_ENV = CLASSPATH=$(javadir):$$CLASSPATH + +JAVA_SOURCE_FILES = \ + com/rsyslog/lib/DiagSess.java \ + com/rsyslog/lib/SyslogMessage.java \ + com/rsyslog/lib/SyslogMsgConsumer.java \ + com/rsyslog/lib/SyslogTrafficGenerator.java \ + com/rsyslog/lib/SyslogSender.java \ + com/rsyslog/lib/UDPSyslogSender.java \ + com/rsyslog/diag/DiagTalker.java \ + com/rsyslog/gui/simpServ/simpServ.java \ + com/rsyslog/gui/simpServ/simpServConsumer.java \ + com/rsyslog/gui/msggen/MsgGen.java \ + com/rsyslog/gui/diaggui/Counters.java \ + com/rsyslog/gui/diaggui/DiagGUI.java + + +java_JAVA = $(JAVA_SOURCE_FILES) + +dist_java = $(JAVA_SOURCE_FILES) diff --git a/java/com/rsyslog/diag/DiagTalker.java b/java/com/rsyslog/diag/DiagTalker.java new file mode 100644 index 00000000..c4e77e95 --- /dev/null +++ b/java/com/rsyslog/diag/DiagTalker.java @@ -0,0 +1,70 @@ +/* A yet very simple tool to talk to imdiag. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.diag; +import java.io.*; +import java.net.*; + +public class DiagTalker { + public static void main(String[] args) throws IOException { + + Socket diagSocket = null; + PrintWriter out = null; + BufferedReader in = null; + final String host = "127.0.0.1"; + final int port = 13500; + + try { + diagSocket = new Socket(host, port); + diagSocket.setSoTimeout(0); /* wait for lenghty operations */ + out = new PrintWriter(diagSocket.getOutputStream(), true); + in = new BufferedReader(new InputStreamReader( + diagSocket.getInputStream())); + } catch (UnknownHostException e) { + System.err.println("can not resolve " + host + "!"); + System.exit(1); + } catch (IOException e) { + System.err.println("Couldn't get I/O for " + + "the connection to: " + host + "."); + System.exit(1); + } + + BufferedReader stdIn = new BufferedReader( + new InputStreamReader(System.in)); + String userInput; + + try { + while ((userInput = stdIn.readLine()) != null) { + out.println(userInput); + System.out.println("imdiag returns: " + in.readLine()); + } + } catch (SocketException e) { + System.err.println("We had a socket exception and consider this to be OK: " + + e.getMessage()); + } + + out.close(); + in.close(); + stdIn.close(); + diagSocket.close(); + } +} + diff --git a/java/com/rsyslog/gui/diaggui/Counters.java b/java/com/rsyslog/gui/diaggui/Counters.java new file mode 100644 index 00000000..363bff43 --- /dev/null +++ b/java/com/rsyslog/gui/diaggui/Counters.java @@ -0,0 +1,138 @@ +/* Display some basic rsyslogd counter variables. + * + * Please note that this program requires imdiag to be loaded inside rsyslogd. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.gui.diaggui; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.*; + +import com.rsyslog.lib.DiagSess; + +public class Counters extends Frame { + + private TextField MainQItems; + private TextField RefreshInterval; + private Checkbox AutoRefresh; + private DiagSess diagSess; + private Timer timer; + + private void createDiagSess() { + try { + diagSess = new DiagSess("127.0.0.1", 13500); // TODO: values from GUI + diagSess.connect(); + } + catch(IOException e) { + System.out.println("Exception trying to open diag session:\n" + e.toString()); + } + } + + private void createGUI() { + MainQItems = new TextField(); + MainQItems.setColumns(8); + Panel pCenter = new Panel(); + pCenter.setLayout(new FlowLayout()); + pCenter.add(new Label("MainQ Items:")); + pCenter.add(MainQItems); + + RefreshInterval = new TextField(); + RefreshInterval.setColumns(5); + RefreshInterval.setText("100"); + AutoRefresh = new Checkbox("Auto Refresh", false); + AutoRefresh.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + setAutoRefresh(); + } + + }); + Button b = new Button("Refresh now"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + refreshCounters(); + } + }); + Panel pSouth = new Panel(); + pSouth.setLayout(new FlowLayout()); + pSouth.add(AutoRefresh); + pSouth.add(new Label("Interval (ms):")); + pSouth.add(RefreshInterval); + pSouth.add(b); + + pack(); + setTitle("rsyslogd Counters"); + setLayout(new BorderLayout()); + add(pCenter, BorderLayout.CENTER); + add(pSouth, BorderLayout.SOUTH); + setSize(400,500); + } + + public Counters() { + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e){ + Counters.this.dispose(); + } + }); + createGUI(); + createDiagSess(); + setAutoRefresh(); + setVisible(true); + } + + + private void startTimer() { + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + public void run() { + refreshCounters(); + } + }, 0, 100); + } + + private void stopTimer() { + if(timer != null) { + timer.cancel(); + timer = null; + } + } + + /** set auto-refresh mode. It is either turned on or off, depending on the + * status of the relevant check box. */ + private void setAutoRefresh() { + if(AutoRefresh.getState() == true) { + startTimer(); + } else { + stopTimer(); + } + } + + /** refresh counter display from rsyslogd data. Does a network round-trip. */ + private void refreshCounters() { + try { + String res = diagSess.request("getmainmsgqueuesize"); + MainQItems.setText(res); + } + catch(IOException e) { + System.out.println("Exception during request:\n" + e.toString()); + } + } +} diff --git a/java/com/rsyslog/gui/diaggui/DiagGUI.java b/java/com/rsyslog/gui/diaggui/DiagGUI.java new file mode 100644 index 00000000..1a03299c --- /dev/null +++ b/java/com/rsyslog/gui/diaggui/DiagGUI.java @@ -0,0 +1,77 @@ +/* A yet very simple diagnostic GUI for rsyslog. + * + * Please note that this program requires imdiag to be loaded inside rsyslogd. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.gui.diaggui; +import java.awt.*; +import java.awt.event.*; + +public class DiagGUI extends Frame { + public Counters counterWin; + public static void main(String args[]) { + new DiagGUI(); + } + + /** show counter window. creates it if not already present */ + public void showCounters() { + if(counterWin == null) { + counterWin = new Counters(); + } else { + counterWin.setVisible(true); + counterWin.toFront(); + } + } + + /** initialize the GUI. */ + public DiagGUI(){ + MenuItem item; + MenuBar menuBar = new MenuBar(); + Menu fileMenu = new Menu("File"); + item = new MenuItem("Exit"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + }); + fileMenu.add(item); + menuBar.add(fileMenu); + + Menu viewMenu = new Menu("View"); + item = new MenuItem("Counters"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showCounters(); + } + }); + viewMenu.add(item); + menuBar.add(viewMenu); + + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e){ + System.exit(0); + } + }); + setMenuBar(menuBar); + setSize(300,400); + setVisible(true); + } +} diff --git a/java/com/rsyslog/gui/msggen/MsgGen.java b/java/com/rsyslog/gui/msggen/MsgGen.java new file mode 100644 index 00000000..c57027ff --- /dev/null +++ b/java/com/rsyslog/gui/msggen/MsgGen.java @@ -0,0 +1,140 @@ +/* A yet very simple syslog message generator + * + * The purpose of this program is to provide a facility that enables + * to generate complex traffic patterns for testing purposes. It still is + * in its infancy, but hopefully will evolve. + * + * Note that this has been created as a stand-alone application because it + * was considered useful to have it as a separate program. But it should still + * be possible to call its class from any other program, specifically the debug + * GUI. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.gui.msggen; +import com.rsyslog.lib.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +public class MsgGen extends Frame { + private TextField target; + private TextField message; + private TextField nummsgs; + private TextField numthrds; + + public static void main(String args[]) { + new MsgGen(); + } + + /** creates the menu bar INCLUDING all menu handlers */ + private void createMenu() { + MenuItem item; + MenuBar menuBar = new MenuBar(); + Menu fileMenu = new Menu("File"); + item = new MenuItem("Exit"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + }); + fileMenu.add(item); + menuBar.add(fileMenu); + + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e){ + System.exit(0); + } + }); + setMenuBar(menuBar); + } + + /** creates the main GUI */ + private void createGUI() { + //target = new TextField("127.0.0.1", 32); + target = new TextField("172.19.3.3", 32); + message = new TextField(80); + //message.setText("<161>Test malformed"); + message.setText("<5>iaalog[171652]: AIB|dcu|2009/08/12 14:48:43|mfa challenge|NNNNNNN|XX.XX.XX.XX"); + nummsgs = new TextField("1000", 8); + numthrds = new TextField("10", 5); + Panel pCenter = new Panel(); + + Panel pnl = new Panel(); + pnl.setLayout(new FlowLayout()); + pnl.add(new Label("Target Host:")); + pnl.add(target); + pCenter.add(pnl); + + pnl = new Panel(); + pnl.setLayout(new FlowLayout()); + pnl.add(new Label("Number of Msgs:")); + pnl.add(nummsgs); + pCenter.add(pnl); + + pnl = new Panel(); + pnl.setLayout(new FlowLayout()); + pnl.add(new Label("Msg:")); + pnl.add(message); + pCenter.add(pnl); + + Panel pSouth = new Panel(); + pSouth.setLayout(new FlowLayout()); + + pnl = new Panel(); + pnl.setLayout(new FlowLayout()); + pnl.add(new Label("Number of Threads:")); + pnl.add(numthrds); + pSouth.add(pnl); + + Button b = new Button("Start Test"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + performTest(); + } + }); + pSouth.add(b); + + pack(); + setTitle("Syslog Message Generator"); + setLayout(new BorderLayout()); + add(pCenter, BorderLayout.CENTER); + add(pSouth, BorderLayout.SOUTH); + setSize(800,400); + } + + /** perform the test, a potentially complex operation */ + private void performTest() { + for(short i = 0 ; i < Integer.parseInt(numthrds.getText()) ; ++ i) { + SyslogTrafficGenerator gen = + new SyslogTrafficGenerator(target.getText(), message.getText(), + Long.parseLong(nummsgs.getText())); + gen.start(); + } + } + + + /** initialize the GUI. */ + public MsgGen(){ + createMenu(); + createGUI(); + setVisible(true); + } +} diff --git a/java/com/rsyslog/gui/simpServ/simpServ.java b/java/com/rsyslog/gui/simpServ/simpServ.java new file mode 100644 index 00000000..2a83dad0 --- /dev/null +++ b/java/com/rsyslog/gui/simpServ/simpServ.java @@ -0,0 +1,45 @@ +/** + * Implementation of a tcp-based syslog server. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +package com.rsyslog.gui.simpServ; +import com.rsyslog.lib.*; +//import com.rsyslog.gui.*; + +public class simpServ { + + public static void main(String args[]) { + try { + simpServConsumer cons = new simpServConsumer(); + System.out.println("Starting server on port " + args[0] + "\n"); + SyslogServerTCP myServ = new + SyslogServerTCP(Integer.parseInt(args[0]), cons); + myServ.start(); + System.out.println("Press ctl-c to terminate\n"); + } + catch(Exception e) { + System.out.println("Error: " + e.toString()); + } + } +} diff --git a/java/com/rsyslog/gui/simpServ/simpServConsumer.java b/java/com/rsyslog/gui/simpServ/simpServConsumer.java new file mode 100644 index 00000000..588f2640 --- /dev/null +++ b/java/com/rsyslog/gui/simpServ/simpServConsumer.java @@ -0,0 +1,32 @@ +/** A syslog message consumer for the simple syslog server. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.gui.simpServ; +import com.rsyslog.lib.*; + +class simpServConsumer implements SyslogMsgConsumer { + public void consumeMsg(String ln) { + SyslogMessage msg = new SyslogMessage(ln); + System.out.println("Line received '" + msg.getRawMsgAfterPRI() + "'\n"); + } +} diff --git a/java/com/rsyslog/lib/DiagSess.java b/java/com/rsyslog/lib/DiagSess.java new file mode 100644 index 00000000..799b9a4a --- /dev/null +++ b/java/com/rsyslog/lib/DiagSess.java @@ -0,0 +1,78 @@ +/* The diagnostic session to an imdiag module (running inside rsyslogd). + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; +import java.io.*; +import java.net.*; + +public class DiagSess { + + private String host = new String("127.0.0.1"); + private int port = 13500; + int timeout = 0; + private PrintWriter out = null; + private BufferedReader in = null; + private Socket diagSocket = null; + + /** set connection timeout */ + public void setTimeout(int timeout_) { + timeout = timeout_; + } + + public DiagSess(String host_, int port_) { + host = host_; + port = port_; + } + + /** connect to remote server. Initializes everything for request-reply + * processing. + * + * @throws IOException + */ + public void connect() throws IOException { + diagSocket = new Socket(host, port); + diagSocket.setSoTimeout(timeout); + out = new PrintWriter(diagSocket.getOutputStream(), true); + in = new BufferedReader(new InputStreamReader( + diagSocket.getInputStream())); + + } + + /** end session with remote server. */ + public void disconnect() throws IOException { + out.close(); + in.close(); + diagSocket.close(); + } + + /** issue a request to imdiag and return its response. + * + * @param req request string + * @return response string (unparsed) + * @throws IOException + */ + public String request(String req) throws IOException { + out.println(req); + String resp = in.readLine(); + return resp; + } + +} diff --git a/java/com/rsyslog/lib/SyslogMessage.java b/java/com/rsyslog/lib/SyslogMessage.java new file mode 100644 index 00000000..b544a6db --- /dev/null +++ b/java/com/rsyslog/lib/SyslogMessage.java @@ -0,0 +1,75 @@ +/** + * Implementation of the syslog message object. + * + * This is a limited-capability implementation of a syslog message together + * with all its properties. It is limit to what is currently needed and may + * be extended as further need arises. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; + +public class SyslogMessage { + + /** message as received from the wire */ + private String rawmsg; + /** the rawmsg without the PRI part */ + private String rawMsgAfterPRI; + /** PRI part */ + private int pri; + + /** a very simple syslog parser. So far, it only parses out the + * PRI part of the message. May be extended later. Rawmsg must have + * been set before the parser is called. It will populate "all" other + * fields. + */ + private void parse() { + int i; + if(rawmsg.charAt(0) == '<') { + pri = 0; + for(i = 1 ; Character.isDigit(rawmsg.charAt(i)) && i < 4 ; ++i) { + pri = pri * 10 + rawmsg.charAt(i) - '0'; + } + if(rawmsg.charAt(i) != '>') + /* not a real cure, but sufficient for the current + * mini-parser... */ + --i; + rawMsgAfterPRI = rawmsg.substring(i + 1); + } else { + pri = 116; + rawMsgAfterPRI = rawmsg; + } + } + + public SyslogMessage(String _rawmsg) { + rawmsg = _rawmsg; + parse(); + } + + public String getRawMsg() { + return rawmsg; + } + + public String getRawMsgAfterPRI() { + return rawMsgAfterPRI; + } +} diff --git a/java/com/rsyslog/lib/SyslogMsgConsumer.java b/java/com/rsyslog/lib/SyslogMsgConsumer.java new file mode 100644 index 00000000..42c9931a --- /dev/null +++ b/java/com/rsyslog/lib/SyslogMsgConsumer.java @@ -0,0 +1,29 @@ +/** + * Interface for SyslogConsumers. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; + +public interface SyslogMsgConsumer { + public void consumeMsg(String msg); +} diff --git a/java/com/rsyslog/lib/SyslogSender.java b/java/com/rsyslog/lib/SyslogSender.java new file mode 100644 index 00000000..fc0e3fec --- /dev/null +++ b/java/com/rsyslog/lib/SyslogSender.java @@ -0,0 +1,96 @@ +/** + * This class specifies all methods common to syslog senders. It also implements + * some generic ways to send data. Actual syslog senders (e.g. UDP, TCP, ...) shall + * be derived from this class. + * + * This is a limited-capability implementation of a syslog message together + * with all its properties. It is limit to what is currently needed and may + * be extended as further need arises. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; + +public abstract class SyslogSender { + + /** the rawmsg without the PRI part */ + private String target; + + /** the rawmsg without the PRI part */ + private boolean isConnected = false; + + /** Constructs Sender, sets target system. + * @param target the system to connect to. Syntax of target is depending + * on the underlying transport. + */ + public SyslogSender(String target) { + this.target = target; + } + + + /** send a message on the wire. + * This needs a complete formatted message, which will be extended by + * the transport framing, if necessary. + * + * @param MSG a validly formatted syslog message as of the RFC (all parts) + * @throws Exception (depending on transport) + */ + protected abstract void sendTransport(String MSG) throws Exception; + + /** send an alread-formatted message. + * Sends a preformatted syslog message payload to the target. Connects + * to the target if not already connected. + * + * @param MSG a validly formatted syslog message as of the RFC (all parts) + * @throws Exception (depending on transport) + */ + public void sendMSG(String MSG) throws Exception { + if(!isConnected) + connect(); + sendTransport(MSG); + } + + /** connect to the target. + * Note that this may be a null operation if there is no session-like entity + * in the underlying transport (as is for example in UDP). + */ + public void connect() throws Exception { + /* the default implementation does (almost) nothing */ + isConnected = true; + } + + /** disconnects from the target. + * Note that this may be a null operation if there is no session-like entity + * in the underlying transport (as is for example in UDP). + */ + public void disconnect() { + /* the default implementation does (almost) nothing */ + isConnected = false; + } + + /** return target of this Sender. + * @returns target as initially set + */ + public String getTarget() { + return target; + } +} diff --git a/java/com/rsyslog/lib/SyslogServerTCP.java b/java/com/rsyslog/lib/SyslogServerTCP.java new file mode 100644 index 00000000..d5376a32 --- /dev/null +++ b/java/com/rsyslog/lib/SyslogServerTCP.java @@ -0,0 +1,126 @@ +/** + * Implementation of a tcp-based syslog server. + * + * This is a limited-capability implementation of a syslog tcp server. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; + +import com.rsyslog.lib.SyslogMsgConsumer; +import java.io.*; +import java.net.*; + + +/** a small test consumer */ +/* +class TestConsumer implements SyslogMsgConsumer { + public void consumeMsg(String ln) { + System.out.println("Line received '" + ln + "'\n"); + } +} +*/ + +public class SyslogServerTCP extends Thread { + private ServerSocket lstnSock; + private boolean contRun; /* continue processing requests? */ + public SyslogMsgConsumer consumer; + + /** Process a single connection */ + class Session extends Thread { + private Socket sock; + private SyslogServerTCP srvr; + + public Session(Socket so, SyslogServerTCP _srvr) { + sock = so; + srvr = _srvr; + } + + public void run() { + try { + BufferedReader data = new BufferedReader( + new InputStreamReader(sock.getInputStream())); + + String ln = data.readLine(); + while(ln != null) { + srvr.getConsumer().consumeMsg(ln); + ln = data.readLine(); + } + System.out.println("End of Session.\n"); + sock.close(); + } + catch(Exception e) { + /* we ignore any errors we may have... */ + System.out.println("Session exception " + e.toString()); + } + } + } + + /** a small test driver */ +/* + public static void main(String args[]) { + try { + SyslogMsgConsumer cons = new TestConsumer(); + System.out.println("Starting server on port " + args[0] + "\n"); + SyslogServerTCP myServ = new + SyslogServerTCP(Integer.parseInt(args[0]), cons); + myServ.start(); + System.out.println("Press ctl-c to terminate\n"); + } + catch(Exception e) { + System.out.println("Fehler! " + e.toString()); + } + } +*/ + + public SyslogServerTCP(int port, SyslogMsgConsumer cons) throws java.io.IOException { + if(lstnSock != null) + terminate(); + lstnSock = new ServerSocket(port); + consumer = cons; + contRun = true; + } + + public void terminate() { + contRun = false; + } + + public SyslogMsgConsumer getConsumer() { + return consumer; + } + + public void run() { + try { + while(contRun) { + Socket sock = lstnSock.accept(); + System.out.println("New connection request! " + sock.toString()); + Thread sess = new Session(sock, this); + sock = null; + sess.start(); + } + } + catch(Exception e) { + System.out.println("Error during server run " + e.toString()); + } + + } +} diff --git a/java/com/rsyslog/lib/SyslogTrafficGenerator.java b/java/com/rsyslog/lib/SyslogTrafficGenerator.java new file mode 100644 index 00000000..79a99495 --- /dev/null +++ b/java/com/rsyslog/lib/SyslogTrafficGenerator.java @@ -0,0 +1,81 @@ +/** + * This class is a syslog traffic generator. It is primarily intended to be used + * together with testing tools, but may have some use cases outside that domain. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; + +public class SyslogTrafficGenerator extends Thread { + + /** the target host to receive traffic */ + private String target; + + /** the message (template) to be sent */ + private String message; + + /** number of messages to be sent */ + private long nummsgs; + + /** Constructs Sender, sets target system. + * @param target the system to connect to. Syntax of target is depending + * on the underlying transport. + */ + public SyslogTrafficGenerator(String target, String message, long nummsgs) { + this.target = target; + this.message = message; + this.nummsgs = nummsgs; + } + + /** Generates the traffic. Stops when either called to terminate + * or the max number of messages have been sent. Note that all + * necessary properties must have been set up before starting the + * generator thread! + */ + private void performTest() throws Exception { + int doDisp = 0; + UDPSyslogSender sender = new UDPSyslogSender(target); + for(long i = 0 ; i < nummsgs ; ++i) { + sender.sendMSG(message + " " + Long.toString(i) + " " + this.toString() + "\0"); + if((doDisp++ % 1000) == 0) + System.out.println(this.toString() + " send message " + Long.toString(i)); + sleep(1); + } + } + + +/** Wrapper around the real traffic generator, catches exceptions. + */ + public void run() { + System.out.println("traffic generator " + this.toString() + " thread started"); + try { + performTest(); + } + catch(Exception e) { + /* at some time, we may find a more intelligent way to + * handle this! ;) + */ + System.out.println(e.toString()); + } + System.out.println("traffic generator " + this.toString() + " thread finished"); + } +} diff --git a/java/com/rsyslog/lib/UDPSyslogSender.java b/java/com/rsyslog/lib/UDPSyslogSender.java new file mode 100644 index 00000000..1a2c4726 --- /dev/null +++ b/java/com/rsyslog/lib/UDPSyslogSender.java @@ -0,0 +1,75 @@ +/** + * A UDP transport implementation of a syslog sender. + * + * Note that there is an anomaly in this version of the code: we query the remote system + * address only once during the connection setup and resue it. If we potentially run for + * an extended period of time, the remote address may change, what we do not reflect. For + * the current use case, this is acceptable, but if this code is put into more wide-spread + * use outside of debugging, a periodic requery should be added. + * + * @author Rainer Gerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see http://www.gnu.org/licenses/. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +package com.rsyslog.lib; +import java.net.*; + +public class UDPSyslogSender extends SyslogSender { + + private final int port = 514; // TODO: take from target! + private InetAddress targetAddr; + + /** the socket to communicate over with the remote system. */ + private DatagramSocket sock; + + /** Constructs Sender, sets target system. + * @param target the system to connect to. Syntax of target is depending + * on the underlying transport. + */ + public UDPSyslogSender(String target) throws Exception { + super(target); + } + + /** send a message on the wire. + * This needs a complete formatted message, which will be extended by + * the transport framing, if necessary. + * + * @param MSG a validly formatted syslog message as of the RFC (all parts) + * @throws Exception (depending on transport) + */ + protected void sendTransport(String MSG) throws Exception { + byte msg[] = MSG.getBytes(); + DatagramPacket pkt = new DatagramPacket(msg, msg.length, targetAddr, port); + sock.send(pkt); + } + + + /** connect to the target. + * For UDP, this means we create the socket. + */ + public void connect() throws Exception { + super.connect(); + sock = new DatagramSocket(); + + // TODO: we should extract the actual hostname & port! + targetAddr = InetAddress.getByName(getTarget()); + } + +} diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 00000000..0f4126cd --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1 @@ +*.m4 diff --git a/m4/atomic_operations.m4 b/m4/atomic_operations.m4 new file mode 100644 index 00000000..ad0ee606 --- /dev/null +++ b/m4/atomic_operations.m4 @@ -0,0 +1,53 @@ +# rsyslog +# +# atomic_operations.m4 - autoconf macro to check if compiler supports atomic +# operations +# +# rgerhards, 2008-09-18, added based on +# http://svn.apache.org/repos/asf/apr/apr/trunk/configure.in +# +# +AC_DEFUN([RS_ATOMIC_OPERATIONS], +[AC_CACHE_CHECK([whether the compiler provides atomic builtins], [ap_cv_atomic_builtins], +[AC_TRY_RUN([ +int main() +{ + unsigned long val = 1010, tmp, *mem = &val; + + if (__sync_fetch_and_add(&val, 1010) != 1010 || val != 2020) + return 1; + + tmp = val; + + if (__sync_fetch_and_sub(mem, 1010) != tmp || val != 1010) + return 1; + + if (__sync_sub_and_fetch(&val, 1010) != 0 || val != 0) + return 1; + + tmp = 3030; + + if (__sync_val_compare_and_swap(mem, 0, tmp) != 0 || val != tmp) + return 1; + + if (__sync_lock_test_and_set(&val, 4040) != 3030) + return 1; + + mem = &tmp; + + if (__sync_val_compare_and_swap(&mem, &tmp, &val) != &tmp) + return 1; + + __sync_synchronize(); + + if (mem != &val) + return 1; + + return 0; +}], [ap_cv_atomic_builtins=yes], [ap_cv_atomic_builtins=no], [ap_cv_atomic_builtins=no])]) + +if test "$ap_cv_atomic_builtins" = "yes"; then + AC_DEFINE(HAVE_ATOMIC_BUILTINS, 1, [Define if compiler provides atomic builtins]) +fi + +]) diff --git a/m4/atomic_operations_64bit.m4 b/m4/atomic_operations_64bit.m4 new file mode 100644 index 00000000..3121cf15 --- /dev/null +++ b/m4/atomic_operations_64bit.m4 @@ -0,0 +1,53 @@ +# rsyslog +# +# atomic_operations.m4 - autoconf macro to check if compiler supports atomic +# operations +# +# rgerhards, 2008-09-18, added based on +# http://svn.apache.org/repos/asf/apr/apr/trunk/configure.in +# +# +AC_DEFUN([RS_ATOMIC_OPERATIONS_64BIT], +[AC_CACHE_CHECK([whether the compiler provides atomic builtins for 64 bit data types], [ap_cv_atomic_builtins_64], +[AC_TRY_RUN([ +int main() +{ + unsigned long long val = 1010, tmp, *mem = &val; + + if (__sync_fetch_and_add(&val, 1010) != 1010 || val != 2020) + return 1; + + tmp = val; + + if (__sync_fetch_and_sub(mem, 1010) != tmp || val != 1010) + return 1; + + if (__sync_sub_and_fetch(&val, 1010) != 0 || val != 0) + return 1; + + tmp = 3030; + + if (__sync_val_compare_and_swap(mem, 0, tmp) != 0 || val != tmp) + return 1; + + if (__sync_lock_test_and_set(&val, 4040) != 3030) + return 1; + + mem = &tmp; + + if (__sync_val_compare_and_swap(&mem, &tmp, &val) != &tmp) + return 1; + + __sync_synchronize(); + + if (mem != &val) + return 1; + + return 0; +}], [ap_cv_atomic_builtins_64=yes], [ap_cv_atomic_builtins_64=no], [ap_cv_atomic_builtins_64=no])]) + +if test "$ap_cv_atomic_builtins_64" = "yes"; then + AC_DEFINE(HAVE_ATOMIC_BUILTINS_64BIT, 1, [Define if compiler provides 64 bit atomic builtins]) +fi + +]) diff --git a/m4/ax_check_off64_t.m4 b/m4/ax_check_off64_t.m4 new file mode 100644 index 00000000..05d6f45d --- /dev/null +++ b/m4/ax_check_off64_t.m4 @@ -0,0 +1,69 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_off64_t.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_OFF64_T +# +# DESCRIPTION +# +# Check if off64_t is defined. On true define HAVE_OFF64_T, also define +# __LARGEFILE64_SOURCE where one is needed. (Note that an appropriative +# entry must be in config.h.in.) +# +# LICENSE +# +# Copyright (c) 2008 Ruslan Shevchenko <Ruslan@Shevchenko.Kiev.UA> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AU_ALIAS([RSSH_CHECK_OFF64_T], [AX_CHECK_OFF64_T]) +AC_DEFUN([AX_CHECK_OFF64_T], [ +AC_REQUIRE([AC_SYS_LARGEFILE])dnl +AC_CHECK_HEADER(unistd.h) +AC_CACHE_CHECK([whether type off64_t support], + [ax_cv_check_off64_t], + [ + AC_COMPILE_IFELSE( +AC_LANG_SOURCE([ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +extern off64_t x1; +]) +,ax_have_off64t=1) + if test "x$ax_have_off64t" = "x" + then + AC_COMPILE_IFELSE( +AC_LANG_SOURCE([ +#define _LARGEFILE64_SOURCE +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +extern off64_t x1; +]), + ax_cv_check_off64_t="_LARGEFILE64_SOURCE", + ax_cv_check_off64_t="no" +)dnl + + else + ax_cv_check_off64_t=yes + fi + ])dnl + +if test "x$ax_cv_check_off64_t" = "x_LARGEFILE64_SOURCE" +then + AC_DEFINE(_LARGEFILE64_SOURCE) + AC_DEFINE(HAVE_OFF64_T) +elif test "x$ax_cv_check_off64_t" = "xyes" +then + AC_DEFINE(HAVE_OFF64_T) +fi +])dnl + diff --git a/outchannel.c b/outchannel.c new file mode 100644 index 00000000..c97d220d --- /dev/null +++ b/outchannel.c @@ -0,0 +1,297 @@ +/* This is the output channel processing code of rsyslog. + * Output channels - in the long term - will define how + * messages will be sent to whatever file or other medium. + * Currently, they mainly provide a way to store some file-related + * information (most importantly the maximum file size allowed). + * Please see syslogd.c for license information. + * begun 2005-06-21 rgerhards + * + * Copyright (C) 2005-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include "stringbuf.h" +#include "outchannel.h" +#include "rsconf.h" +#include "debug.h" + +/* Constructs a outchannel list object. Returns pointer to it + * or NULL (if it fails). + */ +struct outchannel* ochConstruct(void) +{ + struct outchannel *pOch; + if((pOch = calloc(1, sizeof(struct outchannel))) == NULL) + return NULL; + + /* basic initialisaion is done via calloc() - need to + * initialize only values != 0. */ + + if(loadConf->och.ochLast == NULL) + { /* we are the first element! */ + loadConf->och.ochRoot = loadConf->och.ochLast = pOch; + } + else + { + loadConf->och.ochLast->pNext = pOch; + loadConf->och.ochLast = pOch; + } + + return(pOch); +} + + +/* skips the next comma and any whitespace + * in front and after it. + */ +static void skip_Comma(char **pp) +{ + register char *p; + + assert(pp != NULL); + assert(*pp != NULL); + + p = *pp; + while(isspace((int)*p)) + ++p; + if(*p == ',') + ++p; + while(isspace((int)*p)) + ++p; + *pp = p; +} + +/* helper to ochAddLine. Parses a comma-delimited field + * The field is delimited by SP or comma. Leading whitespace + * is "eaten" and does not become part of the field content. + */ +static rsRetVal get_Field(uchar **pp, uchar **pField) +{ + DEFiRet; + register uchar *p; + cstr_t *pStrB = NULL; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pField != NULL); + + skip_Comma((char**)pp); + p = *pp; + + CHKiRet(cstrConstruct(&pStrB)); + + /* copy the field */ + while(*p && *p != ' ' && *p != ',') { + CHKiRet(cstrAppendChar(pStrB, *p++)); + } + + *pp = p; + CHKiRet(cstrFinalize(pStrB)); + CHKiRet(cstrConvSzStrAndDestruct(pStrB, pField, 0)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrB != NULL) + cstrDestruct(&pStrB); + } + + RETiRet; +} + + +/* helper to ochAddLine. Parses a off_t type from the + * input line. + * returns: 0 - ok, 1 - failure + */ +static int get_off_t(uchar **pp, off_t *pOff_t) +{ + register uchar *p; + off_t val; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pOff_t != NULL); + + skip_Comma((char**)pp); + p = *pp; + + val = 0; + while(*p && isdigit((int)*p)) { + val = val * 10 + (*p - '0'); + ++p; + } + + *pp = p; + *pOff_t = val; + + return 0; +} + + +/* helper to ochAddLine. Parses everything from the + * current position to the end of line and returns it + * to the caller. Leading white space is removed, but + * not trailing. + */ +static inline rsRetVal get_restOfLine(uchar **pp, uchar **pBuf) +{ + DEFiRet; + register uchar *p; + cstr_t *pStrB = NULL; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pBuf != NULL); + + skip_Comma((char**)pp); + p = *pp; + + CHKiRet(cstrConstruct(&pStrB)); + + /* copy the field */ + while(*p) { + CHKiRet(cstrAppendChar(pStrB, *p++)); + } + + *pp = p; + CHKiRet(cstrFinalize(pStrB)); + CHKiRet(cstrConvSzStrAndDestruct(pStrB, pBuf, 0)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrB != NULL) + cstrDestruct(&pStrB); + } + + RETiRet; +} + + +/* Add a new outchannel line + * returns pointer to new object if it succeeds, NULL otherwise. + * An outchannel line is primarily a set of fields delemited by commas. + * There might be some whitespace between the field (but not within) + * and the commas. This can be removed. + */ +struct outchannel *ochAddLine(char* pName, uchar** ppRestOfConfLine) +{ + struct outchannel *pOch; + uchar *p; + + assert(pName != NULL); + assert(ppRestOfConfLine != NULL); + + if((pOch = ochConstruct()) == NULL) + return NULL; + + pOch->iLenName = strlen(pName); + pOch->pszName = (char*) MALLOC(sizeof(char) * (pOch->iLenName + 1)); + if(pOch->pszName == NULL) { + dbgprintf("ochAddLine could not alloc memory for outchannel name!"); + pOch->iLenName = 0; + return NULL; + /* I know - we create a memory leak here - but I deem + * it acceptable as it is a) a very small leak b) very + * unlikely to happen. rgerhards 2004-11-17 + */ + } + memcpy(pOch->pszName, pName, pOch->iLenName + 1); + + /* now actually parse the line */ + p = *ppRestOfConfLine; + assert(p != NULL); + + /* get params */ + get_Field(&p, &pOch->pszFileTemplate); + if(*p) get_off_t(&p, &pOch->uSizeLimit); + if(*p) get_restOfLine(&p, &pOch->cmdOnSizeLimit); + + *ppRestOfConfLine = p; + return(pOch); +} + + +/* Find a outchannel object based on name. Search + * currently is case-senstive (should we change?). + * returns pointer to outchannel object if found and + * NULL otherwise. + * rgerhards 2004-11-17 + */ +struct outchannel *ochFind(char *pName, int iLenName) +{ + struct outchannel *pOch; + + assert(pName != NULL); + + pOch = loadConf->och.ochRoot; + while(pOch != NULL && + !(pOch->iLenName == iLenName && + !strcmp(pOch->pszName, pName) + )) + { + pOch = pOch->pNext; + } + return(pOch); +} + +/* Destroy the outchannel structure. This is for de-initialization + * at program end. Everything is deleted. + * rgerhards 2005-02-22 + */ +void ochDeleteAll(void) +{ + struct outchannel *pOch, *pOchDel; + + pOch = loadConf->och.ochRoot; + while(pOch != NULL) { + dbgprintf("Delete Outchannel: Name='%s'\n ", pOch->pszName == NULL? "NULL" : pOch->pszName); + pOchDel = pOch; + pOch = pOch->pNext; + if(pOchDel->pszName != NULL) + free(pOchDel->pszName); + free(pOchDel); + } +} + + +/* Print the outchannel structure. This is more or less a + * debug or test aid, but anyhow I think it's worth it... + */ +void ochPrintList(void) +{ + struct outchannel *pOch; + + pOch = loadConf->och.ochRoot; + while(pOch != NULL) { + dbgprintf("Outchannel: Name='%s'\n", pOch->pszName == NULL? "NULL" : pOch->pszName); + dbgprintf("\tFile Template: '%s'\n", pOch->pszFileTemplate == NULL ? "NULL" : (char*) pOch->pszFileTemplate); + dbgprintf("\tMax Size.....: %lu\n", (long unsigned) pOch->uSizeLimit); + dbgprintf("\tOnSizeLimtCmd: '%s'\n", pOch->cmdOnSizeLimit == NULL ? "NULL" : (char*) pOch->cmdOnSizeLimit); + pOch = pOch->pNext; /* done, go next */ + } +} +/* vi:set ai: + */ diff --git a/outchannel.h b/outchannel.h new file mode 100644 index 00000000..fc8388b2 --- /dev/null +++ b/outchannel.h @@ -0,0 +1,39 @@ +/* This is the header for the output channel code of rsyslog. + * begun 2005-06-21 rgerhards + * + * Copyright(C) 2005-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +struct outchannel { + struct outchannel *pNext; + char *pszName; + int iLenName; + uchar *pszFileTemplate; + off_t uSizeLimit; + uchar *cmdOnSizeLimit; +}; + +struct outchannel* ochConstruct(void); +struct outchannel *ochAddLine(char* pName, unsigned char** pRestOfConfLine); +struct outchannel *ochFind(char *pName, int iLenName); +void ochDeleteAll(void); +void ochPrintList(void); + +/* + * vi:set ai: + */ diff --git a/parse.c b/parse.c new file mode 100644 index 00000000..097e7470 --- /dev/null +++ b/parse.c @@ -0,0 +1,578 @@ +/* parsing routines for the counted string class. for generic + * informaton see parse.h. + * + * begun 2005-09-15 rgerhards + * + * Copyright 2005-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include "rsyslog.h" +#include "net.h" /* struct NetAddr */ +#include "parse.h" +#include "debug.h" + +/* ################################################################# * + * private members * + * ################################################################# */ + + + +/* ################################################################# * + * public members * + * ################################################################# */ + + +/** + * Destruct a rsPars object and its associated string. + * rgerhards, 2005-09-26 + */ +rsRetVal rsParsDestruct(rsParsObj *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + if(pThis->pCStr != NULL) + rsCStrDestruct(&pThis->pCStr); + RSFREEOBJ(pThis); + return RS_RET_OK; +} + + +/** + * Construct a rsPars object. + */ +rsRetVal rsParsConstruct(rsParsObj **ppThis) +{ + rsParsObj *pThis; + + assert(ppThis != NULL); + + if((pThis = (rsParsObj*) calloc(1, sizeof(rsParsObj))) == NULL) + return RS_RET_OUT_OF_MEMORY; + + rsSETOBJTYPE(pThis, OIDrsPars); + + *ppThis = pThis; + return RS_RET_OK; +} + +/** + * Construct a rsPars object and populate it with a + * classical zero-terinated C-String. + * rgerhards, 2005-09-27 + */ +rsRetVal rsParsConstructFromSz(rsParsObj **ppThis, unsigned char *psz) +{ + DEFiRet; + rsParsObj *pThis; + cstr_t *pCS; + + assert(ppThis != NULL); + assert(psz != NULL); + + /* create string for parser */ + CHKiRet(rsCStrConstructFromszStr(&pCS, psz)); + + /* create parser */ + if((iRet = rsParsConstruct(&pThis)) != RS_RET_OK) { + rsCStrDestruct(&pCS); + FINALIZE; + } + + /* assign string to parser */ + if((iRet = rsParsAssignString(pThis, pCS)) != RS_RET_OK) { + rsParsDestruct(pThis); + FINALIZE; + } + *ppThis = pThis; + +finalize_it: + RETiRet; +} + +/** + * Assign the to-be-parsed string. + */ +rsRetVal rsParsAssignString(rsParsObj *pThis, cstr_t *pCStr) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + rsCHECKVALIDOBJECT(pCStr, OIDrsCStr); + + pThis->pCStr = pCStr; + pThis->iCurrPos = 0; + + return RS_RET_OK; +} + +/* parse an integer. The parse pointer is advanced to the + * position directly after the last digit. If no digit is + * found at all, an error is returned and the parse pointer + * is NOT advanced. + * PORTABILITY WARNING: this function depends on the + * continues representation of digits inside the character + * set (as in ASCII). + * rgerhards 2005-09-27 + */ +rsRetVal parsInt(rsParsObj *pThis, int* pInt) +{ + unsigned char *pC; + int iVal; + + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + assert(pInt != NULL); + + iVal = 0; + pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; + + /* order of checks is important, else we might do + * mis-addressing! (off by one) + */ + if(pThis->iCurrPos >= rsCStrLen(pThis->pCStr)) + return RS_RET_NO_MORE_DATA; + if(!isdigit((int)*pC)) + return RS_RET_NO_DIGIT; + + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) && isdigit((int)*pC)) { + iVal = iVal * 10 + *pC - '0'; + ++pThis->iCurrPos; + ++pC; + } + + *pInt = iVal; + + return RS_RET_OK; +} + +/* Skip everything up to a specified character. + * Returns with ParsePointer set BEHIND this character. + * Returns RS_RET_OK if found, RS_RET_NOT_FOUND if not + * found. In that case, the ParsePointer is moved to the + * last character of the string. + * 2005-09-19 rgerhards + */ +rsRetVal parsSkipAfterChar(rsParsObj *pThis, char c) +{ + register unsigned char *pC; + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + pC = rsCStrGetBufBeg(pThis->pCStr); + + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr)) { + if(pC[pThis->iCurrPos] == c) + break; + ++pThis->iCurrPos; + } + + /* delimiter found? */ + if(pC[pThis->iCurrPos] == c) { + if(pThis->iCurrPos+1 < rsCStrLen(pThis->pCStr)) { + iRet = RS_RET_OK; + pThis->iCurrPos++; /* 'eat' delimiter */ + } else { + iRet = RS_RET_FOUND_AT_STRING_END; + } + } else { + iRet = RS_RET_NOT_FOUND; + } + + RETiRet; +} + +/* Skip whitespace. Often used to trim parsable entries. + * Returns with ParsePointer set to first non-whitespace + * character (or at end of string). + * If bRequireOne is set to true, at least one whitespace + * must exist, else an error is returned. + */ +rsRetVal parsSkipWhitespace(rsParsObj *pThis) +{ + register unsigned char *pC; + int numSkipped; + DEFiRet; + + + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + pC = rsCStrGetBufBeg(pThis->pCStr); + + numSkipped = 0; + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr)) { + if(!isspace((int)*(pC+pThis->iCurrPos))) + break; + ++pThis->iCurrPos; + ++numSkipped; + } + + RETiRet; +} + +/* Parse string up to a delimiter. + * + * Input: + * cDelim - the delimiter. Note that SP within a value always is a delimiter, + * so cDelim is actually an *additional* delimiter. + * The following two are for whitespace stripping, + * 0 means "no", 1 "yes" + * - bTrimLeading + * - bTrimTrailing + * - bConvLower - convert string to lower case? + * + * Output: + * ppCStr Pointer to the parsed string - must be freed by caller! + */ +rsRetVal parsDelimCStr(rsParsObj *pThis, cstr_t **ppCStr, char cDelim, int bTrimLeading, int bTrimTrailing, int bConvLower) +{ + DEFiRet; + register unsigned char *pC; + cstr_t *pCStr = NULL; + + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + CHKiRet(rsCStrConstruct(&pCStr)); + + if(bTrimLeading) + parsSkipWhitespace(pThis); + + pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; + + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) && *pC != cDelim) { + CHKiRet(cstrAppendChar(pCStr, bConvLower ? tolower(*pC) : *pC)); + ++pThis->iCurrPos; + ++pC; + } + + if(pThis->iCurrPos < cstrLen(pThis->pCStr)) { //BUGFIX!! + ++pThis->iCurrPos; /* eat delimiter */ + } + + /* We got the string, now take it and see if we need to + * remove anything at its end. + */ + CHKiRet(cstrFinalize(pCStr)); + + if(bTrimTrailing) { + CHKiRet(cstrTrimTrailingWhiteSpace(pCStr)); + } + + /* done! */ + *ppCStr = pCStr; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pCStr != NULL) + rsCStrDestruct(&pCStr); + } + + RETiRet; +} + +/* Parse a quoted string ("-some-data") from the given position. + * Leading whitespace before the first quote is skipped. During + * parsing, escape sequences are detected and converted: + * \\ - backslash character + * \" - quote character + * any other value \<somechar> is reserved for future use. + * + * After return, the parse pointer is paced after the trailing + * quote. + * + * Output: + * ppCStr Pointer to the parsed string - must be freed by caller and + * does NOT include the quotes. + * rgerhards, 2005-09-19 + */ +rsRetVal parsQuotedCStr(rsParsObj *pThis, cstr_t **ppCStr) +{ + register unsigned char *pC; + cstr_t *pCStr = NULL; + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + CHKiRet(parsSkipAfterChar(pThis, '"')); + pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; + + /* OK, we most probably can obtain a value... */ + CHKiRet(cstrConstruct(&pCStr)); + + while(pThis->iCurrPos < cstrLen(pThis->pCStr)) { + if(*pC == '"') { + break; /* we are done! */ + } else if(*pC == '\\') { + ++pThis->iCurrPos; + ++pC; + if(pThis->iCurrPos < cstrLen(pThis->pCStr)) { + /* in this case, we copy the escaped character + * to the output buffer (but do not rely on this, + * we might later introduce other things, like \007! + */ + CHKiRet(cstrAppendChar(pCStr, *pC)); + } + } else { /* regular character */ + CHKiRet(cstrAppendChar(pCStr, *pC)); + } + ++pThis->iCurrPos; + ++pC; + } + + if(*pC == '"') { + ++pThis->iCurrPos; /* 'eat' trailing quote */ + } else { + /* error - improperly quoted string! */ + cstrDestruct(&pCStr); + ABORT_FINALIZE(RS_RET_MISSING_TRAIL_QUOTE); + } + + /* We got the string, let's finish it... */ + CHKiRet(cstrFinalize(pCStr)); + + /* done! */ + *ppCStr = pCStr; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pCStr != NULL) + cstrDestruct(&pCStr); + } + + RETiRet; +} + +/* + * Parsing routine for IPv4, IPv6 and domain name wildcards. + * + * Parses string in the format <addr>[/bits] where + * addr can be a IPv4 address (e.g.: 127.0.0.1), IPv6 address (e.g.: [::1]), + * full hostname (e.g.: localhost.localdomain) or hostname wildcard + * (e.g.: *.localdomain). + */ +#ifdef SYSLOG_INET +rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) +{ + register uchar *pC; + uchar *pszIP; + uchar *pszTmp; + struct addrinfo hints, *res = NULL; + cstr_t *pCStr; + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + assert(pIP != NULL); + assert(pBits != NULL); + + CHKiRet(cstrConstruct(&pCStr)); + + parsSkipWhitespace(pThis); + pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; + + /* we parse everything until either '/', ',' or + * whitespace. Validity will be checked down below. + */ + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) + && *pC != '/' && *pC != ',' && !isspace((int)*pC)) { + if((iRet = cstrAppendChar(pCStr, *pC)) != RS_RET_OK) { + cstrDestruct (&pCStr); + FINALIZE; + } + ++pThis->iCurrPos; + ++pC; + } + + /* We got the string, let's finish it... */ + if((iRet = cstrFinalize(pCStr)) != RS_RET_OK) { + cstrDestruct(&pCStr); + FINALIZE; + } + + /* now we have the string and must check/convert it to + * an NetAddr structure. + */ + CHKiRet(cstrConvSzStrAndDestruct(pCStr, &pszIP, 0)); + + *pIP = calloc(1, sizeof(struct NetAddr)); + + if (*((char*)pszIP) == '[') { + pszTmp = (uchar*)strchr ((char*)pszIP, ']'); + if (pszTmp == NULL) { + free (pszIP); + ABORT_FINALIZE(RS_RET_INVALID_IP); + } + *pszTmp = '\0'; + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = AF_INET6; +# ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST; +# else + hints.ai_flags = AI_NUMERICHOST; +# endif + + switch(getaddrinfo ((char*)pszIP+1, NULL, &hints, &res)) { + case 0: + (*pIP)->addr.NetAddr = MALLOC (res->ai_addrlen); + memcpy ((*pIP)->addr.NetAddr, res->ai_addr, res->ai_addrlen); + freeaddrinfo (res); + break; + case EAI_NONAME: + F_SET((*pIP)->flags, ADDR_NAME|ADDR_PRI6); + (*pIP)->addr.HostWildcard = strdup ((const char*)pszIP+1); + break; + default: + free (pszIP); + free (*pIP); + ABORT_FINALIZE(RS_RET_ERR); + } + + if(*pC == '/') { + /* mask bits follow, let's parse them! */ + ++pThis->iCurrPos; /* eat slash */ + if((iRet = parsInt(pThis, pBits)) != RS_RET_OK) { + free (pszIP); + free (*pIP); + FINALIZE; + } + /* we need to refresh pointer (changed by parsInt()) */ + pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; + } else { + /* no slash, so we assume a single host (/128) */ + *pBits = 128; + } + } else { /* now parse IPv4 */ + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = AF_INET; +# ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST; +# else + hints.ai_flags = AI_NUMERICHOST; +# endif + + switch(getaddrinfo ((char*)pszIP, NULL, &hints, &res)) { + case 0: + (*pIP)->addr.NetAddr = MALLOC (res->ai_addrlen); + memcpy ((*pIP)->addr.NetAddr, res->ai_addr, res->ai_addrlen); + freeaddrinfo (res); + break; + case EAI_NONAME: + F_SET((*pIP)->flags, ADDR_NAME); + (*pIP)->addr.HostWildcard = strdup ((const char*)pszIP); + break; + default: + free (pszIP); + free (*pIP); + ABORT_FINALIZE(RS_RET_ERR); + } + + if(*pC == '/') { + /* mask bits follow, let's parse them! */ + ++pThis->iCurrPos; /* eat slash */ + if((iRet = parsInt(pThis, pBits)) != RS_RET_OK) { + free (pszIP); + free (*pIP); + FINALIZE; + } + /* we need to refresh pointer (changed by parsInt()) */ + pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; + } else { + /* no slash, so we assume a single host (/32) */ + *pBits = 32; + } + } + free(pszIP); /* no longer needed */ + + /* skip to next processable character */ + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) + && (*pC == ',' || isspace((int)*pC))) { + ++pThis->iCurrPos; + ++pC; + } + + iRet = RS_RET_OK; + +finalize_it: + RETiRet; +} +#endif /* #ifdef SYSLOG_INET */ + + +/* tell if the parsepointer is at the end of the + * to-be-parsed string. Returns 1, if so, 0 + * otherwise. rgerhards, 2005-09-27 + */ +int parsIsAtEndOfParseString(rsParsObj *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + return (pThis->iCurrPos < rsCStrLen(pThis->pCStr)) ? 0 : 1; +} + + +/* return the position of the parse pointer + */ +int rsParsGetParsePointer(rsParsObj *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + + if(pThis->iCurrPos < rsCStrLen(pThis->pCStr)) + return pThis->iCurrPos; + else + return rsCStrLen(pThis->pCStr) - 1; +} + +/* peek at the character at the parse pointer + * the caller must ensure that the parse pointer is not + * at the end of the parse buffer (e.g. by first calling + * parsIsAtEndOfParseString). + * rgerhards, 2005-09-27 + */ +char parsPeekAtCharAtParsPtr(rsParsObj *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsPars); + assert(pThis->iCurrPos < rsCStrLen(pThis->pCStr)); + + return(*(pThis->pCStr->pBuf + pThis->iCurrPos)); +} + +/* return the current position inside the parse object. + * rgerhards, 2007-07-04 + */ +int parsGetCurrentPosition(rsParsObj *pThis) +{ + return pThis->iCurrPos; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + * vi:set ai: + */ diff --git a/parse.h b/parse.h new file mode 100644 index 00000000..5121a84c --- /dev/null +++ b/parse.h @@ -0,0 +1,108 @@ +/* parsing routines for the counted string class. These + * routines provide generic parsing aid as well some fairly + * complex routines targeted toward specific needs. + * + * General information - read this: + * All routines work on a single CStr object, which must be supplied + * during construction. The parse class keeps an internal pointer of + * where the next parse operation is to start (you could also say + * this is where the last parse operation stopped). + * + * Each parse operation carried out by this package starts from the + * parse pointer, parses the caller-requested element (e.g. an + * integer or delemited string) and the update the parse pointer. If + * the caller tries to parse beyond the end of the original string, + * an error is returned. In general, all functions return a parsRet + * error code and all require the parseObj to be the first parameter. + * The to-be-parsed string provided to the parse object MUST NOT be + * freed or modified by the caller during the lifetime of the parse + * object. However, the caller must free it when it is no longer needed. + * Optinally, the parse object can be instructed to do that. All objects + * returned by the parse routines must be freed by the caller. For + * simpler data types (like integers), the caller must provide the + * necessary buffer space. + * + * begun 2005-09-09 rgerhards + * + * Copyright (C) 2005-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _PARSE_H_INCLUDED__ +#define _PARSE_H_INCLUDED__ 1 + +#include "stringbuf.h" + +/** + * The parse object + */ +struct rsParsObject +{ +#ifndef NDEBUG + rsObjID OID; /**< object ID */ +#endif + cstr_t *pCStr; /**< pointer to the string object we are parsing */ + int iCurrPos; /**< current parsing position (char offset) */ +}; +typedef struct rsParsObject rsParsObj; + + +/* BEGIN "inline"-like functions */ +/* END "inline"-like functions */ + +int rsParsGetParsePointer(rsParsObj *pThis); + +/** + * Construct a rsPars object. + */ +rsRetVal rsParsConstruct(rsParsObj **ppThis); +rsRetVal rsParsAssignString(rsParsObj *pThis, cstr_t *pCStr); + +/* parse an integer. The parse pointer is advanced */ +rsRetVal parsInt(rsParsObj *pThis, int* pInt); + +/* Skip whitespace. Often used to trim parsable entries. */ +rsRetVal parsSkipWhitespace(rsParsObj *pThis); + +/* Parse string up to a delimiter. + * + * Input: + * cDelim - the delimiter + * The following two are for whitespace stripping, + * 0 means "no", 1 "yes" + * - bTrimLeading + * - bTrimTrailing + * + * Output: + * ppCStr Pointer to the parsed string + */ +rsRetVal parsDelimCStr(rsParsObj *pThis, cstr_t **ppCStr, char cDelim, int bTrimLeading, int bTrimTrailing, int bConvLower); + +rsRetVal parsSkipAfterChar(rsParsObj *pThis, char c); +rsRetVal parsQuotedCStr(rsParsObj *pThis, cstr_t **ppCStr); +rsRetVal rsParsConstructFromSz(rsParsObj **ppThis, unsigned char *psz); +rsRetVal rsParsDestruct(rsParsObj *pThis); +int parsIsAtEndOfParseString(rsParsObj *pThis); +int parsGetCurrentPosition(rsParsObj *pThis); +char parsPeekAtCharAtParsPtr(rsParsObj *pThis); +#ifdef SYSLOG_INET +rsRetVal parsAddrWithBits(rsParsObj *pThis, netAddr_t **pIP, int *pBits); +#endif + +#endif +/* vim:set ai: + */ diff --git a/platform/README b/platform/README new file mode 100644 index 00000000..adbc3013 --- /dev/null +++ b/platform/README @@ -0,0 +1,4 @@ +This subdirectory contains platform-specific files. They are maintained +based on a best effort basis, and are not necessarily the same like the +specific platform ships them. Some files are changed in the way the +rsyslog projects would recommend them; some may even be outdated. diff --git a/platform/freebsd/rsyslogd b/platform/freebsd/rsyslogd new file mode 100755 index 00000000..1cbcb6fe --- /dev/null +++ b/platform/freebsd/rsyslogd @@ -0,0 +1,83 @@ +#!/bin/sh +# Sample startup script for rsyslogd on FreeBSD. +# It worked on my machine, but this does not necessarily +# mean it works on all machines - it's not thouroughly +# tested. Please note that it may also work on other +# BSD variants, too. +# +# As of this writing, there was an issue with the mysql client +# library on startup. If compiled with MySQL support, rsyslogd +# would not necessarily start correctly but could eventually +# die with a "mysql client libary not found" (or similar) +# message. I do not know its cause neither the cure. If you +# have one, let me know. +# +# ATTENTION: you need also to change the /etc/rc.config file +# and disable stock syslogd and then enable rsyslogd! +# +# rgerhards 2005-08-09 <rgehards@adiscon.com> +# + +# PROVIDE: rsyslogd +# REQUIRE: mountcritremote cleanvar +# BEFORE: SERVERS + +. /etc/rc.subr + +name="rsyslogd" +rcvar=`set_rcvar` +pidfile="/var/run/rsyslogd.pid" +command="/usr/sbin/${name}" +required_files="/etc/rsyslog.conf" +start_precmd="rsyslogd_precmd" +extra_commands="reload" + +_sockfile="/var/run/rsyslogd.sockets" +evalargs="rc_flags=\"\`set_socketlist\` \$rc_flags\"" +altlog_proglist="named" + +rsyslogd_precmd() +{ + # Transitional symlink for old binaries + # + if [ ! -L /dev/log ]; then + ln -sf /var/run/log /dev/log + fi + rm -f /var/run/log + + # Create default list of syslog sockets to watch + # + ( umask 022 ; > $_sockfile ) + + # If running named(8) or ntpd(8) chrooted, added appropriate + # syslog socket to list of sockets to watch. + # + for _l in $altlog_proglist; do + eval _ldir=\$${_l}_chrootdir + if checkyesno `set_rcvar $_l` && [ -n "$_ldir" ]; then + echo "${_ldir}/var/run/log" >> $_sockfile + fi + done + + # If other sockets have been provided, change run_rc_command()'s + # internal copy of $rsyslogd_flags to force use of specific + # rsyslogd sockets. + # + if [ -s $_sockfile ]; then + echo "/var/run/log" >> $_sockfile + eval $evalargs + fi + + return 0 +} + +set_socketlist() +{ + _socketargs= + for _s in `cat $_sockfile | tr '\n' ' '` ; do + _socketargs="-l $_s $_socketargs" + done + echo $_socketargs +} +load_rc_config $name +run_rc_command "$1" diff --git a/platform/redhat/rsyslog.conf b/platform/redhat/rsyslog.conf new file mode 100644 index 00000000..340874d1 --- /dev/null +++ b/platform/redhat/rsyslog.conf @@ -0,0 +1,88 @@ +/* rsyslog configuration file (for Red Hat-based systems) + * note that most of this config file uses old-style format, + * because it is well-known AND quite suitable for simple cases + * like we have with the default config. For more advanced + * things, RainerScript configuration is suggested. + * + * For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html + * or latest version online at http://www.rsyslog.com/doc/rsyslog_conf.html + * If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html + */ + +#### MODULES #### + +module(load="imuxsock") # provides support for local system logging (e.g. via logger command) +module(load="imklog") # provides kernel logging support (previously done by rklogd) +#module(load"immark") # provides --MARK-- message capability + +# Provides UDP syslog reception +# for parameters see http://www.rsyslog.com/doc/imudp.html +#module(load="imudp") # needs to be done just once +#input(type="imudp" port="514") + +# Provides TCP syslog reception +# for parameters see http://www.rsyslog.com/doc/imtcp.html +#module(load="imtcp") # needs to be done just once +#input(type="imtcp" port="514") + + +#### GLOBAL DIRECTIVES #### + +# Use default timestamp format +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# File syncing capability is disabled by default. This feature is usually not required, +# not useful and an extreme performance hit +#$ActionFileEnableSync on + +# Include all config files in /etc/rsyslog.d/ +$IncludeConfig /etc/rsyslog.d/*.conf + + +#### RULES #### + +# Log all kernel messages to the console. +# Logging much else clutters up the screen. +#kern.* /dev/console + +# Log anything (except mail) of level info or higher. +# Don't log private authentication messages! +*.info;mail.none;authpriv.none;cron.none /var/log/messages + +# The authpriv file has restricted access. +authpriv.* /var/log/secure + +# Log all the mail messages in one place. +mail.* /var/log/maillog + + +# Log cron stuff +cron.* /var/log/cron + +# Everybody gets emergency messages +*.emerg :omusrmsg:* + +# Save news errors of level crit and higher in a special file. +uucp,news.crit /var/log/spooler + +# Save boot messages also to boot.log +local7.* /var/log/boot.log + + +# ### begin forwarding rule ### +# The statement between the begin ... end define a SINGLE forwarding +# rule. They belong together, do NOT split them. If you create multiple +# forwarding rules, duplicate the whole block! +# Remote Logging (we use TCP for reliable delivery) +# +# An on-disk queue is created for this action. If the remote host is +# down, messages are spooled to disk and sent when it is up again. +#$WorkDirectory /var/lib/rsyslog # where to place spool files +#$ActionQueueFileName fwdRule1 # unique name prefix for spool files +#$ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible) +#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown +#$ActionQueueType LinkedList # run asynchronously +#$ActionResumeRetryCount -1 # infinite retries if host is down +# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional +#*.* @@remote-host:514 +# ### end of the forwarding rule ### diff --git a/platform/slackware/rc.rsyslogd b/platform/slackware/rc.rsyslogd new file mode 100755 index 00000000..f5f8f458 --- /dev/null +++ b/platform/slackware/rc.rsyslogd @@ -0,0 +1,68 @@ +#!/bin/sh +# Start/stop/restart the system logging daemons. +# +# Written for Slackware Linux by Patrick J. Volkerding <volkerdi@slackware.com>. +# Modded for rsyslogd by Chris Elvidge <chris@lowe.ae> Sept 2005 +# + +create_xconsole() +{ + if [ ! -e /dev/xconsole ]; then + mknod -m 640 /dev/xconsole p + else + chmod 0640 /dev/xconsole + fi + chown 0:0 /dev/xconsole +} + +rsyslogd_start() { + if [ -x /usr/sbin/rsyslogd -a -x /usr/sbin/klogd ]; then + echo "Starting rsyslogd / klogd daemons: " +# this one listens on the "usual" socket /dev/log + echo "/usr/sbin/rsyslogd -i $pidfile1" + /usr/sbin/rsyslogd -i "$pidfile1" +# this one listens only to the UDP port + sleep 1 + echo "/usr/sbin/rsyslogd -o -r0 -f $confile2 -i $pidfile2" + /usr/sbin/rsyslogd -o -r0 -f "$confile2" -i "$pidfile2" + sleep 1 # prevent syslogd/klogd race condition on SMP kernels + echo "/usr/sbin/klogd -c 3 -x" + # '-c 3' = display level 'error' or higher messages on console + # '-x' = turn off broken EIP translation + /usr/sbin/klogd -c 3 -x + fi +} + +rsyslogd_stop() { + killall rsyslogd 2> /dev/null + killall klogd 2> /dev/null + /usr/bin/rm pidfile1 2> /dev/null + /usr/bin/rm pidfile2 2> /dev/null +} + +rsyslogd_restart() { + rsyslogd_stop + sleep 1 + rsyslogd_start +} + +confile1=/etc/rsyslog.conf +pidfile1=/var/run/rsyslogd.pid + +confile2=/etc/rsyslog.udp.conf +pidfile2=/var/run/rsyslogd.udp.pid + +case "$1" in +'start') + create_xconsole + rsyslogd_start + ;; +'stop') + rsyslogd_stop + ;; +'restart') + rsyslogd_restart + ;; +*) + echo "usage $0 start|stop|restart" +esac diff --git a/plugins/im3195/Makefile.am b/plugins/im3195/Makefile.am new file mode 100644 index 00000000..5af0b6f5 --- /dev/null +++ b/plugins/im3195/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = im3195.la + +im3195_la_SOURCES = im3195.c +im3195_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(LIBLOGGING_CFLAGS) +im3195_la_LDFLAGS = -module -avoid-version +im3195_la_LIBADD = $(LIBLOGGING_LIBS) + +EXTRA_DIST = diff --git a/plugins/im3195/im3195.c b/plugins/im3195/im3195.c new file mode 100644 index 00000000..b8a4a140 --- /dev/null +++ b/plugins/im3195/im3195.c @@ -0,0 +1,215 @@ +/** + * The rfc3195 input module. + * + * Please note that this file replaces the rfc3195d daemon that was + * also present in pre-v3 versions of rsyslog. + * + * WARNING: due to no demand at all for RFC3195, we have converted rfc3195d + * to this input module, but we have NOT conducted any testing. Also, + * the module does not yet properly handle the recovery case. If someone + * intends to put this module into production, good testing should be + * made and it also is a good idea to notify me that you intend to use + * it in production. In this case, I'll probably give the module another + * cleanup. I don't do this now because so far it looks just like a big + * waste of time. -- rgerhards, 2008-04-16 + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * + * Copyright (C) 2003-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/errno.h> +#include <assert.h> +#include "rsyslog.h" +#include "dirty.h" +#include "liblogging/liblogging.h" +#include "liblogging/srAPI.h" +#include "liblogging/syslogmessage.h" +#include "module-template.h" +#include "cfsysline.h" +#include "msg.h" +#include "errmsg.h" +#include "unicode-helper.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("im3195") + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(prop) + +/* configuration settings */ + +struct modConfData_s { + EMPTY_STRUCT; +}; + +static int listenPort = 601; + +/* we use a global API object below, because this listener is + * not very complex. As such, this hack should not harm anything. + * rgerhards, 2005-10-12 + */ +static srAPIObj* pAPI; + +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this module */ + + +/* This method is called when a message has been fully received. + * It passes the received message to the rsyslog main message + * queue. Please note that this callback is synchronous, thus + * liblogging will be on hold until it returns. This is important + * to note because in an error case we might stay in this code + * for an extended amount of time. So far, we think this is the + * best solution, but real-world experience might tell us a + * different truth ;) + */ +void OnReceive(srAPIObj __attribute__((unused)) *pMyAPI, srSLMGObj* pSLMG) +{ + uchar *pszRawMsg; + uchar *fromHost = (uchar*) "[unset]"; /* TODO: get hostname */ + uchar *fromHostIP = (uchar*) "[unset]"; /* TODO: get hostname */ + + srSLMGGetRawMSG(pSLMG, &pszRawMsg); + + parseAndSubmitMessage(fromHost, fromHostIP, pszRawMsg, strlen((char*)pszRawMsg), + PARSE_HOSTNAME, eFLOWCTL_FULL_DELAY, pInputName, NULL, 0, NULL); +} + + +#if 0 +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf +#endif + + +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(!pThrd->bShallStop) { + /* now move the listener to running state. Control will only + * return after SIGUSR1. + */ + if((iRet = srAPIRunListener(pAPI)) != SR_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d running liblogging listener - im3195 is defunct", iRet); + FINALIZE; /* this causes im3195 to become defunct; TODO: recovery handling */ + } + } +finalize_it: +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + if((pAPI = srAPIInitLib()) == NULL) { + errmsg.LogError(0, NO_ERRCODE, "error initializing liblogging - im3195 is defunct"); + ABORT_FINALIZE(RS_RET_ERR); + } + + if((iRet = srAPISetOption(pAPI, srOPTION_BEEP_LISTENPORT, listenPort)) != SR_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d setting liblogging listen port - im3195 is defunct", iRet); + FINALIZE; + } + + if((iRet = srAPISetupListener(pAPI, OnReceive)) != SR_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d setting up liblogging listener - im3195 is defunct", iRet); + FINALIZE; + } + +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + dbgprintf("Shutting down rfc3195d. Be patient, this can take up to 30 seconds...\n"); + srAPIShutdownListener(pAPI); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + srAPIExitLib(pAPI); /* terminate liblogging */ + /* global variable cleanup */ + if(pInputName != NULL) + prop.Destruct(&pInputName); + /* release objects we used */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + listenPort = 601; + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"input3195listenport", 0, eCmdHdlrInt, NULL, &listenPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("im3195"), sizeof("im3195") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); + +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imdiag/Makefile.am b/plugins/imdiag/Makefile.am new file mode 100644 index 00000000..33e86e93 --- /dev/null +++ b/plugins/imdiag/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imdiag.la + +imdiag_la_SOURCES = imdiag.c +imdiag_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imdiag_la_LDFLAGS = -module -avoid-version +imdiag_la_LIBADD = diff --git a/plugins/imdiag/imdiag.c b/plugins/imdiag/imdiag.c new file mode 100644 index 00000000..5fdc6ef1 --- /dev/null +++ b/plugins/imdiag/imdiag.c @@ -0,0 +1,556 @@ +/* imdiag.c + * This is a diagnostics module, primarily meant for troubleshooting + * and information about the runtime state of rsyslog. It is implemented + * as an input plugin, because that interface best suits our needs + * and also enables us to inject test messages (something not yet + * implemented). + * + * File begun on 2008-07-25 by RGerhards + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "net.h" +#include "netstrm.h" +#include "errmsg.h" +#include "tcpsrv.h" +#include "srUtils.h" +#include "msg.h" +#include "datetime.h" +#include "ratelimit.h" +#include "net.h" /* for permittedPeers, may be removed when this is removed */ + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(datetime) +DEFobjCurrIf(prop) + +/* Module static data */ +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ +static permittedPeers_t *pPermPeersRoot = NULL; +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this input */ +static prop_t *pRcvDummy = NULL; +static prop_t *pRcvIPDummy = NULL; + + +/* config settings */ +struct modConfData_s { + EMPTY_STRUCT; +}; + +static int iTCPSessMax = 20; /* max number of sessions */ +static int iStrmDrvrMode = 0; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */ +static uchar *pszStrmDrvrAuthMode = NULL; /* authentication mode to use */ +static uchar *pszInputName = NULL; /* value for inputname property, NULL is OK and handled by core engine */ + + +/* callbacks */ +/* this shall go into a specific ACL module! */ +static int +isPermittedHost(struct sockaddr __attribute__((unused)) *addr, char __attribute__((unused)) *fromHostFQDN, + void __attribute__((unused)) *pUsrSrv, void __attribute__((unused)) *pUsrSess) +{ + return 1; /* TODO: implement ACLs ... or via some other way? */ +} + + +static rsRetVal +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + ISOBJ_TYPE_assert(pSrv, tcpsrv); + return tcpsrv.create_tcp_socket(pSrv); +} + + +static rsRetVal +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd) +{ + DEFiRet; + assert(pSess != NULL); + assert(piLenRcvd != NULL); + + *piLenRcvd = lenBuf; + CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd)); +finalize_it: + RETiRet; +} + +static rsRetVal +onRegularClose(tcps_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + /* process any incomplete frames left over */ + tcps_sess.PrepareClose(pSess); + /* Session closed */ + tcps_sess.Close(pSess); + RETiRet; +} + + +static rsRetVal +onErrClose(tcps_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + tcps_sess.Close(pSess); + RETiRet; +} + +/* ------------------------------ end callbacks ------------------------------ */ + + +/* get the first word delimited by space from a given string. The pointer is + * advanced to after the word. Any leading spaces are discarded. If the + * output buffer is too small, parsing ends on buffer full condition. + * An empty buffer is returned if there is no more data inside the string. + * rgerhards, 2009-05-27 + */ +#define TO_LOWERCASE 1 +#define NO_MODIFY 0 +static void +getFirstWord(uchar **ppszSrc, uchar *pszBuf, size_t lenBuf, int options) +{ + uchar c; + uchar *pszSrc = *ppszSrc; + + while(*pszSrc && *pszSrc == ' ') + ++pszSrc; /* skip to first non-space */ + + while(*pszSrc && *pszSrc != ' ' && lenBuf > 1) { + c = *pszSrc++; + if(options & TO_LOWERCASE) + c = tolower(c); + *pszBuf++ = c; + lenBuf--; + } + + *pszBuf = '\0'; + *ppszSrc = pszSrc; +} + + +/* send a response back to the originator + * rgerhards, 2009-05-27 + */ +static rsRetVal __attribute__((format(printf, 2, 3))) +sendResponse(tcps_sess_t *pSess, char *fmt, ...) +{ + va_list ap; + ssize_t len; + uchar buf[1024]; + DEFiRet; + + va_start(ap, fmt); + len = vsnprintf((char*)buf, sizeof(buf), fmt, ap); + va_end(ap); + CHKiRet(netstrm.Send(pSess->pStrm, buf, &len)); + +finalize_it: + RETiRet; +} + + +/* actually submit a message to the rsyslog core + */ +static rsRetVal +doInjectMsg(int iNum, ratelimit_t *ratelimiter) +{ + uchar szMsg[1024]; + msg_t *pMsg; + struct syslogTime stTime; + time_t ttGenTime; + DEFiRet; + + snprintf((char*)szMsg, sizeof(szMsg)/sizeof(uchar), + "<167>Mar 1 01:00:00 172.20.245.8 tag msgnum:%8.8d:", iNum); + + datetime.getCurrTime(&stTime, &ttGenTime); + /* we now create our own message object and submit it to the queue */ + CHKiRet(msgConstructWithTime(&pMsg, &stTime, ttGenTime)); + MsgSetRawMsg(pMsg, (char*) szMsg, ustrlen(szMsg)); + MsgSetInputName(pMsg, pInputName); + MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY); + pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME; + MsgSetRcvFrom(pMsg, pRcvDummy); + CHKiRet(MsgSetRcvFromIP(pMsg, pRcvIPDummy)); + CHKiRet(ratelimitAddMsg(ratelimiter, NULL, pMsg)); + +finalize_it: + RETiRet; +} + + +/* This function injects messages. Command format: + * injectmsg <fromnbr> <number-of-messages> + * rgerhards, 2009-05-27 + */ +static rsRetVal +injectMsg(uchar *pszCmd, tcps_sess_t *pSess) +{ + uchar wordBuf[1024]; + int iFrom; + int nMsgs; + int i; + ratelimit_t *ratelimit; + DEFiRet; + + /* we do not check errors here! */ + getFirstWord(&pszCmd, wordBuf, sizeof(wordBuf)/sizeof(uchar), TO_LOWERCASE); + iFrom = atoi((char*)wordBuf); + getFirstWord(&pszCmd, wordBuf, sizeof(wordBuf)/sizeof(uchar), TO_LOWERCASE); + nMsgs = atoi((char*)wordBuf); + ratelimitNew(&ratelimit, "imdiag", "injectmsg"); + + for(i = 0 ; i < nMsgs ; ++i) { + doInjectMsg(i + iFrom, ratelimit); + } + + CHKiRet(sendResponse(pSess, "%d messages injected\n", nMsgs)); + DBGPRINTF("imdiag: %d messages injected\n", nMsgs); + ratelimitDestruct(ratelimit); + +finalize_it: + RETiRet; +} + + +/* This function waits until the main queue is drained (size = 0) + * To make sure it really is drained, we check three times. Otherwise we + * may just see races. + */ +static rsRetVal +waitMainQEmpty(tcps_sess_t *pSess) +{ + int iMsgQueueSize; + int iPrint = 0; + DEFiRet; + + CHKiRet(diagGetMainMsgQSize(&iMsgQueueSize)); + while(1) { + if(iMsgQueueSize == 0) { + /* verify that queue is still empty (else it could just be a race!) */ + srSleep(0,250000);/* wait a little bit */ + CHKiRet(diagGetMainMsgQSize(&iMsgQueueSize)); + if(iMsgQueueSize == 0) { + srSleep(0,500000);/* wait a little bit */ + CHKiRet(diagGetMainMsgQSize(&iMsgQueueSize)); + } + } + if(iMsgQueueSize == 0) + break; + if(iPrint++ % 500 == 0) + dbgprintf("imdiag sleeping, wait mainq drain, curr size %d\n", iMsgQueueSize); + srSleep(0,200000);/* wait a little bit */ + CHKiRet(diagGetMainMsgQSize(&iMsgQueueSize)); + } + + CHKiRet(sendResponse(pSess, "mainqueue empty\n")); + DBGPRINTF("imdiag: mainqueue empty\n"); + +finalize_it: + RETiRet; +} + +/* Function to handle received messages. This is our core function! + * rgerhards, 2009-05-24 + */ +static rsRetVal +OnMsgReceived(tcps_sess_t *pSess, uchar *pRcv, int iLenMsg) +{ + int iMsgQueueSize; + uchar *pszMsg; + uchar *pToFree = NULL; + uchar cmdBuf[1024]; + DEFiRet; + + assert(pSess != NULL); + assert(pRcv != NULL); + + /* NOTE: pRcv is NOT a C-String but rather an array of characters + * WITHOUT a termination \0 char. So we need to convert it to one + * before proceeding. + */ + CHKmalloc(pszMsg = MALLOC(sizeof(uchar) * (iLenMsg + 1))); + pToFree = pszMsg; + memcpy(pszMsg, pRcv, iLenMsg); + pszMsg[iLenMsg] = '\0'; + + getFirstWord(&pszMsg, cmdBuf, sizeof(cmdBuf)/sizeof(uchar), TO_LOWERCASE); + + dbgprintf("imdiag received command '%s'\n", cmdBuf); + if(!ustrcmp(cmdBuf, UCHAR_CONSTANT("getmainmsgqueuesize"))) { + CHKiRet(diagGetMainMsgQSize(&iMsgQueueSize)); + CHKiRet(sendResponse(pSess, "%d\n", iMsgQueueSize)); + DBGPRINTF("imdiag: %d messages in main queue\n", iMsgQueueSize); + } else if(!ustrcmp(cmdBuf, UCHAR_CONSTANT("waitmainqueueempty"))) { + CHKiRet(waitMainQEmpty(pSess)); + } else if(!ustrcmp(cmdBuf, UCHAR_CONSTANT("injectmsg"))) { + CHKiRet(injectMsg(pszMsg, pSess)); + } else { + dbgprintf("imdiag unkown command '%s'\n", cmdBuf); + CHKiRet(sendResponse(pSess, "unkown command '%s'\n", cmdBuf)); + } + +finalize_it: + if(pToFree != NULL) + free(pToFree); + RETiRet; +} + + +/* set permitted peer -- rgerhards, 2008-05-19 + */ +static rsRetVal +setPermittedPeer(void __attribute__((unused)) *pVal, uchar *pszID) +{ + DEFiRet; + CHKiRet(net.AddPermittedPeer(&pPermPeersRoot, pszID)); + free(pszID); /* no longer needed, but we need to free as of interface def */ +finalize_it: + RETiRet; +} + + +static rsRetVal addTCPListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + + if(pOurTcpsrv == NULL) { + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + CHKiRet(tcpsrv.SetSessMax(pOurTcpsrv, iTCPSessMax)); + CHKiRet(tcpsrv.SetCBIsPermittedHost(pOurTcpsrv, isPermittedHost)); + CHKiRet(tcpsrv.SetCBRcvData(pOurTcpsrv, doRcvData)); + CHKiRet(tcpsrv.SetCBOpenLstnSocks(pOurTcpsrv, doOpenLstnSocks)); + CHKiRet(tcpsrv.SetCBOnRegularClose(pOurTcpsrv, onRegularClose)); + CHKiRet(tcpsrv.SetCBOnErrClose(pOurTcpsrv, onErrClose)); + CHKiRet(tcpsrv.SetDrvrMode(pOurTcpsrv, iStrmDrvrMode)); + CHKiRet(tcpsrv.SetOnMsgReceive(pOurTcpsrv, OnMsgReceived)); + /* now set optional params, but only if they were actually configured */ + if(pszStrmDrvrAuthMode != NULL) { + CHKiRet(tcpsrv.SetDrvrAuthMode(pOurTcpsrv, pszStrmDrvrAuthMode)); + } + if(pPermPeersRoot != NULL) { + CHKiRet(tcpsrv.SetDrvrPermPeers(pOurTcpsrv, pPermPeersRoot)); + } + } + + /* initialized, now add socket */ + CHKiRet(tcpsrv.SetInputName(pOurTcpsrv, pszInputName == NULL ? + UCHAR_CONSTANT("imdiag") : pszInputName)); + /* we support octect-cuunted frame (constant 1 below) */ + tcpsrv.configureTCPListen(pOurTcpsrv, pNewVal, 1); + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet); + if(pOurTcpsrv != NULL) + tcpsrv.Destruct(&pOurTcpsrv); + } + free(pNewVal); + RETiRet; +} + + +#if 0 /* can be used to integrate into new config system */ +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf +#endif + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); + iRet = tcpsrv.Run(pOurTcpsrv); +finalize_it: +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first apply some config settings */ + if(pOurTcpsrv == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imdiag"), sizeof("imdiag") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); + + CHKiRet(prop.Construct(&pRcvDummy)); + CHKiRet(prop.SetString(pRcvDummy, UCHAR_CONSTANT("127.0.0.1"), sizeof("127.0.0.1") - 1)); + CHKiRet(prop.ConstructFinalize(pRcvDummy)); + + CHKiRet(prop.Construct(&pRcvIPDummy)); + CHKiRet(prop.SetString(pRcvIPDummy, UCHAR_CONSTANT("127.0.0.1"), sizeof("127.0.0.1") - 1)); + CHKiRet(prop.ConstructFinalize(pRcvIPDummy)); + +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + if(pInputName != NULL) + prop.Destruct(&pInputName); + if(pRcvDummy != NULL) + prop.Destruct(&pRcvDummy); + if(pRcvIPDummy != NULL) + prop.Destruct(&pRcvIPDummy); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pOurTcpsrv != NULL) + iRet = tcpsrv.Destruct(&pOurTcpsrv); + + if(pPermPeersRoot != NULL) { + net.DestructPermittedPeers(&pPermPeersRoot); + } + + /* free some globals to keep valgrind happy */ + free(pszInputName); + + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(tcps_sess, LM_TCPSRV_FILENAME); + objRelease(tcpsrv, LM_TCPSRV_FILENAME); + objRelease(errmsg, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iTCPSessMax = 200; + iStrmDrvrMode = 0; + free(pszInputName); + pszInputName = NULL; + if(pszStrmDrvrAuthMode != NULL) { + free(pszStrmDrvrAuthMode); + pszStrmDrvrAuthMode = NULL; + } + return RS_RET_OK; +} + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(tcps_sess, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(tcpsrv, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("imdiagserverrun"), 0, eCmdHdlrGetWord, + addTCPListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("imdiagmaxsessions"), 0, eCmdHdlrInt, + NULL, &iTCPSessMax, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("imdiagserverstreamdrivermode"), 0, + eCmdHdlrInt, NULL, &iStrmDrvrMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("imdiagserverstreamdriverauthmode"), 0, + eCmdHdlrGetWord, NULL, &pszStrmDrvrAuthMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("imdiagserverstreamdriverpermittedpeer"), 0, + eCmdHdlrGetWord, setPermittedPeer, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("imdiagserverinputname"), 0, + eCmdHdlrGetWord, NULL, &pszInputName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("resetconfigvariables"), 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + + +/* vim:set ai: + */ diff --git a/plugins/imfile/Makefile.am b/plugins/imfile/Makefile.am new file mode 100644 index 00000000..551639ba --- /dev/null +++ b/plugins/imfile/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imfile.la + +imfile_la_SOURCES = imfile.c +imfile_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imfile_la_LDFLAGS = -module -avoid-version +imfile_la_LIBADD = diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c new file mode 100644 index 00000000..349acead --- /dev/null +++ b/plugins/imfile/imfile.c @@ -0,0 +1,910 @@ +/* imfile.c + * + * This is the input module for reading text file data. A text file is a + * non-binary file who's lines are delemited by the \n character. + * + * Work originally begun on 2008-02-01 by Rainer Gerhards + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" /* this is for autotools and always must be the first include */ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> /* do NOT remove: will soon be done by the module generation macros */ +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#include "rsyslog.h" /* error codes etc... */ +#include "dirty.h" +#include "cfsysline.h" /* access to config file objects */ +#include "module-template.h" /* generic module interface code - very important, read it! */ +#include "srUtils.h" /* some utility functions */ +#include "msg.h" +#include "stream.h" +#include "errmsg.h" +#include "glbl.h" +#include "datetime.h" +#include "unicode-helper.h" +#include "prop.h" +#include "stringbuf.h" +#include "ruleset.h" +#include "ratelimit.h" + +MODULE_TYPE_INPUT /* must be present for input modules, do not remove */ +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imfile") + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA /* must be present, starts static data */ +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(datetime) +DEFobjCurrIf(strm) +DEFobjCurrIf(prop) +DEFobjCurrIf(ruleset) + +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + +#define NUM_MULTISUB 1024 /* default max number of submits */ +#define DFLT_PollInterval 10 + +typedef struct fileInfo_s { + uchar *pszFileName; + uchar *pszTag; + size_t lenTag; + uchar *pszStateFile; /* file in which state between runs is to be stored */ + int iFacility; + int iSeverity; + int maxLinesAtOnce; + int nRecords; /**< How many records did we process before persisting the stream? */ + int iPersistStateInterval; /**< how often should state be persisted? (0=on close only) */ + strm_t *pStrm; /* its stream (NULL if not assigned) */ + int readMode; /* which mode to use in ReadMulteLine call? */ + ruleset_t *pRuleset; /* ruleset to bind listener to (use system default if unspecified) */ + ratelimit_t *ratelimiter; + multi_submit_t multiSub; +} fileInfo_t; + +static struct configSettings_s { + uchar *pszFileName; + uchar *pszFileTag; + uchar *pszStateFile; + uchar *pszBindRuleset; + int iPollInterval; + int iPersistStateInterval; /* how often if state file to be persisted? (default 0->never) */ + int iFacility; /* local0 */ + int iSeverity; /* notice, as of rfc 3164 */ + int readMode; /* mode to use for ReadMultiLine call */ + int maxLinesAtOnce; /* how many lines to process in a row? */ + ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ +} cs; + +struct instanceConf_s { + uchar *pszFileName; + uchar *pszTag; + uchar *pszStateFile; + uchar *pszBindRuleset; + int nMultiSub; + int iPersistStateInterval; + int iFacility; + int iSeverity; + int readMode; + int maxLinesAtOnce; + ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ + struct instanceConf_s *next; +}; + + +/* forward definitions */ +static rsRetVal persistStrmState(fileInfo_t *pInfo); +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* config variables */ +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + int iPollInterval; /* number of seconds to sleep when there was no file activity */ + instanceConf_t *root, *tail; + sbool configSetViaV2Method; +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +static int iFilPtr = 0; /* number of files to be monitored; pointer to next free spot during config */ +#define MAX_INPUT_FILES 100 +static fileInfo_t files[MAX_INPUT_FILES]; + +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this input */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "pollinginterval", eCmdHdlrPositiveInt, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* input instance parameters */ +static struct cnfparamdescr inppdescr[] = { + { "file", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "statefile", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "tag", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "severity", eCmdHdlrSeverity, 0 }, + { "facility", eCmdHdlrFacility, 0 }, + { "ruleset", eCmdHdlrString, 0 }, + { "readmode", eCmdHdlrInt, 0 }, + { "maxlinesatonce", eCmdHdlrInt, 0 }, + { "maxsubmitatonce", eCmdHdlrInt, 0 }, + { "persiststateinterval", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + +#include "im-helper.h" /* must be included AFTER the type definitions! */ + +/* enqueue the read file line as a message. The provided string is + * not freed - thuis must be done by the caller. + */ +static rsRetVal enqLine(fileInfo_t *pInfo, cstr_t *cstrLine) +{ + DEFiRet; + msg_t *pMsg; + + if(rsCStrLen(cstrLine) == 0) { + /* we do not process empty lines */ + FINALIZE; + } + + CHKiRet(msgConstruct(&pMsg)); + MsgSetFlowControlType(pMsg, eFLOWCTL_FULL_DELAY); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsg(pMsg, (char*)rsCStrGetSzStr(cstrLine), cstrLen(cstrLine)); + MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */ + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetTAG(pMsg, pInfo->pszTag, pInfo->lenTag); + pMsg->iFacility = LOG_FAC(pInfo->iFacility); + pMsg->iSeverity = LOG_PRI(pInfo->iSeverity); + MsgSetRuleset(pMsg, pInfo->pRuleset); + ratelimitAddMsg(pInfo->ratelimiter, &pInfo->multiSub, pMsg); +finalize_it: + RETiRet; +} + + +/* try to open a file. This involves checking if there is a status file and, + * if so, reading it in. Processing continues from the last know location. + */ +static rsRetVal +openFile(fileInfo_t *pThis) +{ + DEFiRet; + strm_t *psSF = NULL; + uchar pszSFNam[MAXFNAME]; + size_t lenSFNam; + struct stat stat_buf; + + /* Construct file name */ + lenSFNam = snprintf((char*)pszSFNam, sizeof(pszSFNam) / sizeof(uchar), "%s/%s", + (char*) glbl.GetWorkDir(), (char*)pThis->pszStateFile); + + /* check if the file exists */ + if(stat((char*) pszSFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + dbgprintf("filemon %p: clean startup, no .si file found\n", pThis); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + dbgprintf("filemon %p: error %d trying to access .si file\n", pThis, errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + + /* If we reach this point, we have a .si file */ + + CHKiRet(strm.Construct(&psSF)); + CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_READ)); + CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strm.SetFName(psSF, pszSFNam, lenSFNam)); + CHKiRet(strm.ConstructFinalize(psSF)); + + /* read back in the object */ + CHKiRet(obj.Deserialize(&pThis->pStrm, (uchar*) "strm", psSF, NULL, pThis)); + + strm.CheckFileChange(pThis->pStrm); + CHKiRet(strm.SeekCurrOffs(pThis->pStrm)); + + /* note: we do not delete the state file, so that the last position remains + * known even in the case that rsyslogd aborts for some reason (like powerfail) + */ + +finalize_it: + if(psSF != NULL) + strm.Destruct(&psSF); + + if(iRet != RS_RET_OK) { + if(pThis->pStrm != NULL) + strm.Destruct(&pThis->pStrm); + CHKiRet(strm.Construct(&pThis->pStrm)); + CHKiRet(strm.SettOperationsMode(pThis->pStrm, STREAMMODE_READ)); + CHKiRet(strm.SetsType(pThis->pStrm, STREAMTYPE_FILE_MONITOR)); + CHKiRet(strm.SetFName(pThis->pStrm, pThis->pszFileName, strlen((char*) pThis->pszFileName))); + CHKiRet(strm.ConstructFinalize(pThis->pStrm)); + } + + RETiRet; +} + + +/* The following is a cancel cleanup handler for strmReadLine(). It is necessary in case + * strmReadLine() is cancelled while processing the stream. -- rgerhards, 2008-03-27 + */ +static void pollFileCancelCleanup(void *pArg) +{ + BEGINfunc; + cstr_t **ppCStr = (cstr_t**) pArg; + if(*ppCStr != NULL) + rsCStrDestruct(ppCStr); + ENDfunc; +} + + +/* poll a file, need to check file rollover etc. open file if not open */ +#pragma GCC diagnostic ignored "-Wempty-body" +static rsRetVal pollFile(fileInfo_t *pThis, int *pbHadFileData) +{ + cstr_t *pCStr = NULL; + int nProcessed = 0; + DEFiRet; + + ASSERT(pbHadFileData != NULL); + + /* Note: we must do pthread_cleanup_push() immediately, because the POXIS macros + * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14 + */ + pthread_cleanup_push(pollFileCancelCleanup, &pCStr); + if(pThis->pStrm == NULL) { + CHKiRet(openFile(pThis)); /* open file */ + } + + /* loop below will be exited when strmReadLine() returns EOF */ + while(glbl.GetGlobalInputTermState() == 0) { + if(pThis->maxLinesAtOnce != 0 && nProcessed >= pThis->maxLinesAtOnce) + break; + CHKiRet(strm.ReadLine(pThis->pStrm, &pCStr, pThis->readMode)); + ++nProcessed; + *pbHadFileData = 1; /* this is just a flag, so set it and forget it */ + CHKiRet(enqLine(pThis, pCStr)); /* process line */ + rsCStrDestruct(&pCStr); /* discard string (must be done by us!) */ + if(pThis->iPersistStateInterval > 0 && pThis->nRecords++ >= pThis->iPersistStateInterval) { + persistStrmState(pThis); + pThis->nRecords = 0; + } + } + +finalize_it: + multiSubmitFlush(&pThis->multiSub); + pthread_cleanup_pop(0); + + if(pCStr != NULL) { + rsCStrDestruct(&pCStr); + } + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* create input instance, set default paramters, and + * add it to the list of instances. + */ +static rsRetVal +createInstance(instanceConf_t **pinst) +{ + instanceConf_t *inst; + DEFiRet; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + inst->next = NULL; + inst->pBindRuleset = NULL; + + inst->pszBindRuleset = NULL; + inst->pszFileName = NULL; + inst->pszTag = NULL; + inst->pszStateFile = NULL; + inst->nMultiSub = NUM_MULTISUB; + inst->iSeverity = 5; + inst->iFacility = 128; + inst->maxLinesAtOnce = 10240; + inst->iPersistStateInterval = 0; + inst->readMode = 0; + + /* node created, let's add to config */ + if(loadModConf->tail == NULL) { + loadModConf->tail = loadModConf->root = inst; + } else { + loadModConf->tail->next = inst; + loadModConf->tail = inst; + } + + *pinst = inst; +finalize_it: + RETiRet; +} + + +/* add a new monitor */ +static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + instanceConf_t *inst; + DEFiRet; + + if(cs.pszFileName == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile error: no file name given, file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if(cs.pszFileTag == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile error: no tag value given , file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if(cs.pszStateFile == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile error: not state file name given, file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + + CHKiRet(createInstance(&inst)); + if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) { + inst->pszBindRuleset = NULL; + } else { + CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset)); + } + inst->pszFileName = (uchar*) strdup((char*) cs.pszFileName); + inst->pszTag = (uchar*) strdup((char*) cs.pszFileTag); + inst->pszStateFile = (uchar*) strdup((char*) cs.pszStateFile); + inst->iSeverity = cs.iSeverity; + inst->iFacility = cs.iFacility; + inst->maxLinesAtOnce = cs.maxLinesAtOnce; + inst->iPersistStateInterval = cs.iPersistStateInterval; + inst->readMode = cs.readMode; + + /* reset legacy system */ + cs.iPersistStateInterval = 0; + resetConfigVariables(NULL, NULL); /* values are both dummies */ + +finalize_it: + free(pNewVal); /* we do not need it, but we must free it! */ + RETiRet; +} + + +/* This function is called when a new listener (monitor) shall be added. */ +static inline rsRetVal +addListner(instanceConf_t *inst) +{ + DEFiRet; + fileInfo_t *pThis; + + if(iFilPtr < MAX_INPUT_FILES) { + pThis = &files[iFilPtr]; + //TODO: optimize, save strdup? + pThis->pszFileName = (uchar*) strdup((char*) inst->pszFileName); + pThis->pszTag = (uchar*) strdup((char*) inst->pszTag); + pThis->lenTag = ustrlen(pThis->pszTag); + pThis->pszStateFile = (uchar*) strdup((char*) inst->pszStateFile); + + CHKiRet(ratelimitNew(&pThis->ratelimiter, "imfile", (char*)inst->pszFileName)); + CHKmalloc(pThis->multiSub.ppMsgs = MALLOC(inst->nMultiSub * sizeof(msg_t*))); + pThis->multiSub.maxElem = inst->nMultiSub; + pThis->multiSub.nElem = 0; + pThis->iSeverity = inst->iSeverity; + pThis->iFacility = inst->iFacility; + pThis->maxLinesAtOnce = inst->maxLinesAtOnce; + pThis->iPersistStateInterval = inst->iPersistStateInterval; + pThis->readMode = inst->readMode; + pThis->pRuleset = inst->pBindRuleset; + pThis->nRecords = 0; + } else { + errmsg.LogError(0, RS_RET_OUT_OF_DESRIPTORS, + "Too many file monitors configured - ignoring %s", + inst->pszFileName); + ABORT_FINALIZE(RS_RET_OUT_OF_DESRIPTORS); + } + ++iFilPtr; /* we got a new file to monitor */ + + resetConfigVariables(NULL, NULL); /* values are both dummies */ +finalize_it: + RETiRet; +} + + +BEGINnewInpInst + struct cnfparamvals *pvals; + instanceConf_t *inst; + int i; +CODESTARTnewInpInst + DBGPRINTF("newInpInst (imfile)\n"); + + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imfile: required parameter are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("input param blk in imfile:\n"); + cnfparamsPrint(&inppblk, pvals); + } + + CHKiRet(createInstance(&inst)); + + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "file")) { + inst->pszFileName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "statefile")) { + inst->pszStateFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "tag")) { + inst->pszTag = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "ruleset")) { + inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "severity")) { + inst->iSeverity = pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "facility")) { + inst->iSeverity = pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "readmode")) { + inst->readMode = pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "maxlinesatonce")) { + inst->maxLinesAtOnce = pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "persistStateInterval")) { + inst->iPersistStateInterval = pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "maxsubmitatonce")) { + inst->nMultiSub = pvals[i].val.d.n; + } else { + dbgprintf("imfile: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + } +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + loadModConf->iPollInterval = DFLT_PollInterval; + loadModConf->configSetViaV2Method = 0; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config vars */ + cs.pszFileName = NULL; + cs.pszFileTag = NULL; + cs.pszStateFile = NULL; + cs.iPollInterval = DFLT_PollInterval; + cs.iPersistStateInterval = 0; + cs.iFacility = 128; + cs.iSeverity = 5; + cs.readMode = 0; + cs.maxLinesAtOnce = 10240; + cs.pBindRuleset = NULL; +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imfile: error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imfile:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "pollinginterval")) { + loadModConf->iPollInterval = (int) pvals[i].val.d.n; + } else { + dbgprintf("imfile: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* remove all of our legacy handlers, as they can not used in addition + * the the new-style config method. + */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->iPollInterval = cs.iPollInterval; + } + dbgprintf("imfile: polling interval is %d\n", loadModConf->iPollInterval); + + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.pszFileName); + free(cs.pszFileTag); + free(cs.pszStateFile); +ENDendCnfLoad + + +BEGINcheckCnf + instanceConf_t *inst; +CODESTARTcheckCnf + for(inst = pModConf->root ; inst != NULL ; inst = inst->next) { + std_checkRuleset(pModConf, inst); + } + if(pModConf->root == NULL) { + errmsg.LogError(0, RS_RET_NO_LISTNERS, + "imfile: no files configured to be monitored - " + "no input will be gathered"); + iRet = RS_RET_NO_LISTNERS; + } +ENDcheckCnf + + +/* note: we do access files AFTER we have dropped privileges. This is + * intentional, user must make sure the files have the right permissions. + */ +BEGINactivateCnf + instanceConf_t *inst; +CODESTARTactivateCnf + runModConf = pModConf; + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + addListner(inst); + } + /* if we could not set up any listners, there is no point in running... */ + if(iFilPtr == 0) { + errmsg.LogError(0, NO_ERRCODE, "imfile: no file monitors could be started, " + "input not activated.\n"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } +finalize_it: +ENDactivateCnf + + +BEGINfreeCnf + instanceConf_t *inst, *del; +CODESTARTfreeCnf + for(inst = pModConf->root ; inst != NULL ; ) { + free(inst->pszBindRuleset); + free(inst->pszFileName); + free(inst->pszTag); + free(inst->pszStateFile); + del = inst; + inst = inst->next; + free(del); + } +ENDfreeCnf + + + +/* This function is the cancel cleanup handler. It is called when rsyslog decides the + * module must be stopped, what most probably happens during shutdown of rsyslogd. When + * this function is called, the runInput() function (below) is already terminated - somewhere + * in the middle of what it was doing. The cancel cleanup handler below should take + * care of any locked mutexes and such, things that really need to be cleaned up + * before processing continues. In general, many plugins do not need to provide + * any code at all here. + * + * IMPORTANT: the calling interface of this function can NOT be modified. It actually is + * called by pthreads. The provided argument is currently not being used. + */ +static void +inputModuleCleanup(void __attribute__((unused)) *arg) +{ + BEGINfunc + ENDfunc +} + + +/* This function is called by the framework to gather the input. The module stays + * most of its lifetime inside this function. It MUST NEVER exit this function. Doing + * so would end module processing and rsyslog would NOT reschedule the module. If + * you exit from this function, you violate the interface specification! + * + * We go through all files and remember if at least one had data. If so, we do + * another run (until no data was present in any file). Then we sleep for + * PollInterval seconds and restart the whole process. This ensures that as + * long as there is some data present, it will be processed at the fastest + * possible pace - probably important for busy systmes. If we monitor just a + * single file, the algorithm is slightly modified. In that case, the sleep + * hapens immediately. The idea here is that if we have just one file, we + * returned from the file processer because that file had no additional data. + * So even if we found some lines, it is highly unlikely to find a new one + * just now. Trying it would result in a performance-costly additional try + * which in the very, very vast majority of cases will never find any new + * lines. + * On spamming the main queue: keep in mind that it will automatically rate-limit + * ourselfes if we begin to overrun it. So we really do not need to care here. + */ +#pragma GCC diagnostic ignored "-Wempty-body" +BEGINrunInput + int i; + int bHadFileData; /* were there at least one file with data during this run? */ +CODESTARTrunInput + pthread_cleanup_push(inputModuleCleanup, NULL); + while(glbl.GetGlobalInputTermState() == 0) { + do { + bHadFileData = 0; + for(i = 0 ; i < iFilPtr ; ++i) { + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + pollFile(&files[i], &bHadFileData); + } + } while(iFilPtr > 1 && bHadFileData == 1 && glbl.GetGlobalInputTermState() == 0); /* warning: do...while()! */ + + /* Note: the additional 10ns wait is vitally important. It guards rsyslog against totally + * hogging the CPU if the users selects a polling interval of 0 seconds. It doesn't hurt any + * other valid scenario. So do not remove. -- rgerhards, 2008-02-14 + */ + if(glbl.GetGlobalInputTermState() == 0) + srSleep(runModConf->iPollInterval, 10); + } + DBGPRINTF("imfile: terminating upon request of rsyslog core\n"); + + pthread_cleanup_pop(0); /* just for completeness, but never called... */ + RETiRet; /* use it to make sure the housekeeping is done! */ +ENDrunInput +#pragma GCC diagnostic warning "-Wempty-body" + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + + +/* The function is called by rsyslog before runInput() is called. It is a last chance + * to set up anything specific. Most importantly, it can be used to tell rsyslog if the + * input shall run or not. The idea is that if some config settings (or similiar things) + * are not OK, the input can tell rsyslog it will not execute. To do so, return + * RS_RET_NO_RUN or a specific error code. If RS_RET_OK is returned, rsyslog will + * proceed and call the runInput() entry point. + */ +BEGINwillRun +CODESTARTwillRun + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imfile"), sizeof("imfile") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); + +finalize_it: +ENDwillRun + + + +/* This function persists information for a specific file being monitored. + * To do so, it simply persists the stream object. We do NOT abort on error + * iRet as that makes matters worse (at least we can try persisting the others...). + * rgerhards, 2008-02-13 + */ +static rsRetVal +persistStrmState(fileInfo_t *pInfo) +{ + DEFiRet; + strm_t *psSF = NULL; /* state file (stream) */ + size_t lenDir; + + ASSERT(pInfo != NULL); + + /* TODO: create a function persistObj in obj.c? */ + CHKiRet(strm.Construct(&psSF)); + lenDir = ustrlen(glbl.GetWorkDir()); + if(lenDir > 0) + CHKiRet(strm.SetDir(psSF, glbl.GetWorkDir(), lenDir)); + CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_WRITE_TRUNC)); + CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strm.SetFName(psSF, pInfo->pszStateFile, strlen((char*) pInfo->pszStateFile))); + CHKiRet(strm.ConstructFinalize(psSF)); + + CHKiRet(strm.Serialize(pInfo->pStrm, psSF)); + CHKiRet(strm.Flush(psSF)); + + CHKiRet(strm.Destruct(&psSF)); + +finalize_it: + if(psSF != NULL) + strm.Destruct(&psSF); + + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "imfile: could not persist state " + "file %s - data may be repeated on next " + "startup. Is WorkDirectory set?", + pInfo->pszStateFile); + } + + RETiRet; +} + + +/* This function is called by the framework after runInput() has been terminated. It + * shall free any resources and prepare the module for unload. + */ +BEGINafterRun + int i; +CODESTARTafterRun + /* Close files and persist file state information. We do NOT abort on error iRet as that makes + * matters worse (at least we can try persisting the others...). Please note that, under stress + * conditions, it may happen that we are terminated before we actuall could open all streams. So + * before we change anything, we need to make sure the stream was open. + */ + for(i = 0 ; i < iFilPtr ; ++i) { + if(files[i].pStrm != NULL) { /* stream open? */ + persistStrmState(&files[i]); + strm.Destruct(&(files[i].pStrm)); + } + ratelimitDestruct(files[i].ratelimiter); + free(files[i].multiSub.ppMsgs); + free(files[i].pszFileName); + free(files[i].pszTag); + free(files[i].pszStateFile); + } + + if(pInputName != NULL) + prop.Destruct(&pInputName); +ENDafterRun + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +/* The following entry points are defined in module-template.h. + * In general, they need to be present, but you do NOT need to provide + * any code here. + */ +BEGINmodExit +CODESTARTmodExit + /* release objects we used */ + objRelease(strm, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +/* The following function shall reset all configuration variables to their + * default values. The code provided in modInit() below registers it to be + * called on "$ResetConfigVariables". You may also call it from other places, + * but in general this is not necessary. Once runInput() has been called, this + * function here is never again called. + */ +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + + free(cs.pszFileName); + cs.pszFileName = NULL; + free(cs.pszFileTag); + cs.pszFileTag = NULL; + free(cs.pszFileTag); + cs.pszFileTag = NULL; + + /* set defaults... */ + cs.iPollInterval = DFLT_PollInterval; + cs.iFacility = 128; /* local0 */ + cs.iSeverity = 5; /* notice, as of rfc 3164 */ + cs.readMode = 0; + cs.pBindRuleset = NULL; + cs.maxLinesAtOnce = 10240; + + RETiRet; +} + +static inline void +std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) +{ + errmsg.LogError(0, NO_ERRCODE, "imfile: ruleset '%s' for %s not found - " + "using default ruleset instead", inst->pszBindRuleset, + inst->pszFileName); +} + +/* modInit() is called once the module is loaded. It must perform all module-wide + * initialization tasks. There are also a number of housekeeping tasks that the + * framework requires. These are handled by the macros. Please note that the + * complexity of processing is depending on the actual module. However, only + * thing absolutely necessary should be done here. Actual app-level processing + * is to be performed in runInput(). A good sample of what to do here may be to + * set some variable defaults. The most important thing probably is registration + * of config command handlers. + */ +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(strm, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + DBGPRINTF("imfile: version %s initializing\n", VERSION); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilename", 0, eCmdHdlrGetWord, + NULL, &cs.pszFileName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfiletag", 0, eCmdHdlrGetWord, + NULL, &cs.pszFileTag, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilestatefile", 0, eCmdHdlrGetWord, + NULL, &cs.pszStateFile, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfileseverity", 0, eCmdHdlrSeverity, + NULL, &cs.iSeverity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilefacility", 0, eCmdHdlrFacility, + NULL, &cs.iFacility, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilereadmode", 0, eCmdHdlrInt, + NULL, &cs.readMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilemaxlinesatonce", 0, eCmdHdlrSize, + NULL, &cs.maxLinesAtOnce, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilepersiststateinterval", 0, eCmdHdlrInt, + NULL, &cs.iPersistStateInterval, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilebindruleset", 0, eCmdHdlrGetWord, + NULL, &cs.pszBindRuleset, STD_LOADABLE_MODULE_ID)); + /* that command ads a new file! */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrunfilemonitor", 0, eCmdHdlrGetWord, + addInstance, NULL, STD_LOADABLE_MODULE_ID)); + /* module-global config params - will be disabled in configs that are loaded + * via module(...). + */ + CHKiRet(regCfSysLineHdlr2((uchar *)"inputfilepollinterval", 0, eCmdHdlrInt, + NULL, &cs.iPollInterval, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imgssapi/Makefile.am b/plugins/imgssapi/Makefile.am new file mode 100644 index 00000000..ea016353 --- /dev/null +++ b/plugins/imgssapi/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imgssapi.la + +imgssapi_la_SOURCES = imgssapi.c +imgssapi_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imgssapi_la_LDFLAGS = -module -avoid-version +imgssapi_la_LIBADD = $(GSS_LIBS) diff --git a/plugins/imgssapi/imgssapi.c b/plugins/imgssapi/imgssapi.c new file mode 100644 index 00000000..15d994cc --- /dev/null +++ b/plugins/imgssapi/imgssapi.c @@ -0,0 +1,792 @@ +/* imgssapi.c + * This is the implementation of the GSSAPI input module. + * + * Note: the root gssapi code was contributed by varmojfekoj and is most often + * maintened by him. I am just doing the plumbing around it (I event don't have a + * test lab for gssapi yet... ). I am very grateful for this useful code + * contribution -- rgerhards, 2008-03-05 + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007, 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <gssapi/gssapi.h> +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "net.h" +#include "srUtils.h" +#include "gss-misc.h" +#include "tcpsrv.h" +#include "tcps_sess.h" +#include "errmsg.h" +#include "netstrm.h" +#include "glbl.h" +#include "debug.h" +#include "unlimited_select.h" + + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imgssapi") + +/* defines */ +#define ALLOWEDMETHOD_GSS 2 +#define ALLOWEDMETHOD_TCP 1 + + +/* some forward definitions - they may go away when we no longer include imtcp.c */ +static rsRetVal addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal); +static rsRetVal actGSSListener(uchar *port); +static int TCPSessGSSInit(void); +static void TCPSessGSSClose(tcps_sess_t* pSess); +static rsRetVal TCPSessGSSRecv(tcps_sess_t *pSess, void *buf, size_t buf_len, ssize_t *); +static rsRetVal onSessAccept(tcpsrv_t *pThis, tcps_sess_t *ppSess); +static rsRetVal OnSessAcceptGSS(tcpsrv_t *pThis, tcps_sess_t *ppSess); + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(gssutil) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(net) +DEFobjCurrIf(glbl) + +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ +static gss_cred_id_t gss_server_creds = GSS_C_NO_CREDENTIAL; +static uchar *srvPort; + +/* our usr structure for the tcpsrv object */ +typedef struct gsssrv_s { + char allowedMethods; +} gsssrv_t; + +/* our usr structure for the session object */ +typedef struct gss_sess_s { + OM_uint32 gss_flags; + gss_ctx_id_t gss_context; + char allowedMethods; +} gss_sess_t; + + +/* config variables */ +struct modConfData_s { + EMPTY_STRUCT; +}; + +static int iTCPSessMax = 200; /* max number of sessions */ +static char *gss_listen_service_name = NULL; +static int bPermitPlainTcp = 0; /* plain tcp syslog allowed on GSSAPI port? */ + + +/* methods */ +/* callbacks */ +static rsRetVal OnSessConstructFinalize(void *ppUsr) +{ + DEFiRet; + gss_sess_t **ppGSess = (gss_sess_t**) ppUsr; + gss_sess_t *pGSess; + + assert(ppGSess != NULL); + + if((pGSess = calloc(1, sizeof(gss_sess_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pGSess->gss_flags = 0; + pGSess->gss_context = GSS_C_NO_CONTEXT; + pGSess->allowedMethods = 0; + + *ppGSess = pGSess; + +finalize_it: + RETiRet; +} + + +/* Destruct the user session pointer for a GSSAPI session. Please note + * that it *is* valid to receive a NULL user pointer. In this case, the + * sessions is to be torn down before it was fully initialized. This + * happens in error cases, e.g. when the host ACL did not match. + * rgerhards, 2008-03-03 + */ +static rsRetVal +OnSessDestruct(void *ppUsr) +{ + DEFiRet; + gss_sess_t **ppGSess = (gss_sess_t**) ppUsr; + + assert(ppGSess != NULL); + if(*ppGSess == NULL) + FINALIZE; + + if((*ppGSess)->allowedMethods & ALLOWEDMETHOD_GSS) { + OM_uint32 maj_stat, min_stat; + maj_stat = gss_delete_sec_context(&min_stat, &(*ppGSess)->gss_context, GSS_C_NO_BUFFER); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("deleting context", maj_stat, min_stat); + } + + free(*ppGSess); + *ppGSess = NULL; + +finalize_it: + RETiRet; +} + + +/* Check if the host is permitted to send us messages. + * Note: the pUsrSess may be zero if the server is running in tcp-only mode! + */ +static int +isPermittedHost(struct sockaddr *addr, char *fromHostFQDN, void *pUsrSrv, void*pUsrSess) +{ + gsssrv_t *pGSrv; + gss_sess_t *pGSess; + char allowedMethods = 0; + + BEGINfunc + assert(pUsrSrv != NULL); + pGSrv = (gsssrv_t*) pUsrSrv; + pGSess = (gss_sess_t*) pUsrSess; + + if((pGSrv->allowedMethods & ALLOWEDMETHOD_TCP) && + net.isAllowedSender2((uchar*)"TCP", addr, (char*)fromHostFQDN, 1)) + allowedMethods |= ALLOWEDMETHOD_TCP; + if((pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) && + net.isAllowedSender2((uchar*)"GSS", addr, (char*)fromHostFQDN, 1)) + allowedMethods |= ALLOWEDMETHOD_GSS; + if(allowedMethods && pGSess != NULL) + pGSess->allowedMethods = allowedMethods; + ENDfunc + return allowedMethods; +} + + +static rsRetVal +onSessAccept(tcpsrv_t *pThis, tcps_sess_t *pSess) +{ + DEFiRet; + gsssrv_t *pGSrv; + + pGSrv = (gsssrv_t*) pThis->pUsr; + + if(pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) { + iRet = OnSessAcceptGSS(pThis, pSess); + } + + RETiRet; +} + + +static rsRetVal +onRegularClose(tcps_sess_t *pSess) +{ + DEFiRet; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + if(pGSess->allowedMethods & ALLOWEDMETHOD_GSS) + TCPSessGSSClose(pSess); + else { + /* process any incomplete frames left over */ + tcps_sess.PrepareClose(pSess); + /* Session closed */ + tcps_sess.Close(pSess); + } + RETiRet; +} + + +static rsRetVal +onErrClose(tcps_sess_t *pSess) +{ + DEFiRet; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + if(pGSess->allowedMethods & ALLOWEDMETHOD_GSS) + TCPSessGSSClose(pSess); + else + tcps_sess.Close(pSess); + + RETiRet; +} + + +/* open the listen sockets */ +static rsRetVal +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + gsssrv_t *pGSrv; + DEFiRet; + + ISOBJ_TYPE_assert(pSrv, tcpsrv); + pGSrv = pSrv->pUsr; + assert(pGSrv != NULL); + + /* first apply some config settings */ + if(pGSrv->allowedMethods) { + if(pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) { + if(TCPSessGSSInit()) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API initialization failed\n"); + pGSrv->allowedMethods &= ~(ALLOWEDMETHOD_GSS); + } + } + if(pGSrv->allowedMethods) { + /* fallback to plain TCP */ + CHKiRet(tcpsrv.create_tcp_socket(pSrv)); + } else { + ABORT_FINALIZE(RS_RET_GSS_ERR); + } + } + +finalize_it: + RETiRet; +} + + +static rsRetVal +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd) +{ + DEFiRet; + int allowedMethods; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + assert(piLenRcvd != NULL); + + allowedMethods = pGSess->allowedMethods; + if(allowedMethods & ALLOWEDMETHOD_GSS) { + CHKiRet(TCPSessGSSRecv(pSess, buf, lenBuf, piLenRcvd)); + } else { + *piLenRcvd = lenBuf; + CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd) != RS_RET_OK); + } + +finalize_it: + RETiRet; +} + + +/* end callbacks */ + +static rsRetVal +addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + + srvPort = pNewVal; + + RETiRet; +} + +static rsRetVal +actGSSListener(uchar *port) +{ + DEFiRet; + gsssrv_t *pGSrv; + + if(pOurTcpsrv == NULL) { + /* first create/init the gsssrv "object" */ + if((pGSrv = calloc(1, sizeof(gsssrv_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pGSrv->allowedMethods = ALLOWEDMETHOD_GSS; + if(bPermitPlainTcp) + pGSrv->allowedMethods |= ALLOWEDMETHOD_TCP; + /* gsssrv initialized */ + + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + CHKiRet(tcpsrv.SetUsrP(pOurTcpsrv, pGSrv)); + CHKiRet(tcpsrv.SetCBOnSessConstructFinalize(pOurTcpsrv, OnSessConstructFinalize)); + CHKiRet(tcpsrv.SetCBOnSessDestruct(pOurTcpsrv, OnSessDestruct)); + CHKiRet(tcpsrv.SetCBIsPermittedHost(pOurTcpsrv, isPermittedHost)); + CHKiRet(tcpsrv.SetCBRcvData(pOurTcpsrv, doRcvData)); + CHKiRet(tcpsrv.SetCBOpenLstnSocks(pOurTcpsrv, doOpenLstnSocks)); + CHKiRet(tcpsrv.SetCBOnSessAccept(pOurTcpsrv, onSessAccept)); + CHKiRet(tcpsrv.SetCBOnRegularClose(pOurTcpsrv, onRegularClose)); + CHKiRet(tcpsrv.SetCBOnErrClose(pOurTcpsrv, onErrClose)); + CHKiRet(tcpsrv.SetInputName(pOurTcpsrv, UCHAR_CONSTANT("imgssapi"))); + tcpsrv.configureTCPListen(pOurTcpsrv, port, 1); + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet); + if(pOurTcpsrv != NULL) + tcpsrv.Destruct(&pOurTcpsrv); + } + RETiRet; +} + + +/* returns 0 if all went OK, -1 if it failed */ +static int TCPSessGSSInit(void) +{ + gss_buffer_desc name_buf; + gss_name_t server_name; + OM_uint32 maj_stat, min_stat; + + if (gss_server_creds != GSS_C_NO_CREDENTIAL) + return 0; + + name_buf.value = (gss_listen_service_name == NULL) ? "host" : gss_listen_service_name; + name_buf.length = strlen(name_buf.value) + 1; + maj_stat = gss_import_name(&min_stat, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, &server_name); + if (maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("importing name", maj_stat, min_stat); + return -1; + } + + maj_stat = gss_acquire_cred(&min_stat, server_name, 0, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, + &gss_server_creds, NULL, NULL); + if (maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("acquiring credentials", maj_stat, min_stat); + return -1; + } + + gss_release_name(&min_stat, &server_name); + dbgprintf("GSS-API initialized\n"); + return 0; +} + + +/* returns 0 if all went OK, -1 if it failed + * tries to guess if the connection uses gssapi. + */ +static rsRetVal +OnSessAcceptGSS(tcpsrv_t *pThis, tcps_sess_t *pSess) +{ + DEFiRet; + gss_buffer_desc send_tok, recv_tok; + gss_name_t client; + OM_uint32 maj_stat, min_stat, acc_sec_min_stat; + gss_ctx_id_t *context; + OM_uint32 *sess_flags; + int fdSess; + char allowedMethods; + gsssrv_t *pGSrv; + gss_sess_t *pGSess; + + assert(pSess != NULL); + + pGSrv = (gsssrv_t*) pThis->pUsr; + pGSess = (gss_sess_t*) pSess->pUsr; + allowedMethods = pGSrv->allowedMethods; + if(allowedMethods & ALLOWEDMETHOD_GSS) { + /* Buffer to store raw message in case that + * gss authentication fails halfway through. This buffer + * is currently dynamically allocated, for performance + * reasons we should look for a better way to do it. + * rgerhars, 2008-09-02 + */ + char *buf; + int ret = 0; + CHKmalloc(buf = (char*) MALLOC(sizeof(char) * (glbl.GetMaxLine() + 1))); + + dbgprintf("GSS-API Trying to accept TCP session %p\n", pSess); + + CHKiRet(netstrm.GetSock(pSess->pStrm, &fdSess)); // TODO: method access! + if (allowedMethods & ALLOWEDMETHOD_TCP) { + int len; + struct timeval tv; +#ifdef USE_UNLIMITED_SELECT + fd_set *pFds = malloc(glbl.GetFdSetSize()); +#else + fd_set fds; + fd_set *pFds = &fds; +#endif + + do { + FD_ZERO(pFds); + FD_SET(fdSess, pFds); + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = select(fdSess + 1, pFds, NULL, NULL, &tv); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + errmsg.LogError(0, RS_RET_ERR, "TCP session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } else if (ret == 0) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } + + do { + ret = recv(fdSess, buf, sizeof (buf), MSG_PEEK); + } while (ret < 0 && errno == EINTR); + if (ret <= 0) { + if (ret == 0) + dbgprintf("GSS-API Connection closed by peer\n"); + else + errmsg.LogError(0, RS_RET_ERR, "TCP(GSS) session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + + if (ret < 4) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } else if (ret == 4) { + /* The client might has been interupted after sending + * the data length (4B), give him another chance. + */ + srSleep(1, 0); + do { + ret = recv(fdSess, buf, sizeof (buf), MSG_PEEK); + } while (ret < 0 && errno == EINTR); + if (ret <= 0) { + if (ret == 0) + dbgprintf("GSS-API Connection closed by peer\n"); + else + errmsg.LogError(0, NO_ERRCODE, "TCP session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + } + + /* TODO: how does this work together with IPv6? Does it? */ + len = ntohl((buf[0] << 24) + | (buf[1] << 16) + | (buf[2] << 8) + | buf[3]); + if ((ret - 4) < len || len == 0) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } + + freeFdSet(pFds); + } + + context = &pGSess->gss_context; + *context = GSS_C_NO_CONTEXT; + sess_flags = &pGSess->gss_flags; + do { + if (gssutil.recv_token(fdSess, &recv_tok) <= 0) { + errmsg.LogError(0, NO_ERRCODE, "TCP session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + maj_stat = gss_accept_sec_context(&acc_sec_min_stat, context, gss_server_creds, + &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &client, + NULL, &send_tok, sess_flags, NULL, NULL); + if (recv_tok.value) { + free(recv_tok.value); + recv_tok.value = NULL; + } + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { + gss_release_buffer(&min_stat, &send_tok); + if (*context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + if ((allowedMethods & ALLOWEDMETHOD_TCP) && + (GSS_ROUTINE_ERROR(maj_stat) == GSS_S_DEFECTIVE_TOKEN)) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + dbgprintf("tcp session socket with new data: #%d\n", fdSess); + if(tcps_sess.DataRcvd(pSess, buf, ret) != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "Tearing down TCP Session %p - see " + "previous messages for reason(s)\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } + gssutil.display_status("accepting context", maj_stat, acc_sec_min_stat); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + if (send_tok.length != 0) { + if(gssutil.send_token(fdSess, &send_tok) < 0) { + gss_release_buffer(&min_stat, &send_tok); + errmsg.LogError(0, NO_ERRCODE, "TCP session %p will be closed, error ignored\n", pSess); + if (*context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + gss_release_buffer(&min_stat, &send_tok); + } + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + maj_stat = gss_display_name(&min_stat, client, &recv_tok, NULL); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("displaying name", maj_stat, min_stat); + else + dbgprintf("GSS-API Accepted connection from: %s\n", (char*) recv_tok.value); + gss_release_name(&min_stat, &client); + gss_release_buffer(&min_stat, &recv_tok); + + dbgprintf("GSS-API Provided context flags:\n"); + gssutil.display_ctx_flags(*sess_flags); + pGSess->allowedMethods = ALLOWEDMETHOD_GSS; + } + +finalize_it: + RETiRet; +} + + +/* Replaces recv() for gssapi connections. + */ +int TCPSessGSSRecv(tcps_sess_t *pSess, void *buf, size_t buf_len, ssize_t *piLenRcvd) +{ + DEFiRet; + gss_buffer_desc xmit_buf, msg_buf; + gss_ctx_id_t *context; + OM_uint32 maj_stat, min_stat; + int fdSess; + int conf_state; + int state; + gss_sess_t *pGSess; + + assert(pSess->pUsr != NULL); + assert(piLenRcvd != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + netstrm.GetSock(pSess->pStrm, &fdSess); // TODO: method access, CHKiRet! + if ((state = gssutil.recv_token(fdSess, &xmit_buf)) <= 0) + ABORT_FINALIZE(RS_RET_GSS_ERR); + + context = &pGSess->gss_context; + maj_stat = gss_unwrap(&min_stat, *context, &xmit_buf, &msg_buf, + &conf_state, (gss_qop_t *) NULL); + if(maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("unsealing message", maj_stat, min_stat); + if(xmit_buf.value) { + free(xmit_buf.value); + xmit_buf.value = 0; + } + ABORT_FINALIZE(RS_RET_GSS_ERR); + } + if (xmit_buf.value) { + free(xmit_buf.value); + xmit_buf.value = 0; + } + + *piLenRcvd = msg_buf.length < buf_len ? msg_buf.length : buf_len; + memcpy(buf, msg_buf.value, *piLenRcvd); + gss_release_buffer(&min_stat, &msg_buf); + +finalize_it: + RETiRet; +} + + +/* Takes care of cleaning up gssapi stuff and then calls + * TCPSessClose(). + */ +void TCPSessGSSClose(tcps_sess_t* pSess) +{ + OM_uint32 maj_stat, min_stat; + gss_ctx_id_t *context; + gss_sess_t *pGSess; + + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + context = &pGSess->gss_context; + maj_stat = gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("deleting context", maj_stat, min_stat); + *context = GSS_C_NO_CONTEXT; + pGSess->gss_flags = 0; + pGSess->allowedMethods = 0; + + tcps_sess.Close(pSess); +} + + +/* Counterpart of TCPSessGSSInit(). This is called to exit the GSS system + * at all. It is a server-based session exit. + */ +static rsRetVal +TCPSessGSSDeinit(void) +{ + DEFiRet; + OM_uint32 maj_stat, min_stat; + + if (gss_server_creds != GSS_C_NO_CREDENTIAL) { + maj_stat = gss_release_cred(&min_stat, &gss_server_creds); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("releasing credentials", maj_stat, min_stat); + } + RETiRet; +} + + +#if 0 /* can be used to integrate into new config system */ +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf +#endif + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + /* This will fail if the priviledges are dropped. Should be + * moved to the '*activateCnfPrePrivDrop' section eventually. + */ + actGSSListener(srvPort); + + iRet = tcpsrv.Run(pOurTcpsrv); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + if(srvPort == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + + net.PrintAllowedSenders(2); /* TCP */ + net.PrintAllowedSenders(3); /* GSS */ +finalize_it: +ENDwillRun + + + +BEGINmodExit +CODESTARTmodExit + if(pOurTcpsrv != NULL) + iRet = tcpsrv.Destruct(&pOurTcpsrv); + TCPSessGSSDeinit(); + + /* release objects we used */ + objRelease(tcps_sess, LM_TCPSRV_FILENAME); + objRelease(tcpsrv, LM_TCPSRV_FILENAME); + objRelease(gssutil, LM_GSSUTIL_FILENAME); + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(netstrm, LM_NETSTRM_FILENAME); + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"TCP"); + net.clearAllowedSenders((uchar*)"GSS"); +ENDafterRun + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + if (gss_listen_service_name != NULL) { + free(gss_listen_service_name); + gss_listen_service_name = NULL; + } + bPermitPlainTcp = 0; + iTCPSessMax = 200; + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current definition */ +CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(tcps_sess, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(tcpsrv, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(gssutil, LM_GSSUTIL_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(netstrm, LM_NETSTRM_FILENAME)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssserverpermitplaintcp", 0, eCmdHdlrBinary, + NULL, &bPermitPlainTcp, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssserverrun", 0, eCmdHdlrGetWord, + addGSSListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssserverservicename", 0, eCmdHdlrGetWord, + NULL, &gss_listen_service_name, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssservermaxsessions", 0, eCmdHdlrInt, + NULL, &iTCPSessMax, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/imjournal/Makefile.am b/plugins/imjournal/Makefile.am new file mode 100644 index 00000000..df088a3a --- /dev/null +++ b/plugins/imjournal/Makefile.am @@ -0,0 +1,7 @@ +pkglib_LTLIBRARIES = imjournal.la +imjournal_la_SOURCES = imjournal.c imjournal.h + +imjournal_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(LIBSYSTEMD_JOURNAL_CFLAGS) +#imjournal_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(LIBSYSTEMD_JOURNAL_CFLAGS) +imjournal_la_LDFLAGS = -module -avoid-version +imjournal_la_LIBADD = $(LIBSYSTEMD_JOURNAL_LIBS) diff --git a/plugins/imjournal/imjournal.c b/plugins/imjournal/imjournal.c new file mode 100755 index 00000000..36c7e046 --- /dev/null +++ b/plugins/imjournal/imjournal.c @@ -0,0 +1,717 @@ +/* The systemd journal import module + * + * To test under Linux: + * emmit log message into systemd journal + * + * Copyright (C) 2008-2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> +#include <stdlib.h> +#include <time.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <errno.h> +#include <systemd/sd-journal.h> + +#include "dirty.h" +#include "cfsysline.h" +#include "obj.h" +#include "msg.h" +#include "module-template.h" +#include "datetime.h" +#include "imjournal.h" +#include "net.h" +#include "glbl.h" +#include "prop.h" +#include "errmsg.h" +#include "srUtils.h" +#include "unicode-helper.h" +#include "ratelimit.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imjournal") + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(datetime) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(net) +DEFobjCurrIf(errmsg) + +static struct configSettings_s { + char *stateFile; + int iPersistStateInterval; + int ratelimitInterval; + int ratelimitBurst; + int bIgnorePrevious; +} cs; + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "statefile", eCmdHdlrGetWord, 0 }, + { "ratelimit.interval", eCmdHdlrInt, 0 }, + { "ratelimit.burst", eCmdHdlrInt, 0 }, + { "persiststateinterval", eCmdHdlrInt, 0 }, + { "ignorepreviousmessages", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +#define DFLT_persiststateinterval 10 + +static int bLegacyCnfModGlobalsPermitted = 1;/* are legacy module-global config parameters permitted? */ + +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this module */ +static prop_t *pLocalHostIP = NULL; /* a pseudo-constant propterty for 127.0.0.1 */ + +static ratelimit_t *ratelimiter = NULL; +static sd_journal *j; + +/* enqueue the the journal message into the message queue. + * The provided msg string is not freed - thus must be done + * by the caller. + */ +static rsRetVal +enqMsg(uchar *msg, uchar *pszTag, int iFacility, int iSeverity, struct timeval *tp, struct json_object *json) +{ + struct syslogTime st; + msg_t *pMsg; + DEFiRet; + + assert(msg != NULL); + assert(pszTag != NULL); + + if(tp == NULL) { + CHKiRet(msgConstruct(&pMsg)); + } else { + datetime.timeval2syslogTime(tp, &st); + CHKiRet(msgConstructWithTime(&pMsg, &st, tp->tv_sec)); + } + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsgWOSize(pMsg, (char*)msg); + MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */ + MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); + MsgSetRcvFromIP(pMsg, pLocalHostIP); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetTAG(pMsg, pszTag, ustrlen(pszTag)); + pMsg->iFacility = iFacility; + pMsg->iSeverity = iSeverity; + + if(json != NULL) { + msgAddJSON(pMsg, (uchar*)"!", json); + } + + CHKiRet(ratelimitAddMsg(ratelimiter, NULL, pMsg)); + +finalize_it: + RETiRet; +} + + +/* Read journal log while data are available, each read() reads one + * record of printk buffer. + */ +static rsRetVal +readjournal() { + DEFiRet; + + struct timeval tv; + uint64_t timestamp; + + struct json_object *json = NULL; + int r; + + /* Information from messages */ + char *message; + char *sys_pid; + char *sys_iden; + char *sys_iden_help; + + const void *get; + const void *pidget; + char *parse; + char *get2; + size_t length; + size_t pidlength; + + const void *equal_sign; + struct json_object *jval; + char *data; + char *name; + size_t l; + + long prefixlen = 0; + + int priority = 0; + int facility = 0; + + /* Get message text */ + if (sd_journal_get_data(j, "MESSAGE", &get, &length) < 0) { + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar *)"log message from journal doesn't have MESSAGE", 0); + iRet = RS_RET_OK; + goto ret; + } + message = strndup(get+8, length-8); + if (message == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + goto ret; + } + + /* Get message priority */ + if (sd_journal_get_data(j, "PRIORITY", &get, &length) >= 0) { + get2 = strndup(get, length); + priority = ((char *)get2)[9] - '0'; + free (get2); + } + + /* Get syslog facility */ + if (sd_journal_get_data(j, "SYSLOG_FACILITY", &get, &length) >= 0) { + get2 = strndup(get, length); + char f = ((char *)get2)[16]; + if (f >= '0' && f <= '9') { + facility += f - '0'; + } + f = ((char *)get2)[17]; + if (f >= '0' && f <= '9') { + facility *= 10; + facility += (f - '0'); + } + free (get2); + } else { + /* message is missing facility -> internal systemd journal msg, drop */ + iRet = RS_RET_OK; + goto free_message; + } + + /* Get message identifier, client pid and add ':' */ + if (sd_journal_get_data(j, "SYSLOG_IDENTIFIER", &get, &length) >= 0) { + sys_iden = strndup(get+18, length-18); + } else { + sys_iden = strdup("journal"); + } + if (sys_iden == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + goto free_message; + } + + if (sd_journal_get_data(j, "SYSLOG_PID", &pidget, &pidlength) >= 0) { + sys_pid = strndup(pidget+11, pidlength-11); + if (sys_pid == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + free (sys_iden); + goto free_message; + } + } else { + sys_pid = NULL; + } + + if (sys_pid) { + r = asprintf(&sys_iden_help, "%s[%s]:", sys_iden, sys_pid); + } else { + r = asprintf(&sys_iden_help, "%s:", sys_iden); + } + + free (sys_iden); + free (sys_pid); + + if (-1 == r) { + iRet = RS_RET_OUT_OF_MEMORY; + goto finalize_it; + } + + json = json_object_new_object(); + + SD_JOURNAL_FOREACH_DATA(j, get, l) { + /* locate equal sign, this is always present */ + equal_sign = memchr(get, '=', l); + + /* ... but we know better than to trust the specs */ + if (equal_sign == NULL) { + errmsg.LogError(0, RS_RET_ERR, "SD_JOURNAL_FOREACH_DATA()" + "returned a malformed field (has no '='): '%s'", get); + continue; /* skip the entry */ + } + + /* get length of journal data prefix */ + prefixlen = ((char *)equal_sign - (char *)get); + + /* translate name fields to lumberjack names */ + parse = (char *)get; + + switch (*parse) + { + case '_': + ++parse; + if (*parse == 'P') { + if (!strncmp(parse+1, "ID=", 4)) { + name = strdup("pid"); + } else { + name = strndup(get, prefixlen); + } + } else if (*parse == 'G') { + if (!strncmp(parse+1, "ID=", 4)) { + name = strdup("gid"); + } else { + name = strndup(get, prefixlen); + } + } else if (*parse == 'U') { + if (!strncmp(parse+1, "ID=", 4)) { + name = strdup("uid"); + } else { + name = strndup(get, prefixlen); + } + } else if (*parse == 'E') { + if (!strncmp(parse+1, "XE=", 4)) { + name = strdup("exe"); + } else { + name = strndup(get, prefixlen); + } + } else if (*parse == 'C') { + parse++; + if (*parse == 'O') { + if (!strncmp(parse+1, "MM=", 4)) { + name = strdup("appname"); + } else { + name = strndup(get, prefixlen); + } + } else if (*parse == 'M') { + if (!strncmp(parse+1, "DLINE=", 7)) { + name = strdup("cmd"); + } else { + name = strndup(get, prefixlen); + } + } else { + name = strndup(get, prefixlen); + } + } else { + name = strndup(get, prefixlen); + } + break; + + default: + name = strndup(get, prefixlen); + break; + } + + if (name == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + goto ret; + } + + prefixlen++; /* remove '=' */ + + data = strndup(get + prefixlen, l - prefixlen); + if (data == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + free (name); + goto ret; + } + + /* and save them to json object */ + jval = json_object_new_string((char *)data); + json_object_object_add(json, name, jval); + free (data); + free (name); + } + + /* calculate timestamp */ + if (sd_journal_get_realtime_usec(j, ×tamp) >= 0) { + tv.tv_sec = timestamp / 1000000; + tv.tv_usec = timestamp % 1000000; + } + + /* submit message */ + enqMsg((uchar *)message, (uchar *) sys_iden_help, facility, priority, &tv, json); + +finalize_it: + free(sys_iden_help); +free_message: + free(message); +ret: + RETiRet; +} + + +/* This function gets journal cursor and saves it into state file + */ +static rsRetVal +persistJournalState () { + DEFiRet; + FILE *sf; /* state file */ + char *cursor; + int ret = 0; + + /* On success, sd_journal_get_cursor() returns 1 in systemd + 197 or older and 0 in systemd 198 or newer */ + if ((ret = sd_journal_get_cursor(j, &cursor)) >= 0) { + if ((sf = fopen(cs.stateFile, "wb")) != NULL) { + if (fprintf(sf, "%s", cursor) < 0) { + iRet = RS_RET_IO_ERROR; + } + fclose(sf); + free(cursor); + } else { + char errStr[256]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_FOPEN_FAILURE, "fopen() failed: " + "'%s', path: '%s'\n", errStr, cs.stateFile); + iRet = RS_RET_FOPEN_FAILURE; + } + } else { + char errStr[256]; + rs_strerror_r(-(ret), errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR, "sd_journal_get_cursor() failed: '%s'\n", errStr); + iRet = RS_RET_ERR; + } + RETiRet; +} + + +/* Polls the journal for new messages. Similar to sd_journal_wait() + * except for the special handling of EINTR. + */ +static rsRetVal +pollJournal() +{ + DEFiRet; + struct pollfd pollfd; + int r; + + pollfd.fd = sd_journal_get_fd(j); + pollfd.events = sd_journal_get_events(j); + r = poll(&pollfd, 1, -1); + if (r == -1) { + if (errno == EINTR) { + /* EINTR is also received during termination + * so return now to check the term state. + */ + ABORT_FINALIZE(RS_RET_OK); + } else { + char errStr[256]; + + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR, + "poll() failed: '%s'", errStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } + + assert(r == 1); + + r = sd_journal_process(j); + if (r < 0) { + char errStr[256]; + + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR, + "sd_journal_process() failed: '%s'", errStr); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + RETiRet; +} + + +/* This function loads a journal cursor from the state file. + */ +static rsRetVal +loadJournalState() +{ + DEFiRet; + + if (cs.stateFile[0] != '/') { + char *new_stateFile; + + if (-1 == asprintf(&new_stateFile, "%s/%s", (char *)glbl.GetWorkDir(), cs.stateFile)) { + errmsg.LogError(0, RS_RET_OUT_OF_MEMORY, "imjournal: asprintf failed\n"); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + free (cs.stateFile); + cs.stateFile = new_stateFile; + } + + /* if state file exists, set cursor to appropriate position */ + if (access(cs.stateFile, F_OK|R_OK) != -1) { + FILE *r_sf; + + if ((r_sf = fopen(cs.stateFile, "rb")) != NULL) { + char readCursor[128 + 1]; + + if (fscanf(r_sf, "%128s\n", readCursor) != EOF) { + if (sd_journal_seek_cursor(j, readCursor) != 0) { + errmsg.LogError(0, RS_RET_ERR, "imjournal: " + "couldn't seek to cursor `%s'\n", readCursor); + iRet = RS_RET_ERR; + goto finalize_it; + } + sd_journal_next(j); + } else { + errmsg.LogError(0, RS_RET_IO_ERROR, "imjournal: " + "fscanf on state file `%s' failed\n", cs.stateFile); + iRet = RS_RET_IO_ERROR; + goto finalize_it; + } + fclose(r_sf); + } else { + errmsg.LogError(0, RS_RET_FOPEN_FAILURE, "imjournal: " + "open on state file `%s' failed\n", cs.stateFile); + } + } else { + /* when IgnorePrevious, seek to the end of journal */ + if (cs.bIgnorePrevious) { + if (sd_journal_seek_tail(j) < 0) { + char errStr[256]; + + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR, + "sd_journal_seek_tail() failed: '%s'", errStr); + ABORT_FINALIZE(RS_RET_ERR); + } + + if (sd_journal_previous(j) < 0) { + char errStr[256]; + + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR, + "sd_journal_previous() failed: '%s'", errStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } + } + +finalize_it: + RETiRet; +} + +BEGINrunInput +CODESTARTrunInput + CHKiRet(ratelimitNew(&ratelimiter, "imjournal", NULL)); + dbgprintf("imjournal: ratelimiting burst %d, interval %d\n", cs.ratelimitBurst, + cs.ratelimitInterval); + ratelimitSetLinuxLike(ratelimiter, cs.ratelimitInterval, cs.ratelimitBurst); + ratelimitSetNoTimeCache(ratelimiter); + + if (cs.stateFile) { + CHKiRet(loadJournalState()); + } + + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework. + */ + while (glbl.GetGlobalInputTermState() == 0) { + int count = 0, r; + + r = sd_journal_next(j); + if (r < 0) { + char errStr[256]; + + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR, + "sd_journal_next() failed: '%s'", errStr); + ABORT_FINALIZE(RS_RET_ERR); + } + + if (r == 0) { + /* No new messages, wait for activity. */ + CHKiRet(pollJournal()); + continue; + } + + CHKiRet(readjournal()); + if (cs.stateFile) { /* can't persist without a state file */ + /* TODO: This could use some finer metric. */ + count++; + if (count == cs.iPersistStateInterval) { + count = 0; + persistJournalState(); + } + } + } + +finalize_it: +ENDrunInput + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + bLegacyCnfModGlobalsPermitted = 1; + + cs.iPersistStateInterval = DFLT_persiststateinterval; + cs.stateFile = NULL; + cs.ratelimitBurst = 20000; + cs.ratelimitInterval = 600; +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + +/* open journal */ +BEGINwillRun +CODESTARTwillRun + int ret; + ret = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (ret < 0) { + iRet = RS_RET_IO_ERROR; + } +ENDwillRun + +/* close journal */ +BEGINafterRun +CODESTARTafterRun + if (cs.stateFile) { /* can't persist without a state file */ + persistJournalState(); + } + sd_journal_close(j); + ratelimitDestruct(ratelimiter); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pInputName != NULL) + prop.Destruct(&pInputName); + if(pLocalHostIP != NULL) + prop.Destruct(&pLocalHostIP); + + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(net, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if (pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if (Debug) { + dbgprintf("module (global) param blk for imjournal:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for (i = 0 ; i < modpblk.nParams ; ++i) { + if (!pvals[i].bUsed) + continue; + if (!strcmp(modpblk.descr[i].name, "persiststateinterval")) { + cs.iPersistStateInterval = (int) pvals[i].val.d.n; + } else if (!strcmp(modpblk.descr[i].name, "statefile")) { + cs.stateFile = (char *)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "ratelimit.burst")) { + cs.ratelimitBurst = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "ratelimit.interval")) { + cs.ratelimitInterval = (int) pvals[i].val.d.n; + } else if (!strcmp(modpblk.descr[i].name, "ignorepreviousmessages")) { + cs.bIgnorePrevious = (int) pvals[i].val.d.n; + } else { + dbgprintf("imjournal: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + +finalize_it: + if (pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(net, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.CreateStringProp(&pInputName, UCHAR_CONSTANT("imjournal"), sizeof("imjournal") - 1)); + CHKiRet(prop.CreateStringProp(&pLocalHostIP, UCHAR_CONSTANT("127.0.0.1"), sizeof("127.0.0.1") - 1)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalpersiststateinterval", 0, eCmdHdlrInt, + NULL, &cs.iPersistStateInterval, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalratelimitinterval", 0, eCmdHdlrInt, + NULL, &cs.ratelimitInterval, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalratelimitburst", 0, eCmdHdlrInt, + NULL, &cs.ratelimitBurst, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalstatefile", 0, eCmdHdlrGetWord, + NULL, &cs.stateFile, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalignorepreviousmessages", 0, eCmdHdlrBinary, + NULL, &cs.bIgnorePrevious, STD_LOADABLE_MODULE_ID)); + + +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imjournal/imjournal.h b/plugins/imjournal/imjournal.h new file mode 100644 index 00000000..8d2c1a09 --- /dev/null +++ b/plugins/imjournal/imjournal.h @@ -0,0 +1,36 @@ +/* imjournal.h + * These are the definitions for the journal messages import module + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef IMJOURNAL_H_INCLUDED +#define IMJOURNAL_H_INCLUDED 1 + +#include "rsyslog.h" +#include "dirty.h" +#include <systemd/sd-journal.h> + +struct modConfData_s { +}; + +rsRetVal Syslog(int priority, uchar *msg, struct timeval *tp, struct json_object *json); + +#endif /* #ifndef IMJOURNAL_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/plugins/imklog/Makefile.am b/plugins/imklog/Makefile.am new file mode 100644 index 00000000..7d0d37c9 --- /dev/null +++ b/plugins/imklog/Makefile.am @@ -0,0 +1,15 @@ +pkglib_LTLIBRARIES = imklog.la +imklog_la_SOURCES = imklog.c imklog.h + +# select klog "driver" +if ENABLE_IMKLOG_BSD +imklog_la_SOURCES += bsd.c +endif + +if ENABLE_IMKLOG_LINUX +imklog_la_SOURCES += bsd.c +endif + +imklog_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imklog_la_LDFLAGS = -module -avoid-version +imklog_la_LIBADD = diff --git a/plugins/imklog/bsd.c b/plugins/imklog/bsd.c new file mode 100644 index 00000000..9c2eebb2 --- /dev/null +++ b/plugins/imklog/bsd.c @@ -0,0 +1,298 @@ +/* combined imklog driver for BSD and Linux + * + * This contains OS-specific functionality to read the BSD + * or Linux kernel log. For a general overview, see head comment in + * imklog.c. This started out as the BSD-specific drivers, but it + * turned out that on modern Linux the implementation details + * are very small, and so we use a single driver for both OS's with + * a little help of conditional compilation. + * + * Copyright 2008-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#ifdef OS_LINUX +# include <sys/klog.h> +#endif + +#include "rsyslog.h" +#include "srUtils.h" +#include "debug.h" +#include "imklog.h" + +/* globals */ +static int fklog = -1; /* kernel log fd */ + +#ifndef _PATH_KLOG +# ifdef OS_LINUX +# define _PATH_KLOG "/proc/kmsg" +# else +# define _PATH_KLOG "/dev/klog" +# endif +#endif + + +#ifdef OS_LINUX +/* submit a message to imklog Syslog() API. In this function, we check if + * a kernel timestamp is present and, if so, extract and strip it. + * Note that this is heavily Linux specific and thus is not compiled or + * used for BSD. + * Special thanks to Lennart Poettering for suggesting on how to convert + * the kernel timestamp to a realtime timestamp. This method depends on + * the fact the the kernel timestamp is written using the monotonic clock. + * Shall that change (very unlikely), this code must be changed as well. Note + * that due to the way we generate the delta, we are unable to write the + * absolutely correct timestamp (system call overhead of the clock calls + * prevents us from doing so). However, the difference is very minor. + * rgerhards, 2011-06-24 + */ +static void +submitSyslog(modConfData_t *pModConf, int pri, uchar *buf) +{ + long secs; + long usecs; + long secOffs; + long usecOffs; + unsigned i; + unsigned bufsize; + struct timespec monotonic, realtime; + struct timeval tv; + struct timeval *tp = NULL; + + if(!pModConf->bParseKernelStamp) + goto done; + + if(buf[3] != '[') + goto done; + DBGPRINTF("imklog: kernel timestamp detected, extracting it\n"); + + /* we now try to parse the timestamp. iff it parses, we assume + * it is a timestamp. Otherwise we know for sure it is no ts ;) + */ + i = 4; /* space or first digit after '[' */ + while(buf[i] && isspace(buf[i])) + ++i; /* skip space */ + secs = 0; + while(buf[i] && isdigit(buf[i])) { + secs = secs * 10 + buf[i] - '0'; + ++i; + } + if(buf[i] != '.') { + DBGPRINTF("no dot --> no kernel timestamp\n"); + goto done; /* no TS! */ + } + + ++i; /* skip dot */ + usecs = 0; + while(buf[i] && isdigit(buf[i])) { + usecs = usecs * 10 + buf[i] - '0'; + ++i; + } + if(buf[i] != ']') { + DBGPRINTF("no trailing ']' --> no kernel timestamp\n"); + goto done; /* no TS! */ + } + ++i; /* skip ']' */ + + /* we have a timestamp */ + DBGPRINTF("kernel timestamp is %ld %ld\n", secs, usecs); + if(!pModConf->bKeepKernelStamp) { + bufsize= strlen((char*)buf); + memmove(buf+3, buf+i, bufsize - i + 1); + } + + clock_gettime(CLOCK_MONOTONIC, &monotonic); + clock_gettime(CLOCK_REALTIME, &realtime); + secOffs = realtime.tv_sec - monotonic.tv_sec; + usecOffs = (realtime.tv_nsec - monotonic.tv_nsec) / 1000; + if(usecOffs < 0) { + secOffs--; + usecOffs += 1000000l; + } + + usecs += usecOffs; + if(usecs > 999999l) { + secs++; + usecs -= 1000000l; + } + secs += secOffs; + tv.tv_sec = secs; + tv.tv_usec = usecs; + tp = &tv; + +done: + Syslog(pri, buf, tp); +} +#else /* now comes the BSD "code" (just a shim) */ +static void +submitSyslog(modConfData_t *pModConf, int pri, uchar *buf) +{ + Syslog(pri, buf, NULL); +} +#endif /* #ifdef LINUX */ + + +static uchar *GetPath(modConfData_t *pModConf) +{ + return pModConf->pszPath ? pModConf->pszPath : (uchar*) _PATH_KLOG; +} + +/* open the kernel log - will be called inside the willRun() imklog + * entry point. -- rgerhards, 2008-04-09 + */ +rsRetVal +klogWillRun(modConfData_t *pModConf) +{ + char errmsg[2048]; + int r; + DEFiRet; + + fklog = open((char*)GetPath(pModConf), O_RDONLY, 0); + if (fklog < 0) { + imklogLogIntMsg(LOG_ERR, "imklog: cannot open kernel log(%s): %s.", + GetPath(pModConf), rs_strerror_r(errno, errmsg, sizeof(errmsg))); + ABORT_FINALIZE(RS_RET_ERR_OPEN_KLOG); + } + +# ifdef OS_LINUX + /* Set level of kernel console messaging.. */ + if(pModConf->console_log_level != -1) { + r = klogctl(8, NULL, pModConf->console_log_level); + if(r != 0) { + imklogLogIntMsg(LOG_WARNING, "imklog: cannot set console log level: %s", + rs_strerror_r(errno, errmsg, sizeof(errmsg))); + /* make sure we do not try to re-set! */ + pModConf->console_log_level = -1; + } + } +# endif /* #ifdef OS_LINUX */ + +finalize_it: + RETiRet; +} + + +/* Read kernel log while data are available, split into lines. + */ +static void +readklog(modConfData_t *pModConf) +{ + char *p, *q; + int len, i; + int iMaxLine; + uchar bufRcv[128*1024+1]; + char errmsg[2048]; + uchar *pRcv = NULL; /* receive buffer */ + + iMaxLine = klog_getMaxLine(); + + /* we optimize performance: if iMaxLine is below our fixed size buffer (which + * usually is sufficiently large), we use this buffer. if it is higher, heap memory + * is used. We could use alloca() to achive a similar aspect, but there are so + * many issues with alloca() that I do not want to take that route. + * rgerhards, 2008-09-02 + */ + if((size_t) iMaxLine < sizeof(bufRcv) - 1) { + pRcv = bufRcv; + } else { + if((pRcv = (uchar*) MALLOC(sizeof(uchar) * (iMaxLine + 1))) == NULL) + iMaxLine = sizeof(bufRcv) - 1; /* better this than noting */ + } + + len = 0; + for (;;) { + dbgprintf("imklog(BSD/Linux) waiting for kernel log line\n"); + i = read(fklog, pRcv + len, iMaxLine - len); + if (i > 0) { + pRcv[i + len] = '\0'; + } else { + if (i < 0 && errno != EINTR && errno != EAGAIN) { + imklogLogIntMsg(LOG_ERR, + "imklog: error reading kernel log - shutting down: %s", + rs_strerror_r(errno, errmsg, sizeof(errmsg))); + fklog = -1; + } + break; + } + + for (p = (char*)pRcv; (q = strchr(p, '\n')) != NULL; p = q + 1) { + *q = '\0'; + submitSyslog(pModConf, LOG_INFO, (uchar*) p); + } + len = strlen(p); + if (len >= iMaxLine - 1) { + submitSyslog(pModConf, LOG_INFO, (uchar*)p); + len = 0; + } + if(len > 0) + memmove(pRcv, p, len + 1); + } + if (len > 0) + submitSyslog(pModConf, LOG_INFO, pRcv); + + if(pRcv != NULL && (size_t) iMaxLine >= sizeof(bufRcv) - 1) + free(pRcv); +} + + +/* to be called in the module's AfterRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogAfterRun(modConfData_t *pModConf) +{ + DEFiRet; + if(fklog != -1) + close(fklog); +# ifdef OS_LINUX + /* Turn on logging of messages to console, but only if a log level was speficied */ + if(pModConf->console_log_level != -1) + klogctl(7, NULL, 0); +# endif + RETiRet; +} + + + +/* to be called in the module's WillRun entry point, this is the main + * "message pull" mechanism. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(modConfData_t *pModConf) +{ + DEFiRet; + readklog(pModConf); + RETiRet; +} + + +/* provide the (system-specific) default facility for internal messages + * rgerhards, 2008-04-14 + */ +int +klogFacilIntMsg(void) +{ + return LOG_SYSLOG; +} diff --git a/plugins/imklog/imklog.c b/plugins/imklog/imklog.c new file mode 100644 index 00000000..810ac264 --- /dev/null +++ b/plugins/imklog/imklog.c @@ -0,0 +1,488 @@ +/* The kernel log module. + * + * This is an abstracted module. As Linux and BSD kernel log is conceptually the + * same, we do not do different input plugins for them but use + * imklog in both cases, just with different "backend drivers" for + * the different platforms. This also enables a rsyslog.conf to + * be used on multiple platforms without the need to take care of + * what the kernel log is coming from. + * + * See platform-specific files (e.g. linux.c, bsd.c) in the plugin's + * working directory. For other systems with similar kernel logging + * functionality, no new input plugin shall be written but rather a + * driver be developed for imklog. Please note that imklog itself is + * mostly concerned with handling the interface. Any real action happens + * in the drivers, as things may be pretty different on different + * platforms. + * + * Please note that this file replaces the klogd daemon that was + * also present in pre-v3 versions of rsyslog. + * + * To test under Linux: + * echo test1 > /dev/kmsg + * + * Copyright (C) 2008-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> +#include <stdlib.h> +#include <sys/socket.h> + +#include "dirty.h" +#include "cfsysline.h" +#include "obj.h" +#include "msg.h" +#include "module-template.h" +#include "datetime.h" +#include "imklog.h" +#include "net.h" +#include "glbl.h" +#include "prop.h" +#include "errmsg.h" +#include "unicode-helper.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imklog") + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(datetime) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(net) +DEFobjCurrIf(errmsg) + +/* config settings */ +typedef struct configSettings_s { + int bPermitNonKernel; /* permit logging of messages not having LOG_KERN facility */ + int bParseKernelStamp; /* if try to parse kernel timestamps for message time */ + int bKeepKernelStamp; /* keep the kernel timestamp in the message */ + int iFacilIntMsg; /* the facility to use for internal messages (set by driver) */ + uchar *pszPath; + int console_log_level; /* still used for BSD */ +} configSettings_t; +static configSettings_t cs; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "logpath", eCmdHdlrGetWord, 0 }, + { "permitnonkernelfacility", eCmdHdlrBinary, 0 }, + { "consoleloglevel", eCmdHdlrInt, 0 }, + { "parsekerneltimestamp", eCmdHdlrBinary, 0 }, + { "keepkerneltimestamp", eCmdHdlrBinary, 0 }, + { "internalmsgfacility", eCmdHdlrFacility, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this module */ +static prop_t *pLocalHostIP = NULL; + +static inline void +initConfigSettings(void) +{ + cs.bPermitNonKernel = 0; + cs.bParseKernelStamp = 0; + cs.bKeepKernelStamp = 0; + cs.console_log_level = -1; + cs.pszPath = NULL; + cs.iFacilIntMsg = klogFacilIntMsg(); +} + + +/* enqueue the the kernel message into the message queue. + * The provided msg string is not freed - thus must be done + * by the caller. + * rgerhards, 2008-04-12 + */ +static rsRetVal +enqMsg(uchar *msg, uchar* pszTag, int iFacility, int iSeverity, struct timeval *tp) +{ + struct syslogTime st; + msg_t *pMsg; + DEFiRet; + + assert(msg != NULL); + assert(pszTag != NULL); + + if(tp == NULL) { + CHKiRet(msgConstruct(&pMsg)); + } else { + datetime.timeval2syslogTime(tp, &st); + CHKiRet(msgConstructWithTime(&pMsg, &st, tp->tv_sec)); + } + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsgWOSize(pMsg, (char*)msg); + MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */ + MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); + MsgSetRcvFromIP(pMsg, pLocalHostIP); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetTAG(pMsg, pszTag, ustrlen(pszTag)); + pMsg->iFacility = iFacility; + pMsg->iSeverity = iSeverity; + /* note: we do NOT use rate-limiting, as the kernel itself does rate-limiting */ + CHKiRet(submitMsg2(pMsg)); + +finalize_it: + RETiRet; +} + +/* parse the PRI from a kernel message. At least BSD seems to have + * non-kernel messages inside the kernel log... + * Expected format: "<pri>". piPri is only valid if the function + * successfully returns. If there was a proper pri ppSz is advanced to the + * position right after ">". + * rgerhards, 2008-04-14 + */ +static rsRetVal +parsePRI(uchar **ppSz, int *piPri) +{ + DEFiRet; + int i; + uchar *pSz; + + assert(ppSz != NULL); + pSz = *ppSz; + assert(pSz != NULL); + assert(piPri != NULL); + + if(*pSz != '<' || !isdigit(*(pSz+1))) + ABORT_FINALIZE(RS_RET_INVALID_PRI); + + ++pSz; + i = 0; + while(isdigit(*pSz)) { + i = i * 10 + *pSz++ - '0'; + } + + if(*pSz != '>') + ABORT_FINALIZE(RS_RET_INVALID_PRI); + + /* OK, we have a valid PRI */ + *piPri = i; + *ppSz = pSz + 1; /* update msg ptr to position after PRI */ + +finalize_it: + RETiRet; +} + + +/* log an imklog-internal message + * rgerhards, 2008-04-14 + */ +rsRetVal imklogLogIntMsg(int priority, char *fmt, ...) +{ + DEFiRet; + va_list ap; + uchar msgBuf[2048]; /* we use the same size as sysklogd to remain compatible */ + + va_start(ap, fmt); + vsnprintf((char*)msgBuf, sizeof(msgBuf) / sizeof(char), fmt, ap); + va_end(ap); + + logmsgInternal(NO_ERRCODE ,priority, msgBuf, 0); + + RETiRet; +} + + +/* log a kernel message. If tp is non-NULL, it contains the message creation + * time to use. + * rgerhards, 2008-04-14 + */ +rsRetVal Syslog(int priority, uchar *pMsg, struct timeval *tp) +{ + int pri = -1; + rsRetVal localRet; + DEFiRet; + + /* then check if we have two PRIs. This can happen in case of systemd, + * in which case the second PRI is the right one. + */ + if(pMsg[3] == '<' || (pMsg[3] == ' ' && pMsg[4] == '<')) { /* could be a pri... */ + uchar *pMsgTmp = pMsg + ((pMsg[3] == '<') ? 3 : 4); + localRet = parsePRI(&pMsgTmp, &pri); + if(localRet == RS_RET_OK && pri >= 8 && pri <= 192) { + /* *this* is our PRI */ + DBGPRINTF("imklog detected secondary PRI(%d) in klog msg\n", pri); + pMsg = pMsgTmp; + priority = pri; + } + } + if(pri == -1) { + localRet = parsePRI(&pMsg, &priority); + if(localRet != RS_RET_INVALID_PRI && localRet != RS_RET_OK) + FINALIZE; + } + /* if we don't get the pri, we use whatever we were supplied */ + + /* ignore non-kernel messages if not permitted */ + if(cs.bPermitNonKernel == 0 && LOG_FAC(priority) != LOG_KERN) + FINALIZE; /* silently ignore */ + + iRet = enqMsg((uchar*)pMsg, (uchar*) "kernel:", LOG_FAC(priority), LOG_PRI(priority), tp); + +finalize_it: + RETiRet; +} + + +/* helper for some klog drivers which need to know the MaxLine global setting. They can + * not obtain it themselfs, because they are no modules and can not query the object hander. + * It would probably be a good idea to extend the interface to support it, but so far + * we create a (sufficiently valid) work-around. -- rgerhards, 2008-11-24 + */ +int klog_getMaxLine(void) +{ + return glbl.GetMaxLine(); +} + + +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(!pThrd->bShallStop) { + /* klogLogKMsg() waits for the next kernel message, obtains it + * and then submits it to the rsyslog main queue. + * rgerhards, 2008-04-09 + */ + CHKiRet(klogLogKMsg(runModConf)); + } +finalize_it: +ENDrunInput + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + pModConf->pszPath = NULL; + pModConf->bPermitNonKernel = 0; + pModConf->bParseKernelStamp = 0; + pModConf->bKeepKernelStamp = 0; + pModConf->console_log_level = -1; + pModConf->bKeepKernelStamp = 0; + pModConf->iFacilIntMsg = klogFacilIntMsg(); + loadModConf->configSetViaV2Method = 0; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config vars */ + initConfigSettings(); +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imklog:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "logpath")) { + loadModConf->pszPath = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "permitnonkernelfacility")) { + loadModConf->bPermitNonKernel = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "parsekerneltimestamp")) { + loadModConf->bParseKernelStamp = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "keepkerneltimestamp")) { + loadModConf->bKeepKernelStamp = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "consoleloglevel")) { + loadModConf->console_log_level= (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "internalmsgfacility")) { + loadModConf->iFacilIntMsg = (int) pvals[i].val.d.n; + } else { + dbgprintf("imklog: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* disable legacy module-global config directives */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->bPermitNonKernel = cs.bPermitNonKernel; + loadModConf->bParseKernelStamp = cs.bParseKernelStamp; + loadModConf->bKeepKernelStamp = cs.bKeepKernelStamp; + loadModConf->iFacilIntMsg = cs.iFacilIntMsg; + loadModConf->console_log_level = cs.console_log_level; + if((cs.pszPath == NULL) || (cs.pszPath[0] == '\0')) { + loadModConf->pszPath = NULL; + if(cs.pszPath != NULL) + free(cs.pszPath); + } else { + loadModConf->pszPath = cs.pszPath; + } + cs.pszPath = NULL; + } + + loadModConf = NULL; /* done loading */ +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop +CODESTARTactivateCnfPrePrivDrop + runModConf = pModConf; + iRet = klogWillRun(runModConf); +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + iRet = klogAfterRun(runModConf); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pInputName != NULL) + prop.Destruct(&pInputName); + if(pLocalHostIP != NULL) + prop.Destruct(&pLocalHostIP); + + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(net, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.bPermitNonKernel = 0; + cs.bParseKernelStamp = 0; + cs.bKeepKernelStamp = 0; + if(cs.pszPath != NULL) { + free(cs.pszPath); + cs.pszPath = NULL; + } + cs.iFacilIntMsg = klogFacilIntMsg(); + return RS_RET_OK; +} + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(net, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.CreateStringProp(&pInputName, UCHAR_CONSTANT("imklog"), sizeof("imklog") - 1)); + CHKiRet(prop.CreateStringProp(&pLocalHostIP, UCHAR_CONSTANT("127.0.0.1"), sizeof("127.0.0.1") - 1)); + + /* init legacy config settings */ + initConfigSettings(); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"debugprintkernelsymbols", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(regCfSysLineHdlr2((uchar *)"klogpath", 0, eCmdHdlrGetWord, + NULL, &cs.pszPath, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbollookup", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbolstwice", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogusesyscallinterface", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(regCfSysLineHdlr2((uchar *)"klogpermitnonkernelfacility", 0, eCmdHdlrBinary, + NULL, &cs.bPermitNonKernel, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"klogconsoleloglevel", 0, eCmdHdlrInt, + NULL, &cs.console_log_level, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"kloginternalmsgfacility", 0, eCmdHdlrFacility, + NULL, &cs.iFacilIntMsg, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"klogparsekerneltimestamp", 0, eCmdHdlrBinary, + NULL, &cs.bParseKernelStamp, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"klogkeepkerneltimestamp", 0, eCmdHdlrBinary, + NULL, &cs.bKeepKernelStamp, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imklog/imklog.h b/plugins/imklog/imklog.h new file mode 100644 index 00000000..1cf9b05a --- /dev/null +++ b/plugins/imklog/imklog.h @@ -0,0 +1,70 @@ +/* imklog.h + * These are the definitions for the klog message generation module. + * + * File begun on 2007-12-17 by RGerhards + * Major change: 2008-04-09: switched to a driver interface for + * several platforms + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef IMKLOG_H_INCLUDED +#define IMKLOG_H_INCLUDED 1 + +#include "rsyslog.h" +#include "dirty.h" + +/* we need to have the modConf type present in all submodules */ +struct modConfData_s { + rsconf_t *pConf; + int iFacilIntMsg; + uchar *pszPath; + int console_log_level; + sbool bParseKernelStamp; + sbool bKeepKernelStamp; + sbool bPermitNonKernel; + sbool configSetViaV2Method; +}; + +/* interface to "drivers" + * the platform specific drivers must implement these entry points. Only one + * driver may be active at any given time, thus we simply rely on the linker + * to resolve the addresses. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(modConfData_t *pModConf); +rsRetVal klogWillRun(modConfData_t *pModConf); +rsRetVal klogAfterRun(modConfData_t *pModConf); +int klogFacilIntMsg(); + +/* the functions below may be called by the drivers */ +rsRetVal imklogLogIntMsg(int priority, char *fmt, ...) __attribute__((format(printf,2, 3))); +rsRetVal Syslog(int priority, uchar *msg, struct timeval *tp); + +/* prototypes */ +extern int klog_getMaxLine(void); /* work-around for klog drivers to get configured max line size */ +extern int InitKsyms(modConfData_t*); +extern void DeinitKsyms(void); +extern int InitMsyms(void); +extern void DeinitMsyms(void); +extern char * ExpandKadds(char *, char *); +extern void SetParanoiaLevel(int); + +#endif /* #ifndef IMKLOG_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/plugins/imklog/solaris.c b/plugins/imklog/solaris.c new file mode 100644 index 00000000..0a169cdd --- /dev/null +++ b/plugins/imklog/solaris.c @@ -0,0 +1,116 @@ +/* klog driver for solaris + * + * This contains OS-specific functionality to read the + * kernel log. For a general overview, see head comment in + * imklog.c. + * + * This file relies on Sun code in solaris_cddl.c. We have split + * it from Sun's code to keep the copyright issue as simple as possible. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * If that may be required, an exception is granted to permit linking + * this code to the code in solaris_cddl.c that is under the cddl license. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> + + + +#include "rsyslog.h" +#include "imklog.h" +#include "srUtils.h" +#include "unicode-helper.h" +#include "solaris_cddl.h" + +/* globals */ +static int fklog; // TODO: remove +#ifndef _PATH_KLOG +# define _PATH_KLOG "/dev/log" +#endif + + +static uchar *GetPath(void) +{ + return pszPath ? pszPath : UCHAR_CONSTANT(_PATH_KLOG); +} + +/* open the kernel log - will be called inside the willRun() imklog + * entry point. -- rgerhards, 2008-04-09 + */ +rsRetVal +klogWillRun(void) +{ + DEFiRet; + + fklog = sun_openklog((char*) GetPath(), O_RDONLY); + if (fklog < 0) { + char errStr[1024]; + int err = errno; + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGPRINTF("error %s opening log socket: %s\n", + errStr, GetPath()); + iRet = RS_RET_ERR; // TODO: better error code + } + + RETiRet; +} + + +/* to be called in the module's AfterRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogAfterRun(void) +{ + DEFiRet; + if(fklog != -1) + close(fklog); + RETiRet; +} + + + +/* to be called in the module's WillRun entry point, this is the main + * "message pull" mechanism. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(void) +{ + DEFiRet; + sun_sys_poll(); + RETiRet; +} + + +/* provide the (system-specific) default facility for internal messages + * rgerhards, 2008-04-14 + */ +int +klogFacilIntMsg(void) +{ + return LOG_SYSLOG; +} + diff --git a/plugins/imklog/solaris_cddl.c b/plugins/imklog/solaris_cddl.c new file mode 100644 index 00000000..cf5467fc --- /dev/null +++ b/plugins/imklog/solaris_cddl.c @@ -0,0 +1,293 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* Portions Copyright 2010 by Rainer Gerhards and Adiscon + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T + * All Rights Reserved + */ + +/* + * University Copyright- Copyright (c) 1982, 1986, 1988 + * The Regents of the University of California + * All Rights Reserved + * + * University Acknowledgment- Portions of this document are derived from + * software developed by the University of California, Berkeley, and its + * contributors. + */ +#include "config.h" +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <pthread.h> +#include <sys/poll.h> +#include <pthread.h> +#include <fcntl.h> +#include <stropts.h> +#include <assert.h> +#include <sys/strlog.h> + +#include "rsyslog.h" +#include "imklog.h" + +/* TODO: this define should be changed over time to the more generic + * system-provided (configurable) upper limit. However, it is quite + * unexpected that Solaris-emitted messages are so long, so it seems + * acceptable to set a fixed (relatively high) limit for the time + * being -- and gain some experience with it. -- rgerhars, 2010-04-12 + */ +#define MAXLINE 4096 + +static struct pollfd Pfd; /* Pollfd for local the log device */ + + +/* findnl_bkwd: + * Scans each character in buf until it finds the last newline in buf, + * or the scanned character becomes the last COMPLETE character in buf. + * Returns the number of scanned bytes. + * + * buf - pointer to a buffer containing the message string + * len - the length of the buffer + */ +size_t +findnl_bkwd(const char *buf, const size_t len) +{ + const char *p; + size_t mb_cur_max; + + if (len == 0) { + return (0); + } + + mb_cur_max = MB_CUR_MAX; + + if (mb_cur_max == 1) { + /* single-byte locale */ + for (p = buf + len - 1; p != buf; p--) { + if (*p == '\n') { + return ((size_t)(p - buf)); + } + } + return ((size_t)len); + } else { + /* multi-byte locale */ + int mlen; + const char *nl; + size_t rem; + + p = buf; + nl = NULL; + for (rem = len; rem >= mb_cur_max; ) { + mlen = mblen(p, mb_cur_max); + if (mlen == -1) { + /* + * Invalid character found. + */ + dbgprintf("klog:findnl_bkwd: Invalid MB sequence\n"); + /* + * handle as a single byte character. + */ + p++; + rem--; + } else { + /* + * It's guaranteed that *p points to + * the 1st byte of a multibyte character. + */ + if (*p == '\n') { + nl = p; + } + p += mlen; + rem -= mlen; + } + } + if (nl) { + return ((size_t)(nl - buf)); + } + /* + * no newline nor null byte found. + * Also it's guaranteed that *p points to + * the 1st byte of a (multibyte) character + * at this point. + */ + return (len - rem); + } +} +//___ end + + +/* Attempts to open the local log device + * and return a file descriptor. + */ +int +sun_openklog(char *name, int mode) +{ + int fd; + struct strioctl str; + + if ((fd = open(name, mode)) < 0) { + dbgprintf("klog:openklog: cannot open %s (%d)\n", + name, errno); + return (-1); + } + str.ic_cmd = I_CONSLOG; + str.ic_timout = 0; + str.ic_len = 0; + str.ic_dp = NULL; + if (ioctl(fd, I_STR, &str) < 0) { + dbgprintf("klog:openklog: cannot register to log " + "console messages (%d)\n", errno); + return (-1); + } + Pfd.fd = fd; + Pfd.events = POLLIN; + return (fd); +} + + +/* + * Pull up one message from log driver. + */ +void +sun_getkmsg() +{ + int flags = 0, i; + char *lastline; + struct strbuf ctl, dat; + struct log_ctl hdr; + char buf[MAXLINE+1]; + size_t buflen; + size_t len; + char tmpbuf[MAXLINE+1]; + + dat.maxlen = MAXLINE; + dat.buf = buf; + ctl.maxlen = sizeof (struct log_ctl); + ctl.buf = (caddr_t)&hdr; + + while ((i = getmsg(Pfd.fd, &ctl, &dat, &flags)) == MOREDATA) { + lastline = &dat.buf[dat.len]; + *lastline = '\0'; + + dbgprintf("klog:sys_poll: getmsg: dat.len = %d\n", dat.len); + buflen = strlen(buf); + len = findnl_bkwd(buf, buflen); + + (void) memcpy(tmpbuf, buf, len); + tmpbuf[len] = '\0'; + + Syslog(LOG_INFO, (uchar*) buf); + + if (len != buflen) { + /* If anything remains in buf */ + size_t remlen; + + if (buf[len] == '\n') { + /* skip newline */ + len++; + } + + /* Move the remaining bytes to + * the beginnning of buf. + */ + + remlen = buflen - len; + (void) memmove(buf, &buf[len], remlen); + dat.maxlen = MAXLINE - remlen; + dat.buf = &buf[remlen]; + } else { + dat.maxlen = MAXLINE; + dat.buf = buf; + } + } + + if (i == 0 && dat.len > 0) { + dat.buf[dat.len] = '\0'; + /* Format sys will enqueue the log message. + * Set the sync flag if timeout != 0, which + * means that we're done handling all the + * initial messages ready during startup. + */ + dbgprintf("klog:getkmsg: getmsg: dat.maxlen = %d\n", dat.maxlen); + dbgprintf("klog:getkmsg: getmsg: dat.len = %d\n", dat.len); + dbgprintf("klog:getkmsg: getmsg: strlen(dat.buf) = %d\n", strlen(dat.buf)); + dbgprintf("klog:getkmsg: getmsg: dat.buf = \"%s\"\n", dat.buf); + dbgprintf("klog:getkmsg: buf len = %d\n", strlen(buf)); + Syslog(LOG_INFO, (uchar*) buf); + } else if (i < 0 && errno != EINTR) { + if(1){ /* V5-TODO: rsyslog-like termination! (!shutting_down) { */ + dbgprintf("klog:kernel log driver read error"); + } + // TODO trigger retry logic + //(void) close(Pfd.fd); + //Pfd.fd = -1; + } +} + + +/* this thread listens to the local stream log driver for log messages + * generated by this host, formats them, and queues them to the logger + * thread. + */ +/*ARGSUSED*/ +void * +sun_sys_poll() +{ + int nfds; + + dbgprintf("klog:sys_poll: sys_thread started\n"); + + for (;;) { + errno = 0; + + nfds = poll(&Pfd, 1, INFTIM); + + if (nfds == 0) + continue; + + if (nfds < 0) { + if (errno != EINTR) + dbgprintf("klog:poll error"); + continue; + } + if (Pfd.revents & POLLIN) { + sun_getkmsg(); + } else { + /* TODO: shutdown, the rsyslog way (in v5!) -- check shutdown flag */ + if (Pfd.revents & (POLLNVAL|POLLHUP|POLLERR)) { + // TODO: trigger retry logic +/* logerror("kernel log driver poll error"); + (void) close(Pfd.fd); + Pfd.fd = -1; + */ + } + } + + } + /*NOTREACHED*/ + return (NULL); +} diff --git a/plugins/imklog/solaris_cddl.h b/plugins/imklog/solaris_cddl.h new file mode 100644 index 00000000..d48ef628 --- /dev/null +++ b/plugins/imklog/solaris_cddl.h @@ -0,0 +1,2 @@ +void *sun_sys_poll(); +int sun_openklog(char *name, int mode); diff --git a/plugins/imkmsg/Makefile.am b/plugins/imkmsg/Makefile.am new file mode 100644 index 00000000..87c177d2 --- /dev/null +++ b/plugins/imkmsg/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = imkmsg.la +imkmsg_la_SOURCES = imkmsg.c imkmsg.h + +imkmsg_la_SOURCES += kmsg.c + +imkmsg_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imkmsg_la_LDFLAGS = -module -avoid-version +imkmsg_la_LIBADD = diff --git a/plugins/imkmsg/imkmsg.c b/plugins/imkmsg/imkmsg.c new file mode 100644 index 00000000..2a97f82d --- /dev/null +++ b/plugins/imkmsg/imkmsg.c @@ -0,0 +1,295 @@ +/* The kernel log module. + * + * This is rsyslog Linux only module for reading structured kernel logs. + * Module is based on imklog module so it retains its structure + * and other part is currently in kmsg.c file instead of this (imkmsg.c) + * For more information see that file. + * + * To test under Linux: + * echo test1 > /dev/kmsg + * + * Copyright (C) 2008-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> +#include <stdlib.h> +#include <sys/socket.h> + +#include "dirty.h" +#include "cfsysline.h" +#include "obj.h" +#include "msg.h" +#include "module-template.h" +#include "datetime.h" +#include "imkmsg.h" +#include "net.h" +#include "glbl.h" +#include "prop.h" +#include "errmsg.h" +#include "unicode-helper.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imkmsg") + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(datetime) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(net) +DEFobjCurrIf(errmsg) + +/* config settings */ +typedef struct configSettings_s { + int iFacilIntMsg; /* the facility to use for internal messages (set by driver) */ +} configSettings_t; +static configSettings_t cs; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this module */ +static prop_t *pLocalHostIP = NULL; /* a pseudo-constant propterty for 127.0.0.1 */ + +static inline void +initConfigSettings(void) +{ + cs.iFacilIntMsg = klogFacilIntMsg(); +} + + +/* enqueue the the kernel message into the message queue. + * The provided msg string is not freed - thus must be done + * by the caller. + * rgerhards, 2008-04-12 + */ +static rsRetVal +enqMsg(uchar *msg, uchar* pszTag, int iFacility, int iSeverity, struct timeval *tp, struct json_object *json) +{ + struct syslogTime st; + msg_t *pMsg; + DEFiRet; + + assert(msg != NULL); + assert(pszTag != NULL); + + if(tp == NULL) { + CHKiRet(msgConstruct(&pMsg)); + } else { + datetime.timeval2syslogTime(tp, &st); + CHKiRet(msgConstructWithTime(&pMsg, &st, tp->tv_sec)); + } + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsgWOSize(pMsg, (char*)msg); + MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */ + MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); + MsgSetRcvFromIP(pMsg, pLocalHostIP); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetTAG(pMsg, pszTag, ustrlen(pszTag)); + pMsg->iFacility = iFacility; + pMsg->iSeverity = iSeverity; + pMsg->json = json; + CHKiRet(submitMsg(pMsg)); + +finalize_it: + RETiRet; +} + + +/* log an imkmsg-internal message + * rgerhards, 2008-04-14 + */ +rsRetVal imkmsgLogIntMsg(int priority, char *fmt, ...) +{ + DEFiRet; + va_list ap; + uchar msgBuf[2048]; /* we use the same size as sysklogd to remain compatible */ + + va_start(ap, fmt); + vsnprintf((char*)msgBuf, sizeof(msgBuf) / sizeof(char), fmt, ap); + va_end(ap); + + logmsgInternal(NO_ERRCODE ,priority, msgBuf, 0); + + RETiRet; +} + + +/* log a message from /dev/kmsg + */ +rsRetVal Syslog(int priority, uchar *pMsg, struct timeval *tp, struct json_object *json) +{ + DEFiRet; + iRet = enqMsg((uchar*)pMsg, (uchar*) "kernel:", LOG_FAC(priority), LOG_PRI(priority), tp, json); + RETiRet; +} + + +/* helper for some klog drivers which need to know the MaxLine global setting. They can + * not obtain it themselfs, because they are no modules and can not query the object hander. + * It would probably be a good idea to extend the interface to support it, but so far + * we create a (sufficiently valid) work-around. -- rgerhards, 2008-11-24 + */ +int klog_getMaxLine(void) +{ + return glbl.GetMaxLine(); +} + + +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(!pThrd->bShallStop) { + /* klogLogKMsg() waits for the next kernel message, obtains it + * and then submits it to the rsyslog main queue. + * rgerhards, 2008-04-09 + */ + CHKiRet(klogLogKMsg(runModConf)); + } +finalize_it: +ENDrunInput + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + pModConf->iFacilIntMsg = klogFacilIntMsg(); + loadModConf->configSetViaV2Method = 0; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config vars */ + initConfigSettings(); +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->iFacilIntMsg = cs.iFacilIntMsg; + } + + loadModConf = NULL; /* done loading */ +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop +CODESTARTactivateCnfPrePrivDrop + runModConf = pModConf; + iRet = klogWillRun(runModConf); +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + iRet = klogAfterRun(runModConf); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pInputName != NULL) + prop.Destruct(&pInputName); + if(pLocalHostIP != NULL) + prop.Destruct(&pLocalHostIP); + + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(net, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.iFacilIntMsg = klogFacilIntMsg(); + return RS_RET_OK; +} + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(net, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.CreateStringProp(&pInputName, UCHAR_CONSTANT("imkmsg"), sizeof("imkmsg") - 1)); + CHKiRet(prop.CreateStringProp(&pLocalHostIP, UCHAR_CONSTANT("127.0.0.1"), sizeof("127.0.0.1") - 1)); + + /* init legacy config settings */ + initConfigSettings(); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"debugprintkernelsymbols", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbollookup", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbolstwice", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogusesyscallinterface", 0, eCmdHdlrGoneAway, + NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imkmsg/imkmsg.h b/plugins/imkmsg/imkmsg.h new file mode 100644 index 00000000..220a1634 --- /dev/null +++ b/plugins/imkmsg/imkmsg.h @@ -0,0 +1,64 @@ +/* imkmsg.h + * These are the definitions for the kmsg message generation module. + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef IMKLOG_H_INCLUDED +#define IMKLOG_H_INCLUDED 1 + +#include "rsyslog.h" +#include "dirty.h" + +/* we need to have the modConf type present in all submodules */ +struct modConfData_s { + rsconf_t *pConf; + int iFacilIntMsg; + uchar *pszPath; + int console_log_level; + sbool bPermitNonKernel; + sbool configSetViaV2Method; +}; + +/* interface to "drivers" + * the platform specific drivers must implement these entry points. Only one + * driver may be active at any given time, thus we simply rely on the linker + * to resolve the addresses. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(modConfData_t *pModConf); +rsRetVal klogWillRun(modConfData_t *pModConf); +rsRetVal klogAfterRun(modConfData_t *pModConf); +int klogFacilIntMsg(); + +/* the functions below may be called by the drivers */ +rsRetVal imkmsgLogIntMsg(int priority, char *fmt, ...) __attribute__((format(printf,2, 3))); +rsRetVal Syslog(int priority, uchar *msg, struct timeval *tp, struct json_object *json); + +/* prototypes */ +extern int klog_getMaxLine(void); /* work-around for klog drivers to get configured max line size */ +extern int InitKsyms(modConfData_t*); +extern void DeinitKsyms(void); +extern int InitMsyms(void); +extern void DeinitMsyms(void); +extern char * ExpandKadds(char *, char *); +extern void SetParanoiaLevel(int); + +#endif /* #ifndef IMKLOG_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/plugins/imkmsg/kmsg.c b/plugins/imkmsg/kmsg.c new file mode 100644 index 00000000..822d3dbd --- /dev/null +++ b/plugins/imkmsg/kmsg.c @@ -0,0 +1,249 @@ +/* imkmsg driver for Linux /dev/kmsg structured logging + * + * This contains Linux-specific functionality to read /dev/kmsg + * For a general overview, see head comment in imkmsg.c. + * This is heavily based on imklog bsd.c file. + * + * Copyright 2008-2012 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <sys/klog.h> +#include <sys/sysinfo.h> +#include <json/json.h> + +#include "rsyslog.h" +#include "srUtils.h" +#include "debug.h" +#include "imkmsg.h" + +/* globals */ +static int fklog = -1; /* kernel log fd */ + +#ifndef _PATH_KLOG +# define _PATH_KLOG "/dev/kmsg" +#endif + +/* submit a message to imkmsg Syslog() API. In this function, we parse + * necessary information from kernel log line, and make json string + * from the rest. + */ +static void +submitSyslog(uchar *buf) +{ + long offs = 0; + struct timeval tv; + struct sysinfo info; + unsigned long int timestamp = 0; + char name[1024]; + char value[1024]; + char msg[1024]; + int priority = 0; + long int sequnum = 0; + struct json_object *json = NULL, *jval; + + /* create new json object */ + json = json_object_new_object(); + + /* get priority */ + for (; isdigit(*buf); buf++) { + priority += (priority * 10) + (*buf - '0'); + } + buf++; + + /* get messages sequence number and add it to json */ + for (; isdigit(*buf); buf++) { + sequnum = (sequnum * 10) + (*buf - '0'); + } + buf++; /* skip , */ + jval = json_object_new_int(sequnum); + json_object_object_add(json, "sequnum", jval); + + /* get timestamp */ + for (; isdigit(*buf); buf++) { + timestamp = (timestamp * 10) + (*buf - '0'); + } + + while (*buf != ';') { + buf++; /* skip everything till the first ; */ + } + buf++; /* skip ; */ + + /* get message */ + offs = 0; + for (; *buf != '\n' && *buf != '\0'; buf++, offs++) { + msg[offs] = *buf; + } + msg[offs] = '\0'; + jval = json_object_new_string((char*)msg); + json_object_object_add(json, "msg", jval); + + if (*buf != '\0') /* message has appended properties, skip \n */ + buf++; + + while (*buf) { + /* get name of the property */ + buf++; /* skip ' ' */ + offs = 0; + for (; *buf != '=' && *buf != ' '; buf++, offs++) { + name[offs] = *buf; + } + name[offs] = '\0'; + buf++; /* skip = or ' ' */; + + offs = 0; + for (; *buf != '\n' && *buf != '\0'; buf++, offs++) { + value[offs] = *buf; + } + value[offs] = '\0'; + if (*buf != '\0') { + buf++; /* another property, skip \n */ + } + + jval = json_object_new_string((char*)value); + json_object_object_add(json, name, jval); + } + + /* calculate timestamp */ + sysinfo(&info); + gettimeofday(&tv, NULL); + + /* get boot time */ + tv.tv_sec -= info.uptime; + + tv.tv_sec += timestamp / 1000000; + tv.tv_usec += timestamp % 1000000; + + while (tv.tv_usec < 0) { + tv.tv_sec--; + tv.tv_usec += 1000000; + } + + while (tv.tv_usec >= 1000000) { + tv.tv_sec++; + tv.tv_usec -= 1000000; + } + + Syslog(priority, (uchar *)msg, &tv, json); +} + + +/* open the kernel log - will be called inside the willRun() imkmsg entry point + */ +rsRetVal +klogWillRun(modConfData_t *pModConf) +{ + char errmsg[2048]; + DEFiRet; + + fklog = open(_PATH_KLOG, O_RDONLY, 0); + if (fklog < 0) { + imkmsgLogIntMsg(RS_RET_ERR_OPEN_KLOG, "imkmsg: cannot open kernel log(%s): %s.", + _PATH_KLOG, rs_strerror_r(errno, errmsg, sizeof(errmsg))); + ABORT_FINALIZE(RS_RET_ERR_OPEN_KLOG); + } + +finalize_it: + RETiRet; +} + +/* Read kernel log while data are available, each read() reads one + * record of printk buffer. + */ +static void +readkmsg(void) +{ + int i; + uchar pRcv[8192+1]; + char errmsg[2048]; + + for (;;) { + dbgprintf("imkmsg waiting for kernel log line\n"); + + /* every read() from the opened device node receives one record of the printk buffer */ + i = read(fklog, pRcv, 8192); + + if (i > 0) { + /* successful read of message of nonzero length */ + pRcv[i] = '\0'; + } else if (i == -EPIPE) { + imkmsgLogIntMsg(LOG_WARNING, + "imkmsg: some messages in circular buffer got overwritten"); + continue; + } else { + /* something went wrong - error or zero length message */ + if (i < 0 && errno != EINTR && errno != EAGAIN) { + /* error occured */ + imkmsgLogIntMsg(LOG_ERR, + "imkmsg: error reading kernel log - shutting down: %s", + rs_strerror_r(errno, errmsg, sizeof(errmsg))); + fklog = -1; + } + break; + } + + submitSyslog(pRcv); + } +} + + +/* to be called in the module's AfterRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogAfterRun(modConfData_t *pModConf) +{ + DEFiRet; + if(fklog != -1) + close(fklog); + /* Turn on logging of messages to console, but only if a log level was speficied */ + if(pModConf->console_log_level != -1) + klogctl(7, NULL, 0); + RETiRet; +} + + +/* to be called in the module's WillRun entry point, this is the main + * "message pull" mechanism. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(modConfData_t __attribute__((unused)) *pModConf) +{ + DEFiRet; + readkmsg(); + RETiRet; +} + + +/* provide the (system-specific) default facility for internal messages + * rgerhards, 2008-04-14 + */ +int +klogFacilIntMsg(void) +{ + return LOG_SYSLOG; +} + diff --git a/plugins/immark/Makefile.am b/plugins/immark/Makefile.am new file mode 100644 index 00000000..6d8ed24a --- /dev/null +++ b/plugins/immark/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = immark.la + +immark_la_SOURCES = immark.c immark.h +immark_la_CPPFLAGS = $(RSRT_CFLAGS) -I$(top_srcdir) $(PTHREADS_CFLAGS) +immark_la_LDFLAGS = -module -avoid-version +immark_la_LIBADD = diff --git a/plugins/immark/immark.c b/plugins/immark/immark.c new file mode 100644 index 00000000..0e946c0b --- /dev/null +++ b/plugins/immark/immark.c @@ -0,0 +1,240 @@ +/* immark.c + * This is the implementation of the build-in mark message input module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <signal.h> +#include <string.h> +#include <pthread.h> +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" +#include "msg.h" +#include "srUtils.h" +#include "glbl.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("immark") + +/* defines */ +#define DEFAULT_MARK_PERIOD (20 * 60) + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(glbl) +DEFobjCurrIf(errmsg) + +static int iMarkMessagePeriod = DEFAULT_MARK_PERIOD; +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + int iMarkMessagePeriod; + sbool configSetViaV2Method; +}; + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "interval", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINafterRun +CODESTARTafterRun +ENDafterRun + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + pModConf->iMarkMessagePeriod = DEFAULT_MARK_PERIOD; + loadModConf->configSetViaV2Method = 0; + bLegacyCnfModGlobalsPermitted = 1; +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imuxsock:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "interval")) { + loadModConf->iMarkMessagePeriod = (int) pvals[i].val.d.n; + } else { + dbgprintf("imuxsock: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* disable legacy module-global config directives */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + pModConf->iMarkMessagePeriod = iMarkMessagePeriod; + } +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf + if(pModConf->iMarkMessagePeriod == 0) { + errmsg.LogError(0, NO_ERRCODE, "immark: mark message period must not be 0, can not run"); + ABORT_FINALIZE(RS_RET_NO_RUN); /* we can not run with this error */ + } +finalize_it: +ENDcheckCnf + + +BEGINactivateCnf +CODESTARTactivateCnf + MarkInterval = pModConf->iMarkMessagePeriod; + DBGPRINTF("immark set MarkInterval to %d\n", MarkInterval); +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +/* This function is called to gather input. It must terminate only + * a) on failure (iRet set accordingly) + * b) on termination of the input module (as part of the unload process) + * Code begun 2007-12-12 rgerhards + * + * This code must simply spawn emit a mark message at each mark interval. + * We are running on our own thread, so this is extremely easy: we just + * sleep MarkInterval seconds and each time we awake, we inject the message. + * Please note that we do not do the other fancy things that sysklogd + * (and pre 1.20.2 releases of rsyslog) did in mark procesing. They simply + * do not belong here. + */ +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(1) { + srSleep(MarkInterval, 0); /* seconds, micro seconds */ + + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + + dbgprintf("immark: injecting mark message\n"); + logmsgInternal(NO_ERRCODE, LOG_INFO, (uchar*)"-- MARK --", MARK); + } +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iMarkMessagePeriod = DEFAULT_MARK_PERIOD; + return RS_RET_OK; +} + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* legacy config handlers */ + CHKiRet(regCfSysLineHdlr2((uchar *)"markmessageperiod", 0, eCmdHdlrInt, NULL, + &iMarkMessagePeriod, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vi:set ai: + */ diff --git a/plugins/immark/immark.h b/plugins/immark/immark.h new file mode 100644 index 00000000..977d63b3 --- /dev/null +++ b/plugins/immark/immark.h @@ -0,0 +1,34 @@ +/* immark.h + * These are the definitions for the built-in mark message generation module. This + * file may disappear when this has become a loadable module. + * + * File begun on 2007-12-12 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef IMMARK_H_INCLUDED +#define IMMARK_H_INCLUDED 1 + +/* prototypes */ +rsRetVal immark_runInput(void); + +#endif /* #ifndef IMMARK_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/plugins/impstats/Makefile.am b/plugins/impstats/Makefile.am new file mode 100644 index 00000000..187bbca4 --- /dev/null +++ b/plugins/impstats/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = impstats.la + +impstats_la_SOURCES = impstats.c +impstats_la_CPPFLAGS = $(RSRT_CFLAGS) -I$(top_srcdir) $(PTHREADS_CFLAGS) +impstats_la_LDFLAGS = -module -avoid-version +impstats_la_LIBADD = diff --git a/plugins/impstats/impstats.c b/plugins/impstats/impstats.c new file mode 100644 index 00000000..cdd205fd --- /dev/null +++ b/plugins/impstats/impstats.c @@ -0,0 +1,421 @@ +/* impstats.c + * A module to periodically output statistics gathered by rsyslog. + * + * Copyright 2010-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <signal.h> +#include <string.h> +#include <pthread.h> +#include <fcntl.h> +#include <sys/uio.h> +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" +#include "msg.h" +#include "srUtils.h" +#include "unicode-helper.h" +#include "glbl.h" +#include "statsobj.h" +#include "prop.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("impstats") + +/* defines */ +#define DEFAULT_STATS_PERIOD (5 * 60) +#define DEFAULT_FACILITY 5 /* syslog */ +#define DEFAULT_SEVERITY 6 /* info */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(statsobj) +DEFobjCurrIf(errmsg) + +typedef struct configSettings_s { + int iStatsInterval; + int iFacility; + int iSeverity; + int bJSON; + int bCEE; +} configSettings_t; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + int iStatsInterval; + int iFacility; + int iSeverity; + int logfd; /* fd if logging to file, or -1 if closed */ + statsFmtType_t statsFmt; + sbool bLogToSyslog; + char *logfile; + sbool configSetViaV2Method; +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +static configSettings_t cs; +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ +static prop_t *pInputName = NULL; + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "interval", eCmdHdlrInt, 0 }, + { "facility", eCmdHdlrInt, 0 }, + { "severity", eCmdHdlrInt, 0 }, + { "log.syslog", eCmdHdlrBinary, 0 }, + { "log.file", eCmdHdlrGetWord, 0 }, + { "format", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +BEGINmodExit +CODESTARTmodExit + prop.Destruct(&pInputName); + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); +ENDmodExit + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + +static inline void +initConfigSettings(void) +{ + cs.iStatsInterval = DEFAULT_STATS_PERIOD; + cs.iFacility = DEFAULT_FACILITY; + cs.iSeverity = DEFAULT_SEVERITY; + cs.bJSON = 0; + cs.bCEE = 0; +} + + +/* actually submit a message to the rsyslog core + */ +static inline void +doSubmitMsg(uchar *line) +{ + msg_t *pMsg; + DEFiRet; + + CHKiRet(msgConstruct(&pMsg)); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsgWOSize(pMsg, (char*)line); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); + MsgSetRcvFromIP(pMsg, glbl.GetLocalHostIP()); + MsgSetMSGoffs(pMsg, 0); + MsgSetTAG(pMsg, UCHAR_CONSTANT("rsyslogd-pstats:"), sizeof("rsyslogd-pstats:") - 1); + pMsg->iFacility = runModConf->iFacility; + pMsg->iSeverity = runModConf->iSeverity; + pMsg->msgFlags = 0; + + /* we do not use rate-limiting, as the stats message always need to be emitted */ + submitMsg2(pMsg); + DBGPRINTF("impstats: submit [%d,%d] msg '%s'\n", runModConf->iFacility, + runModConf->iSeverity, line); + +finalize_it: + return; +} + + +/* log stats message to file; limited error handling done */ +static inline void +doLogToFile(cstr_t *cstr) +{ + struct iovec iov[4]; + ssize_t nwritten; + ssize_t nexpect; + time_t t; + char timebuf[32]; + + if(cstrLen(cstr) == 0) + goto done; + if(runModConf->logfd == -1) { + runModConf->logfd = open(runModConf->logfile, O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, S_IRUSR|S_IWUSR); + if(runModConf->logfd == -1) { + dbgprintf("error opening stats file %s\n", runModConf->logfile); + goto done; + } + } + + time(&t); + iov[0].iov_base = ctime_r(&t, timebuf); + iov[0].iov_len = nexpect = strlen(iov[0].iov_base) - 1; /* -1: strip \n */ + iov[1].iov_base = ": "; + iov[1].iov_len = 2; + nexpect += 2; + iov[2].iov_base = rsCStrGetSzStrNoNULL(cstr); + iov[2].iov_len = (size_t) cstrLen(cstr); + nexpect += cstrLen(cstr); + iov[3].iov_base = "\n"; + iov[3].iov_len = 1; + nexpect++; + nwritten = writev(runModConf->logfd, iov, 4); + + if(nwritten != nexpect) { + dbgprintf("error writing stats file %s, nwritten %lld, expected %lld\n", + runModConf->logfile, (long long) nwritten, (long long) nexpect); + } +done: return; +} + + +/* callback for statsobj + * Note: usrptr exists only to satisfy requirements of statsobj callback interface! + */ +static rsRetVal +doStatsLine(void __attribute__((unused)) *usrptr, cstr_t *cstr) +{ + DEFiRet; + if(runModConf->bLogToSyslog) + doSubmitMsg(rsCStrGetSzStrNoNULL(cstr)); + if(runModConf->logfile != NULL) + doLogToFile(cstr); + RETiRet; +} + + +/* the function to generate the actual statistics messages + * rgerhards, 2010-09-09 + */ +static inline void +generateStatsMsgs(void) +{ + statsobj.GetAllStatsLines(doStatsLine, NULL, runModConf->statsFmt); +} + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + loadModConf->configSetViaV2Method = 0; + loadModConf->iStatsInterval = DEFAULT_STATS_PERIOD; + loadModConf->iFacility = DEFAULT_FACILITY; + loadModConf->iSeverity = DEFAULT_SEVERITY; + loadModConf->statsFmt = statsFmt_Legacy; + loadModConf->logfd = -1; + loadModConf->logfile = NULL; + loadModConf->bLogToSyslog = 1; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config vars */ + initConfigSettings(); +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + char *mode; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for impstats:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "interval")) { + loadModConf->iStatsInterval = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "facility")) { + loadModConf->iFacility = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "severity")) { + loadModConf->iSeverity = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "log.syslog")) { + loadModConf->bLogToSyslog = (sbool) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "log.file")) { + loadModConf->logfile = es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "format")) { + mode = es_str2cstr(pvals[i].val.d.estr, NULL); + if(!strcasecmp(mode, "json")) { + loadModConf->statsFmt = statsFmt_JSON; + } else if(!strcasecmp(mode, "cee")) { + loadModConf->statsFmt = statsFmt_CEE; + } else if(!strcasecmp(mode, "legacy")) { + loadModConf->statsFmt = statsFmt_Legacy; + } else { + errmsg.LogError(0, RS_RET_ERR, "impstats: invalid format %s", + mode); + } + free(mode); + } else { + dbgprintf("impstats: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + loadModConf->configSetViaV2Method = 1; + bLegacyCnfModGlobalsPermitted = 0; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->iStatsInterval = cs.iStatsInterval; + loadModConf->iFacility = cs.iFacility; + loadModConf->iSeverity = cs.iSeverity; + if (cs.bCEE == 1) { + loadModConf->statsFmt = statsFmt_CEE; + } else if (cs.bJSON == 1) { + loadModConf->statsFmt = statsFmt_JSON; + } else { + loadModConf->statsFmt = statsFmt_Legacy; + } + } +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf + if(pModConf->iStatsInterval == 0) { + errmsg.LogError(0, NO_ERRCODE, "impstats: stats interval zero not permitted, using " + "default of %d seconds", DEFAULT_STATS_PERIOD); + pModConf->iStatsInterval = DEFAULT_STATS_PERIOD; + } +ENDcheckCnf + + +BEGINactivateCnf + rsRetVal localRet; +CODESTARTactivateCnf + runModConf = pModConf; + DBGPRINTF("impstats: stats interval %d seconds, logToSyslog %d, logFile %s\n", + runModConf->iStatsInterval, runModConf->bLogToSyslog, + runModConf->logfile == NULL ? "deactivated" : (char*)runModConf->logfile); + localRet = statsobj.EnableStats(); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "impstats: error enabling statistics gathering"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } +finalize_it: +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf + if(runModConf->logfd != -1) + close(runModConf->logfd); + free(runModConf->logfile); +ENDfreeCnf + + +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(1) { + srSleep(runModConf->iStatsInterval, 0); /* seconds, micro seconds */ + + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + + DBGPRINTF("impstats: woke up, generating messages\n"); + generateStatsMsgs(); + } +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun +ENDafterRun + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + initConfigSettings(); + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("impstats version %s loading\n", VERSION); + initConfigSettings(); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + /* the pstatsinverval is an alias to support a previous screwed-up syntax... */ + CHKiRet(regCfSysLineHdlr2((uchar *)"pstatsinterval", 0, eCmdHdlrInt, NULL, &cs.iStatsInterval, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"pstatinterval", 0, eCmdHdlrInt, NULL, &cs.iStatsInterval, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"pstatfacility", 0, eCmdHdlrInt, NULL, &cs.iFacility, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"pstatseverity", 0, eCmdHdlrInt, NULL, &cs.iSeverity, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"pstatjson", 0, eCmdHdlrBinary, NULL, &cs.bJSON, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"pstatcee", 0, eCmdHdlrBinary, NULL, &cs.bCEE, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("impstats"), sizeof("impstats") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); +ENDmodInit +/* vi:set ai: + */ diff --git a/plugins/imptcp/Makefile.am b/plugins/imptcp/Makefile.am new file mode 100644 index 00000000..bfacc884 --- /dev/null +++ b/plugins/imptcp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imptcp.la + +imptcp_la_SOURCES = imptcp.c +imptcp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imptcp_la_LDFLAGS = -module -avoid-version +imptcp_la_LIBADD = diff --git a/plugins/imptcp/imptcp.c b/plugins/imptcp/imptcp.c new file mode 100644 index 00000000..e9a20c1c --- /dev/null +++ b/plugins/imptcp/imptcp.c @@ -0,0 +1,1957 @@ +/* imptcp.c + * This is a native implementation of plain tcp. It is intentionally + * duplicate work (imtcp). The intent is to gain very fast and simple + * native ptcp support, utilizing the best interfaces Linux (no cross- + * platform intended!) has to offer. + * + * Note that in this module we try out some new naming conventions, + * so it may look a bit "different" from the other modules. We are + * investigating if removing prefixes can help make code more readable. + * + * File begun on 2010-08-10 by RGerhards + * + * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#if !defined(HAVE_EPOLL_CREATE) +# error imptcp requires OS support for epoll - can not build + /* imptcp gains speed by using modern Linux capabilities. As such, + * it can only be build on platforms supporting the epoll API. + */ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/epoll.h> +#include <netinet/tcp.h> +#include <stdint.h> +#include <zlib.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "cfsysline.h" +#include "prop.h" +#include "dirty.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "glbl.h" +#include "prop.h" +#include "errmsg.h" +#include "srUtils.h" +#include "datetime.h" +#include "ruleset.h" +#include "msg.h" +#include "statsobj.h" +#include "ratelimit.h" +#include "net.h" /* for permittedPeers, may be removed when this is removed */ + +/* the define is from tcpsrv.h, we need to find a new (but easier!!!) abstraction layer some time ... */ +#define TCPSRV_NO_ADDTL_DELIMITER -1 /* specifies that no additional delimiter is to be used in TCP framing */ + + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imptcp") + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(prop) +DEFobjCurrIf(datetime) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(ruleset) +DEFobjCurrIf(statsobj) + +/* forward references */ +static void * wrkr(void *myself); + +#define DFLT_wrkrMax 2 + +#define COMPRESS_NEVER 0 +#define COMPRESS_SINGLE_MSG 1 /* old, single-message compression */ +/* all other settings are for stream-compression */ +#define COMPRESS_STREAM_ALWAYS 2 + +/* config settings */ +typedef struct configSettings_s { + int bKeepAlive; /* support keep-alive packets */ + int iKeepAliveIntvl; + int iKeepAliveProbes; + int iKeepAliveTime; + int bEmitMsgOnClose; /* emit an informational message on close by remote peer */ + int bSuppOctetFram; /* support octet-counted framing? */ + int iAddtlFrameDelim; /* addtl frame delimiter, e.g. for netscreen, default none */ + uchar *pszInputName; /* value for inputname property, NULL is OK and handled by core engine */ + uchar *lstnIP; /* which IP we should listen on? */ + uchar *pszBindRuleset; + int wrkrMax; /* max number of workers (actually "helper workers") */ +} configSettings_t; +static configSettings_t cs; + +struct instanceConf_s { + int bKeepAlive; /* support keep-alive packets */ + int iKeepAliveIntvl; + int iKeepAliveProbes; + int iKeepAliveTime; + int bEmitMsgOnClose; + int bSuppOctetFram; /* support octet-counted framing? */ + int iAddtlFrameDelim; + uint8_t compressionMode; + uchar *pszBindPort; /* port to bind to */ + uchar *pszBindAddr; /* IP to bind socket to */ + uchar *pszBindRuleset; /* name of ruleset to bind to */ + uchar *pszInputName; /* value for inputname property, NULL is OK and handled by core engine */ + ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ + int ratelimitInterval; + int ratelimitBurst; + struct instanceConf_s *next; +}; + + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + instanceConf_t *root, *tail; + int wrkrMax; + sbool configSetViaV2Method; +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "threads", eCmdHdlrPositiveInt, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* input instance parameters */ +static struct cnfparamdescr inppdescr[] = { + { "port", eCmdHdlrString, CNFPARAM_REQUIRED }, /* legacy: InputTCPServerRun */ + { "address", eCmdHdlrString, 0 }, + { "name", eCmdHdlrString, 0 }, + { "ruleset", eCmdHdlrString, 0 }, + { "supportoctetcountedframing", eCmdHdlrBinary, 0 }, + { "notifyonconnectionclose", eCmdHdlrBinary, 0 }, + { "compression.mode", eCmdHdlrGetWord, 0 }, + { "keepalive", eCmdHdlrBinary, 0 }, + { "keepalive.probes", eCmdHdlrInt, 0 }, + { "keepalive.time", eCmdHdlrInt, 0 }, + { "keepalive.interval", eCmdHdlrInt, 0 }, + { "addtlframedelimiter", eCmdHdlrInt, 0 }, + { "ratelimit.interval", eCmdHdlrInt, 0 }, + { "ratelimit.burst", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + +#include "im-helper.h" /* must be included AFTER the type definitions! */ +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + +/* data elements describing our running config */ +typedef struct ptcpsrv_s ptcpsrv_t; +typedef struct ptcplstn_s ptcplstn_t; +typedef struct ptcpsess_s ptcpsess_t; +typedef struct epolld_s epolld_t; + +/* the ptcp server (listener) object + * Note that the object contains support for forming a linked list + * of them. It does not make sense to do this seperately. + */ +struct ptcpsrv_s { + ptcpsrv_t *pNext; /* linked list maintenance */ + uchar *port; /* Port to listen to */ + uchar *lstnIP; /* which IP we should listen on? */ + int iAddtlFrameDelim; + int iKeepAliveIntvl; + int iKeepAliveProbes; + int iKeepAliveTime; + uint8_t compressionMode; + uchar *pszInputName; + prop_t *pInputName; /* InputName in (fast to process) property format */ + ruleset_t *pRuleset; + ptcplstn_t *pLstn; /* root of our listeners */ + ptcpsess_t *pSess; /* root of our sessions */ + pthread_mutex_t mutSessLst; + sbool bKeepAlive; /* support keep-alive packets */ + sbool bEmitMsgOnClose; + sbool bSuppOctetFram; + ratelimit_t *ratelimiter; +}; + +/* the ptcp session object. Describes a single active session. + * includes support for doubly-linked list. + */ +struct ptcpsess_s { + ptcplstn_t *pLstn; /* our listener */ + ptcpsess_t *prev, *next; + int sock; + epolld_t *epd; + sbool bzInitDone; /* did we do an init of zstrm already? */ + z_stream zstrm; /* zip stream to use for tcp compression */ + uint8_t compressionMode; +//--- from tcps_sess.h + int iMsg; /* index of next char to store in msg */ + int bAtStrtOfFram; /* are we at the very beginning of a new frame? */ + sbool bSuppOctetFram; /**< copy from listener, to speed up access */ + enum { + eAtStrtFram, + eInOctetCnt, + eInMsg + } inputState; /* our current state */ + int iOctetsRemain; /* Number of Octets remaining in message */ + TCPFRAMINGMODE eFraming; + uchar *pMsg; /* message (fragment) received */ + prop_t *peerName; /* host name we received messages from */ + prop_t *peerIP; +//--- END from tcps_sess.h +}; + + +/* the ptcp listener object. Describes a single active listener. + */ +struct ptcplstn_s { + ptcpsrv_t *pSrv; /* our server */ + ptcplstn_t *prev, *next; + int sock; + sbool bSuppOctetFram; + epolld_t *epd; + statsobj_t *stats; /* listener stats */ + intctr_t rcvdBytes; + intctr_t rcvdDecompressed; + STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit) +}; + + +/* The following structure controls the worker threads. Global data is + * needed for their access. + */ +static struct wrkrInfo_s { + pthread_t tid; /* the worker's thread ID */ + pthread_cond_t run; + struct epoll_event *event; /* event == NULL -> idle */ + long long unsigned numCalled; /* how often was this called */ +} wrkrInfo[16]; +static pthread_mutex_t wrkrMut; +static pthread_cond_t wrkrIdle; +static int wrkrRunning; + + +/* type of object stored in epoll descriptor */ +typedef enum { + epolld_lstn, + epolld_sess +} epolld_type_t; + +/* an epoll descriptor. contains all information necessary to process + * the result of epoll. + */ +struct epolld_s { + epolld_type_t typ; + void *ptr; + struct epoll_event ev; +}; + + +/* global data */ +pthread_attr_t wrkrThrdAttr; /* Attribute for session threads; read only after startup */ +static ptcpsrv_t *pSrvRoot = NULL; +static int epollfd = -1; /* (sole) descriptor for epoll */ +static int iMaxLine; /* maximum size of a single message */ + +/* forward definitions */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); +static rsRetVal addLstn(ptcpsrv_t *pSrv, int sock, int isIPv6); + + +/* some simple constructors/destructors */ +static void +destructSess(ptcpsess_t *pSess) +{ + free(pSess->pMsg); + free(pSess->epd); + prop.Destruct(&pSess->peerName); + prop.Destruct(&pSess->peerIP); + /* TODO: make these inits compile-time switch depending: */ + pSess->pMsg = NULL; + pSess->epd = NULL; + free(pSess); +} + +static void +destructSrv(ptcpsrv_t *pSrv) +{ + ratelimitDestruct(pSrv->ratelimiter); + prop.Destruct(&pSrv->pInputName); + pthread_mutex_destroy(&pSrv->mutSessLst); + free(pSrv->pszInputName); + free(pSrv->port); + free(pSrv); +} + +/****************************************** TCP SUPPORT FUNCTIONS ***********************************/ +/* We may later think about moving this into a helper library again. But the whole point + * so far was to keep everything related close togehter. -- rgerhards, 2010-08-10 + */ + + +/* Start up a server. That means all of its listeners are created. + * Does NOT yet accept/process any incoming data (but binds ports). Hint: this + * code is to be executed before dropping privileges. + */ +static rsRetVal +startupSrv(ptcpsrv_t *pSrv) +{ + DEFiRet; + int error, maxs, on = 1; + int sock = -1; + int numSocks; + int sockflags; + struct addrinfo hints, *res = NULL, *r; + uchar *lstnIP; + int isIPv6 = 0; + + lstnIP = pSrv->lstnIP == NULL ? UCHAR_CONSTANT("") : pSrv->lstnIP; + + DBGPRINTF("imptcp: creating listen socket on server '%s', port %s\n", lstnIP, pSrv->port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo((char*)pSrv->lstnIP, (char*) pSrv->port, &hints, &res); + if(error) { + DBGPRINTF("error %d querying server '%s', port '%s'\n", error, pSrv->lstnIP, pSrv->port); + ABORT_FINALIZE(RS_RET_INVALID_PORT); + } + + /* Count max number of sockets we may open */ + for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + + numSocks = 0; /* num of sockets counter at start of array */ + for(r = res; r != NULL ; r = r->ai_next) { + sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if(sock < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + DBGPRINTF("error %d creating tcp listen socket", errno); + /* it is debatable if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + + if(r->ai_family == AF_INET6) { + isIPv6 = 1; +#ifdef IPV6_V6ONLY + int iOn = 1; + if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&iOn, sizeof (iOn)) < 0) { + close(sock); + sock = -1; + continue; + } +#endif + } else { + isIPv6 = 0; + } + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) { + DBGPRINTF("error %d setting tcp socket option\n", errno); + close(sock); + sock = -1; + continue; + } + + /* We use non-blocking IO! */ + if((sockflags = fcntl(sock, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(sock, F_SETFL, sockflags); + } + if(sockflags == -1) { + DBGPRINTF("error %d setting fcntl(O_NONBLOCK) on tcp socket", errno); + close(sock); + sock = -1; + continue; + } + + + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#ifndef BSD + if(net.should_use_so_bsdcompat()) { + if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)"); + close(sock); + sock = -1; + continue; + } + } +#endif + + if( (bind(sock, r->ai_addr, r->ai_addrlen) < 0) +#ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) +#endif + ) { + /* TODO: check if *we* bound the socket - else we *have* an error! */ + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("error %d while binding tcp socket: %s\n", errno, errStr); + close(sock); + sock = -1; + continue; + } + + if(listen(sock, 511) < 0) { + DBGPRINTF("tcp listen error %d, suspending\n", errno); + close(sock); + sock = -1; + continue; + } + + /* if we reach this point, we were able to obtain a valid socket, so we can + * create our listener object. -- rgerhards, 2010-08-10 + */ + CHKiRet(addLstn(pSrv, sock, isIPv6)); + ++numSocks; + } + + if(numSocks != maxs) + DBGPRINTF("We could initialize %d TCP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", numSocks, maxs); + + if(numSocks == 0) { + DBGPRINTF("No TCP listen sockets could successfully be initialized"); + ABORT_FINALIZE(RS_RET_COULD_NOT_BIND); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + if(sock != -1) + close(sock); + } + + RETiRet; +} + + +/* Set pRemHost based on the address provided. This is to be called upon accept()ing + * a connection request. It must be provided by the socket we received the + * message on as well as a NI_MAXHOST size large character buffer for the FQDN. + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. If we detect a malicious + * hostname, we return RS_RET_MALICIOUS_HNAME and let the caller decide + * on how to deal with that. + * rgerhards, 2008-03-31 + */ +static rsRetVal +getPeerNames(prop_t **peerName, prop_t **peerIP, struct sockaddr *pAddr) +{ + int error; + uchar szIP[NI_MAXHOST] = ""; + uchar szHname[NI_MAXHOST] = ""; + struct addrinfo hints, *res; + + DEFiRet; + + error = getnameinfo(pAddr, SALEN(pAddr), (char*)szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST); + + if(error) { + DBGPRINTF("Malformed from address %s\n", gai_strerror(error)); + strcpy((char*)szHname, "???"); + strcpy((char*)szIP, "???"); + ABORT_FINALIZE(RS_RET_INVALID_HNAME); + } + + if(!glbl.GetDisableDNS()) { + error = getnameinfo(pAddr, SALEN(pAddr), (char*)szHname, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + if(error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_STREAM; + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if(getaddrinfo((char*)szHname, NULL, &hints, &res) == 0) { + freeaddrinfo (res); + /* OK, we know we have evil, so let's indicate this to our caller */ + snprintf((char*)szHname, NI_MAXHOST, "[MALICIOUS:IP=%s]", szIP); + DBGPRINTF("Malicious PTR record, IP = \"%s\" HOST = \"%s\"", szIP, szHname); + iRet = RS_RET_MALICIOUS_HNAME; + } + } else { + strcpy((char*)szHname, (char*)szIP); + } + } else { + strcpy((char*)szHname, (char*)szIP); + } + + /* We now have the names, so now let's allocate memory and store them permanently. */ + CHKiRet(prop.Construct(peerName)); + CHKiRet(prop.SetString(*peerName, szHname, ustrlen(szHname))); + CHKiRet(prop.ConstructFinalize(*peerName)); + CHKiRet(prop.Construct(peerIP)); + CHKiRet(prop.SetString(*peerIP, szIP, ustrlen(szIP))); + CHKiRet(prop.ConstructFinalize(*peerIP)); + +finalize_it: + RETiRet; +} + + +/* Enable KEEPALIVE handling on the socket. */ +static inline rsRetVal +EnableKeepAlive(ptcplstn_t *pLstn, int sock) +{ + int ret; + int optval; + socklen_t optlen; + DEFiRet; + + optval = 1; + optlen = sizeof(optval); + ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); + if(ret < 0) { + dbgprintf("EnableKeepAlive socket call returns error %d\n", ret); + ABORT_FINALIZE(RS_RET_ERR); + } + +# if defined(TCP_KEEPCNT) + if(pLstn->pSrv->iKeepAliveProbes > 0) { + optval = pLstn->pSrv->iKeepAliveProbes; + optlen = sizeof(optval); + ret = setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &optval, optlen); + } else { + ret = 0; + } +# else + ret = -1; +# endif + if(ret < 0) { + errmsg.LogError(ret, NO_ERRCODE, "imptcp cannot set keepalive probes - ignored"); + } + +# if defined(TCP_KEEPCNT) + if(pLstn->pSrv->iKeepAliveTime > 0) { + optval = pLstn->pSrv->iKeepAliveTime; + optlen = sizeof(optval); + ret = setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &optval, optlen); + } else { + ret = 0; + } +# else + ret = -1; +# endif + if(ret < 0) { + errmsg.LogError(ret, NO_ERRCODE, "imptcp cannot set keepalive time - ignored"); + } + +# if defined(TCP_KEEPCNT) + if(pLstn->pSrv->iKeepAliveIntvl > 0) { + optval = pLstn->pSrv->iKeepAliveIntvl; + optlen = sizeof(optval); + ret = setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &optval, optlen); + } else { + ret = 0; + } +# else + ret = -1; +# endif + if(ret < 0) { + errmsg.LogError(errno, NO_ERRCODE, "imptcp cannot set keepalive intvl - ignored"); + } + + dbgprintf("KEEPALIVE enabled for socket %d\n", sock); + +finalize_it: + RETiRet; +} + + +/* accept an incoming connection request + * rgerhards, 2008-04-22 + */ +static rsRetVal +AcceptConnReq(ptcplstn_t *pLstn, int *newSock, prop_t **peerName, prop_t **peerIP) +{ + int sockflags; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int iNewSock = -1; + + DEFiRet; + + iNewSock = accept(pLstn->sock, (struct sockaddr*) &addr, &addrlen); + if(iNewSock < 0) { + if(errno == EAGAIN || errno == EWOULDBLOCK) + ABORT_FINALIZE(RS_RET_NO_MORE_DATA); + ABORT_FINALIZE(RS_RET_ACCEPT_ERR); + } + + if(pLstn->pSrv->bKeepAlive) + EnableKeepAlive(pLstn, iNewSock);/* we ignore errors, best to do! */ + + + CHKiRet(getPeerNames(peerName, peerIP, (struct sockaddr*) &addr)); + + /* set the new socket to non-blocking IO */ + if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(iNewSock, F_SETFL, sockflags); + } + if(sockflags == -1) { + DBGPRINTF("error %d setting fcntl(O_NONBLOCK) on tcp socket %d", errno, iNewSock); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + *newSock = iNewSock; + +finalize_it: + if(iRet != RS_RET_OK) { + /* the close may be redundant, but that doesn't hurt... */ + if(iNewSock != -1) + close(iNewSock); + } + + RETiRet; +} + + +/* This is a helper for submitting the message to the rsyslog core. + * It does some common processing, including resetting the various + * state variables to a "processed" state. + * Note that this function is also called if we had a buffer overflow + * due to a too-long message. So far, there is no indication this + * happened and it may be worth thinking about different handling + * of this case (what obviously would require a change to this + * function or some related code). + * rgerhards, 2009-04-23 + * EXTRACT from tcps_sess.c + */ +static rsRetVal +doSubmitMsg(ptcpsess_t *pThis, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub) +{ + msg_t *pMsg; + ptcpsrv_t *pSrv; + DEFiRet; + + if(pThis->iMsg == 0) { + DBGPRINTF("discarding zero-sized message\n"); + FINALIZE; + } + pSrv = pThis->pLstn->pSrv; + + /* we now create our own message object and submit it to the queue */ + CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime)); + MsgSetRawMsg(pMsg, (char*)pThis->pMsg, pThis->iMsg); + MsgSetInputName(pMsg, pSrv->pInputName); + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME; + MsgSetRcvFrom(pMsg, pThis->peerName); + CHKiRet(MsgSetRcvFromIP(pMsg, pThis->peerIP)); + MsgSetRuleset(pMsg, pSrv->pRuleset); + STATSCOUNTER_INC(pThis->pLstn->ctrSubmit, pThis->pLstn->mutCtrSubmit); + + ratelimitAddMsg(pSrv->ratelimiter, pMultiSub, pMsg); + +finalize_it: + /* reset status variables */ + pThis->bAtStrtOfFram = 1; + pThis->iMsg = 0; + + RETiRet; +} + + +/* process the data received. As TCP is stream based, we need to process the + * data inside a state machine. The actual data received is passed in byte-by-byte + * from DataRcvd, and this function here compiles messages from them and submits + * the end result to the queue. Introducing this function fixes a long-term bug ;) + * rgerhards, 2008-03-14 + * EXTRACT from tcps_sess.c + */ +static inline rsRetVal +processDataRcvd(ptcpsess_t *pThis, char c, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub) +{ + DEFiRet; + + if(pThis->inputState == eAtStrtFram) { + if(pThis->bSuppOctetFram && isdigit((int) c)) { + pThis->inputState = eInOctetCnt; + pThis->iOctetsRemain = 0; + pThis->eFraming = TCP_FRAMING_OCTET_COUNTING; + } else { + pThis->inputState = eInMsg; + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; + } + } + + if(pThis->inputState == eInOctetCnt) { + if(isdigit(c)) { + pThis->iOctetsRemain = pThis->iOctetsRemain * 10 + c - '0'; + } else { /* done with the octet count, so this must be the SP terminator */ + DBGPRINTF("TCP Message with octet-counter, size %d.\n", pThis->iOctetsRemain); + if(c != ' ') { + errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: " + "delimiter is not SP but has ASCII value %d.\n", c); + } + if(pThis->iOctetsRemain < 1) { + /* TODO: handle the case where the octet count is 0! */ + DBGPRINTF("Framing Error: invalid octet count\n"); + errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: " + "invalid octet count %d.\n", pThis->iOctetsRemain); + } else if(pThis->iOctetsRemain > iMaxLine) { + /* while we can not do anything against it, we can at least log an indication + * that something went wrong) -- rgerhards, 2008-03-14 + */ + DBGPRINTF("truncating message with %d octets - max msg size is %d\n", + pThis->iOctetsRemain, iMaxLine); + errmsg.LogError(0, NO_ERRCODE, "received oversize message: size is %d bytes, " + "max msg size is %d, truncating...\n", pThis->iOctetsRemain, iMaxLine); + } + pThis->inputState = eInMsg; + } + } else { + assert(pThis->inputState == eInMsg); + if(pThis->iMsg >= iMaxLine) { + /* emergency, we now need to flush, no matter if we are at end of message or not... */ + DBGPRINTF("error: message received is larger than max msg size, we split it\n"); + doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub); + /* we might think if it is better to ignore the rest of the + * message than to treat it as a new one. Maybe this is a good + * candidate for a configuration parameter... + * rgerhards, 2006-12-04 + */ + } + + if(( (c == '\n') + || ((pThis->pLstn->pSrv->iAddtlFrameDelim != TCPSRV_NO_ADDTL_DELIMITER) + && (c == pThis->pLstn->pSrv->iAddtlFrameDelim)) + ) && pThis->eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delimiter? */ + doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub); + pThis->inputState = eAtStrtFram; + } else { + /* IMPORTANT: here we copy the actual frame content to the message - for BOTH framing modes! + * If we have a message that is larger than the max msg size, we truncate it. This is the best + * we can do in light of what the engine supports. -- rgerhards, 2008-03-14 + */ + if(pThis->iMsg < iMaxLine) { + *(pThis->pMsg + pThis->iMsg++) = c; + } + } + + if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) { + /* do we need to find end-of-frame via octet counting? */ + pThis->iOctetsRemain--; + if(pThis->iOctetsRemain < 1) { + /* we have end of frame! */ + doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub); + pThis->inputState = eAtStrtFram; + } + } + } + + RETiRet; +} + + +/* Processes the data received via a TCP session. If there + * is no other way to handle it, data is discarded. + * Input parameter data is the data received, iLen is its + * len as returned from recv(). iLen must be 1 or more (that + * is errors must be handled by caller!). iTCPSess must be + * the index of the TCP session that received the data. + * rgerhards 2005-07-04 + * And another change while generalizing. We now return either + * RS_RET_OK, which means the session should be kept open + * or anything else, which means it must be closed. + * rgerhards, 2008-03-01 + * As a performance optimization, we pick up the timestamp here. Acutally, + * this *is* the *correct* reception step for all the data we received, because + * we have just received a bunch of data! -- rgerhards, 2009-06-16 + * EXTRACT from tcps_sess.c + */ +static rsRetVal +DataRcvdUncompressed(ptcpsess_t *pThis, char *pData, size_t iLen, time_t ttGenTime) +{ + multi_submit_t multiSub; + msg_t *pMsgs[CONF_NUM_MULTISUB]; + struct syslogTime stTime; + char *pEnd; + DEFiRet; + + assert(pData != NULL); + assert(iLen > 0); + + if(ttGenTime == 0) + datetime.getCurrTime(&stTime, &ttGenTime); + multiSub.ppMsgs = pMsgs; + multiSub.maxElem = CONF_NUM_MULTISUB; + multiSub.nElem = 0; + + /* We now copy the message to the session buffer. */ + pEnd = pData + iLen; /* this is one off, which is intensional */ + + while(pData < pEnd) { + CHKiRet(processDataRcvd(pThis, *pData++, &stTime, ttGenTime, &multiSub)); + } + + iRet = multiSubmitFlush(&multiSub); + +finalize_it: + RETiRet; +} + +static rsRetVal +DataRcvdCompressed(ptcpsess_t *pThis, char *buf, size_t len) +{ + struct syslogTime stTime; + time_t ttGenTime; + int zRet; /* zlib return state */ + unsigned outavail; + uchar zipBuf[64*1024]; // TODO: alloc on heap, and much larger (512KiB? batch size!) + DEFiRet; + // TODO: can we do stats counters? Even if they are not 100% correct under all cases, + // by simply updating the input and output sizes? + uint64_t outtotal; + + assert(iLen > 0); + + datetime.getCurrTime(&stTime, &ttGenTime); + outtotal = 0; + + if(!pThis->bzInitDone) { + /* allocate deflate state */ + pThis->zstrm.zalloc = Z_NULL; + pThis->zstrm.zfree = Z_NULL; + pThis->zstrm.opaque = Z_NULL; + zRet = inflateInit(&pThis->zstrm); + if(zRet != Z_OK) { + DBGPRINTF("imptcp: error %d returned from zlib/inflateInit()\n", zRet); + ABORT_FINALIZE(RS_RET_ZLIB_ERR); + } + pThis->bzInitDone = RSTRUE; + } + + pThis->zstrm.next_in = (Bytef*) buf; + pThis->zstrm.avail_in = len; + /* run inflate() on buffer until everything has been uncompressed */ + do { + DBGPRINTF("imptcp: in inflate() loop, avail_in %d, total_in %ld\n", pThis->zstrm.avail_in, pThis->zstrm.total_in); + pThis->zstrm.avail_out = sizeof(zipBuf); + pThis->zstrm.next_out = zipBuf; + zRet = inflate(&pThis->zstrm, Z_NO_FLUSH); /* no bad return value */ + DBGPRINTF("after inflate, ret %d, avail_out %d\n", zRet, pThis->zstrm.avail_out); + outavail = sizeof(zipBuf) - pThis->zstrm.avail_out; + if(outavail != 0) { + outtotal += outavail; + pThis->pLstn->rcvdDecompressed += outavail; + CHKiRet(DataRcvdUncompressed(pThis, (char*)zipBuf, outavail, ttGenTime)); + } + } while (pThis->zstrm.avail_out == 0); + + dbgprintf("end of DataRcvCompress, sizes: in %lld, out %llu\n", (long long) len, outtotal); +finalize_it: + RETiRet; +} + +static rsRetVal +DataRcvd(ptcpsess_t *pThis, char *pData, size_t iLen) +{ + DEFiRet; + pThis->pLstn->rcvdBytes += iLen; + if(pThis->compressionMode >= COMPRESS_STREAM_ALWAYS) + iRet = DataRcvdCompressed(pThis, pData, iLen); + else + iRet = DataRcvdUncompressed(pThis, pData, iLen, 0); + RETiRet; +} + + +/****************************************** --END-- TCP SUPPORT FUNCTIONS ***********************************/ + + +static inline void +initConfigSettings(void) +{ + cs.bEmitMsgOnClose = 0; + cs.wrkrMax = DFLT_wrkrMax; + cs.bSuppOctetFram = 1; + cs.iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + cs.pszInputName = NULL; + cs.pszBindRuleset = NULL; + cs.pszInputName = NULL; + cs.lstnIP = NULL; +} + + +/* add socket to the epoll set + */ +static inline rsRetVal +addEPollSock(epolld_type_t typ, void *ptr, int sock, epolld_t **pEpd) +{ + DEFiRet; + epolld_t *epd = NULL; + + CHKmalloc(epd = calloc(sizeof(epolld_t), 1)); + epd->typ = typ; + epd->ptr = ptr; + *pEpd = epd; + epd->ev.events = EPOLLIN|EPOLLET; + epd->ev.data.ptr = (void*) epd; + + if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &(epd->ev)) != 0) { + char errStr[1024]; + int eno = errno; + errmsg.LogError(0, RS_RET_EPOLL_CTL_FAILED, "os error (%d) during epoll ADD: %s", + eno, rs_strerror_r(eno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_EPOLL_CTL_FAILED); + } + + DBGPRINTF("imptcp: added socket %d to epoll[%d] set\n", sock, epollfd); + +finalize_it: + if(iRet != RS_RET_OK) { + free(epd); + } + RETiRet; +} + + +/* remove a socket from the epoll set. Note that the epd parameter + * is not really required -- it is used to satisfy older kernels where + * epoll_ctl() required a non-NULL pointer even though the ptr is never used. + * For simplicity, we supply the same pointer we had when we created the + * event (it's simple because we have it at hand). + */ +static inline rsRetVal +removeEPollSock(int sock, epolld_t *epd) +{ + DEFiRet; + + DBGPRINTF("imptcp: removing socket %d from epoll[%d] set\n", sock, epollfd); + + if(epoll_ctl(epollfd, EPOLL_CTL_DEL, sock, &(epd->ev)) != 0) { + char errStr[1024]; + int eno = errno; + errmsg.LogError(0, RS_RET_EPOLL_CTL_FAILED, "os error (%d) during epoll DEL: %s", + eno, rs_strerror_r(eno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_EPOLL_CTL_FAILED); + } + +finalize_it: + RETiRet; +} + + +/* add a listener to the server + */ +static rsRetVal +addLstn(ptcpsrv_t *pSrv, int sock, int isIPv6) +{ + DEFiRet; + ptcplstn_t *pLstn; + uchar statname[64]; + + CHKmalloc(pLstn = malloc(sizeof(ptcplstn_t))); + pLstn->pSrv = pSrv; + pLstn->bSuppOctetFram = pSrv->bSuppOctetFram; + pLstn->sock = sock; + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&(pLstn->stats))); + snprintf((char*)statname, sizeof(statname), "imptcp(%s/%s/%s)", + (pSrv->lstnIP == NULL) ? "*" : (char*)pSrv->lstnIP, pSrv->port, + isIPv6 ? "IPv6" : "IPv4"); + statname[sizeof(statname)-1] = '\0'; /* just to be on the save side... */ + CHKiRet(statsobj.SetName(pLstn->stats, statname)); + STATSCOUNTER_INIT(pLstn->ctrSubmit, pLstn->mutCtrSubmit); + CHKiRet(statsobj.AddCounter(pLstn->stats, UCHAR_CONSTANT("submitted"), + ctrType_IntCtr, &(pLstn->ctrSubmit))); + /* the following counters are not protected by mutexes; we accept + * that they may not be 100% correct */ + pLstn->rcvdBytes = 0, + pLstn->rcvdDecompressed = 0; + CHKiRet(statsobj.AddCounter(pLstn->stats, UCHAR_CONSTANT("bytes.received"), + ctrType_IntCtr, &(pLstn->rcvdBytes))); + CHKiRet(statsobj.AddCounter(pLstn->stats, UCHAR_CONSTANT("bytes.decompressed"), + ctrType_IntCtr, &(pLstn->rcvdDecompressed))); + CHKiRet(statsobj.ConstructFinalize(pLstn->stats)); + + /* add to start of server's listener list */ + pLstn->prev = NULL; + pLstn->next = pSrv->pLstn; + if(pSrv->pLstn != NULL) + pSrv->pLstn->prev = pLstn; + pSrv->pLstn = pLstn; + + iRet = addEPollSock(epolld_lstn, pLstn, sock, &pLstn->epd); + +finalize_it: +dbgprintf("DDDD: addLstn return %d\n", iRet); + RETiRet; +} + + +/* add a session to the server + */ +static rsRetVal +addSess(ptcplstn_t *pLstn, int sock, prop_t *peerName, prop_t *peerIP) +{ + DEFiRet; + ptcpsess_t *pSess = NULL; + ptcpsrv_t *pSrv = pLstn->pSrv; + + CHKmalloc(pSess = malloc(sizeof(ptcpsess_t))); + CHKmalloc(pSess->pMsg = malloc(iMaxLine * sizeof(uchar))); + pSess->pLstn = pLstn; + pSess->sock = sock; + pSess->bSuppOctetFram = pLstn->bSuppOctetFram; + pSess->inputState = eAtStrtFram; + pSess->iMsg = 0; + pSess->bAtStrtOfFram = 1; + pSess->peerName = peerName; + pSess->peerIP = peerIP; + pSess->compressionMode = pLstn->pSrv->compressionMode; + + /* add to start of server's listener list */ + pSess->prev = NULL; + pthread_mutex_lock(&pSrv->mutSessLst); + pSess->next = pSrv->pSess; + if(pSrv->pSess != NULL) + pSrv->pSess->prev = pSess; + pSrv->pSess = pSess; + pthread_mutex_unlock(&pSrv->mutSessLst); + + iRet = addEPollSock(epolld_sess, pSess, sock, &pSess->epd); + +finalize_it: + RETiRet; +} + + +/* finish zlib buffer, to be called before closing the session. + */ +static rsRetVal +doZipFinish(ptcpsess_t *pSess) +{ + int zRet; /* zlib return state */ + DEFiRet; + unsigned outavail; + uchar zipBuf[32*1024]; // TODO: use "global" one from pSess + + if(!pSess->bzInitDone) + goto done; + + pSess->zstrm.avail_in = 0; + /* run inflate() on buffer until everything has been compressed */ + do { + DBGPRINTF("doZipFinish: in inflate() loop, avail_in %d, total_in %ld\n", pSess->zstrm.avail_in, pSess->zstrm.total_in); + pSess->zstrm.avail_out = sizeof(zipBuf); + pSess->zstrm.next_out = zipBuf; + zRet = inflate(&pSess->zstrm, Z_FINISH); /* no bad return value */ + DBGPRINTF("after inflate, ret %d, avail_out %d\n", zRet, pSess->zstrm.avail_out); + outavail = sizeof(zipBuf) - pSess->zstrm.avail_out; + if(outavail != 0) { + pSess->pLstn->rcvdDecompressed += outavail; + CHKiRet(DataRcvdUncompressed(pSess, (char*)zipBuf, outavail, 0)); // TODO: query time! + } + } while (pSess->zstrm.avail_out == 0); + +finalize_it: + zRet = inflateEnd(&pSess->zstrm); + if(zRet != Z_OK) { + DBGPRINTF("imptcp: error %d returned from zlib/inflateEnd()\n", zRet); + } + + pSess->bzInitDone = 0; +done: RETiRet; +} + +/* close/remove a session + * NOTE: we must first remove the fd from the epoll set and then close it -- else we + * get an error "bad file descriptor" from epoll. + */ +static rsRetVal +closeSess(ptcpsess_t *pSess) +{ + int sock; + DEFiRet; + + if(pSess->compressionMode >= COMPRESS_STREAM_ALWAYS) + doZipFinish(pSess); + + sock = pSess->sock; + CHKiRet(removeEPollSock(sock, pSess->epd)); + close(sock); + + pthread_mutex_lock(&pSess->pLstn->pSrv->mutSessLst); + /* finally unlink session from structures */ + if(pSess->next != NULL) + pSess->next->prev = pSess->prev; + if(pSess->prev == NULL) { + /* need to update root! */ + pSess->pLstn->pSrv->pSess = pSess->next; + } else { + pSess->prev->next = pSess->next; + } + pthread_mutex_unlock(&pSess->pLstn->pSrv->mutSessLst); + + /* unlinked, now remove structure */ + destructSess(pSess); + +finalize_it: + DBGPRINTF("imptcp: session on socket %d closed with iRet %d.\n", sock, iRet); + RETiRet; +} + + +/* create input instance, set default paramters, and + * add it to the list of instances. + */ +static rsRetVal +createInstance(instanceConf_t **pinst) +{ + instanceConf_t *inst; + DEFiRet; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + inst->next = NULL; + + inst->pszBindPort = NULL; + inst->pszBindAddr = NULL; + inst->pszBindRuleset = NULL; + inst->pszInputName = NULL; + inst->bSuppOctetFram = 1; + inst->bKeepAlive = 0; + inst->iKeepAliveIntvl = 0; + inst->iKeepAliveProbes = 0; + inst->iKeepAliveTime = 0; + inst->bEmitMsgOnClose = 0; + inst->iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + inst->pBindRuleset = NULL; + inst->ratelimitBurst = 10000; /* arbitrary high limit */ + inst->ratelimitInterval = 0; /* off */ + inst->compressionMode = COMPRESS_SINGLE_MSG; + + /* node created, let's add to config */ + if(loadModConf->tail == NULL) { + loadModConf->tail = loadModConf->root = inst; + } else { + loadModConf->tail->next = inst; + loadModConf->tail = inst; + } + + *pinst = inst; +finalize_it: + RETiRet; +} + + +/* This function is called when a new listener instace shall be added to + * the current config object via the legacy config system. It just shuffles + * all parameters to the listener in-memory instance. + */ +static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + instanceConf_t *inst; + DEFiRet; + + CHKiRet(createInstance(&inst)); + if(pNewVal == NULL || *pNewVal == '\0') { + errmsg.LogError(0, NO_ERRCODE, "imptcp: port number must be specified, listener ignored"); + } + if((pNewVal == NULL) || (pNewVal == '\0')) { + inst->pszBindPort = NULL; + } else { + CHKmalloc(inst->pszBindPort = ustrdup(pNewVal)); + } + if((cs.lstnIP == NULL) || (cs.lstnIP[0] == '\0')) { + inst->pszBindAddr = NULL; + } else { + CHKmalloc(inst->pszBindAddr = ustrdup(cs.lstnIP)); + } + if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) { + inst->pszBindRuleset = NULL; + } else { + CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset)); + } + if((cs.pszInputName == NULL) || (cs.pszInputName[0] == '\0')) { + inst->pszInputName = NULL; + } else { + CHKmalloc(inst->pszInputName = ustrdup(cs.pszInputName)); + } + inst->pBindRuleset = NULL; + inst->bSuppOctetFram = cs.bSuppOctetFram; + inst->bKeepAlive = cs.bKeepAlive; + inst->iKeepAliveIntvl = cs.iKeepAliveTime; + inst->iKeepAliveProbes = cs.iKeepAliveProbes; + inst->iKeepAliveTime = cs.iKeepAliveTime; + inst->bEmitMsgOnClose = cs.bEmitMsgOnClose; + inst->iAddtlFrameDelim = cs.iAddtlFrameDelim; + +finalize_it: + free(pNewVal); + RETiRet; +} + + +static inline rsRetVal +addListner(modConfData_t __attribute__((unused)) *modConf, instanceConf_t *inst) +{ + DEFiRet; + ptcpsrv_t *pSrv; + + CHKmalloc(pSrv = MALLOC(sizeof(ptcpsrv_t))); + pthread_mutex_init(&pSrv->mutSessLst, NULL); + pSrv->pSess = NULL; + pSrv->pLstn = NULL; + pSrv->bSuppOctetFram = inst->bSuppOctetFram; + pSrv->bKeepAlive = inst->bKeepAlive; + pSrv->iKeepAliveIntvl = inst->iKeepAliveTime; + pSrv->iKeepAliveProbes = inst->iKeepAliveProbes; + pSrv->iKeepAliveTime = inst->iKeepAliveTime; + pSrv->bEmitMsgOnClose = inst->bEmitMsgOnClose; + pSrv->compressionMode = inst->compressionMode; + CHKiRet(ratelimitNew(&pSrv->ratelimiter, "imtcp", (char*)inst->pszBindPort)); + ratelimitSetLinuxLike(pSrv->ratelimiter, inst->ratelimitInterval, inst->ratelimitBurst); + ratelimitSetThreadSafe(pSrv->ratelimiter); + CHKmalloc(pSrv->port = ustrdup(inst->pszBindPort)); + pSrv->iAddtlFrameDelim = inst->iAddtlFrameDelim; + if(inst->pszBindAddr == NULL) + pSrv->lstnIP = NULL; + else { + CHKmalloc(pSrv->lstnIP = ustrdup(inst->pszBindAddr)); + } + pSrv->pRuleset = inst->pBindRuleset; + pSrv->pszInputName = ustrdup((inst->pszInputName == NULL) ? UCHAR_CONSTANT("imptcp") : inst->pszInputName); + CHKiRet(prop.Construct(&pSrv->pInputName)); + CHKiRet(prop.SetString(pSrv->pInputName, pSrv->pszInputName, ustrlen(pSrv->pszInputName))); + CHKiRet(prop.ConstructFinalize(pSrv->pInputName)); + + /* add to linked list */ + pSrv->pNext = pSrvRoot; + pSrvRoot = pSrv; + + /* all config vars are auto-reset -- this also is very useful with the + * new config format effort (v6). + */ + resetConfigVariables(NULL, NULL); + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet); + } + RETiRet; +} + + +/* destroy worker pool structures and wait for workers to terminate + */ +static inline void +startWorkerPool(void) +{ + int i; + wrkrRunning = 0; + if(runModConf->wrkrMax > 16) + runModConf->wrkrMax = 16; /* TODO: make dynamic? */ + DBGPRINTF("imptcp: starting worker pool, %d workers\n", runModConf->wrkrMax); + pthread_mutex_init(&wrkrMut, NULL); + pthread_cond_init(&wrkrIdle, NULL); + for(i = 0 ; i < runModConf->wrkrMax ; ++i) { + /* init worker info structure! */ + pthread_cond_init(&wrkrInfo[i].run, NULL); + wrkrInfo[i].event = NULL; + wrkrInfo[i].numCalled = 0; + pthread_create(&wrkrInfo[i].tid, &wrkrThrdAttr, wrkr, &(wrkrInfo[i])); + } + +} + +/* destroy worker pool structures and wait for workers to terminate + */ +static inline void +stopWorkerPool(void) +{ + int i; + DBGPRINTF("imptcp: stoping worker pool\n"); + for(i = 0 ; i < runModConf->wrkrMax ; ++i) { + pthread_cond_signal(&wrkrInfo[i].run); /* awake wrkr if not running */ + pthread_join(wrkrInfo[i].tid, NULL); + DBGPRINTF("imptcp: info: worker %d was called %llu times\n", i, wrkrInfo[i].numCalled); + pthread_cond_destroy(&wrkrInfo[i].run); + } + pthread_cond_destroy(&wrkrIdle); + pthread_mutex_destroy(&wrkrMut); +} + + + +/* start up all listeners + * This is a one-time stop once the module is set to start. + */ +static inline rsRetVal +startupServers() +{ + DEFiRet; + rsRetVal localRet, lastErr; + int iOK; + int iAll; + ptcpsrv_t *pSrv; + + iAll = iOK = 0; + lastErr = RS_RET_ERR; + pSrv = pSrvRoot; + while(pSrv != NULL) { + DBGPRINTF("imptcp: starting up server for port %s, name '%s'\n", pSrv->port, pSrv->pszInputName); + localRet = startupSrv(pSrv); + if(localRet == RS_RET_OK) + iOK++; + else + lastErr = localRet; + ++iAll; + pSrv = pSrv->pNext; + } + + DBGPRINTF("imptcp: %d out of %d servers started successfully\n", iOK, iAll); + if(iOK == 0) /* iff all fails, we report an error */ + iRet = lastErr; + + RETiRet; +} + + +/* process new activity on listener. This means we need to accept a new + * connection. + */ +static inline rsRetVal +lstnActivity(ptcplstn_t *pLstn) +{ + int newSock; + prop_t *peerName; + prop_t *peerIP; + rsRetVal localRet; + DEFiRet; + + DBGPRINTF("imptcp: new connection on listen socket %d\n", pLstn->sock); + while(glbl.GetGlobalInputTermState() == 0) { + localRet = AcceptConnReq(pLstn, &newSock, &peerName, &peerIP); + if(localRet == RS_RET_NO_MORE_DATA || glbl.GetGlobalInputTermState() == 1) + break; + CHKiRet(localRet); + CHKiRet(addSess(pLstn, newSock, peerName, peerIP)); + } + +finalize_it: + RETiRet; +} + + +/* process new activity on session. This means we need to accept data + * or close the session. + */ +static inline rsRetVal +sessActivity(ptcpsess_t *pSess) +{ + int lenRcv; + int lenBuf; + uchar *peerName; + int lenPeer; + int remsock = 0; /* init just to keep compiler happy... :-( */ + sbool bEmitOnClose = 0; + char rcvBuf[128*1024]; + DEFiRet; + + DBGPRINTF("imptcp: new activity on session socket %d\n", pSess->sock); + + while(1) { + lenBuf = sizeof(rcvBuf); + lenRcv = recv(pSess->sock, rcvBuf, lenBuf, 0); + + if(lenRcv > 0) { + /* have data, process it */ + DBGPRINTF("imptcp: data(%d) on socket %d: %s\n", lenBuf, pSess->sock, rcvBuf); + CHKiRet(DataRcvd(pSess, rcvBuf, lenRcv)); + } else if (lenRcv == 0) { + /* session was closed, do clean-up */ + if(pSess->pLstn->pSrv->bEmitMsgOnClose) { + prop.GetString(pSess->peerName, &peerName, &lenPeer), + remsock = pSess->sock; + bEmitOnClose = 1; + } + CHKiRet(closeSess(pSess)); /* close may emit more messages in strmzip mode! */ + if(bEmitOnClose) { + errmsg.LogError(0, RS_RET_PEER_CLOSED_CONN, "imptcp session %d closed by " + "remote peer %s.\n", remsock, peerName); + } + break; + } else { + if(errno == EAGAIN || errno == EWOULDBLOCK) + break; + DBGPRINTF("imptcp: error on session socket %d - closed.\n", pSess->sock); + closeSess(pSess); /* try clean-up by dropping session */ + break; + } + } + +finalize_it: + RETiRet; +} + + +/* This function is called to process a single request. This may + * be carried out by the main worker or a helper. It can be run + * concurrently. + */ +static inline void +processWorkItem(struct epoll_event *event) +{ + epolld_t *epd; + + epd = (epolld_t*) event->data.ptr; + switch(epd->typ) { + case epolld_lstn: + lstnActivity((ptcplstn_t *) epd->ptr); + break; + case epolld_sess: + sessActivity((ptcpsess_t *) epd->ptr); + break; + default: + errmsg.LogError(0, RS_RET_INTERNAL_ERROR, + "error: invalid epolld_type_t %d after epoll", epd->typ); + break; + } +} + + +/* This function is called to process a complete workset, that + * is a set of events returned from epoll. + */ +static inline void +processWorkSet(int nEvents, struct epoll_event events[]) +{ + int iEvt; + int i; + int remainEvents; + + remainEvents = nEvents; + for(iEvt = 0 ; (iEvt < nEvents) && (glbl.GetGlobalInputTermState() == 0) ; ++iEvt) { + if(remainEvents == 1) { + /* process self, save context switch */ + processWorkItem(events+iEvt); + } else { + pthread_mutex_lock(&wrkrMut); + /* check if there is a free worker */ + for(i = 0 ; (i < runModConf->wrkrMax) && (wrkrInfo[i].event != NULL) ; ++i) + /*do search*/; + if(i < runModConf->wrkrMax) { + /* worker free -> use it! */ + wrkrInfo[i].event = events+iEvt; + ++wrkrRunning; + pthread_cond_signal(&wrkrInfo[i].run); + pthread_mutex_unlock(&wrkrMut); + } else { + pthread_mutex_unlock(&wrkrMut); + /* no free worker, so we process this one ourselfs */ + processWorkItem(events+iEvt); + } + } + --remainEvents; + } + + if(nEvents > 1) { + /* we now need to wait until all workers finish. This is because the + * rest of this module can not handle the concurrency introduced + * by workers running during the epoll call. + */ + pthread_mutex_lock(&wrkrMut); + while(wrkrRunning > 0) { + pthread_cond_wait(&wrkrIdle, &wrkrMut); + } + pthread_mutex_unlock(&wrkrMut); + } + +} + + +/* worker to process incoming requests + */ +static void * +wrkr(void *myself) +{ + struct wrkrInfo_s *me = (struct wrkrInfo_s*) myself; + + pthread_mutex_lock(&wrkrMut); + while(1) { + while(me->event == NULL && glbl.GetGlobalInputTermState() == 0) { + pthread_cond_wait(&me->run, &wrkrMut); + } + if(glbl.GetGlobalInputTermState() == 1) + break; + pthread_mutex_unlock(&wrkrMut); + + ++me->numCalled; + processWorkItem(me->event); + + pthread_mutex_lock(&wrkrMut); + me->event = NULL; /* indicate we are free again */ + --wrkrRunning; + pthread_cond_signal(&wrkrIdle); + } + pthread_mutex_unlock(&wrkrMut); + + return NULL; +} + + +BEGINnewInpInst + struct cnfparamvals *pvals; + instanceConf_t *inst; + char *cstr; + int i; +CODESTARTnewInpInst + DBGPRINTF("newInpInst (imptcp)\n"); + + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imptcp: required parameter are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("input param blk in imptcp:\n"); + cnfparamsPrint(&inppblk, pvals); + } + + CHKiRet(createInstance(&inst)); + + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "port")) { + inst->pszBindPort = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "address")) { + inst->pszBindAddr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "name")) { + inst->pszInputName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "ruleset")) { + inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "supportoctetcountedframing")) { + inst->bSuppOctetFram = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "compression.mode")) { + cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + if(!strcasecmp(cstr, "stream:always")) { + inst->compressionMode = COMPRESS_STREAM_ALWAYS; + } else if(!strcasecmp(cstr, "none")) { + inst->compressionMode = COMPRESS_NEVER; + } else { + errmsg.LogError(0, RS_RET_PARAM_ERROR, "omfwd: invalid value for 'compression.mode' " + "parameter (given is '%s')", cstr); + free(cstr); + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + } + free(cstr); + } else if(!strcmp(inppblk.descr[i].name, "keepalive")) { + inst->bKeepAlive = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "keepalive.probes")) { + inst->iKeepAliveProbes = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "keepalive.time")) { + inst->iKeepAliveTime = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "keepalive.interval")) { + inst->iKeepAliveIntvl = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "addtlframedelimiter")) { + inst->iAddtlFrameDelim = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "notifyonconnectionclose")) { + inst->bEmitMsgOnClose = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "ratelimit.burst")) { + inst->ratelimitBurst = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "ratelimit.interval")) { + inst->ratelimitInterval = (int) pvals[i].val.d.n; + } else { + dbgprintf("imptcp: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + } +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + loadModConf->wrkrMax = DFLT_wrkrMax; + loadModConf->configSetViaV2Method = 0; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config vars */ + initConfigSettings(); +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imptcp: error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imptcp:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "threads")) { + loadModConf->wrkrMax = (int) pvals[i].val.d.n; + } else { + dbgprintf("imptcp: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* remove all of our legacy handlers, as they can not used in addition + * the the new-style config method. + */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->wrkrMax = cs.wrkrMax; + } + + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.pszInputName); + free(cs.lstnIP); + cs.pszInputName = NULL; + cs.lstnIP = NULL; +ENDendCnfLoad + + +/* function to generate error message if framework does not find requested ruleset */ +static inline void +std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) +{ + errmsg.LogError(0, NO_ERRCODE, "imptcp: ruleset '%s' for port %s not found - " + "using default ruleset instead", inst->pszBindRuleset, + inst->pszBindPort); +} +BEGINcheckCnf + instanceConf_t *inst; +CODESTARTcheckCnf + for(inst = pModConf->root ; inst != NULL ; inst = inst->next) { + std_checkRuleset(pModConf, inst); + } +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop + instanceConf_t *inst; +CODESTARTactivateCnfPrePrivDrop + iMaxLine = glbl.GetMaxLine(); /* get maximum size we currently support */ + + runModConf = pModConf; + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + addListner(pModConf, inst); + } + if(pSrvRoot == NULL) { + errmsg.LogError(0, RS_RET_NO_LSTN_DEFINED, "imptcp: no ptcp server defined, module can not run."); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + +# if defined(EPOLL_CLOEXEC) && defined(HAVE_EPOLL_CREATE1) + DBGPRINTF("imptcp uses epoll_create1()\n"); + epollfd = epoll_create1(EPOLL_CLOEXEC); + if(epollfd < 0 && errno == ENOSYS) +# endif + { + DBGPRINTF("imptcp uses epoll_create()\n"); + /* reading the docs, the number of epoll events passed to + * epoll_create() seems not to be used at all in kernels. So + * we just provide "a" number, happens to be 10. + */ + epollfd = epoll_create(10); + } + + if(epollfd < 0) { + errmsg.LogError(0, RS_RET_EPOLL_CR_FAILED, "error: epoll_create() failed"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + + /* start up servers, but do not yet read input data */ + CHKiRet(startupServers()); + DBGPRINTF("imptcp started up, but not yet receiving data\n"); +finalize_it: +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf + /* nothing to do, all done pre priv drop */ +ENDactivateCnf + + +BEGINfreeCnf + instanceConf_t *inst, *del; +CODESTARTfreeCnf + for(inst = pModConf->root ; inst != NULL ; ) { + free(inst->pszBindPort); + free(inst->pszBindAddr); + free(inst->pszBindRuleset); + free(inst->pszInputName); + del = inst; + inst = inst->next; + free(del); + } +ENDfreeCnf + + +/* This function is called to gather input. + */ +BEGINrunInput + int nEvents; + struct epoll_event events[128]; +CODESTARTrunInput + startWorkerPool(); + DBGPRINTF("imptcp: now beginning to process input data\n"); + while(glbl.GetGlobalInputTermState() == 0) { + DBGPRINTF("imptcp going on epoll_wait\n"); + nEvents = epoll_wait(epollfd, events, sizeof(events)/sizeof(struct epoll_event), -1); + DBGPRINTF("imptcp: epoll returned %d events\n", nEvents); + processWorkSet(nEvents, events); + } + DBGPRINTF("imptcp: successfully terminated\n"); + /* we stop the worker pool in AfterRun, in case we get cancelled for some reason (old Interface) */ +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +/* completely shut down a server, that means closing all of its + * listeners and sessions. + */ +static inline void +shutdownSrv(ptcpsrv_t *pSrv) +{ + ptcplstn_t *pLstn, *lstnDel; + ptcpsess_t *pSess, *sessDel; + +dbgprintf("DDDD: enter shutdownSrv\n"); + /* listeners */ + pLstn = pSrv->pLstn; + while(pLstn != NULL) { + close(pLstn->sock); + statsobj.Destruct(&(pLstn->stats)); + /* now unlink listner */ + lstnDel = pLstn; + pLstn = pLstn->next; + DBGPRINTF("imptcp shutdown listen socket %d (rcvd %lld bytes, " + "decompressed %lld)\n", lstnDel->sock, lstnDel->rcvdBytes, + lstnDel->rcvdDecompressed); + free(lstnDel->epd); + free(lstnDel); + } + + /* sessions */ + pSess = pSrv->pSess; + while(pSess != NULL) { + close(pSess->sock); + sessDel = pSess; + pSess = pSess->next; + DBGPRINTF("imptcp shutdown session socket %d\n", sessDel->sock); + destructSess(sessDel); + } +} + + +BEGINafterRun + ptcpsrv_t *pSrv, *srvDel; +CODESTARTafterRun + stopWorkerPool(); + + /* we need to close everything that is still open */ + pSrv = pSrvRoot; + while(pSrv != NULL) { + srvDel = pSrv; + pSrv = pSrv->pNext; + shutdownSrv(srvDel); + destructSrv(srvDel); + } + + close(epollfd); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + pthread_attr_destroy(&wrkrThrdAttr); + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(datetime, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.bEmitMsgOnClose = 0; + cs.wrkrMax = DFLT_wrkrMax; + cs.bKeepAlive = 0; + cs.iKeepAliveProbes = 0; + cs.iKeepAliveTime = 0; + cs.iKeepAliveIntvl = 0; + cs.bSuppOctetFram = 1; + cs.iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + free(cs.pszInputName); + cs.pszInputName = NULL; + free(cs.lstnIP); + cs.lstnIP = NULL; + return RS_RET_OK; +} + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + + /* initialize "read-only" thread attributes */ + pthread_attr_init(&wrkrThrdAttr); + pthread_attr_setstacksize(&wrkrThrdAttr, 4096*1024); + + /* init legacy config settings */ + initConfigSettings(); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverrun"), 0, eCmdHdlrGetWord, + addInstance, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverkeepalive"), 0, eCmdHdlrBinary, + NULL, &cs.bKeepAlive, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverkeepalive_probes"), 0, eCmdHdlrInt, + NULL, &cs.iKeepAliveProbes, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverkeepalive_time"), 0, eCmdHdlrInt, + NULL, &cs.iKeepAliveTime, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverkeepalive_intvl"), 0, eCmdHdlrInt, + NULL, &cs.iKeepAliveIntvl, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserversupportoctetcountedframing"), 0, eCmdHdlrBinary, + NULL, &cs.bSuppOctetFram, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpservernotifyonconnectionclose"), 0, + eCmdHdlrBinary, NULL, &cs.bEmitMsgOnClose, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserveraddtlframedelimiter"), 0, eCmdHdlrInt, + NULL, &cs.iAddtlFrameDelim, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverinputname"), 0, + eCmdHdlrGetWord, NULL, &cs.pszInputName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverlistenip"), 0, + eCmdHdlrGetWord, NULL, &cs.lstnIP, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputptcpserverbindruleset"), 0, + eCmdHdlrGetWord, NULL, &cs.pszBindRuleset, STD_LOADABLE_MODULE_ID)); + /* module-global parameters */ + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputptcpserverhelperthreads"), 0, eCmdHdlrInt, + NULL, &cs.wrkrMax, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("resetconfigvariables"), 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + + +/* vim:set ai: + */ diff --git a/plugins/imrelp/Makefile.am b/plugins/imrelp/Makefile.am new file mode 100644 index 00000000..8c6faff1 --- /dev/null +++ b/plugins/imrelp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imrelp.la + +imrelp_la_SOURCES = imrelp.c +imrelp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RELP_CFLAGS) $(RSRT_CFLAGS) +imrelp_la_LDFLAGS = -module -avoid-version +imrelp_la_LIBADD = $(RELP_LIBS) diff --git a/plugins/imrelp/imrelp.c b/plugins/imrelp/imrelp.c new file mode 100644 index 00000000..9d131ba0 --- /dev/null +++ b/plugins/imrelp/imrelp.c @@ -0,0 +1,610 @@ +/* imrelp.c + * + * This is the implementation of the RELP input module. + * + * File begun on 2008-03-13 by RGerhards + * + * Copyright 2008-2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <signal.h> +#include <librelp.h> +#include "rsyslog.h" +#include "dirty.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "msg.h" +#include "unicode-helper.h" +#include "prop.h" +#include "ruleset.h" +#include "glbl.h" +#include "statsobj.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imrelp") + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(net) +DEFobjCurrIf(prop) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(ruleset) +DEFobjCurrIf(glbl) +DEFobjCurrIf(statsobj) + +/* forward definitions */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + + +/* Module static data */ +/* config vars for legacy config system */ +static relpEngine_t *pRelpEngine; /* our relp engine */ +static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this module */ +static struct configSettings_s { + uchar *pszBindRuleset; /* name of Ruleset to bind to */ +} cs; + +struct instanceConf_s { + uchar *pszBindPort; /* port to bind to */ + sbool bEnableTLS; + sbool bEnableTLSZip; + int dhBits; + uchar *pristring; /* GnuTLS priority string (NULL if not to be provided) */ + uchar *caCertFile; + uchar *myCertFile; + uchar *myPrivKeyFile; + struct { + int nmemb; + uchar **name; + } permittedPeers; + + struct instanceConf_s *next; + /* with librelp, this module does not have any own specific session + * or listener active data item. As a "work-around", we keep some + * data items inside the configuration object. To keep things + * decently clean, we put them all into their dedicated struct. So + * it is easy to judge what is actual configuration and what is + * dynamic runtime data. -- rgerhards, 2013-06-18 + */ + struct { + statsobj_t *stats; /* listener stats */ + STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit) + } data; +}; + + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + instanceConf_t *root, *tail; + uchar *pszBindRuleset; /* name of Ruleset to bind to */ + ruleset_t *pBindRuleset; /* due to librelp limitation, we need to bind all listerns to the same set */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "ruleset", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* input instance parameters */ +static struct cnfparamdescr inppdescr[] = { + { "port", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "tls", eCmdHdlrBinary, 0 }, + { "tls.permittedpeer", eCmdHdlrArray, 0 }, + { "tls.dhbits", eCmdHdlrInt, 0 }, + { "tls.prioritystring", eCmdHdlrString, 0 }, + { "tls.cacert", eCmdHdlrString, 0 }, + { "tls.mycert", eCmdHdlrString, 0 }, + { "tls.myprivkey", eCmdHdlrString, 0 }, + { "tls.compression", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + + + +/* ------------------------------ callbacks ------------------------------ */ + +/* callback for receiving syslog messages. This function is invoked from the + * RELP engine when a syslog message arrived. It must return a relpRetVal, + * with anything else but RELP_RET_OK terminating the relp session. Please note + * that RELP_RET_OK is equal to RS_RET_OK and the other libRELP error codes + * are different from our rsRetVal. So we can simply use our own iRet system + * to fulfill the requirement. + * rgerhards, 2008-03-21 + * Note: librelp 1.0.0 is required in order to receive the IP address, otherwise + * we will only see the hostname (twice). -- rgerhards, 2009-10-14 + */ +static relpRetVal +onSyslogRcv(void *pUsr, uchar *pHostname, uchar *pIP, uchar *msg, size_t lenMsg) +{ + prop_t *pProp = NULL; + msg_t *pMsg; + instanceConf_t *inst = (instanceConf_t*) pUsr; + DEFiRet; + + CHKiRet(msgConstruct(&pMsg)); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsg(pMsg, (char*)msg, lenMsg); + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + MsgSetRuleset(pMsg, runModConf->pBindRuleset); + pMsg->msgFlags = PARSE_HOSTNAME | NEEDS_PARSING; + + /* TODO: optimize this, we can store it inside the session, requires + * changes to librelp --> next librelp iteration?. rgerhards, 2012-10-29 + */ + MsgSetRcvFromStr(pMsg, pHostname, ustrlen(pHostname), &pProp); + CHKiRet(prop.Destruct(&pProp)); + CHKiRet(MsgSetRcvFromIPStr(pMsg, pIP, ustrlen(pIP), &pProp)); + CHKiRet(prop.Destruct(&pProp)); + CHKiRet(submitMsg2(pMsg)); + STATSCOUNTER_INC(inst->data.ctrSubmit, inst->data.mutCtrSubmit); + +finalize_it: + + RETiRet; +} + + +/* ------------------------------ end callbacks ------------------------------ */ + +/* create input instance, set default paramters, and + * add it to the list of instances. + */ +static rsRetVal +createInstance(instanceConf_t **pinst) +{ + instanceConf_t *inst; + DEFiRet; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + inst->next = NULL; + + inst->pszBindPort = NULL; + inst->bEnableTLS = 0; + inst->bEnableTLSZip = 0; + inst->dhBits = 0; + inst->pristring = NULL; + inst->permittedPeers.nmemb = 0; + + /* node created, let's add to config */ + if(loadModConf->tail == NULL) { + loadModConf->tail = loadModConf->root = inst; + } else { + loadModConf->tail->next = inst; + loadModConf->tail = inst; + } + + *pinst = inst; +finalize_it: + RETiRet; +} + + +/* modified to work for module, not instance (as usual) */ +static inline void +std_checkRuleset_genErrMsg(modConfData_t *modConf, __attribute__((unused)) instanceConf_t *inst) +{ + errmsg.LogError(0, NO_ERRCODE, "imrelp: ruleset '%s' not found - " + "using default ruleset instead", modConf->pszBindRuleset); +} + + +/* This function is called when a new listener instance shall be added to + * the current config object via the legacy config system. It just shuffles + * all parameters to the listener in-memory instance. + * rgerhards, 2011-05-04 + */ +static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + instanceConf_t *inst; + DEFiRet; + + CHKiRet(createInstance(&inst)); + + if(pNewVal == NULL || *pNewVal == '\0') { + errmsg.LogError(0, NO_ERRCODE, "imrelp: port number must be specified, listener ignored"); + } + inst->pszBindPort = pNewVal; + +finalize_it: + RETiRet; +} + + +static rsRetVal +addListner(modConfData_t __attribute__((unused)) *modConf, instanceConf_t *inst) +{ + relpSrv_t *pSrv; + uchar statname[64]; + int i; + DEFiRet; + if(pRelpEngine == NULL) { + CHKiRet(relpEngineConstruct(&pRelpEngine)); + CHKiRet(relpEngineSetDbgprint(pRelpEngine, dbgprintf)); + CHKiRet(relpEngineSetFamily(pRelpEngine, glbl.GetDefPFFamily())); + CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar*) "syslog", eRelpCmdState_Required)); + CHKiRet(relpEngineSetSyslogRcv2(pRelpEngine, onSyslogRcv)); + if (!glbl.GetDisableDNS()) { + CHKiRet(relpEngineSetDnsLookupMode(pRelpEngine, 1)); + } + } + + CHKiRet(relpEngineListnerConstruct(pRelpEngine, &pSrv)); + CHKiRet(relpSrvSetLstnPort(pSrv, inst->pszBindPort)); + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&(inst->data.stats))); + snprintf((char*)statname, sizeof(statname), "imrelp(%s)", + inst->pszBindPort); + statname[sizeof(statname)-1] = '\0'; /* just to be on the save side... */ + CHKiRet(statsobj.SetName(inst->data.stats, statname)); + STATSCOUNTER_INIT(inst->data.ctrSubmit, inst->data.mutCtrSubmit); + CHKiRet(statsobj.AddCounter(inst->data.stats, UCHAR_CONSTANT("submitted"), + ctrType_IntCtr, &(inst->data.ctrSubmit))); + CHKiRet(statsobj.ConstructFinalize(inst->data.stats)); + /* end stats counters */ + relpSrvSetUsrPtr(pSrv, inst); + if(inst->bEnableTLS) { + relpSrvEnableTLS(pSrv); + if(inst->bEnableTLSZip) { + relpSrvEnableTLSZip(pSrv); + } + if(inst->dhBits) { + relpSrvSetDHBits(pSrv, inst->dhBits); + } + relpSrvSetGnuTLSPriString(pSrv, (char*)inst->pristring); + if(relpSrvSetCACert(pSrv, (char*) inst->caCertFile) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(relpSrvSetOwnCert(pSrv, (char*) inst->myCertFile) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(relpSrvSetPrivKey(pSrv, (char*) inst->myPrivKeyFile) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + for(i = 0 ; i < inst->permittedPeers.nmemb ; ++i) { + relpSrvAddPermittedPeer(pSrv, (char*)inst->permittedPeers.name[i]); + } + } + CHKiRet(relpEngineListnerConstructFinalize(pRelpEngine, pSrv)); + +finalize_it: + RETiRet; +} + + +BEGINnewInpInst + struct cnfparamvals *pvals; + instanceConf_t *inst; + int i,j; +CODESTARTnewInpInst + DBGPRINTF("newInpInst (imrelp)\n"); + + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imrelp: required parameter are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("input param blk in imrelp:\n"); + cnfparamsPrint(&inppblk, pvals); + } + + CHKiRet(createInstance(&inst)); + + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "port")) { + inst->pszBindPort = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "tls")) { + inst->bEnableTLS = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "tls.dhbits")) { + inst->dhBits = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "tls.prioritystring")) { + inst->pristring = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "tls.compression")) { + inst->bEnableTLSZip = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "tls.cacert")) { + inst->caCertFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "tls.mycert")) { + inst->myCertFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "tls.myprivkey")) { + inst->myPrivKeyFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "tls.permittedpeer")) { + inst->permittedPeers.nmemb = pvals[i].val.d.ar->nmemb; + CHKmalloc(inst->permittedPeers.name = + malloc(sizeof(uchar*) * inst->permittedPeers.nmemb)); + for(j = 0 ; j < pvals[i].val.d.ar->nmemb ; ++j) { + inst->permittedPeers.name[j] = (uchar*)es_str2cstr(pvals[i].val.d.ar->arr[j], NULL); + } + } else { + dbgprintf("imrelp: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + } +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->pszBindRuleset = NULL; + pModConf->pBindRuleset = NULL; + /* init legacy config variables */ + cs.pszBindRuleset = NULL; +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imrelp:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "ruleset")) { + loadModConf->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("imrelp: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(loadModConf->pszBindRuleset == NULL) { + if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) { + loadModConf->pszBindRuleset = NULL; + } else { + CHKmalloc(loadModConf->pszBindRuleset = ustrdup(cs.pszBindRuleset)); + } + } else { + if((cs.pszBindRuleset != NULL) && (cs.pszBindRuleset[0] != '\0')) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "imrelp: warning: ruleset " + "set via legacy directive ignored"); + } + } +finalize_it: + free(cs.pszBindRuleset); + loadModConf = NULL; /* done loading */ +ENDendCnfLoad + + +BEGINcheckCnf + rsRetVal localRet; + ruleset_t *pRuleset; +CODESTARTcheckCnf + /* we emulate the standard "ruleset query" code provided by the framework + * for *instances* (which we can currently not support due to librelp). + */ + if(pModConf->pszBindRuleset == NULL) { + pModConf->pBindRuleset = NULL; + } else { + DBGPRINTF("imrelp: using ruleset '%s'\n", pModConf->pszBindRuleset); + localRet = ruleset.GetRuleset(pModConf->pConf, &pRuleset, pModConf->pszBindRuleset); + if(localRet == RS_RET_NOT_FOUND) { + std_checkRuleset_genErrMsg(pModConf, NULL); + } + CHKiRet(localRet); + pModConf->pBindRuleset = pRuleset; + } +finalize_it: +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop + instanceConf_t *inst; +CODESTARTactivateCnfPrePrivDrop + runModConf = pModConf; + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + addListner(pModConf, inst); + } + if(pRelpEngine == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); +finalize_it: +ENDactivateCnfPrePrivDrop + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf + instanceConf_t *inst, *del; + int i; +CODESTARTfreeCnf + for(inst = pModConf->root ; inst != NULL ; ) { + free(inst->pszBindPort); + statsobj.Destruct(&(inst->data.stats)); + for(i = 0 ; i < inst->permittedPeers.nmemb ; ++i) { + free(inst->permittedPeers.name[i]); + } + del = inst; + inst = inst->next; + free(del); + } +ENDfreeCnf + +/* This is used to terminate the plugin. Note that the signal handler blocks + * other activity on the thread. As such, it is safe to request the stop. When + * we terminate, relpEngine is called, and it's select() loop interrupted. But + * only *after this function is done*. So we do not have a race! + */ +static void +doSIGTTIN(int __attribute__((unused)) sig) +{ + DBGPRINTF("imrelp: termination requested via SIGTTIN - telling RELP engine\n"); + relpEngineSetStop(pRelpEngine); +} + + +/* This function is called to gather input. + */ +BEGINrunInput + sigset_t sigSet; + struct sigaction sigAct; +CODESTARTrunInput + /* we want to support non-cancel input termination. To do so, we must signal librelp + * when to stop. As we run on the same thread, we need to register as SIGTTIN handler, + * which will be used to put the terminating condition into librelp. + */ + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGTTIN); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = doSIGTTIN; + sigaction(SIGTTIN, &sigAct, NULL); + + iRet = relpEngineRun(pRelpEngine); +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pRelpEngine != NULL) + iRet = relpEngineDestruct(&pRelpEngine); + + /* global variable cleanup */ + if(pInputName != NULL) + prop.Destruct(&pInputName); + + /* release objects we used */ + objRelease(statsobj, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + free(cs.pszBindRuleset); + cs.pszBindRuleset = NULL; + return RS_RET_OK; +} + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + pRelpEngine = NULL; + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpserverbindruleset", 0, eCmdHdlrGetWord, + NULL, &cs.pszBindRuleset, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpserverrun", 0, eCmdHdlrGetWord, + addInstance, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imrelp"), sizeof("imrelp") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); +ENDmodInit + + +/* vim:set ai: + */ diff --git a/plugins/imsolaris/Makefile.am b/plugins/imsolaris/Makefile.am new file mode 100644 index 00000000..b4ee1c29 --- /dev/null +++ b/plugins/imsolaris/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imsolaris.la + +imsolaris_la_SOURCES = imsolaris.c sun_cddl.c sun_cddl.h imsolaris.h +imsolaris_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imsolaris_la_LDFLAGS = -module -avoid-version +imsolaris_la_LIBADD = -ldoor -lpthread diff --git a/plugins/imsolaris/imsolaris.c b/plugins/imsolaris/imsolaris.c new file mode 100644 index 00000000..a220e72a --- /dev/null +++ b/plugins/imsolaris/imsolaris.c @@ -0,0 +1,430 @@ +/* imsolaris.c + * This input module is used to gather local log data under Solaris. This + * includes messages from local applications AS WELL AS the kernel log. + * I first considered to make all of this available via imklog, but that + * did not lock appropriately on second thought. So I created this module + * that does anything for local message recption. + * + * This module is not meant to be used on plaforms other than Solaris. As + * such, trying to compile it elswhere will probably fail with all sorts + * of errors. + * + * Some notes on the Solaris syslog mechanism: + * Both system (kernel) and application log messages are provided via + * a single message stream. + * + * Solaris checks if the syslogd is running. If so, syslog() emits messages + * to the log socket, only. Otherwise, it emits messages to the console. + * It is possible to gather these console messages as well. However, then + * we clutter the console. + * Solaris does this "syslogd alive check" in a somewhat unexpected way + * (at least unexpected for me): it uses the so-called "door" mechanism, a + * fast RPC facility. I first thought that the door API was used to submit + * the actual syslog messages. But this is not the case. Instead, a door + * call is done, and the server process inside rsyslog simply does NOTHING + * but return. All that Solaris sylsogd() is interested in is if the door + * server (we) responds and thus can be considered alive. The actual message + * is then submitted via the usual stream. I have to admit I do not + * understand why the message itself is not passed via this high-performance + * API. But anyhow, that's nothing I can change, so the most important thing + * is to note how Solaris does this thing ;) + * The syslog() library call checks syslogd state for *each* call (what a + * waste of time...) and decides each time if the message should go to the + * console or not. According to OpenSolaris sources, it looks like there is + * message loss potential when the door file is created before all data has + * been pulled from the stream. While I have to admit that I do not fully + * understand that problem, I will follow the original code advise and do + * one complete pull cycle on the log socket (until it has no further data + * available) and only thereafter create the door file and start the "regular" + * pull cycle. As of my understanding, there is a minimal race between the + * point where the intial pull cycle has ended and the door file is created, + * but that race is also present in OpenSolaris syslogd code, so it should + * not matter that much (plus, I do not know how to avoid it...) + * + * File begun on 2010-04-15 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <stropts.h> +#include <sys/strlog.h> +#include <errno.h> +#include "dirty.h" +#include "cfsysline.h" +#include "unicode-helper.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" +#include "net.h" +#include "glbl.h" +#include "msg.h" +#include "prop.h" +#include "sun_cddl.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imsolaris") + +/* defines */ +#define PATH_LOG "/dev/log" + + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) + + +/* config settings */ +struct modConfData_s { + EMPTY_STRUCT; +}; + +static prop_t *pInputName = NULL; /* our inputName currently is always "imuxsock", and this will hold it */ +static char *LogName = NULL; /* the log socket name TODO: make configurable! */ + + +/* a function to replace the sun logerror() function. + * It generates an error message from the supplied string. The main + * reason for not calling logError directly is that sun_cddl.c does not + * know or has acces to rsyslog objects (namely errmsg) -- and we do not + * want to do this effort. -- rgerhards, 2010-04-19 + */ +void +imsolaris_logerror(int err, char *errStr) +{ + errmsg.LogError(err, RS_RET_ERR_DOOR, "%s", errStr); +} + + +/* we try to recover a failed file by closing and re-opening + * it. We loop until the re-open works, but wait between each + * failure. If the open succeeds, we assume all is well. If it is + * not, we will run into the retry process with the next + * iteration. + * rgerhards, 2010-04-19 + */ +static void +tryRecover(void) +{ + int tryNum = 1; + int waitsecs; + int waitusecs; + rsRetVal iRet; + + close(sun_Pfd.fd); + sun_Pfd.fd = -1; + + while(1) { /* loop broken inside */ + iRet = sun_openklog((LogName == NULL) ? PATH_LOG : LogName); + if(iRet == RS_RET_OK) { + if(tryNum > 1) { + errmsg.LogError(0, iRet, "failure on system log socket recovered."); + } + break; + } + /* failure, so sleep a bit. We wait try*10 ms, with a max of 15 seconds */ + if(tryNum == 1) { + errmsg.LogError(0, iRet, "failure on system log socket, trying to recover..."); + } + waitusecs = tryNum * 10000; + waitsecs = waitusecs / 1000000; + DBGPRINTF("imsolaris: try %d to recover system log socket in %d.%d seconds\n", + tryNum, waitsecs, waitusecs); + if(waitsecs > 15) { + waitsecs = 15; + waitusecs = 0; + } else { + waitusecs = waitusecs % 1000000; + } + srSleep(waitsecs, waitusecs); + ++tryNum; + } +} + + +/* This function receives data from a socket indicated to be ready + * to receive and submits the message received for processing. + * rgerhards, 2007-12-20 + * Interface changed so that this function is passed the array index + * of the socket which is to be processed. This eases access to the + * growing number of properties. -- rgerhards, 2008-08-01 + */ +static rsRetVal +readLog(int fd, uchar *pRcv, int iMaxLine) +{ + DEFiRet; + struct strbuf data; + struct strbuf ctl; + struct log_ctl hdr; + int flags; + msg_t *pMsg; + int ret; + char errStr[1024]; + + data.buf = (char*)pRcv; + data.maxlen = iMaxLine; + ctl.maxlen = sizeof (struct log_ctl); + ctl.buf = (caddr_t)&hdr; + flags = 0; + ret = getmsg(fd, &ctl, &data, &flags); + if(ret < 0) { + if(errno == EINTR) { + FINALIZE; + } else { + int en = errno; + rs_strerror_r(errno, errStr, sizeof(errStr)); + DBGPRINTF("imsolaris: stream input error on fd %d: %s.\n", fd, errStr); + errmsg.LogError(en, NO_ERRCODE, "imsolaris: stream input error: %s", errStr); + tryRecover(); + } + } else { + DBGPRINTF("imsolaris: message from log stream %d: %s\n", fd, pRcv); + pRcv[data.len] = '\0'; /* make sure it is a valid C-String */ + CHKiRet(msgConstruct(&pMsg)); + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsg(pMsg, (char*)pRcv, strlen((char*)pRcv)); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + pMsg->iFacility = LOG_FAC(hdr.pri); + pMsg->iSeverity = LOG_PRI(hdr.pri); + pMsg->msgFlags = NEEDS_PARSING | NO_PRI_IN_RAW; + CHKiRet(submitMsg(pMsg)); + } + +finalize_it: + RETiRet; +} + + +/* once the system is fully initialized, we wait for new messages. + * We may think about replacing this with a read-loop, thus saving + * us the overhead of the poll. + * The timeout variable is the timeout to use for poll. During startup, + * it should be set to 0 (non-blocking) and later to -1 (infinit, blocking). + * This mimics the (strange) behaviour of the original syslogd. + * rgerhards, 2010-04-19 + */ +static inline rsRetVal +getMsgs(thrdInfo_t *pThrd, int timeout) +{ + DEFiRet; + int nfds; + int iMaxLine; + uchar *pRcv = NULL; /* receive buffer */ + uchar bufRcv[4096+1]; + char errStr[1024]; + + iMaxLine = glbl.GetMaxLine(); + + /* we optimize performance: if iMaxLine is below 4K (which it is in almost all + * cases, we use a fixed buffer on the stack. Only if it is higher, heap memory + * is used. We could use alloca() to achive a similar aspect, but there are so + * many issues with alloca() that I do not want to take that route. + * rgerhards, 2008-09-02 + */ + if((size_t) iMaxLine < sizeof(bufRcv) - 1) { + pRcv = bufRcv; + } else { + CHKmalloc(pRcv = (uchar*) malloc(sizeof(uchar) * (iMaxLine + 1))); + } + + while(pThrd->bShallStop != RSTRUE) { + DBGPRINTF("imsolaris: waiting for next message (timeout %d)...\n", timeout); + if(timeout == 0) { + nfds = poll(&sun_Pfd, 1, timeout); /* wait without timeout */ + + if(pThrd->bShallStop == RSTRUE) { + break; + } + + if(nfds == 0) { + if(timeout == 0) { + DBGPRINTF("imsolaris: no more messages, getMsgs() terminates\n"); + FINALIZE; + } else { + continue; + } + } + + if(nfds < 0) { + if(errno != EINTR) { + int en = errno; + rs_strerror_r(en, errStr, sizeof(errStr)); + DBGPRINTF("imsolaris: poll error: %d = %s.\n", errno, errStr); + errmsg.LogError(en, NO_ERRCODE, "imsolaris: poll error: %s", + errStr); + } + continue; + } + if(sun_Pfd.revents & POLLIN) { + readLog(sun_Pfd.fd, pRcv, iMaxLine); + } else if(sun_Pfd.revents & (POLLNVAL|POLLHUP|POLLERR)) { + tryRecover(); + } + } else { + /* if we have an infinite wait, we do not use poll at all + * I'd consider this a waste of time. However, I do not totally + * remove the code, as it may be useful if we decide at some + * point to provide a capability to support multiple input streams + * at once (this may be useful for a jail). In that case, the poll() + * loop would be needed, and so it doesn't make much sense to change + * the code to not support it. -- rgerhards, 2010-04-20 + */ + readLog(sun_Pfd.fd, pRcv, iMaxLine); + } + + } + +finalize_it: + if(pRcv != NULL && (size_t) iMaxLine >= sizeof(bufRcv) - 1) + free(pRcv); + + RETiRet; +} + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +/* This function is called to gather input. */ +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + + DBGPRINTF("imsolaris: doing startup poll before openeing door()\n"); + CHKiRet(getMsgs(pThrd, 0)); + + /* note: sun's syslogd code claims that the door should only + * be opened when the log stream has been polled. So file header + * comment of this file for more details. + */ + sun_open_door(); + DBGPRINTF("imsolaris: starting regular poll loop\n"); + iRet = getMsgs(pThrd, -1); /* this is the primary poll loop, infinite timeout */ + + DBGPRINTF("imsolaris: terminating (bShallStop=%d)\n", pThrd->bShallStop); +finalize_it: + RETiRet; +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imsolaris"), sizeof("imsolaris") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); + + iRet = sun_openklog((LogName == NULL) ? PATH_LOG : LogName); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error opening system log socket"); + } +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + if(pInputName != NULL) + prop.Destruct(&pInputName); + free(LogName); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + sun_delete_doorfiles(); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); +ENDmodExit + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, + void __attribute__((unused)) *pVal) +{ + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + DBGPRINTF("imsolaris version %s initializing\n", PACKAGE_VERSION); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imsolarislogsocketname", 0, eCmdHdlrGetWord, + NULL, &LogName, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imsolaris/imsolaris.h b/plugins/imsolaris/imsolaris.h new file mode 100644 index 00000000..e73380fa --- /dev/null +++ b/plugins/imsolaris/imsolaris.h @@ -0,0 +1,2 @@ +rsRetVal solaris_readLog(int fd); +void imsolaris_logerror(int err, char *errStr); diff --git a/plugins/imsolaris/sun_cddl.c b/plugins/imsolaris/sun_cddl.c new file mode 100644 index 00000000..6d49c8bc --- /dev/null +++ b/plugins/imsolaris/sun_cddl.c @@ -0,0 +1,419 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* Portions Copyright 2010 by Rainer Gerhards and Adiscon + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T + * All Rights Reserved + */ + +/* + * University Copyright- Copyright (c) 1982, 1986, 1988 + * The Regents of the University of California + * All Rights Reserved + * + * University Acknowledgment- Portions of this document are derived from + * software developed by the University of California, Berkeley, and its + * contributors. + */ +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <pthread.h> +#include <fcntl.h> +#include <stropts.h> +#include <assert.h> + +#include <sys/param.h> +#include <sys/strlog.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/poll.h> +#include <door.h> +#include <sys/door.h> + +#include "rsyslog.h" +#include "srUtils.h" +#include "debug.h" +#include "imsolaris.h" + +#define DOORFILE "/var/run/syslog_door" +#define RELATIVE_DOORFILE "../var/run/syslog_door" +#define OLD_DOORFILE "/etc/.syslog_door" + +/* Buffer to allocate for error messages: */ +#define ERRMSG_LEN 1024 + +/* Max number of door server threads for syslogd. Since door is used + * to check the health of syslogd, we don't need large number of + * server threads. + */ +#define MAX_DOOR_SERVER_THR 3 + + +struct pollfd sun_Pfd; /* Pollfd for local log device */ + +static int DoorFd = -1; +static int DoorCreated = 0; +static char *DoorFileName = DOORFILE; + +/* for managing door server threads */ +static pthread_mutex_t door_server_cnt_lock = PTHREAD_MUTEX_INITIALIZER; +static uint_t door_server_cnt = 0; +static pthread_attr_t door_thr_attr; + +/* the 'server' function that we export via the door. It does + * nothing but return. + */ +/*ARGSUSED*/ +static void +server( void __attribute__((unused)) *cookie, + char __attribute__((unused)) *argp, + size_t __attribute__((unused)) arg_size, + door_desc_t __attribute__((unused)) *dp, + __attribute__((unused)) uint_t n ) +{ + (void) door_return(NULL, 0, NULL, 0); + /* NOTREACHED */ +} + +/*ARGSUSED*/ +static void * +create_door_thr(void __attribute__((unused)) *arg) +{ + (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + (void) door_return(NULL, 0, NULL, 0); + + /* If there is an error in door_return(), it will return here and + * the thread will exit. Hence we need to decrement door_server_cnt. + */ + (void) pthread_mutex_lock(&door_server_cnt_lock); + door_server_cnt--; + (void) pthread_mutex_unlock(&door_server_cnt_lock); + return (NULL); +} + +/* + * Manage door server thread pool. + */ +/*ARGSUSED*/ +static void +door_server_pool(door_info_t __attribute__((unused)) *dip) +{ + (void) pthread_mutex_lock(&door_server_cnt_lock); + if (door_server_cnt <= MAX_DOOR_SERVER_THR && + pthread_create(NULL, &door_thr_attr, create_door_thr, NULL) == 0) { + door_server_cnt++; + (void) pthread_mutex_unlock(&door_server_cnt_lock); + return; + } + + (void) pthread_mutex_unlock(&door_server_cnt_lock); +} + +void +sun_delete_doorfiles(void) +{ + struct stat sb; + int err; + char line[ERRMSG_LEN+1]; + + if (lstat(DoorFileName, &sb) == 0 && !S_ISDIR(sb.st_mode)) { + if (unlink(DoorFileName) < 0) { + err = errno; + (void) snprintf(line, sizeof (line), + "unlink() of %s failed - fatal", DoorFileName); + imsolaris_logerror(err, line); + DBGPRINTF("delete_doorfiles: error: %s, " + "errno=%d\n", line, err); + exit(1); + } + + DBGPRINTF("delete_doorfiles: deleted %s\n", DoorFileName); + } + + if (strcmp(DoorFileName, DOORFILE) == 0) { + if (lstat(OLD_DOORFILE, &sb) == 0 && !S_ISDIR(sb.st_mode)) { + if (unlink(OLD_DOORFILE) < 0) { + err = errno; + (void) snprintf(line, sizeof (line), + "unlink() of %s failed", OLD_DOORFILE); + DBGPRINTF("delete_doorfiles: %s\n", line); + + if (err != EROFS) { + errno = err; + (void) strlcat(line, " - fatal", + sizeof (line)); + imsolaris_logerror(err, line); + DBGPRINTF("delete_doorfiles: " + "error: %s, errno=%d\n", + line, err); + exit(1); + } + + DBGPRINTF("delete_doorfiles: unlink() " + "failure OK on RO file system\n"); + } + + DBGPRINTF("delete_doorfiles: deleted %s\n", + OLD_DOORFILE); + } + } + + if (DoorFd != -1) { + (void) door_revoke(DoorFd); + } + + DBGPRINTF("delete_doorfiles: revoked door: DoorFd=%d\n", + DoorFd); +} + + +/* Create the door file. If the filesystem + * containing /etc is writable, create symlinks /etc/.syslog_door + * to them. On systems that do not support /var/run, create + * /etc/.syslog_door directly. + */ +void +sun_open_door(void) +{ + struct stat buf; + door_info_t info; + char line[ERRMSG_LEN+1]; + int err; + + /* first see if another instance of imsolaris OR another + * syslogd is running by trying a door call - if it succeeds, + * there is already one active. + */ + + if (!DoorCreated) { + int door; + + if ((door = open(DoorFileName, O_RDONLY)) >= 0) { + DBGPRINTF("open_door: %s opened " + "successfully\n", DoorFileName); + + if (door_info(door, &info) >= 0) { + DBGPRINTF("open_door: " + "door_info:info.di_target = %ld\n", + info.di_target); + + if (info.di_target > 0) { + (void) sprintf(line, "syslogd pid %ld" + " already running. Cannot " + "start another syslogd pid %ld", + info.di_target, getpid()); + DBGPRINTF("open_door: error: " + "%s\n", line); + imsolaris_logerror(0, line); + exit(1); + } + } + + (void) close(door); + } else { + if (lstat(DoorFileName, &buf) < 0) { + err = errno; + + DBGPRINTF("open_door: lstat() of %s " + "failed, errno=%d\n", + DoorFileName, err); + + if ((door = creat(DoorFileName, 0644)) < 0) { + err = errno; + (void) snprintf(line, sizeof (line), + "creat() of %s failed - fatal", + DoorFileName); + DBGPRINTF("open_door: error: %s, " + "errno=%d\n", line, + err); + imsolaris_logerror(err, line); + sun_delete_doorfiles(); + exit(1); + } + + (void) fchmod(door, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + + DBGPRINTF("open_door: creat() of %s " + "succeeded\n", + DoorFileName); + + (void) close(door); + } + } + + if (strcmp(DoorFileName, DOORFILE) == 0) { + if (lstat(OLD_DOORFILE, &buf) == 0) { + DBGPRINTF("open_door: lstat() of %s " + "succeeded\n", OLD_DOORFILE); + + if (S_ISDIR(buf.st_mode)) { + (void) snprintf(line, sizeof (line), + "%s is a directory - fatal", + OLD_DOORFILE); + DBGPRINTF("open_door: error: " + "%s\n", line); + imsolaris_logerror(0, line); + sun_delete_doorfiles(); + exit(1); + } + + DBGPRINTF("open_door: %s is not a " + "directory\n", OLD_DOORFILE); + if (unlink(OLD_DOORFILE) < 0) { + err = errno; + (void) snprintf(line, sizeof (line), + "unlink() of %s failed", + OLD_DOORFILE); + DBGPRINTF("open_door: %s\n", + line); + + if (err != EROFS) { + DBGPRINTF("open_door: " + "error: %s, " + "errno=%d\n", + line, err); + (void) strcat(line, " - fatal"); + imsolaris_logerror(err, line); + sun_delete_doorfiles(); + exit(1); + } + + DBGPRINTF("open_door: unlink " + "failure OK on RO file " + "system\n"); + } + } else { + DBGPRINTF("open_door: file %s doesn't " + "exist\n", OLD_DOORFILE); + } + + if (symlink(RELATIVE_DOORFILE, OLD_DOORFILE) < 0) { + err = errno; + (void) snprintf(line, sizeof (line), + "symlink %s -> %s failed", OLD_DOORFILE, + RELATIVE_DOORFILE); + DBGPRINTF("open_door: %s\n", + line); + + if (err != EROFS) { + DBGPRINTF("open_door: error: %s, " + "errno=%d\n", line, + err); + (void) strcat(line, " - fatal"); + imsolaris_logerror(err, line); + sun_delete_doorfiles(); + exit(1); + } + + DBGPRINTF("open_door: symlink failure OK " + "on RO file system\n"); + } else { + DBGPRINTF("open_door: symlink %s -> %s " + "succeeded\n", + OLD_DOORFILE, RELATIVE_DOORFILE); + } + } + + if ((DoorFd = door_create(server, 0, + DOOR_REFUSE_DESC)) < 0) { + //???? DOOR_NO_CANEL requires newer libs??? DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) < 0) { + err = errno; + (void) sprintf(line, "door_create() failed - fatal"); + DBGPRINTF("open_door: error: %s, errno=%d\n", + line, err); + imsolaris_logerror(err, line); + sun_delete_doorfiles(); + exit(1); + } + //???? (void) door_setparam(DoorFd, DOOR_PARAM_DATA_MAX, 0); + DBGPRINTF("open_door: door_create() succeeded, " + "DoorFd=%d\n", DoorFd); + + DoorCreated = 1; + } + + (void) fdetach(DoorFileName); /* just in case... */ + + (void) door_server_create(door_server_pool); + + if (fattach(DoorFd, DoorFileName) < 0) { + err = errno; + (void) snprintf(line, sizeof (line), "fattach() of fd" + " %d to %s failed - fatal", DoorFd, DoorFileName); + DBGPRINTF("open_door: error: %s, errno=%d\n", + line, err); + imsolaris_logerror(err, line); + sun_delete_doorfiles(); + exit(1); + } + + DBGPRINTF("open_door: attached server() to %s\n", + DoorFileName); + +} + + +/* Attempts to open the local log device + * and return a file descriptor. + */ +rsRetVal +sun_openklog(char *name) +{ + DEFiRet; + int fd; + struct strioctl str; + char errBuf[1024]; + + if((fd = open(name, O_RDONLY)) < 0) { + rs_strerror_r(errno, errBuf, sizeof(errBuf)); + DBGPRINTF("imsolaris:openklog: cannot open %s: %s\n", + name, errBuf); + ABORT_FINALIZE(RS_RET_ERR_OPEN_KLOG); + } + str.ic_cmd = I_CONSLOG; + str.ic_timout = 0; + str.ic_len = 0; + str.ic_dp = NULL; + if (ioctl(fd, I_STR, &str) < 0) { + rs_strerror_r(errno, errBuf, sizeof(errBuf)); + DBGPRINTF("imsolaris:openklog: cannot register to log " + "console messages: %s\n", errBuf); + ABORT_FINALIZE(RS_RET_ERR_AQ_CONLOG); + } + sun_Pfd.fd = fd; + sun_Pfd.events = POLLIN; + DBGPRINTF("imsolaris/openklog: opened '%s' as fd %d.\n", name, fd); + +finalize_it: + RETiRet; +} diff --git a/plugins/imsolaris/sun_cddl.h b/plugins/imsolaris/sun_cddl.h new file mode 100644 index 00000000..42e4b799 --- /dev/null +++ b/plugins/imsolaris/sun_cddl.h @@ -0,0 +1,7 @@ +rsRetVal sun_openklog(char *name); +void prepare_sys_poll(void); +void sun_sys_poll(void); +void sun_open_door(void); +void sun_delete_doorfiles(void); + +extern struct pollfd sun_Pfd; /* Pollfd for local log device */ diff --git a/plugins/imtcp/Makefile.am b/plugins/imtcp/Makefile.am new file mode 100644 index 00000000..26653536 --- /dev/null +++ b/plugins/imtcp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imtcp.la + +imtcp_la_SOURCES = imtcp.c +imtcp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imtcp_la_LDFLAGS = -module -avoid-version +imtcp_la_LIBADD = diff --git a/plugins/imtcp/imtcp.c b/plugins/imtcp/imtcp.c new file mode 100644 index 00000000..fc22d452 --- /dev/null +++ b/plugins/imtcp/imtcp.c @@ -0,0 +1,708 @@ +/* imtcp.c + * This is the implementation of the TCP input module. + * + * File begun on 2007-12-21 by RGerhards (extracted from syslogd.c, + * which at the time of the rsyslog fork was BSD-licensed) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This note shall explain the calling sequence while we do not have + * have full RainerScript support for (TLS) sender authentication: + * + * imtcp --> tcpsrv --> netstrms (this sequence stored pPermPeers in netstrms class) + * then a callback (doOpenLstnSocks) into imtcp happens, which in turn calls + * into tcpsrv.create_tcp_socket(), + * which calls into netstrm.LstnInit(), which receives a pointer to netstrms obj + * which calls into the driver function LstnInit (again, netstrms obj passed) + * which finally calls back into netstrms obj's get functions to obtain the auth + * parameters and then applies them to the driver object instance + * + * rgerhards, 2008-05-19 + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "net.h" +#include "netstrm.h" +#include "errmsg.h" +#include "tcpsrv.h" +#include "ruleset.h" +#include "rainerscript.h" +#include "net.h" /* for permittedPeers, may be removed when this is removed */ + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imtcp") + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(ruleset) + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* Module static data */ +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ +static permittedPeers_t *pPermPeersRoot = NULL; + + +/* config settings */ +static struct configSettings_s { + int iTCPSessMax; + int iTCPLstnMax; + int bSuppOctetFram; + int iStrmDrvrMode; + int bKeepAlive; + int bEmitMsgOnClose; + int iAddtlFrameDelim; + int bDisableLFDelim; + int bUseFlowControl; + uchar *pszStrmDrvrAuthMode; + uchar *pszInputName; + uchar *pszBindRuleset; +} cs; + +struct instanceConf_s { + uchar *pszBindPort; /* port to bind to */ + uchar *pszBindRuleset; /* name of ruleset to bind to */ + ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ + uchar *pszInputName; /* value for inputname property, NULL is OK and handled by core engine */ + int ratelimitInterval; + int ratelimitBurst; + int bSuppOctetFram; + struct instanceConf_s *next; +}; + + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + instanceConf_t *root, *tail; + int iTCPSessMax; /* max number of sessions */ + int iTCPLstnMax; /* max number of sessions */ + int iStrmDrvrMode; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */ + int iAddtlFrameDelim; /* addtl frame delimiter, e.g. for netscreen, default none */ + int bSuppOctetFram; + sbool bDisableLFDelim; /* disable standard LF delimiter */ + sbool bUseFlowControl; /* use flow control, what means indicate ourselfs a "light delayable" */ + sbool bKeepAlive; + sbool bEmitMsgOnClose; /* emit an informational message on close by remote peer */ + uchar *pszStrmDrvrAuthMode; /* authentication mode to use */ + struct cnfarray *permittedPeers; + sbool configSetViaV2Method; +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "flowcontrol", eCmdHdlrBinary, 0 }, + { "disablelfdelimiter", eCmdHdlrBinary, 0 }, + { "octetcountedframing", eCmdHdlrBinary, 0 }, + { "notifyonconnectionclose", eCmdHdlrBinary, 0 }, + { "addtlframedelimiter", eCmdHdlrPositiveInt, 0 }, + { "maxsessions", eCmdHdlrPositiveInt, 0 }, + { "maxlistners", eCmdHdlrPositiveInt, 0 }, + { "streamdriver.mode", eCmdHdlrPositiveInt, 0 }, + { "streamdriver.authmode", eCmdHdlrString, 0 }, + { "permittedpeer", eCmdHdlrArray, 0 }, + { "keepalive", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* input instance parameters */ +static struct cnfparamdescr inppdescr[] = { + { "port", eCmdHdlrString, CNFPARAM_REQUIRED }, /* legacy: InputTCPServerRun */ + { "name", eCmdHdlrString, 0 }, + { "ruleset", eCmdHdlrString, 0 }, + { "supportOctetCountedFraming", eCmdHdlrBinary, 0 }, + { "ratelimit.interval", eCmdHdlrInt, 0 }, + { "ratelimit.burst", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + +#include "im-helper.h" /* must be included AFTER the type definitions! */ + +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + +/* callbacks */ +/* this shall go into a specific ACL module! */ +static int +isPermittedHost(struct sockaddr *addr, char *fromHostFQDN, void __attribute__((unused)) *pUsrSrv, + void __attribute__((unused)) *pUsrSess) +{ + return net.isAllowedSender2(UCHAR_CONSTANT("TCP"), addr, fromHostFQDN, 1); +} + + +static rsRetVal +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + ISOBJ_TYPE_assert(pSrv, tcpsrv); + return tcpsrv.create_tcp_socket(pSrv); +} + + +static rsRetVal +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd) +{ + DEFiRet; + assert(pSess != NULL); + assert(piLenRcvd != NULL); + + *piLenRcvd = lenBuf; + CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd)); +finalize_it: + RETiRet; +} + +static rsRetVal +onRegularClose(tcps_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + /* process any incomplete frames left over */ + tcps_sess.PrepareClose(pSess); + /* Session closed */ + tcps_sess.Close(pSess); + RETiRet; +} + + +static rsRetVal +onErrClose(tcps_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + tcps_sess.Close(pSess); + RETiRet; +} + +/* ------------------------------ end callbacks ------------------------------ */ + + +/* set permitted peer -- rgerhards, 2008-05-19 + */ +static rsRetVal +setPermittedPeer(void __attribute__((unused)) *pVal, uchar *pszID) +{ + DEFiRet; + CHKiRet(net.AddPermittedPeer(&pPermPeersRoot, pszID)); + free(pszID); /* no longer needed, but we need to free as of interface def */ +finalize_it: + RETiRet; +} + + +/* create input instance, set default paramters, and + * add it to the list of instances. + */ +static rsRetVal +createInstance(instanceConf_t **pinst) +{ + instanceConf_t *inst; + DEFiRet; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + inst->next = NULL; + inst->pszBindRuleset = NULL; + inst->pszInputName = NULL; + inst->bSuppOctetFram = 1; + inst->ratelimitInterval = 0; + inst->ratelimitBurst = 10000; + + /* node created, let's add to config */ + if(loadModConf->tail == NULL) { + loadModConf->tail = loadModConf->root = inst; + } else { + loadModConf->tail->next = inst; + loadModConf->tail = inst; + } + + *pinst = inst; +finalize_it: + RETiRet; +} + + +/* This function is called when a new listener instace shall be added to + * the current config object via the legacy config system. It just shuffles + * all parameters to the listener in-memory instance. + * rgerhards, 2011-05-04 + */ +static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + instanceConf_t *inst; + DEFiRet; + + CHKiRet(createInstance(&inst)); + + CHKmalloc(inst->pszBindPort = ustrdup((pNewVal == NULL || *pNewVal == '\0') + ? (uchar*) "10514" : pNewVal)); + if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) { + inst->pszBindRuleset = NULL; + } else { + CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset)); + } + if((cs.pszInputName == NULL) || (cs.pszInputName[0] == '\0')) { + inst->pszInputName = NULL; + } else { + CHKmalloc(inst->pszInputName = ustrdup(cs.pszInputName)); + } + inst->bSuppOctetFram = cs.bSuppOctetFram; + +finalize_it: + free(pNewVal); + RETiRet; +} + + +static rsRetVal +addListner(modConfData_t *modConf, instanceConf_t *inst) +{ + DEFiRet; + + if(pOurTcpsrv == NULL) { + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + /* callbacks */ + CHKiRet(tcpsrv.SetCBIsPermittedHost(pOurTcpsrv, isPermittedHost)); + CHKiRet(tcpsrv.SetCBRcvData(pOurTcpsrv, doRcvData)); + CHKiRet(tcpsrv.SetCBOpenLstnSocks(pOurTcpsrv, doOpenLstnSocks)); + CHKiRet(tcpsrv.SetCBOnRegularClose(pOurTcpsrv, onRegularClose)); + CHKiRet(tcpsrv.SetCBOnErrClose(pOurTcpsrv, onErrClose)); + /* params */ + CHKiRet(tcpsrv.SetKeepAlive(pOurTcpsrv, modConf->bKeepAlive)); + CHKiRet(tcpsrv.SetSessMax(pOurTcpsrv, modConf->iTCPSessMax)); + CHKiRet(tcpsrv.SetLstnMax(pOurTcpsrv, modConf->iTCPLstnMax)); + CHKiRet(tcpsrv.SetDrvrMode(pOurTcpsrv, modConf->iStrmDrvrMode)); + CHKiRet(tcpsrv.SetUseFlowControl(pOurTcpsrv, modConf->bUseFlowControl)); + CHKiRet(tcpsrv.SetAddtlFrameDelim(pOurTcpsrv, modConf->iAddtlFrameDelim)); + CHKiRet(tcpsrv.SetbDisableLFDelim(pOurTcpsrv, modConf->bDisableLFDelim)); + CHKiRet(tcpsrv.SetNotificationOnRemoteClose(pOurTcpsrv, modConf->bEmitMsgOnClose)); + /* now set optional params, but only if they were actually configured */ + if(modConf->pszStrmDrvrAuthMode != NULL) { + CHKiRet(tcpsrv.SetDrvrAuthMode(pOurTcpsrv, modConf->pszStrmDrvrAuthMode)); + } + if(pPermPeersRoot != NULL) { + CHKiRet(tcpsrv.SetDrvrPermPeers(pOurTcpsrv, pPermPeersRoot)); + } + } + + /* initialized, now add socket and listener params */ + DBGPRINTF("imtcp: trying to add port *:%s\n", inst->pszBindPort); + CHKiRet(tcpsrv.SetRuleset(pOurTcpsrv, inst->pBindRuleset)); + CHKiRet(tcpsrv.SetInputName(pOurTcpsrv, inst->pszInputName == NULL ? + UCHAR_CONSTANT("imtcp") : inst->pszInputName)); + CHKiRet(tcpsrv.SetLinuxLikeRatelimiters(pOurTcpsrv, inst->ratelimitInterval, inst->ratelimitBurst)); + tcpsrv.configureTCPListen(pOurTcpsrv, inst->pszBindPort, inst->bSuppOctetFram); + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "imtcp: error %d trying to add listener", iRet); + } + RETiRet; +} + + +BEGINnewInpInst + struct cnfparamvals *pvals; + instanceConf_t *inst; + int i; +CODESTARTnewInpInst + DBGPRINTF("newInpInst (imtcp)\n"); + + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imtcp: required parameter are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("input param blk in imtcp:\n"); + cnfparamsPrint(&inppblk, pvals); + } + + CHKiRet(createInstance(&inst)); + + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "port")) { + inst->pszBindPort = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "name")) { + inst->pszInputName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "ruleset")) { + inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "supportOctetCountedFraming")) { + inst->bSuppOctetFram = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "ratelimit.burst")) { + inst->ratelimitBurst = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "ratelimit.interval")) { + inst->ratelimitInterval = (int) pvals[i].val.d.n; + } else { + dbgprintf("imtcp: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + } +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + loadModConf->iTCPSessMax = 200; + loadModConf->iTCPLstnMax = 20; + loadModConf->bSuppOctetFram = 1; + loadModConf->iStrmDrvrMode = 0; + loadModConf->bUseFlowControl = 0; + loadModConf->bKeepAlive = 0; + loadModConf->bEmitMsgOnClose = 0; + loadModConf->iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + loadModConf->bDisableLFDelim = 0; + loadModConf->pszStrmDrvrAuthMode = NULL; + loadModConf->permittedPeers = NULL; + loadModConf->configSetViaV2Method = 0; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config variables */ + cs.pszStrmDrvrAuthMode = NULL; + resetConfigVariables(NULL, NULL); /* dummy parameters just to fulfill interface def */ +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imtcp: error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imtcp:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "flowcontrol")) { + loadModConf->bUseFlowControl = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "disablelfdelimiter")) { + loadModConf->bDisableLFDelim = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "octetcountedframing")) { + loadModConf->bSuppOctetFram = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "notifyonconnectionclose")) { + loadModConf->bEmitMsgOnClose = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "addtlframedelimiter")) { + loadModConf->iAddtlFrameDelim = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "maxsessions")) { + loadModConf->iTCPSessMax = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "maxlistners")) { + loadModConf->iTCPLstnMax = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "keepalive")) { + loadModConf->bKeepAlive = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "streamdriver.mode")) { + loadModConf->iStrmDrvrMode = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "streamdriver.authmode")) { + loadModConf->pszStrmDrvrAuthMode = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "permittedpeer")) { + loadModConf->permittedPeers = cnfarrayDup(pvals[i].val.d.ar); + } else { + dbgprintf("imtcp: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* remove all of our legacy handlers, as they can not used in addition + * the the new-style config method. + */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + pModConf->iTCPSessMax = cs.iTCPSessMax; + pModConf->iTCPLstnMax = cs.iTCPLstnMax; + pModConf->iStrmDrvrMode = cs.iStrmDrvrMode; + pModConf->bEmitMsgOnClose = cs.bEmitMsgOnClose; + pModConf->bSuppOctetFram = cs.bSuppOctetFram; + pModConf->iAddtlFrameDelim = cs.iAddtlFrameDelim; + pModConf->bDisableLFDelim = cs.bDisableLFDelim; + pModConf->bUseFlowControl = cs.bUseFlowControl; + pModConf->bKeepAlive = cs.bKeepAlive; + if((cs.pszStrmDrvrAuthMode == NULL) || (cs.pszStrmDrvrAuthMode[0] == '\0')) { + loadModConf->pszStrmDrvrAuthMode = NULL; + } else { + loadModConf->pszStrmDrvrAuthMode = cs.pszStrmDrvrAuthMode; + cs.pszStrmDrvrAuthMode = NULL; + } + } + free(cs.pszStrmDrvrAuthMode); + cs.pszStrmDrvrAuthMode = NULL; + + loadModConf = NULL; /* done loading */ +ENDendCnfLoad + + +/* function to generate error message if framework does not find requested ruleset */ +static inline void +std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) +{ + errmsg.LogError(0, NO_ERRCODE, "imtcp: ruleset '%s' for port %s not found - " + "using default ruleset instead", inst->pszBindRuleset, + inst->pszBindPort); +} + +BEGINcheckCnf + instanceConf_t *inst; +CODESTARTcheckCnf + for(inst = pModConf->root ; inst != NULL ; inst = inst->next) { + std_checkRuleset(pModConf, inst); + } + if(pModConf->root == NULL) { + errmsg.LogError(0, RS_RET_NO_LISTNERS , "imtcp: module loaded, but " + "no listeners defined - no input will be gathered"); + iRet = RS_RET_NO_LISTNERS; + } +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop + instanceConf_t *inst; + int i; +CODESTARTactivateCnfPrePrivDrop + runModConf = pModConf; + if(runModConf->permittedPeers != NULL) { + for(i = 0 ; i < runModConf->permittedPeers->nmemb ; ++i) { + setPermittedPeer(NULL, (uchar*) + es_str2cstr(runModConf->permittedPeers->arr[i], NULL)); + } + } + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + addListner(pModConf, inst); + } + if(pOurTcpsrv == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); +finalize_it: +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf + /* sorry, nothing to do here... */ +ENDactivateCnf + + +BEGINfreeCnf + instanceConf_t *inst, *del; +CODESTARTfreeCnf + free(pModConf->pszStrmDrvrAuthMode); + if(pModConf->permittedPeers != NULL) { + cnfarrayContentDestruct(pModConf->permittedPeers); + free(pModConf->permittedPeers); + } + for(inst = pModConf->root ; inst != NULL ; ) { + free(inst->pszBindPort); + free(inst->pszInputName); + del = inst; + inst = inst->next; + free(del); + } +ENDfreeCnf + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + iRet = tcpsrv.Run(pOurTcpsrv); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + net.PrintAllowedSenders(2); /* TCP */ +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + if(pOurTcpsrv != NULL) + iRet = tcpsrv.Destruct(&pOurTcpsrv); + + net.clearAllowedSenders(UCHAR_CONSTANT("TCP")); +ENDafterRun + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINmodExit +CODESTARTmodExit + if(pPermPeersRoot != NULL) { + net.DestructPermittedPeers(&pPermPeersRoot); + } + + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(tcps_sess, LM_TCPSRV_FILENAME); + objRelease(tcpsrv, LM_TCPSRV_FILENAME); + objRelease(errmsg, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.iTCPSessMax = 200; + cs.iTCPLstnMax = 20; + cs.bSuppOctetFram = 1; + cs.iStrmDrvrMode = 0; + cs.bUseFlowControl = 0; + cs.bKeepAlive = 0; + cs.bEmitMsgOnClose = 0; + cs.iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + cs.bDisableLFDelim = 0; + free(cs.pszInputName); + cs.pszInputName = NULL; + free(cs.pszStrmDrvrAuthMode); + cs.pszStrmDrvrAuthMode = NULL; + return RS_RET_OK; +} + + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(tcps_sess, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(tcpsrv, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputtcpserverrun"), 0, eCmdHdlrGetWord, + addInstance, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputtcpserverinputname"), 0, eCmdHdlrGetWord, + NULL, &cs.pszInputName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputtcpserverbindruleset"), 0, eCmdHdlrGetWord, + NULL, &cs.pszBindRuleset, STD_LOADABLE_MODULE_ID)); + /* module-global config params - will be disabled in configs that are loaded + * via module(...). + */ + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserverstreamdriverpermittedpeer"), 0, eCmdHdlrGetWord, + setPermittedPeer, NULL, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserverstreamdriverauthmode"), 0, eCmdHdlrGetWord, + NULL, &cs.pszStrmDrvrAuthMode, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserverkeepalive"), 0, eCmdHdlrBinary, + NULL, &cs.bKeepAlive, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpflowcontrol"), 0, eCmdHdlrBinary, + NULL, &cs.bUseFlowControl, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserverdisablelfdelimiter"), 0, eCmdHdlrBinary, + NULL, &cs.bDisableLFDelim, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserveraddtlframedelimiter"), 0, eCmdHdlrInt, + NULL, &cs.iAddtlFrameDelim, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserversupportoctetcountedframing"), 0, eCmdHdlrBinary, + NULL, &cs.bSuppOctetFram, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpmaxsessions"), 0, eCmdHdlrInt, + NULL, &cs.iTCPSessMax, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpmaxlisteners"), 0, eCmdHdlrInt, + NULL, &cs.iTCPLstnMax, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpservernotifyonconnectionclose"), 0, eCmdHdlrBinary, + NULL, &cs.bEmitMsgOnClose, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2(UCHAR_CONSTANT("inputtcpserverstreamdrivermode"), 0, eCmdHdlrInt, + NULL, &cs.iStrmDrvrMode, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("resetconfigvariables"), 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/imttcp/Makefile.am b/plugins/imttcp/Makefile.am new file mode 100644 index 00000000..9b09b4bf --- /dev/null +++ b/plugins/imttcp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imttcp.la + +imttcp_la_SOURCES = imttcp.c +imttcp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imttcp_la_LDFLAGS = -module -avoid-version +imttcp_la_LIBADD = diff --git a/plugins/imttcp/imttcp.c b/plugins/imttcp/imttcp.c new file mode 100644 index 00000000..9bd11f77 --- /dev/null +++ b/plugins/imttcp/imttcp.c @@ -0,0 +1,1153 @@ +/* imttcp.c + * This is an experimental plain tcp input module which follows the + * multiple thread paradigm. + * + * WARNING + * This module is unfinished. It seems to work, but there also seems to be a problem + * if it is under large stress (e.g. tcpflood with more than 500 to 1000 concurrent + * connections). I quickly put together this module after I worked on a larger paper + * and read [1], which claims that using massively threaded applications is + * preferrable to an event driven approach. So I put this to test, especially as + * that would also lead to a much simpler programming paradigm. Unfortuantely, the + * performance results are devastive: while there is a very slight speedup with + * a low connection number (close to the number of cores on the system), there + * is a dramatic negative speedup if running with many threads. Even at only 50 + * connections, rsyslog is dramatically slower (80 seconds for the same workload + * which was processed in 60 seconds with traditional imtcp or when running on + * a single connection). At 1,000 connections, the run was *extremely* slow. So + * this is definitely a dead-end. To be honest, Behren, condit and Brewer claim + * that the problem lies in the current implementation of thread libraries. + * As one cure, they propose user-level threads. However, as far as I could + * find out, User-Level threads seem not to be much faster under Linux than + * Kernel-Level threads (which I used in my approach). + * + * Even more convincing is, from the rsyslog PoV, that there are clear reasons + * why the highly threaded input must be slower: + * o batch sizes are smaller, leading to much more overhead + * o many more context switches are needed to switch between the various + * i/o handlers + * o more OS API calls are required because in this model we get more + * frequent wakeups on new incoming data, so we have less data available + * to read at each instant + * o more lock contention because many more threads compete on the + * main queue mutex + * + * All in all, this means that the approach is not the right one, at least + * not for rsyslog (it may work better if the input can be processed + * totally independent, but I have note evaluated this). So I will look into + * an enhanced event-based model with a small set of input workers pulling + * off data (I assume this is useful for e.g. TLS, as TLS transport is much + * more computebound than other inputs, and this computation becomes a + * limiting factor for the overall processing speed under some + * circumstances - see [2]). + * + * For obvious reasons, I will not try to finish imttcp. However, I have + * decided to leave it included in the source tree, so that + * a) someone else can build on it, if he sees value in that + * b) I may use it for some other tests in the future + * + * But if you intend to actually use this module unmodified, be prepared + * for problems. + * + * [1] R. Von Behren, J. Condit, and E. Brewer. Why events are a bad idea + * (for high-concurrency servers). In Proceedings of the 9th conference on Hot + * Topics in Operating Systems-Volume 9, page 4. USENIX Association, 2003. + * + * [2] http://kb.monitorware.com/tls-limited-17800-messages-per-second-t10598.html + * + * File begun on 2011-01-24 by RGerhards + * + * Copyright 2007-2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#if !defined(HAVE_EPOLL_CREATE) +# error imttcp requires OS support for epoll - can not build + /* imttcp gains speed by using modern Linux capabilities. As such, + * it can only be build on platforms supporting the epoll API. + */ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/epoll.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "cfsysline.h" +#include "prop.h" +#include "dirty.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "glbl.h" +#include "prop.h" +#include "errmsg.h" +#include "srUtils.h" +#include "datetime.h" +#include "ruleset.h" +#include "msg.h" +#include "net.h" /* for permittedPeers, may be removed when this is removed */ + +/* the define is from tcpsrv.h, we need to find a new (but easier!!!) abstraction layer some time ... */ +#define TCPSRV_NO_ADDTL_DELIMITER -1 /* specifies that no additional delimiter is to be used in TCP framing */ + + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imttcp") + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(prop) +DEFobjCurrIf(datetime) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(ruleset) + + + +/* config settings */ +struct modConfData_s { + EMPTY_STRUCT; +}; + +typedef struct configSettings_s { + int bEmitMsgOnClose; /* emit an informational message on close by remote peer */ + int iAddtlFrameDelim; /* addtl frame delimiter, e.g. for netscreen, default none */ + uchar *pszInputName; /* value for inputname property, NULL is OK and handled by core engine */ + uchar *lstnIP; /* which IP we should listen on? */ + ruleset_t *pRuleset; /* ruleset to bind listener to (use system default if unspecified) */ +} configSettings_t; + +static configSettings_t cs; + +/* data elements describing our running config */ +typedef struct ttcpsrv_s ttcpsrv_t; +typedef struct ttcplstn_s ttcplstn_t; +typedef struct ttcpsess_s ttcpsess_t; +typedef struct epolld_s epolld_t; + +/* the ttcp server (listener) object + * Note that the object contains support for forming a linked list + * of them. It does not make sense to do this seperately. + */ +struct ttcpsrv_s { + ttcpsrv_t *pNext; /* linked list maintenance */ + uchar *port; /* Port to listen to */ + uchar *lstnIP; /* which IP we should listen on? */ + int bEmitMsgOnClose; + int iAddtlFrameDelim; + uchar *pszInputName; + prop_t *pInputName; /* InputName in (fast to process) property format */ + ruleset_t *pRuleset; + ttcplstn_t *pLstn; /* root of our listeners */ + ttcpsess_t *pSess; /* root of our sessions */ + pthread_mutex_t mutSess; /* mutex for session list updates */ +}; + +/* the ttcp session object. Describes a single active session. + * includes support for doubly-linked list. + */ +struct ttcpsess_s { + ttcpsrv_t *pSrv; /* our server */ + ttcpsess_t *prev, *next; + int sock; + pthread_t tid; +//--- from tcps_sess.h + int iMsg; /* index of next char to store in msg */ + int bAtStrtOfFram; /* are we at the very beginning of a new frame? */ + enum { + eAtStrtFram, + eInOctetCnt, + eInMsg + } inputState; /* our current state */ + int iOctetsRemain; /* Number of Octets remaining in message */ + TCPFRAMINGMODE eFraming; + uchar *pMsg; /* message (fragment) received */ + prop_t *peerName; /* host name we received messages from */ + prop_t *peerIP; +//--- END from tcps_sess.h +}; + + +/* the ttcp listener object. Describes a single active listener. + */ +struct ttcplstn_s { + ttcpsrv_t *pSrv; /* our server */ + ttcplstn_t *prev, *next; + int sock; + pthread_t tid; /* ID of our listener thread */ +}; + + +/* type of object stored in epoll descriptor */ +typedef enum { + epolld_lstn, + epolld_sess +} epolld_type_t; + +/* an epoll descriptor. contains all information necessary to process + * the result of epoll. + */ +struct epolld_s { + epolld_type_t typ; + void *ptr; + struct epoll_event ev; +}; + + +/* global data */ +static ttcpsrv_t *pSrvRoot = NULL; +static int iMaxLine; /* maximum size of a single message */ +pthread_attr_t sessThrdAttr; /* Attribute for session threads; read only after startup */ + +/* forward definitions */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); +static rsRetVal addLstn(ttcpsrv_t *pSrv, int sock); +static void * sessThrd(void *arg); + + +/* some simple constructors/destructors */ +static void +destructSess(ttcpsess_t *pSess) +{ + free(pSess->pMsg); + prop.Destruct(&pSess->peerName); + prop.Destruct(&pSess->peerIP); + /* TODO: make these inits compile-time switch depending: */ + pSess->pMsg = NULL; + free(pSess); +} + +static void +destructSrv(ttcpsrv_t *pSrv) +{ + prop.Destruct(&pSrv->pInputName); + free(pSrv->port); + free(pSrv); +} + + +/* common initialisation for new threads */ +static inline void +initThrd(void) +{ + /* block all signals */ + sigset_t sigSet; + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + + /* but ignore SIGTTN, which we (ab)use to signal the thread to shutdown -- rgerhards, 2009-07-20 */ + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGTTIN); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + +} + + + +/****************************************** TCP SUPPORT FUNCTIONS ***********************************/ +/* We may later think about moving this into a helper library again. But the whole point + * so far was to keep everything related close togehter. -- rgerhards, 2010-08-10 + */ + + +/* Start up a server. That means all of its listeners are created. + * Does NOT yet accept/process any incoming data (but binds ports). Hint: this + * code is to be executed before dropping privileges. + */ +static rsRetVal +createSrv(ttcpsrv_t *pSrv) +{ + DEFiRet; + int error, maxs, on = 1; + int sock = -1; + int numSocks; + struct addrinfo hints, *res = NULL, *r; + uchar *lstnIP; + + lstnIP = pSrv->lstnIP == NULL ? UCHAR_CONSTANT("") : pSrv->lstnIP; + + DBGPRINTF("imttcp creating listen socket on server '%s', port %s\n", lstnIP, pSrv->port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo((char*)pSrv->lstnIP, (char*) pSrv->port, &hints, &res); + if(error) { + DBGPRINTF("error %d querying server '%s', port '%s'\n", error, pSrv->lstnIP, pSrv->port); + ABORT_FINALIZE(RS_RET_INVALID_PORT); + } + + /* Count max number of sockets we may open */ + for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + + numSocks = 0; /* num of sockets counter at start of array */ + for(r = res; r != NULL ; r = r->ai_next) { + sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if(sock < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + DBGPRINTF("error %d creating tcp listen socket", errno); + /* it is debatable if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +#ifdef IPV6_V6ONLY + if(r->ai_family == AF_INET6) { + int iOn = 1; + if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&iOn, sizeof (iOn)) < 0) { + close(sock); + sock = -1; + continue; + } + } +#endif + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) { + DBGPRINTF("error %d setting tcp socket option\n", errno); + close(sock); + sock = -1; + continue; + } + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#ifndef BSD + if(net.should_use_so_bsdcompat()) { + if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)"); + close(sock); + sock = -1; + continue; + } + } +#endif + + if( (bind(sock, r->ai_addr, r->ai_addrlen) < 0) +#ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) +#endif + ) { + /* TODO: check if *we* bound the socket - else we *have* an error! */ + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("error %d while binding tcp socket: %s\n", errno, errStr); + close(sock); + sock = -1; + continue; + } + + if(listen(sock, 511) < 0) { + DBGPRINTF("tcp listen error %d, suspending\n", errno); + close(sock); + sock = -1; + continue; + } + + /* if we reach this point, we were able to obtain a valid socket, so we can + * create our listener object. -- rgerhards, 2010-08-10 + */ + CHKiRet(addLstn(pSrv, sock)); + ++numSocks; + } + + if(numSocks != maxs) + DBGPRINTF("We could initialize %d TCP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", numSocks, maxs); + + if(numSocks == 0) { + DBGPRINTF("No TCP listen sockets could successfully be initialized"); + ABORT_FINALIZE(RS_RET_COULD_NOT_BIND); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + if(sock != -1) + close(sock); + } + + RETiRet; +} + + +/* Set pRemHost based on the address provided. This is to be called upon accept()ing + * a connection request. It must be provided by the socket we received the + * message on as well as a NI_MAXHOST size large character buffer for the FQDN. + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. If we detect a malicious + * hostname, we return RS_RET_MALICIOUS_HNAME and let the caller decide + * on how to deal with that. + * rgerhards, 2008-03-31 + */ +static rsRetVal +getPeerNames(prop_t **peerName, prop_t **peerIP, struct sockaddr *pAddr) +{ + int error; + uchar szIP[NI_MAXHOST] = ""; + uchar szHname[NI_MAXHOST] = ""; + struct addrinfo hints, *res; + + DEFiRet; + + error = getnameinfo(pAddr, SALEN(pAddr), (char*)szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST); + + if(error) { + DBGPRINTF("Malformed from address %s\n", gai_strerror(error)); + strcpy((char*)szHname, "???"); + strcpy((char*)szIP, "???"); + ABORT_FINALIZE(RS_RET_INVALID_HNAME); + } + + if(!glbl.GetDisableDNS()) { + error = getnameinfo(pAddr, SALEN(pAddr), (char*)szHname, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + if(error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_STREAM; + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if(getaddrinfo((char*)szHname, NULL, &hints, &res) == 0) { + freeaddrinfo (res); + /* OK, we know we have evil, so let's indicate this to our caller */ + snprintf((char*)szHname, NI_MAXHOST, "[MALICIOUS:IP=%s]", szIP); + DBGPRINTF("Malicious PTR record, IP = \"%s\" HOST = \"%s\"", szIP, szHname); + iRet = RS_RET_MALICIOUS_HNAME; + } + } else { + strcpy((char*)szHname, (char*)szIP); + } + } else { + strcpy((char*)szHname, (char*)szIP); + } + + /* We now have the names, so now let's allocate memory and store them permanently. */ + CHKiRet(prop.Construct(peerName)); + CHKiRet(prop.SetString(*peerName, szHname, ustrlen(szHname))); + CHKiRet(prop.ConstructFinalize(*peerName)); + CHKiRet(prop.Construct(peerIP)); + CHKiRet(prop.SetString(*peerIP, szIP, ustrlen(szIP))); + CHKiRet(prop.ConstructFinalize(*peerIP)); + +finalize_it: + RETiRet; +} + + + +/* accept an incoming connection request + * rgerhards, 2008-04-22 + */ +static rsRetVal +AcceptConnReq(int sock, int *newSock, prop_t **peerName, prop_t **peerIP) +{ + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int iNewSock = -1; + + DEFiRet; + + iNewSock = accept(sock, (struct sockaddr*) &addr, &addrlen); + if(iNewSock < 0) { + if(errno == EAGAIN || errno == EWOULDBLOCK) + ABORT_FINALIZE(RS_RET_NO_MORE_DATA); + ABORT_FINALIZE(RS_RET_ACCEPT_ERR); + } + + CHKiRet(getPeerNames(peerName, peerIP, (struct sockaddr*) &addr)); + + *newSock = iNewSock; + +finalize_it: + if(iRet != RS_RET_OK) { + /* the close may be redundant, but that doesn't hurt... */ + if(iNewSock != -1) + close(iNewSock); + } + + RETiRet; +} + + +/* This is a helper for submitting the message to the rsyslog core. + * It does some common processing, including resetting the various + * state variables to a "processed" state. + * Note that this function is also called if we had a buffer overflow + * due to a too-long message. So far, there is no indication this + * happened and it may be worth thinking about different handling + * of this case (what obviously would require a change to this + * function or some related code). + * rgerhards, 2009-04-23 + * EXTRACT from tcps_sess.c + */ +static rsRetVal +doSubmitMsg(ttcpsess_t *pThis, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub) +{ + msg_t *pMsg; + DEFiRet; + + if(pThis->iMsg == 0) { + DBGPRINTF("discarding zero-sized message\n"); + FINALIZE; + } + + /* we now create our own message object and submit it to the queue */ + CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime)); + MsgSetRawMsg(pMsg, (char*)pThis->pMsg, pThis->iMsg); + MsgSetInputName(pMsg, pThis->pSrv->pInputName); + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME; + MsgSetRcvFrom(pMsg, pThis->peerName); + CHKiRet(MsgSetRcvFromIP(pMsg, pThis->peerIP)); + MsgSetRuleset(pMsg, pThis->pSrv->pRuleset); + + if(pMultiSub == NULL) { + CHKiRet(submitMsg(pMsg)); + } else { + pMultiSub->ppMsgs[pMultiSub->nElem++] = pMsg; + if(pMultiSub->nElem == pMultiSub->maxElem) + CHKiRet(multiSubmitMsg(pMultiSub)); + } + + +finalize_it: + /* reset status variables */ + pThis->bAtStrtOfFram = 1; + pThis->iMsg = 0; + + RETiRet; +} + + +/* process the data received. As TCP is stream based, we need to process the + * data inside a state machine. The actual data received is passed in byte-by-byte + * from DataRcvd, and this function here compiles messages from them and submits + * the end result to the queue. Introducing this function fixes a long-term bug ;) + * rgerhards, 2008-03-14 + * EXTRACT from tcps_sess.c + */ +static inline rsRetVal +processDataRcvd(ttcpsess_t *pThis, char c, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub) +{ + DEFiRet; + + if(pThis->inputState == eAtStrtFram) { + if(isdigit((int) c)) { + pThis->inputState = eInOctetCnt; + pThis->iOctetsRemain = 0; + pThis->eFraming = TCP_FRAMING_OCTET_COUNTING; + } else { + pThis->inputState = eInMsg; + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; + } + } + + if(pThis->inputState == eInOctetCnt) { + if(isdigit(c)) { + pThis->iOctetsRemain = pThis->iOctetsRemain * 10 + c - '0'; + } else { /* done with the octet count, so this must be the SP terminator */ + DBGPRINTF("TCP Message with octet-counter, size %d.\n", pThis->iOctetsRemain); + if(c != ' ') { + errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: " + "delimiter is not SP but has ASCII value %d.\n", c); + } + if(pThis->iOctetsRemain < 1) { + /* TODO: handle the case where the octet count is 0! */ + DBGPRINTF("Framing Error: invalid octet count\n"); + errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: " + "invalid octet count %d.\n", pThis->iOctetsRemain); + } else if(pThis->iOctetsRemain > iMaxLine) { + /* while we can not do anything against it, we can at least log an indication + * that something went wrong) -- rgerhards, 2008-03-14 + */ + DBGPRINTF("truncating message with %d octets - max msg size is %d\n", + pThis->iOctetsRemain, iMaxLine); + errmsg.LogError(0, NO_ERRCODE, "received oversize message: size is %d bytes, " + "max msg size is %d, truncating...\n", pThis->iOctetsRemain, iMaxLine); + } + pThis->inputState = eInMsg; + } + } else { + assert(pThis->inputState == eInMsg); + if(pThis->iMsg >= iMaxLine) { + /* emergency, we now need to flush, no matter if we are at end of message or not... */ + DBGPRINTF("error: message received is larger than max msg size, we split it\n"); + doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub); + /* we might think if it is better to ignore the rest of the + * message than to treat it as a new one. Maybe this is a good + * candidate for a configuration parameter... + * rgerhards, 2006-12-04 + */ + } + + if(( (c == '\n') + || ((pThis->pSrv->iAddtlFrameDelim != TCPSRV_NO_ADDTL_DELIMITER) && (c == pThis->pSrv->iAddtlFrameDelim)) + ) && pThis->eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delimiter? */ + doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub); + pThis->inputState = eAtStrtFram; + } else { + /* IMPORTANT: here we copy the actual frame content to the message - for BOTH framing modes! + * If we have a message that is larger than the max msg size, we truncate it. This is the best + * we can do in light of what the engine supports. -- rgerhards, 2008-03-14 + */ + if(pThis->iMsg < iMaxLine) { + *(pThis->pMsg + pThis->iMsg++) = c; + } + } + + if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) { + /* do we need to find end-of-frame via octet counting? */ + pThis->iOctetsRemain--; + if(pThis->iOctetsRemain < 1) { + /* we have end of frame! */ + doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub); + pThis->inputState = eAtStrtFram; + } + } + } + + RETiRet; +} + + +/* Processes the data received via a TCP session. If there + * is no other way to handle it, data is discarded. + * Input parameter data is the data received, iLen is its + * len as returned from recv(). iLen must be 1 or more (that + * is errors must be handled by caller!). iTCPSess must be + * the index of the TCP session that received the data. + * rgerhards 2005-07-04 + * And another change while generalizing. We now return either + * RS_RET_OK, which means the session should be kept open + * or anything else, which means it must be closed. + * rgerhards, 2008-03-01 + * As a performance optimization, we pick up the timestamp here. Acutally, + * this *is* the *correct* reception step for all the data we received, because + * we have just received a bunch of data! -- rgerhards, 2009-06-16 + * EXTRACT from tcps_sess.c + */ +#define NUM_MULTISUB 1024 +static rsRetVal +DataRcvd(ttcpsess_t *pThis, char *pData, size_t iLen) +{ + multi_submit_t multiSub; + msg_t *pMsgs[NUM_MULTISUB]; + struct syslogTime stTime; + time_t ttGenTime; + char *pEnd; + DEFiRet; + + assert(pData != NULL); + assert(iLen > 0); + + datetime.getCurrTime(&stTime, &ttGenTime); + multiSub.ppMsgs = pMsgs; + multiSub.maxElem = NUM_MULTISUB; + multiSub.nElem = 0; + + /* We now copy the message to the session buffer. */ + pEnd = pData + iLen; /* this is one off, which is intensional */ + + while(pData < pEnd) { + CHKiRet(processDataRcvd(pThis, *pData++, &stTime, ttGenTime, &multiSub)); + } + + if(multiSub.nElem > 0) { + /* submit anything that was not yet submitted */ + CHKiRet(multiSubmitMsg(&multiSub)); + } + +finalize_it: + RETiRet; +} +#undef NUM_MULTISUB + + +/****************************************** --END-- TCP SUPPORT FUNCTIONS ***********************************/ + + +static inline void +initConfigSettings(void) +{ + cs.bEmitMsgOnClose = 0; + cs.iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + cs.pszInputName = NULL; + cs.pRuleset = NULL; + cs.lstnIP = NULL; +} + + +/* add a listener to the server + */ +static rsRetVal +addLstn(ttcpsrv_t *pSrv, int sock) +{ + DEFiRet; + ttcplstn_t *pLstn; + + CHKmalloc(pLstn = malloc(sizeof(ttcplstn_t))); + pLstn->pSrv = pSrv; + pLstn->sock = sock; + + /* add to start of server's listener list */ + pLstn->prev = NULL; + pLstn->next = pSrv->pLstn; + if(pSrv->pLstn != NULL) + pSrv->pLstn->prev = pLstn; + pSrv->pLstn = pLstn; + +finalize_it: + RETiRet; +} + + +/* add a session to the server + */ +static rsRetVal +addSess(ttcpsrv_t *pSrv, int sock, prop_t *peerName, prop_t *peerIP) +{ + DEFiRet; + ttcpsess_t *pSess = NULL; + + CHKmalloc(pSess = malloc(sizeof(ttcpsess_t))); + CHKmalloc(pSess->pMsg = malloc(iMaxLine * sizeof(uchar))); + pSess->pSrv = pSrv; + pSess->sock = sock; + pSess->inputState = eAtStrtFram; + pSess->iMsg = 0; + pSess->bAtStrtOfFram = 1; + pSess->peerName = peerName; + pSess->peerIP = peerIP; + + /* add to start of server's listener list */ + pSess->prev = NULL; + pthread_mutex_lock(&pSrv->mutSess); + pSess->next = pSrv->pSess; + if(pSrv->pSess != NULL) + pSrv->pSess->prev = pSess; + pSrv->pSess = pSess; + pthread_mutex_unlock(&pSrv->mutSess); + + /* finally run session handler */ + pthread_create(&pSess->tid, &sessThrdAttr, sessThrd, (void*) pSess); + +finalize_it: + RETiRet; +} + + +/* close/remove a session + * NOTE: we must first remove the fd from the epoll set and then close it -- else we + * get an error "bad file descriptor" from epoll. + */ +static inline rsRetVal +closeSess(ttcpsess_t *pSess) +{ + int sock; + DEFiRet; + + sock = pSess->sock; + close(sock); + + /* finally unlink session from structures */ + pthread_mutex_lock(&pSess->pSrv->mutSess); + if(pSess->next != NULL) + pSess->next->prev = pSess->prev; + if(pSess->prev == NULL) { + /* need to update root! */ + pSess->pSrv->pSess = pSess->next; + } else { + pSess->prev->next = pSess->next; + } + pthread_mutex_unlock(&pSess->pSrv->mutSess); + + /* unlinked, now remove structure */ + destructSess(pSess); + + RETiRet; +} + + +/* accept a new ruleset to bind. Checks if it exists and complains, if not */ +static rsRetVal setRuleset(void __attribute__((unused)) *pVal, uchar *pszName) +{ + ruleset_t *pRuleset; + rsRetVal localRet; + DEFiRet; + + localRet = ruleset.GetRuleset(ourConf, &pRuleset, pszName); + if(localRet == RS_RET_NOT_FOUND) { + errmsg.LogError(0, NO_ERRCODE, "error: ruleset '%s' not found - ignored", pszName); + } + CHKiRet(localRet); + cs.pRuleset = pRuleset; + DBGPRINTF("imttcp current bind ruleset %p: '%s'\n", pRuleset, pszName); + +finalize_it: + free(pszName); /* no longer needed */ + RETiRet; +} + + +static rsRetVal addTCPListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + ttcpsrv_t *pSrv; + + CHKmalloc(pSrv = malloc(sizeof(ttcpsrv_t))); + pthread_mutex_init(&pSrv->mutSess, NULL); + pSrv->pSess = NULL; + pSrv->pLstn = NULL; + pSrv->bEmitMsgOnClose = cs.bEmitMsgOnClose; + pSrv->port = pNewVal; + pSrv->iAddtlFrameDelim = cs.iAddtlFrameDelim; + cs.pszInputName = NULL; /* moved over to pSrv, we do not own */ + pSrv->lstnIP = cs.lstnIP; + cs.lstnIP = NULL; /* moved over to pSrv, we do not own */ + pSrv->pRuleset = cs.pRuleset; + pSrv->pszInputName = (cs.pszInputName == NULL) ? UCHAR_CONSTANT("imttcp") : cs.pszInputName; + CHKiRet(prop.Construct(&pSrv->pInputName)); + CHKiRet(prop.SetString(pSrv->pInputName, pSrv->pszInputName, ustrlen(pSrv->pszInputName))); + CHKiRet(prop.ConstructFinalize(pSrv->pInputName)); + + /* add to linked list */ + pSrv->pNext = pSrvRoot; + pSrvRoot = pSrv; + + /* all config vars are auto-reset -- this also is very useful with the + * new config format effort (v6). + */ + resetConfigVariables(NULL, NULL); + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet); + } + RETiRet; +} + + +/* create up all listeners + * This is a one-time stop once the module is set to start. + */ +static inline rsRetVal +createServers() +{ + DEFiRet; + ttcpsrv_t *pSrv; + + pSrv = pSrvRoot; + while(pSrv != NULL) { + DBGPRINTF("Starting up ttcp server for port %s, name '%s'\n", pSrv->port, pSrv->pszInputName); + createSrv(pSrv); + pSrv = pSrv->pNext; + } + + RETiRet; +} + + +/* This function implements the thread to be used for listeners. + * The function terminates if shutdown is required. + */ +static void * +lstnThrd(void *arg) +{ + ttcplstn_t *pLstn = (ttcplstn_t *) arg; + rsRetVal iRet = RS_RET_OK; + int newSock; + prop_t *peerName; + prop_t *peerIP; + rsRetVal localRet; + + initThrd(); + + while(glbl.GetGlobalInputTermState() == 0) { + localRet = AcceptConnReq(pLstn->sock, &newSock, &peerName, &peerIP); + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + CHKiRet(localRet); + DBGPRINTF("imttcp: new connection %d on listen socket %d\n", newSock, pLstn->sock); + CHKiRet(addSess(pLstn->pSrv, newSock, peerName, peerIP)); + } + +finalize_it: + close(pLstn->sock); + DBGPRINTF("imttcp shutdown listen socket %d\n", pLstn->sock); + /* Note: we do NOT unlink the deleted session. While this sounds not 100% clean, + * it is fine with the current implementation as we will never reuse these elements. + * However, it make sense (and not cost notable performance) to do it "right"... + */ + return NULL; +} + + +/* This function implements the thread to be used for a session + * The function terminates if shutdown is required. + */ +static void * +sessThrd(void *arg) +{ + ttcpsess_t *pSess = (ttcpsess_t*) arg; + rsRetVal iRet = RS_RET_OK; + int lenRcv; + int lenBuf; + char rcvBuf[64*1024]; + + initThrd(); + + while(glbl.GetGlobalInputTermState() == 0) { + lenBuf = sizeof(rcvBuf); + lenRcv = recv(pSess->sock, rcvBuf, lenBuf, 0); + + if(glbl.GetGlobalInputTermState() == 1) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + + if(lenRcv > 0) { + /* have data, process it */ + DBGPRINTF("imttcp: data(%d) on socket %d: %s\n", lenRcv, pSess->sock, rcvBuf); + CHKiRet(DataRcvd(pSess, rcvBuf, lenRcv)); + } else if (lenRcv == 0) { + /* session was closed, do clean-up */ + if(pSess->pSrv->bEmitMsgOnClose) { + uchar *peerName; + int lenPeer; + prop.GetString(pSess->peerName, &peerName, &lenPeer); + errmsg.LogError(0, RS_RET_PEER_CLOSED_CONN, "imttcp session %d closed by remote peer %s.\n", + pSess->sock, peerName); + } + break; + } else { + if(errno == EAGAIN) + break; + DBGPRINTF("imttcp: error on session socket %d - closing.\n", pSess->sock); + break; + } + } + +finalize_it: + DBGPRINTF("imttcp: session thread terminates, socket %d\n", pSess->sock); + closeSess(pSess); /* try clean-up by dropping session */ + return NULL; +} + +/* startup all listeners + */ +static inline rsRetVal +startupListeners() +{ + DEFiRet; + ttcpsrv_t *pSrv; + ttcplstn_t *pLstn; + + pSrv = pSrvRoot; + while(pSrv != NULL) { + for(pLstn = pSrv->pLstn ; pLstn != NULL ; pLstn = pLstn->next) { + pthread_create(&pLstn->tid, NULL, lstnThrd, (void*) pLstn); + } + pSrv = pSrv->pNext; + } + + RETiRet; +} + + +/* This function is called to gather input. + */ +BEGINrunInput + struct timeval tvSelectTimeout; +CODESTARTrunInput + DBGPRINTF("imttcp: now beginning to process input data\n"); + CHKiRet(startupListeners()); + + // TODO: this loop is a quick hack, do it right! + while(glbl.GetGlobalInputTermState() == 0) { + tvSelectTimeout.tv_sec = 86400 /*1 day*/; + tvSelectTimeout.tv_usec = 0; + select(1, NULL, NULL, NULL, &tvSelectTimeout); + } +finalize_it: ; +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first apply some config settings */ + iMaxLine = glbl.GetMaxLine(); /* get maximum size we currently support */ + + if(pSrvRoot == NULL) { + errmsg.LogError(0, RS_RET_NO_LSTN_DEFINED, "error: no ttcp server defined, module can not run."); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + + /* start up servers, but do not yet read input data */ + CHKiRet(createServers()); + DBGPRINTF("imttcp started up, but not yet receiving data\n"); +finalize_it: +ENDwillRun + + +/* completely shut down a server. All we need to do is unblock the + * various session and listerner threads as they then check the termination + * praedicate themselves. + */ +static inline void +shutdownSrv(ttcpsrv_t *pSrv) +{ + ttcplstn_t *pLstn; + ttcplstn_t *pLstnDel; + ttcpsess_t *pSess; + pthread_t tid; + + pLstn = pSrv->pLstn; + while(pLstn != NULL) { + tid = pLstn->tid; /* pSess will be destructed! */ + pthread_kill(tid, SIGTTIN); + DBGPRINTF("imttcp: termination request for listen thread %x\n", (unsigned) tid); + pthread_join(tid, NULL); + DBGPRINTF("imttcp: listen thread %x terminated \n", (unsigned) tid); + pLstnDel = pLstn; + pLstn = pLstn->next; + free(pLstnDel); + } + + pSess = pSrv->pSess; + while(pSess != NULL) { + tid = pSess->tid; /* pSess will be destructed! */ + pSess = pSess->next; + pthread_kill(tid, SIGTTIN); + DBGPRINTF("imttcp: termination request for session thread %x\n", (unsigned) tid); + //pthread_join(tid, NULL); + DBGPRINTF("imttcp: session thread %x terminated \n", (unsigned) tid); + } +} + + +BEGINafterRun + ttcpsrv_t *pSrv, *srvDel; +CODESTARTafterRun + /* do cleanup here */ + /* we need to close everything that is still open */ + pSrv = pSrvRoot; + while(pSrv != NULL) { + srvDel = pSrv; + pSrv = pSrv->pNext; + shutdownSrv(srvDel); + destructSrv(srvDel); + } +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + pthread_attr_destroy(&sessThrdAttr); + + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(datetime, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.bEmitMsgOnClose = 0; + cs.iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + free(cs.pszInputName); + cs.pszInputName = NULL; + free(cs.lstnIP); + cs.lstnIP = NULL; + return RS_RET_OK; +} + + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + initConfigSettings(); + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + + /* initialize "read-only" thread attributes */ + pthread_attr_init(&sessThrdAttr); + pthread_attr_setdetachstate(&sessThrdAttr, PTHREAD_CREATE_DETACHED); + pthread_attr_setstacksize(&sessThrdAttr, 4096*1024); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputttcpserverrun"), 0, eCmdHdlrGetWord, + addTCPListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputttcpservernotifyonconnectionclose"), 0, + eCmdHdlrBinary, NULL, &cs.bEmitMsgOnClose, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputttcpserveraddtlframedelimiter"), 0, eCmdHdlrInt, + NULL, &cs.iAddtlFrameDelim, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputttcpserverinputname"), 0, + eCmdHdlrGetWord, NULL, &cs.pszInputName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputttcpserverlistenip"), 0, + eCmdHdlrGetWord, NULL, &cs.lstnIP, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("inputttcpserverbindruleset"), 0, + eCmdHdlrGetWord, setRuleset, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr(UCHAR_CONSTANT("resetconfigvariables"), 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + + +/* vim:set ai: + */ diff --git a/plugins/imudp/Makefile.am b/plugins/imudp/Makefile.am new file mode 100644 index 00000000..bc64b8c8 --- /dev/null +++ b/plugins/imudp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imudp.la + +imudp_la_SOURCES = imudp.c +imudp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imudp_la_LDFLAGS = -module -avoid-version +imudp_la_LIBADD = $(IMUDP_LIBS) diff --git a/plugins/imudp/imudp.c b/plugins/imudp/imudp.c new file mode 100644 index 00000000..7bf1473a --- /dev/null +++ b/plugins/imudp/imudp.c @@ -0,0 +1,1035 @@ +/* imudp.c + * This is the implementation of the UDP input module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#if HAVE_SYS_EPOLL_H +# include <sys/epoll.h> +#endif +#ifdef HAVE_SCHED_H +# include <sched.h> +#endif +#include "rsyslog.h" +#include "dirty.h" +#include "net.h" +#include "cfsysline.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" +#include "glbl.h" +#include "msg.h" +#include "parser.h" +#include "datetime.h" +#include "prop.h" +#include "ruleset.h" +#include "statsobj.h" +#include "ratelimit.h" +#include "unicode-helper.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imudp") + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(datetime) +DEFobjCurrIf(prop) +DEFobjCurrIf(ruleset) +DEFobjCurrIf(statsobj) + + +static struct lstn_s { + struct lstn_s *next; + int sock; /* socket */ + ruleset_t *pRuleset; /* bound ruleset */ + prop_t *pInputName; + statsobj_t *stats; /* listener stats */ + ratelimit_t *ratelimiter; + STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit) +} *lcnfRoot = NULL, *lcnfLast = NULL; + +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ +static int bDoACLCheck; /* are ACL checks neeed? Cached once immediately before listener startup */ +static int iMaxLine; /* maximum UDP message size supported */ +static time_t ttLastDiscard = 0; /* timestamp when a message from a non-permitted sender was last discarded + * This shall prevent remote DoS when the "discard on disallowed sender" + * message is configured to be logged on occurance of such a case. + */ +static uchar *pRcvBuf = NULL; /* receive buffer (for a single packet). We use a global and alloc + * it so that we can check available memory in willRun() and request + * termination if we can not get it. -- rgerhards, 2007-12-27 + */ + +#define TIME_REQUERY_DFLT 2 +#define SCHED_PRIO_UNSET -12345678 /* a value that indicates that the scheduling priority has not been set */ +/* config vars for legacy config system */ +static struct configSettings_s { + uchar *pszBindAddr; /* IP to bind socket to */ + uchar *pszSchedPolicy; /* scheduling policy string */ + uchar *pszBindRuleset; /* name of Ruleset to bind to */ + int iSchedPrio; /* scheduling priority */ + int iTimeRequery; /* how often is time to be queried inside tight recv loop? 0=always */ +} cs; + +struct instanceConf_s { + uchar *pszBindAddr; /* IP to bind socket to */ + uchar *pszBindPort; /* Port to bind socket to */ + uchar *pszBindRuleset; /* name of ruleset to bind to */ + uchar *inputname; + ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ + int ratelimitInterval; + int ratelimitBurst; + struct instanceConf_s *next; + sbool bAppendPortToInpname; +}; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + instanceConf_t *root, *tail; + uchar *pszSchedPolicy; /* scheduling policy string */ + int iSchedPolicy; /* scheduling policy as SCHED_xxx */ + int iSchedPrio; /* scheduling priority */ + int iTimeRequery; /* how often is time to be queried inside tight recv loop? 0=always */ + sbool configSetViaV2Method; +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "schedulingpolicy", eCmdHdlrGetWord, 0 }, + { "schedulingpriority", eCmdHdlrInt, 0 }, + { "timerequery", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* input instance parameters */ +static struct cnfparamdescr inppdescr[] = { + { "port", eCmdHdlrArray, CNFPARAM_REQUIRED }, /* legacy: InputTCPServerRun */ + { "inputname", eCmdHdlrGetWord, 0 }, + { "inputname.appendport", eCmdHdlrBinary, 0 }, + { "address", eCmdHdlrString, 0 }, + { "ruleset", eCmdHdlrString, 0 }, + { "ratelimit.interval", eCmdHdlrInt, 0 }, + { "ratelimit.burst", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + +#include "im-helper.h" /* must be included AFTER the type definitions! */ + +/* create input instance, set default paramters, and + * add it to the list of instances. + */ +static rsRetVal +createInstance(instanceConf_t **pinst) +{ + instanceConf_t *inst; + DEFiRet; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + inst->next = NULL; + inst->pBindRuleset = NULL; + + inst->pszBindPort = NULL; + inst->pszBindAddr = NULL; + inst->pszBindRuleset = NULL; + inst->inputname = NULL; + inst->bAppendPortToInpname = 0; + inst->ratelimitBurst = 10000; /* arbitrary high limit */ + inst->ratelimitInterval = 0; /* off */ + + /* node created, let's add to config */ + if(loadModConf->tail == NULL) { + loadModConf->tail = loadModConf->root = inst; + } else { + loadModConf->tail->next = inst; + loadModConf->tail = inst; + } + + *pinst = inst; +finalize_it: + RETiRet; +} + +/* This function is called when a new listener instace shall be added to + * the current config object via the legacy config system. It just shuffles + * all parameters to the listener in-memory instance. + * rgerhards, 2011-05-04 + */ +static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + instanceConf_t *inst; + DEFiRet; + + CHKiRet(createInstance(&inst)); + CHKmalloc(inst->pszBindPort = ustrdup((pNewVal == NULL || *pNewVal == '\0') + ? (uchar*) "514" : pNewVal)); + if((cs.pszBindAddr == NULL) || (cs.pszBindAddr[0] == '\0')) { + inst->pszBindAddr = NULL; + } else { + CHKmalloc(inst->pszBindAddr = ustrdup(cs.pszBindAddr)); + } + if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) { + inst->pszBindRuleset = NULL; + } else { + CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset)); + } + +finalize_it: + free(pNewVal); + RETiRet; +} + + +/* This function is called when a new listener shall be added. It takes + * the instance config description, tries to bind the socket and, if that + * succeeds, adds it to the list of existing listen sockets. + */ +static inline rsRetVal +addListner(instanceConf_t *inst) +{ + DEFiRet; + uchar *bindAddr; + int *newSocks; + int iSrc; + struct lstn_s *newlcnfinfo; + uchar *bindName; + uchar *port; + uchar dispname[64], inpnameBuf[128]; + uchar *inputname; + + /* check which address to bind to. We could do this more compact, but have not + * done so in order to make the code more readable. -- rgerhards, 2007-12-27 + */ + if(inst->pszBindAddr == NULL) + bindAddr = NULL; + else if(inst->pszBindAddr[0] == '*' && inst->pszBindAddr[1] == '\0') + bindAddr = NULL; + else + bindAddr = inst->pszBindAddr; + bindName = (bindAddr == NULL) ? (uchar*)"*" : bindAddr; + port = (inst->pszBindPort == NULL || *inst->pszBindPort == '\0') ? (uchar*) "514" : inst->pszBindPort; + + DBGPRINTF("Trying to open syslog UDP ports at %s:%s.\n", bindName, inst->pszBindPort); + + newSocks = net.create_udp_socket(bindAddr, port, 1); + if(newSocks != NULL) { + /* we now need to add the new sockets to the existing set */ + /* ready to copy */ + for(iSrc = 1 ; iSrc <= newSocks[0] ; ++iSrc) { + CHKmalloc(newlcnfinfo = (struct lstn_s*) MALLOC(sizeof(struct lstn_s))); + newlcnfinfo->next = NULL; + newlcnfinfo->sock = newSocks[iSrc]; + newlcnfinfo->pRuleset = inst->pBindRuleset; + snprintf((char*)dispname, sizeof(dispname), "imudp(%s:%s)", bindName, port); + dispname[sizeof(dispname)-1] = '\0'; /* just to be on the save side... */ + CHKiRet(ratelimitNew(&newlcnfinfo->ratelimiter, (char*)dispname, NULL)); + if(inst->inputname == NULL) { + inputname = (uchar*)"imudp"; + } else { + inputname = inst->inputname; + } + if(inst->bAppendPortToInpname) { + snprintf((char*)inpnameBuf, sizeof(inpnameBuf), "%s%s", + inputname, port); + inpnameBuf[sizeof(inpnameBuf)-1] = '\0'; + inputname = inpnameBuf; + } + CHKiRet(prop.Construct(&newlcnfinfo->pInputName)); + CHKiRet(prop.SetString(newlcnfinfo->pInputName, + inputname, ustrlen(inputname))); + CHKiRet(prop.ConstructFinalize(newlcnfinfo->pInputName)); + ratelimitSetLinuxLike(newlcnfinfo->ratelimiter, inst->ratelimitInterval, + inst->ratelimitBurst); + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&(newlcnfinfo->stats))); + CHKiRet(statsobj.SetName(newlcnfinfo->stats, dispname)); + STATSCOUNTER_INIT(newlcnfinfo->ctrSubmit, newlcnfinfo->mutCtrSubmit); + CHKiRet(statsobj.AddCounter(newlcnfinfo->stats, UCHAR_CONSTANT("submitted"), + ctrType_IntCtr, &(newlcnfinfo->ctrSubmit))); + CHKiRet(statsobj.ConstructFinalize(newlcnfinfo->stats)); + /* link to list. Order must be preserved to take care for + * conflicting matches. + */ + if(lcnfRoot == NULL) + lcnfRoot = newlcnfinfo; + if(lcnfLast == NULL) + lcnfLast = newlcnfinfo; + else { + lcnfLast->next = newlcnfinfo; + lcnfLast = newlcnfinfo; + } + } + } + +finalize_it: + free(newSocks); + RETiRet; +} + + +static inline void +std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) +{ + errmsg.LogError(0, NO_ERRCODE, "imudp: ruleset '%s' for %s:%s not found - " + "using default ruleset instead", inst->pszBindRuleset, + inst->pszBindAddr == NULL ? "*" : (char*) inst->pszBindAddr, + inst->pszBindPort); +} + + +/* This function is a helper to runInput. I have extracted it + * from the main loop just so that we do not have that large amount of code + * in a single place. This function takes a socket and pulls messages from + * it until the socket does not have any more waiting. + * rgerhards, 2008-01-08 + * We try to read from the file descriptor until there + * is no more data. This is done in the hope to get better performance + * out of the system. However, this also means that a descriptor + * monopolizes processing while it contains data. This can lead to + * data loss in other descriptors. However, if the system is incapable of + * handling the workload, we will loss data in any case. So it doesn't really + * matter where the actual loss occurs - it is always random, because we depend + * on scheduling order. -- rgerhards, 2008-10-02 + */ +static inline rsRetVal +processSocket(thrdInfo_t *pThrd, struct lstn_s *lstn, struct sockaddr_storage *frominetPrev, int *pbIsPermitted) +{ + int iNbrTimeUsed; + time_t ttGenTime; + struct syslogTime stTime; + socklen_t socklen; + ssize_t lenRcvBuf; + struct sockaddr_storage frominet; + msg_t *pMsg; + prop_t *propFromHost = NULL; + prop_t *propFromHostIP = NULL; + multi_submit_t multiSub; + msg_t *pMsgs[CONF_NUM_MULTISUB]; + char errStr[1024]; + DEFiRet; + + assert(pThrd != NULL); + multiSub.ppMsgs = pMsgs; + multiSub.maxElem = CONF_NUM_MULTISUB; + multiSub.nElem = 0; + iNbrTimeUsed = 0; + while(1) { /* loop is terminated if we have a bad receive, done below in the body */ + if(pThrd->bShallStop == RSTRUE) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + socklen = sizeof(struct sockaddr_storage); + lenRcvBuf = recvfrom(lstn->sock, (char*) pRcvBuf, iMaxLine, 0, (struct sockaddr *)&frominet, &socklen); + if(lenRcvBuf < 0) { + if(errno != EINTR && errno != EAGAIN) { + rs_strerror_r(errno, errStr, sizeof(errStr)); + DBGPRINTF("INET socket error: %d = %s.\n", errno, errStr); + errmsg.LogError(errno, NO_ERRCODE, "recvfrom inet"); + } + ABORT_FINALIZE(RS_RET_ERR); // this most often is NOT an error, state is not checked by caller! + } + + if(lenRcvBuf == 0) + continue; /* this looks a bit strange, but practice shows it happens... */ + + /* if we reach this point, we had a good receive and can process the packet received */ + /* check if we have a different sender than before, if so, we need to query some new values */ + if(bDoACLCheck) { + if(net.CmpHost(&frominet, frominetPrev, socklen) != 0) { + memcpy(frominetPrev, &frominet, socklen); /* update cache indicator */ + /* Here we check if a host is permitted to send us syslog messages. If it isn't, + * we do not further process the message but log a warning (if we are + * configured to do this). However, if the check would require name resolution, + * it is postponed to the main queue. See also my blog post at + * http://blog.gerhards.net/2009/11/acls-imudp-and-accepting-messages.html + * rgerhards, 2009-11-16 + */ + *pbIsPermitted = net.isAllowedSender2((uchar*)"UDP", + (struct sockaddr *)&frominet, "", 0); + + if(*pbIsPermitted == 0) { + DBGPRINTF("msg is not from an allowed sender\n"); + if(glbl.GetOption_DisallowWarning) { + time_t tt; + datetime.GetTime(&tt); + if(tt > ttLastDiscard + 60) { + ttLastDiscard = tt; + errmsg.LogError(0, NO_ERRCODE, + "UDP message from disallowed sender discarded"); + } + } + } + } + } else { + *pbIsPermitted = 1; /* no check -> everything permitted */ + } + + DBGPRINTF("imudp:recv(%d,%d),acl:%d,msg:%s\n", lstn->sock, (int) lenRcvBuf, *pbIsPermitted, pRcvBuf); + + if(*pbIsPermitted != 0) { + if((runModConf->iTimeRequery == 0) || (iNbrTimeUsed++ % runModConf->iTimeRequery) == 0) { + datetime.getCurrTime(&stTime, &ttGenTime); + } + /* we now create our own message object and submit it to the queue */ + CHKiRet(msgConstructWithTime(&pMsg, &stTime, ttGenTime)); + MsgSetRawMsg(pMsg, (char*)pRcvBuf, lenRcvBuf); + MsgSetInputName(pMsg, lstn->pInputName); + MsgSetRuleset(pMsg, lstn->pRuleset); + MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY); + pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME | NEEDS_DNSRESOL; + if(*pbIsPermitted == 2) + pMsg->msgFlags |= NEEDS_ACLCHK_U; /* request ACL check after resolution */ + CHKiRet(msgSetFromSockinfo(pMsg, &frominet)); + CHKiRet(ratelimitAddMsg(lstn->ratelimiter, &multiSub, pMsg)); + STATSCOUNTER_INC(lstn->ctrSubmit, lstn->mutCtrSubmit); + } + } + + +finalize_it: + multiSubmitFlush(&multiSub); + + if(propFromHost != NULL) + prop.Destruct(&propFromHost); + if(propFromHostIP != NULL) + prop.Destruct(&propFromHostIP); + + RETiRet; +} + + +/* check configured scheduling priority. + * Precondition: iSchedPolicy must have been set + */ +static inline rsRetVal +checkSchedulingPriority(modConfData_t *modConf) +{ + DEFiRet; + +#ifdef HAVE_SCHED_GET_PRIORITY_MAX + if( modConf->iSchedPrio < sched_get_priority_min(modConf->iSchedPolicy) + || modConf->iSchedPrio > sched_get_priority_max(modConf->iSchedPolicy)) { + errmsg.LogError(0, NO_ERRCODE, + "imudp: scheduling priority %d out of range (%d - %d)" + " for scheduling policy '%s' - ignoring settings", + modConf->iSchedPrio, + sched_get_priority_min(modConf->iSchedPolicy), + sched_get_priority_max(modConf->iSchedPolicy), + modConf->pszSchedPolicy); + ABORT_FINALIZE(RS_RET_VALIDATION_RUN); + } +#endif + +finalize_it: + RETiRet; +} + + +/* check scheduling policy string and, if valid, set its + * numeric equivalent in current load config + */ +static rsRetVal +checkSchedulingPolicy(modConfData_t *modConf) +{ + DEFiRet; + + if (0) { /* trick to use conditional compilation */ +#ifdef SCHED_FIFO + } else if (!strcasecmp((char*)modConf->pszSchedPolicy, "fifo")) { + modConf->iSchedPolicy = SCHED_FIFO; +#endif +#ifdef SCHED_RR + } else if (!strcasecmp((char*)modConf->pszSchedPolicy, "rr")) { + modConf->iSchedPolicy = SCHED_RR; +#endif +#ifdef SCHED_OTHER + } else if (!strcasecmp((char*)modConf->pszSchedPolicy, "other")) { + modConf->iSchedPolicy = SCHED_OTHER; +#endif + } else { + errmsg.LogError(errno, NO_ERRCODE, + "imudp: invalid scheduling policy '%s' " + "- ignoring setting", modConf->pszSchedPolicy); + ABORT_FINALIZE(RS_RET_ERR_SCHED_PARAMS); + } +finalize_it: + RETiRet; +} + +/* checks scheduling parameters during config check phase */ +static rsRetVal +checkSchedParam(modConfData_t *modConf) +{ + DEFiRet; + + if(modConf->pszSchedPolicy != NULL && modConf->iSchedPrio == SCHED_PRIO_UNSET) { + errmsg.LogError(0, RS_RET_ERR_SCHED_PARAMS, + "imudp: scheduling policy set, but without priority - ignoring settings"); + ABORT_FINALIZE(RS_RET_ERR_SCHED_PARAMS); + } else if(modConf->pszSchedPolicy == NULL && modConf->iSchedPrio != SCHED_PRIO_UNSET) { + errmsg.LogError(0, RS_RET_ERR_SCHED_PARAMS, + "imudp: scheduling priority set, but without policy - ignoring settings"); + ABORT_FINALIZE(RS_RET_ERR_SCHED_PARAMS); + } else if(modConf->pszSchedPolicy != NULL && modConf->iSchedPrio != SCHED_PRIO_UNSET) { + /* we have parameters set, so check them */ + CHKiRet(checkSchedulingPolicy(modConf)); + CHKiRet(checkSchedulingPriority(modConf)); + } else { /* nothing set */ + modConf->iSchedPrio = SCHED_PRIO_UNSET; /* prevents doing the activation call */ + } +#ifndef HAVE_PTHREAD_SETSCHEDPARAM + errmsg.LogError(0, NO_ERRCODE, + "imudp: cannot set thread scheduling policy, " + "pthread_setschedparam() not available"); + ABORT_FINALIZE(RS_RET_ERR_SCHED_PARAMS); +#endif + +finalize_it: + if(iRet != RS_RET_OK) + modConf->iSchedPrio = SCHED_PRIO_UNSET; /* prevents doing the activation call */ + + RETiRet; +} + +/* set the configured scheduling policy (if possible) */ +static rsRetVal +setSchedParams(modConfData_t *modConf) +{ + DEFiRet; + +# ifdef HAVE_PTHREAD_SETSCHEDPARAM + int err; + struct sched_param sparam; + + if(modConf->iSchedPrio == SCHED_PRIO_UNSET) + FINALIZE; + + memset(&sparam, 0, sizeof sparam); + sparam.sched_priority = modConf->iSchedPrio; + dbgprintf("imudp trying to set sched policy to '%s', prio %d\n", + modConf->pszSchedPolicy, modConf->iSchedPrio); + err = pthread_setschedparam(pthread_self(), modConf->iSchedPolicy, &sparam); + if(err != 0) { + errmsg.LogError(err, NO_ERRCODE, "imudp: pthread_setschedparam() failed - ignoring"); + } +# endif + +finalize_it: + RETiRet; +} + + +/* This function implements the main reception loop. Depending on the environment, + * we either use the traditional (but slower) select() or the Linux-specific epoll() + * interface. ./configure settings control which one is used. + * rgerhards, 2009-09-09 + */ +#if defined(HAVE_EPOLL_CREATE1) || defined(HAVE_EPOLL_CREATE) +#define NUM_EPOLL_EVENTS 10 +rsRetVal rcvMainLoop(thrdInfo_t *pThrd) +{ + DEFiRet; + int nfds; + int efd; + int i; + struct sockaddr_storage frominetPrev; + int bIsPermitted; + struct epoll_event *udpEPollEvt = NULL; + struct epoll_event currEvt[NUM_EPOLL_EVENTS]; + char errStr[1024]; + struct lstn_s *lstn; + int nLstn; + + /* start "name caching" algo by making sure the previous system indicator + * is invalidated. + */ + bIsPermitted = 0; + memset(&frominetPrev, 0, sizeof(frominetPrev)); + + /* count num listeners -- do it here in order to avoid inconsistency */ + nLstn = 0; + for(lstn = lcnfRoot ; lstn != NULL ; lstn = lstn->next) + ++nLstn; + CHKmalloc(udpEPollEvt = calloc(nLstn, sizeof(struct epoll_event))); + +#if defined(EPOLL_CLOEXEC) && defined(HAVE_EPOLL_CREATE1) + DBGPRINTF("imudp uses epoll_create1()\n"); + efd = epoll_create1(EPOLL_CLOEXEC); + if(efd < 0 && errno == ENOSYS) +#endif + { + DBGPRINTF("imudp uses epoll_create()\n"); + efd = epoll_create(NUM_EPOLL_EVENTS); + } + + if(efd < 0) { + DBGPRINTF("epoll_create1() could not create fd\n"); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + /* fill the epoll set - we need to do this only once, as the set + * can not change dyamically. + */ + i = 0; + for(lstn = lcnfRoot ; lstn != NULL ; lstn = lstn->next) { + if(lstn->sock != -1) { + udpEPollEvt[i].events = EPOLLIN | EPOLLET; + udpEPollEvt[i].data.ptr = lstn; + if(epoll_ctl(efd, EPOLL_CTL_ADD, lstn->sock, &(udpEPollEvt[i])) < 0) { + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(errno, NO_ERRCODE, "epoll_ctrl failed on fd %d with %s\n", + lstn->sock, errStr); + } + } + i++; + } + + while(1) { + /* wait for io to become ready */ + nfds = epoll_wait(efd, currEvt, NUM_EPOLL_EVENTS, -1); + DBGPRINTF("imudp: epoll_wait() returned with %d fds\n", nfds); + + if(pThrd->bShallStop == RSTRUE) + break; /* terminate input! */ + + for(i = 0 ; i < nfds ; ++i) { + processSocket(pThrd, currEvt[i].data.ptr, &frominetPrev, &bIsPermitted); + } + } + +finalize_it: + if(udpEPollEvt != NULL) + free(udpEPollEvt); + + RETiRet; +} +#else /* #if HAVE_EPOLL_CREATE1 */ +/* this is the code for the select() interface */ +rsRetVal rcvMainLoop(thrdInfo_t *pThrd) +{ + DEFiRet; + int maxfds; + int nfds; + fd_set readfds; + struct sockaddr_storage frominetPrev; + int bIsPermitted; + struct lstn_s *lstn; + + /* start "name caching" algo by making sure the previous system indicator + * is invalidated. + */ + bIsPermitted = 0; + memset(&frominetPrev, 0, sizeof(frominetPrev)); + DBGPRINTF("imudp uses select()\n"); + + while(1) { + /* Add the Unix Domain Sockets to the list of read descriptors. + */ + maxfds = 0; + FD_ZERO(&readfds); + + /* Add the UDP listen sockets to the list of read descriptors. */ + for(lstn = lcnfRoot ; lstn != NULL ; lstn = lstn->next) { + if (lstn->sock != -1) { + if(Debug) + net.debugListenInfo(lstn->sock, "UDP"); + FD_SET(lstn->sock, &readfds); + if(lstn->sock>maxfds) maxfds=lstn->sock; + } + } + if(Debug) { + dbgprintf("--------imUDP calling select, active file descriptors (max %d): ", maxfds); + for (nfds = 0; nfds <= maxfds; ++nfds) + if(FD_ISSET(nfds, &readfds)) + dbgprintf("%d ", nfds); + dbgprintf("\n"); + } + + /* wait for io to become ready */ + nfds = select(maxfds+1, (fd_set *) &readfds, NULL, NULL, NULL); + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + + for(lstn = lcnfRoot ; nfds && lstn != NULL ; lstn = lstn->next) { + if(FD_ISSET(lstn->sock, &readfds)) { + processSocket(pThrd, lstn, &frominetPrev, &bIsPermitted); + --nfds; /* indicate we have processed one descriptor */ + } + } + /* end of a run, back to loop for next recv() */ + } + + RETiRet; +} +#endif /* #if HAVE_EPOLL_CREATE1 */ + + +static inline rsRetVal +createListner(es_str_t *port, struct cnfparamvals *pvals) +{ + instanceConf_t *inst; + int i; + DEFiRet; + + CHKiRet(createInstance(&inst)); + inst->pszBindPort = (uchar*)es_str2cstr(port, NULL); + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "port")) { + continue; /* array, handled by caller */ + } else if(!strcmp(inppblk.descr[i].name, "inputname")) { + inst->inputname = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "inputname.appendport")) { + inst->bAppendPortToInpname = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "address")) { + inst->pszBindAddr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "ruleset")) { + inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "ratelimit.burst")) { + inst->ratelimitBurst = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "ratelimit.interval")) { + inst->ratelimitInterval = (int) pvals[i].val.d.n; + } else { + dbgprintf("imudp: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + } +finalize_it: + RETiRet; +} + + +BEGINnewInpInst + struct cnfparamvals *pvals; + int i; + int portIdx; +CODESTARTnewInpInst + DBGPRINTF("newInpInst (imudp)\n"); + + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imudp: required parameter are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + if(Debug) { + dbgprintf("input param blk in imudp:\n"); + cnfparamsPrint(&inppblk, pvals); + } + + portIdx = cnfparamGetIdx(&inppblk, "port"); + assert(portIdx != -1); + for(i = 0 ; i < pvals[portIdx].val.d.ar->nmemb ; ++i) { + createListner(pvals[portIdx].val.d.ar->arr[i], pvals); + } + +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + loadModConf->configSetViaV2Method = 0; + loadModConf->iTimeRequery = TIME_REQUERY_DFLT; + loadModConf->iSchedPrio = SCHED_PRIO_UNSET; + loadModConf->pszSchedPolicy = NULL; + bLegacyCnfModGlobalsPermitted = 1; + /* init legacy config vars */ + cs.pszBindRuleset = NULL; + cs.pszSchedPolicy = NULL; + cs.pszBindAddr = NULL; + cs.iSchedPrio = SCHED_PRIO_UNSET; + cs.iTimeRequery = TIME_REQUERY_DFLT; +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imudp: error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imudp:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "timerequery")) { + loadModConf->iTimeRequery = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "schedulingpriority")) { + loadModConf->iSchedPrio = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "schedulingpolicy")) { + loadModConf->pszSchedPolicy = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("imudp: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* remove all of our legacy handlers, as they can not used in addition + * the the new-style config method. + */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->iSchedPrio = cs.iSchedPrio; + loadModConf->iTimeRequery = cs.iTimeRequery; + if((cs.pszSchedPolicy != NULL) && (cs.pszSchedPolicy[0] != '\0')) { + CHKmalloc(loadModConf->pszSchedPolicy = ustrdup(cs.pszSchedPolicy)); + } + } + +finalize_it: + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.pszBindRuleset); + free(cs.pszSchedPolicy); + free(cs.pszBindAddr); +ENDendCnfLoad + + +BEGINcheckCnf + instanceConf_t *inst; +CODESTARTcheckCnf + checkSchedParam(pModConf); /* this can not cause fatal errors */ + for(inst = pModConf->root ; inst != NULL ; inst = inst->next) { + std_checkRuleset(pModConf, inst); + } + if(pModConf->root == NULL) { + errmsg.LogError(0, RS_RET_NO_LISTNERS , "imudp: module loaded, but " + "no listeners defined - no input will be gathered"); + iRet = RS_RET_NO_LISTNERS; + } +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop + instanceConf_t *inst; +CODESTARTactivateCnfPrePrivDrop + runModConf = pModConf; + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + addListner(inst); + } + /* if we could not set up any listners, there is no point in running... */ + if(lcnfRoot == NULL) { + errmsg.LogError(0, NO_ERRCODE, "imudp: no listeners could be started, " + "input not activated.\n"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + +finalize_it: +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf + /* caching various settings */ + iMaxLine = glbl.GetMaxLine(); + CHKmalloc(pRcvBuf = MALLOC((iMaxLine + 1) * sizeof(char))); +finalize_it: +ENDactivateCnf + + +BEGINfreeCnf + instanceConf_t *inst, *del; +CODESTARTfreeCnf + for(inst = pModConf->root ; inst != NULL ; ) { + free(inst->pszBindPort); + free(inst->pszBindAddr); + free(inst->pBindRuleset); + free(inst->inputname); + del = inst; + inst = inst->next; + free(del); + } +ENDfreeCnf + +/* This function is called to gather input. + * Note that sock must be non-NULL because otherwise we would not have + * indicated that we want to run (or we have a programming error ;)). -- rgerhards, 2008-10-02 + */ +BEGINrunInput +CODESTARTrunInput + /* Note well: the setting of scheduling parameters will not work + * when we dropped privileges (if the user is not sufficently + * privileged, of course). Howerver, we can't change the + * scheduling params in PrePrivDrop(), as at that point our thread + * is not yet created. So at least as an interim solution, we do + * NOT support both setting sched parameters and dropping + * privileges within the same instance. + */ + setSchedParams(runModConf); + iRet = rcvMainLoop(pThrd); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + net.PrintAllowedSenders(1); /* UDP */ + net.HasRestrictions(UCHAR_CONSTANT("UDP"), &bDoACLCheck); /* UDP */ +ENDwillRun + + +BEGINafterRun + struct lstn_s *lstn, *lstnDel; +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"UDP"); + for(lstn = lcnfRoot ; lstn != NULL ; ) { + statsobj.Destruct(&(lstn->stats)); + ratelimitDestruct(lstn->ratelimiter); + close(lstn->sock); + prop.Destruct(&lstn->pInputName); + lstnDel = lstn; + lstn = lstn->next; + free(lstnDel); + } + lcnfRoot = lcnfLast = NULL; + if(pRcvBuf != NULL) { + free(pRcvBuf); + pRcvBuf = NULL; + } +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + free(cs.pszBindAddr); + cs.pszBindAddr = NULL; + free(cs.pszSchedPolicy); + cs.pszSchedPolicy = NULL; + free(cs.pszBindRuleset); + cs.pszBindRuleset = NULL; + cs.iSchedPrio = SCHED_PRIO_UNSET; + cs.iTimeRequery = TIME_REQUERY_DFLT;/* the default is to query only every second time */ + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputudpserverbindruleset", 0, eCmdHdlrGetWord, + NULL, &cs.pszBindRuleset, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"udpserverrun", 0, eCmdHdlrGetWord, + addInstance, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"udpserveraddress", 0, eCmdHdlrGetWord, + NULL, &cs.pszBindAddr, STD_LOADABLE_MODULE_ID)); + /* module-global config params - will be disabled in configs that are loaded + * via module(...). + */ + CHKiRet(regCfSysLineHdlr2((uchar *)"imudpschedulingpolicy", 0, eCmdHdlrGetWord, + NULL, &cs.pszSchedPolicy, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"imudpschedulingpriority", 0, eCmdHdlrInt, + NULL, &cs.iSchedPrio, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"udpservertimerequery", 0, eCmdHdlrInt, + NULL, &cs.iTimeRequery, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imuxsock/Makefile.am b/plugins/imuxsock/Makefile.am new file mode 100644 index 00000000..28f9f9e3 --- /dev/null +++ b/plugins/imuxsock/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imuxsock.la + +imuxsock_la_SOURCES = imuxsock.c +imuxsock_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imuxsock_la_LDFLAGS = -module -avoid-version +imuxsock_la_LIBADD = $(RSRT_LIBS) diff --git a/plugins/imuxsock/imuxsock.c b/plugins/imuxsock/imuxsock.c new file mode 100644 index 00000000..dad09ab4 --- /dev/null +++ b/plugins/imuxsock/imuxsock.c @@ -0,0 +1,1612 @@ +/* imuxsock.c + * This is the implementation of the Unix sockets input module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-12-20 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/socket.h> +#include "dirty.h" +#include "cfsysline.h" +#include "unicode-helper.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" +#include "net.h" +#include "glbl.h" +#include "msg.h" +#include "parser.h" +#include "prop.h" +#include "debug.h" +#include "unlimited_select.h" +#include "sd-daemon.h" +#include "statsobj.h" +#include "datetime.h" +#include "hashtable.h" +#include "ratelimit.h" + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imuxsock") + +/* defines */ +#define MAXFUNIX 50 +#ifndef _PATH_LOG +#ifdef BSD +#define _PATH_LOG "/var/run/log" +#else +#define _PATH_LOG "/dev/log" +#endif +#endif +#ifndef SYSTEMD_JOURNAL +#define SYSTEMD_JOURNAL "/run/systemd/journal" +#endif +#ifndef SYSTEMD_PATH_LOG +#define SYSTEMD_PATH_LOG SYSTEMD_JOURNAL "/syslog" +#endif + +/* forward definitions */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* emulate struct ucred for platforms that do not have it */ +#ifndef HAVE_SCM_CREDENTIALS +struct ucred { int pid; uid_t uid; gid_t gid; }; +#endif + +/* handle some defines missing on more than one platform */ +#ifndef SUN_LEN +#define SUN_LEN(su) \ + (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) +#endif +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(net) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) +DEFobjCurrIf(statsobj) + + +statsobj_t *modStats; +STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit) +STATSCOUNTER_DEF(ctrLostRatelimit, mutCtrLostRatelimit) +STATSCOUNTER_DEF(ctrNumRatelimiters, mutCtrNumRatelimiters) + + +/* a very simple "hash function" for process IDs - we simply use the + * pid itself: it is quite expected that all pids may log some time, but + * from a collision point of view it is likely that long-running daemons + * start early and so will stay right in the top spots of the + * collision list. + */ +static unsigned int +hash_from_key_fn(void *k) +{ + return((unsigned) *((pid_t*) k)); +} + +static int +key_equals_fn(void *key1, void *key2) +{ + return *((pid_t*) key1) == *((pid_t*) key2); +} + + +/* structure to describe a specific listener */ +typedef struct lstn_s { + uchar *sockName; /* read-only after startup */ + prop_t *hostName; /* host-name override - if set, use this instead of actual name */ + int fd; /* read-only after startup */ + int flags; /* should parser parse host name? read-only after startup */ + int flowCtl; /* flow control settings for this socket */ + int ratelimitInterval; + int ratelimitBurst; + ratelimit_t *dflt_ratelimiter;/*ratelimiter to apply if none else is to be used */ + intTiny ratelimitSev; /* severity level (and below) for which rate-limiting shall apply */ + struct hashtable *ht; /* our hashtable for rate-limiting */ + sbool bParseHost; /* should parser parse host name? read-only after startup */ + sbool bCreatePath; /* auto-creation of socket directory? */ + sbool bUseCreds; /* pull original creator credentials from socket */ + sbool bAnnotate; /* annotate events with trusted properties */ + sbool bParseTrusted; /* parse trusted properties */ + sbool bWritePid; /* write original PID into tag */ + sbool bDiscardOwnMsgs; /* discard messages that originated from ourselves */ + sbool bUseSysTimeStamp; /* use timestamp from system (instead of from message) */ + sbool bUnlink; /* unlink&re-create socket at start and end of processing */ +} lstn_t; +static lstn_t listeners[MAXFUNIX]; + +static prop_t *pLocalHostIP = NULL; /* there is only one global IP for all internally-generated messages */ +static prop_t *pInputName = NULL; /* our inputName currently is always "imudp", and this will hold it */ +static int startIndexUxLocalSockets; /* process fd from that index on (used to + * suppress local logging. rgerhards 2005-08-01 + * read-only after startup + */ +static int nfd = 1; /* number of Unix sockets open / read-only after startup */ +static int sd_fds = 0; /* number of systemd activated sockets */ + +/* config vars for legacy config system */ +#define DFLT_bCreatePath 0 +#define DFLT_ratelimitInterval 0 +#define DFLT_ratelimitBurst 200 +#define DFLT_ratelimitSeverity 1 /* do not rate-limit emergency messages */ +static struct configSettings_s { + int bOmitLocalLogging; + uchar *pLogSockName; + uchar *pLogHostName; /* host name to use with this socket */ + int bUseFlowCtl; /* use flow control or not (if yes, only LIGHT is used!) */ + int bUseFlowCtlSysSock; + int bIgnoreTimestamp; /* ignore timestamps present in the incoming message? */ + int bIgnoreTimestampSysSock; + int bUseSysTimeStamp; /* use timestamp from system (rather than from message) */ + int bUseSysTimeStampSysSock; /* same, for system log socket */ + int bWritePid; /* use credentials from recvmsg() and fixup PID in TAG */ + int bWritePidSysSock; /* use credentials from recvmsg() and fixup PID in TAG */ + int bCreatePath; /* auto-create socket path? */ + int ratelimitInterval; /* interval in seconds, 0 = off */ + int ratelimitIntervalSysSock; + int ratelimitBurst; /* max nbr of messages in interval */ + int ratelimitBurstSysSock; + int ratelimitSeverity; + int ratelimitSeveritySysSock; + int bAnnotate; /* annotate trusted properties */ + int bAnnotateSysSock; /* same, for system log socket */ + int bParseTrusted; /* parse trusted properties */ +} cs; + +struct instanceConf_s { + uchar *sockName; + uchar *pLogHostName; /* host name to use with this socket */ + sbool bUseFlowCtl; /* use flow control or not (if yes, only LIGHT is used! */ + sbool bIgnoreTimestamp; /* ignore timestamps present in the incoming message? */ + sbool bWritePid; /* use credentials from recvmsg() and fixup PID in TAG */ + sbool bUseSysTimeStamp; /* use timestamp from system (instead of from message) */ + int bCreatePath; /* auto-create socket path? */ + int ratelimitInterval; /* interval in seconds, 0 = off */ + int ratelimitBurst; /* max nbr of messages in interval */ + int ratelimitSeverity; + int bAnnotate; /* annotate trusted properties */ + int bParseTrusted; /* parse trusted properties */ + sbool bDiscardOwnMsgs; /* discard messages that originated from our own pid? */ + sbool bUnlink; + struct instanceConf_s *next; +}; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + instanceConf_t *root, *tail; + uchar *pLogSockName; + int ratelimitIntervalSysSock; + int ratelimitBurstSysSock; + int ratelimitSeveritySysSock; + int bAnnotateSysSock; + int bParseTrusted; + sbool bIgnoreTimestamp; /* ignore timestamps present in the incoming message? */ + sbool bUseFlowCtl; /* use flow control or not (if yes, only LIGHT is used! */ + sbool bOmitLocalLogging; + sbool bWritePidSysSock; + sbool bUseSysTimeStamp; + sbool bDiscardOwnMsgs; + sbool configSetViaV2Method; + sbool bUnlink; +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "syssock.use", eCmdHdlrBinary, 0 }, + { "syssock.name", eCmdHdlrGetWord, 0 }, + { "syssock.unlink", eCmdHdlrBinary, 0 }, + { "syssock.ignoretimestamp", eCmdHdlrBinary, 0 }, + { "syssock.ignoreownmessages", eCmdHdlrBinary, 0 }, + { "syssock.flowcontrol", eCmdHdlrBinary, 0 }, + { "syssock.usesystimestamp", eCmdHdlrBinary, 0 }, + { "syssock.annotate", eCmdHdlrBinary, 0 }, + { "syssock.parsetrusted", eCmdHdlrBinary, 0 }, + { "syssock.usepidfromsystem", eCmdHdlrBinary, 0 }, + { "syssock.ratelimit.interval", eCmdHdlrInt, 0 }, + { "syssock.ratelimit.burst", eCmdHdlrInt, 0 }, + { "syssock.ratelimit.severity", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* input instance parameters */ +static struct cnfparamdescr inppdescr[] = { + { "socket", eCmdHdlrString, CNFPARAM_REQUIRED }, /* legacy: addunixlistensocket */ + { "unlink", eCmdHdlrBinary, 0 }, + { "createpath", eCmdHdlrBinary, 0 }, + { "parsetrusted", eCmdHdlrBinary, 0 }, + { "ignoreownmessages", eCmdHdlrBinary, 0 }, + { "hostname", eCmdHdlrString, 0 }, + { "ignoretimestamp", eCmdHdlrBinary, 0 }, + { "flowcontrol", eCmdHdlrBinary, 0 }, + { "usesystimestamp", eCmdHdlrBinary, 0 }, + { "annotate", eCmdHdlrBinary, 0 }, + { "usepidfromsystem", eCmdHdlrBinary, 0 }, + { "ratelimit.interval", eCmdHdlrInt, 0 }, + { "ratelimit.burst", eCmdHdlrInt, 0 }, + { "ratelimit.severity", eCmdHdlrInt, 0 } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + +/* we do not use this, because we do not bind to a ruleset so far + * enable when this is changed: #include "im-helper.h" */ /* must be included AFTER the type definitions! */ + +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + + +/* create input instance, set default paramters, and + * add it to the list of instances. + */ +static rsRetVal +createInstance(instanceConf_t **pinst) +{ + instanceConf_t *inst; + DEFiRet; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + inst->sockName = NULL; + inst->pLogHostName = NULL; + inst->ratelimitInterval = DFLT_ratelimitInterval; + inst->ratelimitBurst = DFLT_ratelimitBurst; + inst->ratelimitSeverity = DFLT_ratelimitSeverity; + inst->bUseFlowCtl = 0; + inst->bIgnoreTimestamp = 1; + inst->bCreatePath = DFLT_bCreatePath; + inst->bUseSysTimeStamp = 1; + inst->bWritePid = 0; + inst->bAnnotate = 0; + inst->bParseTrusted = 0; + inst->bDiscardOwnMsgs = 1; + inst->bUnlink = 1; + inst->next = NULL; + + /* node created, let's add to config */ + if(loadModConf->tail == NULL) { + loadModConf->tail = loadModConf->root = inst; + } else { + loadModConf->tail->next = inst; + loadModConf->tail = inst; + } + + *pinst = inst; +finalize_it: + RETiRet; +} + + +/* This function is called when a new listen socket instace shall be added to + * the current config object via the legacy config system. It just shuffles + * all parameters to the listener in-memory instance. + * rgerhards, 2011-05-12 + */ +static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + instanceConf_t *inst; + DEFiRet; + + if(pNewVal == NULL || pNewVal[0] == '\0') { + errmsg.LogError(0, RS_RET_SOCKNAME_MISSING , "imuxsock: socket name must be specified, " + "but is not - listener not created\n"); + if(pNewVal != NULL) + free(pNewVal); + ABORT_FINALIZE(RS_RET_SOCKNAME_MISSING); + } + + CHKiRet(createInstance(&inst)); + inst->sockName = pNewVal; + inst->ratelimitInterval = cs.ratelimitInterval; + inst->pLogHostName = cs.pLogHostName; + inst->ratelimitBurst = cs.ratelimitBurst; + inst->ratelimitSeverity = cs.ratelimitSeverity; + inst->bUseFlowCtl = cs.bUseFlowCtl; + inst->bIgnoreTimestamp = cs.bIgnoreTimestamp; + inst->bCreatePath = cs.bCreatePath; + inst->bUseSysTimeStamp = cs.bUseSysTimeStamp; + inst->bWritePid = cs.bWritePid; + inst->bAnnotate = cs.bAnnotate; + inst->bParseTrusted = cs.bParseTrusted; + inst->next = NULL; + + /* some legacy conf processing */ + free(cs.pLogHostName); /* reset hostname for next socket */ + cs.pLogHostName = NULL; + +finalize_it: + RETiRet; +} + + +/* add an additional listen socket. Socket names are added + * until the array is filled up. It is never reset, only at + * module unload. + * TODO: we should change the array to a list so that we + * can support any number of listen socket names. + * rgerhards, 2007-12-20 + * added capability to specify hostname for socket -- rgerhards, 2008-08-01 + */ +static rsRetVal +addListner(instanceConf_t *inst) +{ + DEFiRet; + + if(nfd < MAXFUNIX) { + if(*inst->sockName == ':') { + listeners[nfd].bParseHost = 1; + } else { + listeners[nfd].bParseHost = 0; + } + if(inst->pLogHostName == NULL) { + listeners[nfd].hostName = NULL; + } else { + CHKiRet(prop.Construct(&(listeners[nfd].hostName))); + CHKiRet(prop.SetString(listeners[nfd].hostName, inst->pLogHostName, ustrlen(inst->pLogHostName))); + CHKiRet(prop.ConstructFinalize(listeners[nfd].hostName)); + } + if(inst->ratelimitInterval > 0) { + if((listeners[nfd].ht = create_hashtable(100, hash_from_key_fn, key_equals_fn, + (void(*)(void*))ratelimitDestruct)) == NULL) { + /* in this case, we simply turn off rate-limiting */ + DBGPRINTF("imuxsock: turning off rate limiting because we could not " + "create hash table\n"); + inst->ratelimitInterval = 0; + } + } + listeners[nfd].ratelimitInterval = inst->ratelimitInterval; + listeners[nfd].ratelimitBurst = inst->ratelimitBurst; + listeners[nfd].ratelimitSev = inst->ratelimitSeverity; + listeners[nfd].flowCtl = inst->bUseFlowCtl ? eFLOWCTL_LIGHT_DELAY : eFLOWCTL_NO_DELAY; + listeners[nfd].flags = inst->bIgnoreTimestamp ? IGNDATE : NOFLAG; + listeners[nfd].bCreatePath = inst->bCreatePath; + listeners[nfd].sockName = ustrdup(inst->sockName); + listeners[nfd].bUseCreds = (inst->bDiscardOwnMsgs || inst->bWritePid || inst->ratelimitInterval || inst->bAnnotate) ? 1 : 0; + listeners[nfd].bAnnotate = inst->bAnnotate; + listeners[nfd].bParseTrusted = inst->bParseTrusted; + listeners[nfd].bDiscardOwnMsgs = inst->bDiscardOwnMsgs; + listeners[nfd].bUnlink = inst->bUnlink; + listeners[nfd].bWritePid = inst->bWritePid; + listeners[nfd].bUseSysTimeStamp = inst->bUseSysTimeStamp; + CHKiRet(ratelimitNew(&listeners[nfd].dflt_ratelimiter, "imuxsock", NULL)); + ratelimitSetLinuxLike(listeners[nfd].dflt_ratelimiter, + listeners[nfd].ratelimitInterval, + listeners[nfd].ratelimitBurst); + ratelimitSetSeverity(listeners[nfd].dflt_ratelimiter, + listeners[nfd].ratelimitSev); + nfd++; + } else { + errmsg.LogError(0, NO_ERRCODE, "Out of unix socket name descriptors, ignoring %s\n", + inst->sockName); + } + +finalize_it: + RETiRet; +} + + +/* discard/Destruct all log sockets except for "socket" 0. Data for it comes from + * the constant memory pool - and if not, it is freeed via some other pointer. + */ +static rsRetVal discardLogSockets(void) +{ + int i; + + for (i = 1; i < nfd; i++) { + if(listeners[i].sockName != NULL) { + free(listeners[i].sockName); + listeners[i].sockName = NULL; + } + if(listeners[i].hostName != NULL) { + prop.Destruct(&(listeners[i].hostName)); + } + if(listeners[i].ht != NULL) { + hashtable_destroy(listeners[i].ht, 1); /* 1 => free all values automatically */ + } + ratelimitDestruct(listeners[i].dflt_ratelimiter); + } + + return RS_RET_OK; +} + + +/* used to create a log socket if NOT passed in via systemd. + */ +static inline rsRetVal +createLogSocket(lstn_t *pLstn) +{ + struct sockaddr_un sunx; + DEFiRet; + + if(pLstn->bUnlink) + unlink((char*)pLstn->sockName); + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_UNIX; + if(pLstn->bCreatePath) { + makeFileParentDirs((uchar*)pLstn->sockName, ustrlen(pLstn->sockName), 0755, -1, -1, 0); + } + strncpy(sunx.sun_path, (char*)pLstn->sockName, sizeof(sunx.sun_path)); + pLstn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if(pLstn->fd < 0 || bind(pLstn->fd, (struct sockaddr *) &sunx, SUN_LEN(&sunx)) < 0 || + chmod((char*)pLstn->sockName, 0666) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "cannot create '%s'", pLstn->sockName); + DBGPRINTF("cannot create %s (%d).\n", pLstn->sockName, errno); + if(pLstn->fd != -1) + close(pLstn->fd); + pLstn->fd = -1; + ABORT_FINALIZE(RS_RET_ERR_CRE_AFUX); + } +finalize_it: + RETiRet; +} + + +static inline rsRetVal +openLogSocket(lstn_t *pLstn) +{ + DEFiRet; +# if HAVE_SCM_CREDENTIALS + int one; +# endif /* HAVE_SCM_CREDENTIALS */ + + if(pLstn->sockName[0] == '\0') + return -1; + + pLstn->fd = -1; + + if (sd_fds > 0) { + /* Check if the current socket is a systemd activated one. + * If so, just use it. + */ + int fd; + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + sd_fds; fd++) { + if( sd_is_socket_unix(fd, SOCK_DGRAM, -1, (const char*) pLstn->sockName, 0) == 1) { + /* ok, it matches -- just use as is */ + pLstn->fd = fd; + + DBGPRINTF("imuxsock: Acquired UNIX socket '%s' (fd %d) from systemd.\n", + pLstn->sockName, pLstn->fd); + break; + } + /* + * otherwise it either didn't matched *this* socket and + * we just continue to check the next one or there were + * an error and we will create a new socket bellow. + */ + } + } + + if (pLstn->fd == -1) { + CHKiRet(createLogSocket(pLstn)); + } + +# if HAVE_SCM_CREDENTIALS + if(pLstn->bUseCreds) { + one = 1; + if(setsockopt(pLstn->fd, SOL_SOCKET, SO_PASSCRED, &one, (socklen_t) sizeof(one)) != 0) { + errmsg.LogError(errno, NO_ERRCODE, "set SO_PASSCRED failed on '%s'", pLstn->sockName); + pLstn->bUseCreds = 0; + } +// TODO: move to its own #if + if(setsockopt(pLstn->fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) != 0) { + errmsg.LogError(errno, NO_ERRCODE, "set SO_TIMESTAMP failed on '%s'", pLstn->sockName); + } + } +# else /* HAVE_SCM_CREDENTIALS */ + pLstn->bUseCreds = 0; + pLstn->bAnnotate = 0; +# endif /* HAVE_SCM_CREDENTIALS */ + +finalize_it: + if(iRet != RS_RET_OK) { + if(pLstn->fd != -1) { + close(pLstn->fd); + pLstn->fd = -1; + } + } + + RETiRet; +} + + +/* find ratelimiter to use for this message. Currently, we use the + * pid, but may change to cgroup later (probably via a config switch). + * Returns NULL if not found or rate-limiting not activated for this + * listener (the latter being a performance enhancement). + */ +static inline rsRetVal +findRatelimiter(lstn_t *pLstn, struct ucred *cred, ratelimit_t **prl) +{ + ratelimit_t *rl; + int r; + pid_t *keybuf; + char pidbuf[256]; + DEFiRet; + + if(cred == NULL) + FINALIZE; +#if 0 // TODO: check deactivated? + if(pLstn->ratelimitInterval == 0) { + *prl = NULL; + FINALIZE; + } +#endif + if(pLstn->ht == NULL) { + *prl = NULL; + FINALIZE; + } + + rl = hashtable_search(pLstn->ht, &cred->pid); + if(rl == NULL) { + /* we need to add a new ratelimiter, process not seen before! */ + DBGPRINTF("imuxsock: no ratelimiter for pid %lu, creating one\n", + (unsigned long) cred->pid); + STATSCOUNTER_INC(ctrNumRatelimiters, mutCtrNumRatelimiters); + snprintf(pidbuf, sizeof(pidbuf), "pid %lu", + (unsigned long) cred->pid); + pidbuf[sizeof(pidbuf)-1] = '\0'; /* to be on safe side */ + CHKiRet(ratelimitNew(&rl, "imuxsock", pidbuf)); + ratelimitSetLinuxLike(rl, pLstn->ratelimitInterval, pLstn->ratelimitBurst); + ratelimitSetSeverity(rl, pLstn->ratelimitSev); + CHKmalloc(keybuf = malloc(sizeof(pid_t))); + *keybuf = cred->pid; + r = hashtable_insert(pLstn->ht, keybuf, rl); + if(r == 0) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + *prl = rl; + +finalize_it: + if(*prl == NULL) + *prl = pLstn->dflt_ratelimiter; + RETiRet; +} + + +/* patch correct pid into tag. bufTAG MUST be CONF_TAG_MAXSIZE long! + */ +static inline void +fixPID(uchar *bufTAG, int *lenTag, struct ucred *cred) +{ + int i; + char bufPID[16]; + int lenPID; + + if(cred == NULL) + return; + + lenPID = snprintf(bufPID, sizeof(bufPID), "[%lu]:", (unsigned long) cred->pid); + + for(i = *lenTag ; i >= 0 && bufTAG[i] != '[' ; --i) + /*JUST SKIP*/; + + if(i < 0) + i = *lenTag - 1; /* go right at end of TAG, pid was not present (-1 for ':') */ + + if(i + lenPID > CONF_TAG_MAXSIZE) + return; /* do not touch, as things would break */ + + memcpy(bufTAG + i, bufPID, lenPID); + *lenTag = i + lenPID; +} + + +/* Get an "trusted property" from the system. Returns an empty string if the + * property can not be obtained. Inspired by similiar functionality inside + * journald. Currently works with Linux /proc filesystem, only. + */ +static rsRetVal +getTrustedProp(struct ucred *cred, char *propName, uchar *buf, size_t lenBuf, int *lenProp) +{ + int fd; + int i; + int lenRead; + char namebuf[1024]; + DEFiRet; + + if(snprintf(namebuf, sizeof(namebuf), "/proc/%lu/%s", (long unsigned) cred->pid, + propName) >= (int) sizeof(namebuf)) { + ABORT_FINALIZE(RS_RET_ERR); + } + + if((fd = open(namebuf, O_RDONLY)) == -1) { + DBGPRINTF("error reading '%s'\n", namebuf); + ABORT_FINALIZE(RS_RET_ERR); + } + if((lenRead = read(fd, buf, lenBuf - 1)) == -1) { + DBGPRINTF("error reading file data for '%s'\n", namebuf); + close(fd); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* we strip after the first \n */ + for(i = 0 ; i < lenRead ; ++i) { + if(buf[i] == '\n') + break; + else if(iscntrl(buf[i])) + buf[i] = ' '; + } + buf[i] = '\0'; + *lenProp = i; + + close(fd); + +finalize_it: + RETiRet; +} + + +/* read the exe trusted property path (so far, /proc fs only) + */ +static rsRetVal +getTrustedExe(struct ucred *cred, uchar *buf, size_t lenBuf, int* lenProp) +{ + int lenRead; + char namebuf[1024]; + DEFiRet; + + if(snprintf(namebuf, sizeof(namebuf), "/proc/%lu/exe", (long unsigned) cred->pid) + >= (int) sizeof(namebuf)) { + ABORT_FINALIZE(RS_RET_ERR); + } + + if((lenRead = readlink(namebuf, (char*)buf, lenBuf - 1)) == -1) { + DBGPRINTF("error reading link '%s'\n", namebuf); + ABORT_FINALIZE(RS_RET_ERR); + } + + buf[lenRead] = '\0'; + *lenProp = lenRead; + +finalize_it: + RETiRet; +} + + +/* copy a trusted property in escaped mode. That is, the property can contain + * any character and so it must be properly quoted AND escaped. + * It is assumed the output buffer is large enough. Returns the number of + * characters added. + */ +static inline int +copyescaped(uchar *dstbuf, uchar *inbuf, int inlen) +{ + int iDst, iSrc; + + *dstbuf = '"'; + for(iDst=1, iSrc=0 ; iSrc < inlen ; ++iDst, ++iSrc) { + if(inbuf[iSrc] == '"' || inbuf[iSrc] == '\\') { + dstbuf[iDst++] = '\\'; + } + dstbuf[iDst] = inbuf[iSrc]; + } + dstbuf[iDst++] = '"'; + return iDst; +} + + +/* submit received message to the queue engine + * We now parse the message according to expected format so that we + * can also mangle it if necessary. + */ +static inline rsRetVal +SubmitMsg(uchar *pRcv, int lenRcv, lstn_t *pLstn, struct ucred *cred, struct timeval *ts) +{ + msg_t *pMsg; + int lenMsg; + int offs; + int i; + uchar *parse; + int pri; + int facil; + int sever; + uchar bufParseTAG[CONF_TAG_MAXSIZE]; + struct syslogTime st; + time_t tt; + int lenProp; + ratelimit_t *ratelimiter = NULL; + uchar propBuf[1024]; + uchar msgbuf[8192]; + uchar *pmsgbuf; + int toffs; /* offset for trusted properties */ + struct syslogTime dummyTS; + struct json_object *json = NULL, *jval; + DEFiRet; + + if(pLstn->bDiscardOwnMsgs && cred != NULL && cred->pid == glblGetOurPid()) { + DBGPRINTF("imuxsock: discarding message from our own pid\n"); + FINALIZE; + } + + /* TODO: handle format errors?? */ + /* we need to parse the pri first, because we need the severity for + * rate-limiting as well. + */ + parse = pRcv; + lenMsg = lenRcv; + offs = 1; /* '<' */ + + parse++; + pri = 0; + while(offs < lenMsg && isdigit(*parse)) { + pri = pri * 10 + *parse - '0'; + ++parse; + ++offs; + } + facil = LOG_FAC(pri); + sever = LOG_PRI(pri); + + findRatelimiter(pLstn, cred, &ratelimiter); /* ignore error, better so than others... */ + + if(ts == NULL) { + datetime.getCurrTime(&st, &tt); + } else { + datetime.timeval2syslogTime(ts, &st); + tt = ts->tv_sec; + } + +#if 0 // TODO: think about stats counters (or wait for request...?) + if(ratelimiter != NULL && !withinRatelimit(ratelimiter, tt, cred->pid)) { + STATSCOUNTER_INC(ctrLostRatelimit, mutCtrLostRatelimit); + FINALIZE; + } +#endif + + /* created trusted properties */ + if(cred != NULL && pLstn->bAnnotate) { + if((unsigned) (lenRcv + 4096) < sizeof(msgbuf)) { + pmsgbuf = msgbuf; + } else { + CHKmalloc(pmsgbuf = malloc(lenRcv+4096)); + } + + if (pLstn->bParseTrusted) { + json = json_object_new_object(); + /* create value string, create field, and add it */ + jval = json_object_new_int(cred->pid); + json_object_object_add(json, "pid", jval); + jval = json_object_new_int(cred->uid); + json_object_object_add(json, "uid", jval); + jval = json_object_new_int(cred->gid); + json_object_object_add(json, "gid", jval); + if(getTrustedProp(cred, "comm", propBuf, sizeof(propBuf), &lenProp) == RS_RET_OK) { + jval = json_object_new_string((char*)propBuf); + json_object_object_add(json, "appname", jval); + } + if(getTrustedExe(cred, propBuf, sizeof(propBuf), &lenProp) == RS_RET_OK) { + jval = json_object_new_string((char*)propBuf); + json_object_object_add(json, "exe", jval); + } + if(getTrustedProp(cred, "cmdline", propBuf, sizeof(propBuf), &lenProp) == RS_RET_OK) { + jval = json_object_new_string((char*)propBuf); + json_object_object_add(json, "cmd", jval); + } + } else { + memcpy(pmsgbuf, pRcv, lenRcv); + memcpy(pmsgbuf+lenRcv, " @[", 3); + toffs = lenRcv + 3; /* next free location */ + lenProp = snprintf((char*)propBuf, sizeof(propBuf), "_PID=%lu _UID=%lu _GID=%lu", + (long unsigned) cred->pid, (long unsigned) cred->uid, + (long unsigned) cred->gid); + memcpy(pmsgbuf+toffs, propBuf, lenProp); + toffs = toffs + lenProp; + + if(getTrustedProp(cred, "comm", propBuf, sizeof(propBuf), &lenProp) == RS_RET_OK) { + memcpy(pmsgbuf+toffs, " _COMM=", 7); + memcpy(pmsgbuf+toffs+7, propBuf, lenProp); + toffs = toffs + 7 + lenProp; + } + if(getTrustedExe(cred, propBuf, sizeof(propBuf), &lenProp) == RS_RET_OK) { + memcpy(pmsgbuf+toffs, " _EXE=", 6); + memcpy(pmsgbuf+toffs+6, propBuf, lenProp); + toffs = toffs + 6 + lenProp; + } + if(getTrustedProp(cred, "cmdline", propBuf, sizeof(propBuf), &lenProp) == RS_RET_OK) { + memcpy(pmsgbuf+toffs, " _CMDLINE=", 10); + toffs = toffs + 10 + + copyescaped(pmsgbuf+toffs+10, propBuf, lenProp); + } + + /* finalize string */ + pmsgbuf[toffs] = ']'; + pmsgbuf[toffs+1] = '\0'; + + pRcv = pmsgbuf; + lenRcv = toffs + 1; + } + } + + /* we now create our own message object and submit it to the queue */ + CHKiRet(msgConstructWithTime(&pMsg, &st, tt)); + MsgSetRawMsg(pMsg, (char*)pRcv, lenRcv); + parser.SanitizeMsg(pMsg); + lenMsg = pMsg->iLenRawMsg - offs; /* SanitizeMsg() may have changed the size */ + MsgSetInputName(pMsg, pInputName); + MsgSetFlowControlType(pMsg, pLstn->flowCtl); + + pMsg->iFacility = facil; + pMsg->iSeverity = sever; + MsgSetAfterPRIOffs(pMsg, offs); + + parse++; lenMsg--; /* '>' */ + + if(json != NULL) { + /* as per lumberjack spec, these properties need to go into + * the CEE root. + */ + msgAddJSON(pMsg, (uchar*)"!", json); + } + + if(ts == NULL) { + if((pLstn->flags & IGNDATE)) { + /* in this case, we still need to find out if we have a valid + * datestamp or not .. and advance the parse pointer accordingly. + */ + if (datetime.ParseTIMESTAMP3339(&dummyTS, &parse, &lenMsg) != RS_RET_OK) { + datetime.ParseTIMESTAMP3164(&dummyTS, &parse, &lenMsg); + } + } else { + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &parse, &lenMsg) != RS_RET_OK && + datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &parse, &lenMsg) != RS_RET_OK) { + DBGPRINTF("we have a problem, invalid timestamp in msg!\n"); + } + } + } else { /* if we pulled the time from the system, we need to update the message text */ + uchar *tmpParse = parse; /* just to check correctness of TS */ + if(datetime.ParseTIMESTAMP3339(&dummyTS, &tmpParse, &lenMsg) == RS_RET_OK || + datetime.ParseTIMESTAMP3164(&dummyTS, &tmpParse, &lenMsg) == RS_RET_OK) { + /* We modify the message only if it contained a valid timestamp, + * otherwise we do not touch it at all. */ + datetime.formatTimestamp3164(&st, (char*)parse, 0); + parse[15] = ' '; /* re-write \0 from fromatTimestamp3164 by SP */ + /* update "counters" to reflect processed timestamp */ + parse += 16; + } + } + + /* pull tag */ + + i = 0; + while(lenMsg > 0 && *parse != ' ' && i < CONF_TAG_MAXSIZE - 1) { + bufParseTAG[i++] = *parse++; + --lenMsg; + } + bufParseTAG[i] = '\0'; /* terminate string */ + if(pLstn->bWritePid) + fixPID(bufParseTAG, &i, cred); + MsgSetTAG(pMsg, bufParseTAG, i); + MsgSetMSGoffs(pMsg, pMsg->iLenRawMsg - lenMsg); + + if(pLstn->bParseHost) { + pMsg->msgFlags = pLstn->flags | PARSE_HOSTNAME; + } else { + pMsg->msgFlags = pLstn->flags; + } + + MsgSetRcvFrom(pMsg, pLstn->hostName == NULL ? glbl.GetLocalHostNameProp() : pLstn->hostName); + CHKiRet(MsgSetRcvFromIP(pMsg, pLocalHostIP)); + ratelimitAddMsg(ratelimiter, NULL, pMsg); + STATSCOUNTER_INC(ctrSubmit, mutCtrSubmit); +finalize_it: + RETiRet; +} + + +/* This function receives data from a socket indicated to be ready + * to receive and submits the message received for processing. + * rgerhards, 2007-12-20 + * Interface changed so that this function is passed the array index + * of the socket which is to be processed. This eases access to the + * growing number of properties. -- rgerhards, 2008-08-01 + */ +static rsRetVal readSocket(lstn_t *pLstn) +{ + DEFiRet; + int iRcvd; + int iMaxLine; + struct msghdr msgh; + struct iovec msgiov; + struct cmsghdr *cm; + struct ucred *cred; + struct timeval *ts; + uchar bufRcv[4096+1]; + uchar *pRcv = NULL; /* receive buffer */ +# if HAVE_SCM_CREDENTIALS + char aux[128]; +# endif + + assert(pLstn->fd >= 0); + + iMaxLine = glbl.GetMaxLine(); + + /* we optimize performance: if iMaxLine is below 4K (which it is in almost all + * cases, we use a fixed buffer on the stack. Only if it is higher, heap memory + * is used. We could use alloca() to achive a similar aspect, but there are so + * many issues with alloca() that I do not want to take that route. + * rgerhards, 2008-09-02 + */ + if((size_t) iMaxLine < sizeof(bufRcv) - 1) { + pRcv = bufRcv; + } else { + CHKmalloc(pRcv = (uchar*) MALLOC(sizeof(uchar) * (iMaxLine + 1))); + } + + memset(&msgh, 0, sizeof(msgh)); + memset(&msgiov, 0, sizeof(msgiov)); +# if HAVE_SCM_CREDENTIALS + if(pLstn->bUseCreds) { + memset(&aux, 0, sizeof(aux)); + msgh.msg_control = aux; + msgh.msg_controllen = sizeof(aux); + } +# endif + msgiov.iov_base = pRcv; + msgiov.iov_len = iMaxLine; + msgh.msg_iov = &msgiov; + msgh.msg_iovlen = 1; + iRcvd = recvmsg(pLstn->fd, &msgh, MSG_DONTWAIT); + + DBGPRINTF("Message from UNIX socket: #%d\n", pLstn->fd); + if(iRcvd > 0) { + cred = NULL; + ts = NULL; + if(pLstn->bUseCreds || pLstn->bUseSysTimeStamp) { + for(cm = CMSG_FIRSTHDR(&msgh); cm; cm = CMSG_NXTHDR(&msgh, cm)) { +# if HAVE_SCM_CREDENTIALS + if( pLstn->bUseCreds + && cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_CREDENTIALS) { + cred = (struct ucred*) CMSG_DATA(cm); + } +# endif /* HAVE_SCM_CREDENTIALS */ +# if HAVE_SO_TIMESTAMP + if( pLstn->bUseSysTimeStamp + && cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) { + ts = (struct timeval *)CMSG_DATA(cm); + } +# endif /* HAVE_SO_TIMESTAMP */ + } + } + CHKiRet(SubmitMsg(pRcv, iRcvd, pLstn, cred, ts)); + } else if(iRcvd < 0 && errno != EINTR && errno != EAGAIN) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + DBGPRINTF("UNIX socket error: %d = %s.\n", errno, errStr); + errmsg.LogError(errno, NO_ERRCODE, "imuxsock: recvfrom UNIX"); + } + +finalize_it: + if(pRcv != NULL && (size_t) iMaxLine >= sizeof(bufRcv) - 1) + free(pRcv); + + RETiRet; +} + + +/* activate current listeners */ +static inline rsRetVal +activateListeners() +{ + register int i; + int actSocks; + DEFiRet; + + /* first apply some config settings */ +# ifdef OS_SOLARIS + /* under solaris, we must NEVER process the local log socket, because + * it is implemented there differently. If we used it, we would actually + * delete it and render the system partly unusable. So don't do that. + * rgerhards, 2010-03-26 + */ + startIndexUxLocalSockets = 1; +# else + startIndexUxLocalSockets = runModConf->bOmitLocalLogging ? 1 : 0; +# endif + if(runModConf->pLogSockName != NULL) + listeners[0].sockName = runModConf->pLogSockName; + else if(sd_booted()) { + struct stat st; + if(stat(SYSTEMD_PATH_LOG, &st) != -1 && S_ISSOCK(st.st_mode)) { + listeners[0].sockName = (uchar*) SYSTEMD_PATH_LOG; + } + } + if(runModConf->ratelimitIntervalSysSock > 0) { + if((listeners[0].ht = create_hashtable(100, hash_from_key_fn, key_equals_fn, NULL)) == NULL) { + /* in this case, we simply turn of rate-limiting */ + errmsg.LogError(0, NO_ERRCODE, "imuxsock: turning off rate limiting because we could not " + "create hash table\n"); + runModConf->ratelimitIntervalSysSock = 0; + } + } + listeners[0].ratelimitInterval = runModConf->ratelimitIntervalSysSock; + listeners[0].ratelimitBurst = runModConf->ratelimitBurstSysSock; + listeners[0].ratelimitSev = runModConf->ratelimitSeveritySysSock; + listeners[0].bUseCreds = (runModConf->bWritePidSysSock || runModConf->ratelimitIntervalSysSock || runModConf->bAnnotateSysSock || runModConf->bDiscardOwnMsgs) ? 1 : 0; + listeners[0].bWritePid = runModConf->bWritePidSysSock; + listeners[0].bAnnotate = runModConf->bAnnotateSysSock; + listeners[0].bParseTrusted = runModConf->bParseTrusted; + listeners[0].bDiscardOwnMsgs = runModConf->bDiscardOwnMsgs; + listeners[0].bUnlink = runModConf->bUnlink; + listeners[0].bUseSysTimeStamp = runModConf->bUseSysTimeStamp; + listeners[0].flags = runModConf->bIgnoreTimestamp ? IGNDATE : NOFLAG; + listeners[0].flowCtl = runModConf->bUseFlowCtl ? eFLOWCTL_LIGHT_DELAY : eFLOWCTL_NO_DELAY; + CHKiRet(ratelimitNew(&listeners[0].dflt_ratelimiter, "imuxsock", NULL)); + ratelimitSetLinuxLike(listeners[0].dflt_ratelimiter, + listeners[0].ratelimitInterval, + listeners[0].ratelimitBurst); + ratelimitSetSeverity(listeners[0].dflt_ratelimiter,listeners[0].ratelimitSev); + + sd_fds = sd_listen_fds(0); + if(sd_fds < 0) { + errmsg.LogError(-sd_fds, NO_ERRCODE, "imuxsock: Failed to acquire systemd socket"); + ABORT_FINALIZE(RS_RET_ERR_CRE_AFUX); + } + + /* initialize and return if will run or not */ + actSocks = 0; + for (i = startIndexUxLocalSockets ; i < nfd ; i++) { + if(openLogSocket(&(listeners[i])) == RS_RET_OK) { + ++actSocks; + DBGPRINTF("imuxsock: Opened UNIX socket '%s' (fd %d).\n", + listeners[i].sockName, listeners[i].fd); + } + } + + if(actSocks == 0) { + errmsg.LogError(0, NO_ERRCODE, "imuxsock does not run because we could not aquire any socket\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + RETiRet; +} + + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + /* init our settings */ + pModConf->pLogSockName = NULL; + pModConf->bOmitLocalLogging = 0; + pModConf->bIgnoreTimestamp = 1; + pModConf->bUseFlowCtl = 0; + pModConf->bUseSysTimeStamp = 1; + pModConf->bWritePidSysSock = 0; + pModConf->bAnnotateSysSock = 0; + pModConf->bParseTrusted = 0; + pModConf->bDiscardOwnMsgs = 1; + pModConf->bUnlink = 1; + pModConf->ratelimitIntervalSysSock = DFLT_ratelimitInterval; + pModConf->ratelimitBurstSysSock = DFLT_ratelimitBurst; + pModConf->ratelimitSeveritySysSock = DFLT_ratelimitSeverity; + bLegacyCnfModGlobalsPermitted = 1; + /* reset legacy config vars */ + resetConfigVariables(NULL, NULL); +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for imuxsock:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "syssock.use")) { + loadModConf->bOmitLocalLogging = ((int) pvals[i].val.d.n) ? 0 : 1; + } else if(!strcmp(modpblk.descr[i].name, "syssock.name")) { + loadModConf->pLogSockName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "syssock.ignoretimestamp")) { + loadModConf->bIgnoreTimestamp = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.ignoreownmessages")) { + loadModConf->bDiscardOwnMsgs = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.unlink")) { + loadModConf->bUnlink = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.flowcontrol")) { + loadModConf->bUseFlowCtl = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.usesystimestamp")) { + loadModConf->bUseSysTimeStamp = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.annotate")) { + loadModConf->bAnnotateSysSock = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.parsetrusted")) { + loadModConf->bParseTrusted = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.usepidfromsystem")) { + loadModConf->bWritePidSysSock = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.ratelimit.interval")) { + loadModConf->ratelimitIntervalSysSock = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.ratelimit.burst")) { + loadModConf->ratelimitBurstSysSock = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "syssock.ratelimit.severity")) { + loadModConf->ratelimitSeveritySysSock = (int) pvals[i].val.d.n; + } else { + dbgprintf("imuxsock: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + + /* disable legacy module-global config directives */ + bLegacyCnfModGlobalsPermitted = 0; + loadModConf->configSetViaV2Method = 1; + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINnewInpInst + struct cnfparamvals *pvals; + instanceConf_t *inst; + int i; +CODESTARTnewInpInst + DBGPRINTF("newInpInst (imuxsock)\n"); + + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imuxsock: required parameter are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("input param blk in imuxsock:\n"); + cnfparamsPrint(&inppblk, pvals); + } + + CHKiRet(createInstance(&inst)); + + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "socket")) { + inst->sockName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "createpath")) { + inst->bCreatePath = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "parsetrusted")) { + inst->bParseTrusted = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "ignoreownmessages")) { + inst->bDiscardOwnMsgs = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "unlink")) { + inst->bUnlink = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "hostname")) { + inst->pLogHostName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(modpblk.descr[i].name, "ignoretimestamp")) { + inst->bIgnoreTimestamp = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "flowcontrol")) { + inst->bUseFlowCtl = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "usesystimestamp")) { + inst->bUseSysTimeStamp = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "annotate")) { + inst->bAnnotate = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "usepidfromsystem")) { + inst->bWritePid = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "ratelimit.interval")) { + inst->ratelimitInterval = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "ratelimit.burst")) { + inst->ratelimitBurst = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "ratelimit.severity")) { + inst->ratelimitSeverity = (int) pvals[i].val.d.n; + } else { + dbgprintf("imuxsock: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + } +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + + +BEGINendCnfLoad +CODESTARTendCnfLoad + if(!loadModConf->configSetViaV2Method) { + /* persist module-specific settings from legacy config system */ + loadModConf->bOmitLocalLogging = cs.bOmitLocalLogging; + loadModConf->pLogSockName = cs.pLogSockName; + loadModConf->bIgnoreTimestamp = cs.bIgnoreTimestampSysSock; + loadModConf->bUseFlowCtl = cs.bUseFlowCtlSysSock; + loadModConf->bAnnotateSysSock = cs.bAnnotateSysSock; + loadModConf->bParseTrusted = cs.bParseTrusted; + loadModConf->ratelimitIntervalSysSock = cs.ratelimitIntervalSysSock; + loadModConf->ratelimitBurstSysSock = cs.ratelimitBurstSysSock; + loadModConf->ratelimitSeveritySysSock = cs.ratelimitSeveritySysSock; + } + + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.pLogHostName); + cs.pLogSockName = NULL; + cs.pLogHostName = NULL; +ENDendCnfLoad + + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop + instanceConf_t *inst; +CODESTARTactivateCnfPrePrivDrop + runModConf = pModConf; + if(runModConf->bOmitLocalLogging && nfd == 1) + ABORT_FINALIZE(RS_RET_OK); + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + addListner(inst); + } + CHKiRet(activateListeners()); +finalize_it: +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf +ENDactivateCnf + + +BEGINfreeCnf + instanceConf_t *inst, *del; +CODESTARTfreeCnf + free(pModConf->pLogSockName); + for(inst = pModConf->root ; inst != NULL ; ) { + free(inst->sockName); + free(inst->pLogHostName); + del = inst; + inst = inst->next; + free(del); + } +ENDfreeCnf + + +/* This function is called to gather input. */ +BEGINrunInput + int maxfds; + int nfds; + int i; + int fd; +#ifdef USE_UNLIMITED_SELECT + fd_set *pReadfds = malloc(glbl.GetFdSetSize()); +#else + fd_set readfds; + fd_set *pReadfds = &readfds; +#endif + +CODESTARTrunInput + if(runModConf->bOmitLocalLogging && nfd == 1) + ABORT_FINALIZE(RS_RET_OK); + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(1) { + /* Add the Unix Domain Sockets to the list of read + * descriptors. + * rgerhards 2005-08-01: we must now check if there are + * any local sockets to listen to at all. If the -o option + * is given without -a, we do not need to listen at all.. + */ + maxfds = 0; + FD_ZERO (pReadfds); + /* Copy master connections */ + for (i = startIndexUxLocalSockets; i < nfd; i++) { + if (listeners[i].fd!= -1) { + FD_SET(listeners[i].fd, pReadfds); + if(listeners[i].fd > maxfds) + maxfds=listeners[i].fd; + } + } + + if(Debug) { + dbgprintf("--------imuxsock calling select, active file descriptors (max %d): ", maxfds); + for (nfds= 0; nfds <= maxfds; ++nfds) + if ( FD_ISSET(nfds, pReadfds) ) + dbgprintf("%d ", nfds); + dbgprintf("\n"); + } + + /* wait for io to become ready */ + nfds = select(maxfds+1, (fd_set *) pReadfds, NULL, NULL, NULL); + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + + for (i = 0; i < nfd && nfds > 0; i++) { + if(glbl.GetGlobalInputTermState() == 1) + ABORT_FINALIZE(RS_RET_FORCE_TERM); /* terminate input! */ + if ((fd = listeners[i].fd) != -1 && FD_ISSET(fd, pReadfds)) { + readSocket(&(listeners[i])); + --nfds; /* indicate we have processed one */ + } + } + } + +finalize_it: + freeFdSet(pReadfds); + RETiRet; +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun +ENDwillRun + + +BEGINafterRun + int i; +CODESTARTafterRun + /* do cleanup here */ + /* Close the UNIX sockets. */ + for (i = 0; i < nfd; i++) + if (listeners[i].fd != -1) + close(listeners[i].fd); + + /* Clean-up files. */ + for(i = startIndexUxLocalSockets; i < nfd; i++) + if (listeners[i].sockName && listeners[i].fd != -1) { + /* If systemd passed us a socket it is systemd's job to clean it up. + * Do not unlink it -- we will get same socket (node) from systemd + * e.g. on restart again. + */ + if (sd_fds > 0 && + listeners[i].fd >= SD_LISTEN_FDS_START && + listeners[i].fd < SD_LISTEN_FDS_START + sd_fds) + continue; + + if(listeners[i].bUnlink) { + DBGPRINTF("imuxsock: unlinking unix socket file[%d] %s\n", i, listeners[i].sockName); + unlink((char*) listeners[i].sockName); + } + } + + discardLogSockets(); + nfd = 1; +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pInputName != NULL) + prop.Destruct(&pInputName); + + statsobj.Destruct(&modStats); + + objRelease(parser, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + free(cs.pLogSockName); + cs.pLogSockName = NULL; + free(cs.pLogHostName); + cs.bOmitLocalLogging = 0; + cs.pLogHostName = NULL; + cs.bIgnoreTimestamp = 1; + cs.bIgnoreTimestampSysSock = 1; + cs.bUseFlowCtl = 0; + cs.bUseFlowCtlSysSock = 0; + cs.bUseSysTimeStamp = 1; + cs.bUseSysTimeStampSysSock = 1; + cs.bWritePid = 0; + cs.bWritePidSysSock = 0; + cs.bAnnotate = 0; + cs.bAnnotateSysSock = 0; + cs.bParseTrusted = 0; + cs.bCreatePath = DFLT_bCreatePath; + cs.ratelimitInterval = DFLT_ratelimitInterval; + cs.ratelimitIntervalSysSock = DFLT_ratelimitInterval; + cs.ratelimitBurst = DFLT_ratelimitBurst; + cs.ratelimitBurstSysSock = DFLT_ratelimitBurst; + cs.ratelimitSeverity = DFLT_ratelimitSeverity; + cs.ratelimitSeveritySysSock = DFLT_ratelimitSeverity; + + return RS_RET_OK; +} + + +BEGINmodInit() + int i; +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + + DBGPRINTF("imuxsock version %s initializing\n", PACKAGE_VERSION); + + /* init legacy config vars */ + cs.pLogSockName = NULL; + cs.pLogHostName = NULL; /* host name to use with this socket */ + + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.Construct(&pInputName)); + CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imuxsock"), sizeof("imuxsock") - 1)); + CHKiRet(prop.ConstructFinalize(pInputName)); + + /* right now, glbl does not permit per-instance IP address notation. As long as this + * is the case, it is OK to query the HostIP once here at this location. HOWEVER, the + * whole concept is not 100% clean and needs to be addressed on a higher layer. + * TODO / rgerhards, 2012-04-11 + */ + pLocalHostIP = glbl.GetLocalHostIP(); + + /* init system log socket settings */ + listeners[0].flags = IGNDATE; + listeners[0].sockName = UCHAR_CONSTANT(_PATH_LOG); + listeners[0].hostName = NULL; + listeners[0].flowCtl = eFLOWCTL_NO_DELAY; + listeners[0].fd = -1; + listeners[0].bParseHost = 0; + listeners[0].bUseCreds = 0; + listeners[0].bAnnotate = 0; + listeners[0].bParseTrusted = 0; + listeners[0].bDiscardOwnMsgs = 1; + listeners[0].bUnlink = 1; + listeners[0].bCreatePath = 0; + listeners[0].bUseSysTimeStamp = 1; + if((listeners[0].ht = create_hashtable(100, hash_from_key_fn, key_equals_fn, + (void(*)(void*))ratelimitDestruct)) == NULL) { + /* in this case, we simply turn off rate-limiting */ + DBGPRINTF("imuxsock: turning off rate limiting for system socket " + "because we could not create hash table\n"); + listeners[0].ratelimitInterval = 0; + } + + /* initialize socket names */ + for(i = 1 ; i < MAXFUNIX ; ++i) { + listeners[i].sockName = NULL; + listeners[i].fd = -1; + } + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketignoremsgtimestamp", 0, eCmdHdlrBinary, + NULL, &cs.bIgnoreTimestamp, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensockethostname", 0, eCmdHdlrGetWord, + NULL, &cs.pLogHostName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketflowcontrol", 0, eCmdHdlrBinary, + NULL, &cs.bUseFlowCtl, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketannotate", 0, eCmdHdlrBinary, + NULL, &cs.bAnnotate, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketcreatepath", 0, eCmdHdlrBinary, + NULL, &cs.bCreatePath, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketusesystimestamp", 0, eCmdHdlrBinary, + NULL, &cs.bUseSysTimeStamp, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"addunixlistensocket", 0, eCmdHdlrGetWord, + addInstance, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketusepidfromsystem", 0, eCmdHdlrBinary, + NULL, &cs.bWritePid, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imuxsockratelimitinterval", 0, eCmdHdlrInt, + NULL, &cs.ratelimitInterval, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imuxsockratelimitburst", 0, eCmdHdlrInt, + NULL, &cs.ratelimitBurst, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"imuxsockratelimitseverity", 0, eCmdHdlrInt, + NULL, &cs.ratelimitSeverity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + /* the following one is a (dirty) trick: the system log socket is not added via + * an "addUnixListenSocket" config format. As such, it's properties can not be modified + * via $InputUnixListenSocket*". So we need to add a special directive + * for that. We should revisit all of that once we have the new config format... + * rgerhards, 2008-03-06 + */ + CHKiRet(regCfSysLineHdlr2((uchar *)"omitlocallogging", 0, eCmdHdlrBinary, + NULL, &cs.bOmitLocalLogging, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogsocketname", 0, eCmdHdlrGetWord, + NULL, &cs.pLogSockName, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogsocketignoremsgtimestamp", 0, eCmdHdlrBinary, + NULL, &cs.bIgnoreTimestampSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogsocketflowcontrol", 0, eCmdHdlrBinary, + NULL, &cs.bUseFlowCtlSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogusesystimestamp", 0, eCmdHdlrBinary, + NULL, &cs.bUseSysTimeStampSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogsocketannotate", 0, eCmdHdlrBinary, + NULL, &cs.bAnnotateSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogparsetrusted", 0, eCmdHdlrBinary, + NULL, &cs.bParseTrusted, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogusepidfromsystem", 0, eCmdHdlrBinary, + NULL, &cs.bWritePidSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogratelimitinterval", 0, eCmdHdlrInt, + NULL, &cs.ratelimitIntervalSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogratelimitburst", 0, eCmdHdlrInt, + NULL, &cs.ratelimitBurstSysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(regCfSysLineHdlr2((uchar *)"systemlogratelimitseverity", 0, eCmdHdlrInt, + NULL, &cs.ratelimitSeveritySysSock, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&modStats)); + CHKiRet(statsobj.SetName(modStats, UCHAR_CONSTANT("imuxsock"))); + STATSCOUNTER_INIT(ctrSubmit, mutCtrSubmit); + CHKiRet(statsobj.AddCounter(modStats, UCHAR_CONSTANT("submitted"), + ctrType_IntCtr, &ctrSubmit)); + STATSCOUNTER_INIT(ctrLostRatelimit, mutCtrLostRatelimit); + CHKiRet(statsobj.AddCounter(modStats, UCHAR_CONSTANT("ratelimit.discarded"), + ctrType_IntCtr, &ctrLostRatelimit)); + STATSCOUNTER_INIT(ctrNumRatelimiters, mutCtrNumRatelimiters); + CHKiRet(statsobj.AddCounter(modStats, UCHAR_CONSTANT("ratelimit.numratelimiters"), + ctrType_IntCtr, &ctrNumRatelimiters)); + CHKiRet(statsobj.ConstructFinalize(modStats)); + +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imzmq3/Makefile.am b/plugins/imzmq3/Makefile.am new file mode 100644 index 00000000..f9c84e5d --- /dev/null +++ b/plugins/imzmq3/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = imzmq3.la + +imzmq3_la_SOURCES = imzmq3.c +imzmq3_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(CZMQ_CFLAGS) +imzmq3_la_LDFLAGS = -module -avoid-version +imzmq3_la_LIBADD = $(CZMQ_LIBS) + +EXTRA_DIST = diff --git a/plugins/imzmq3/README b/plugins/imzmq3/README new file mode 100644 index 00000000..9a108a01 --- /dev/null +++ b/plugins/imzmq3/README @@ -0,0 +1,59 @@ +ZeroMQ 3.x Input Plugin + +Building this plugin: +Requires libzmq and libczmq. First, download the tarballs of both libzmq +and its supporting libczmq from http://download.zeromq.org. As of this +writing (04/23/2013), the most recent versions of libzmq and czmq are +3.2.2 and 1.3.2 respectively. Configure, build, and then install both libs. + +Imzmq3 allows you to push data into rsyslog from a zeromq socket. The example +below binds a SUB socket to port 7172, and then any messages with the topic +"foo" will be pushed into rsyslog. + +Please note: +This plugin only supports the newer (v7) config format. Legacy config support +was removed. + +Example Rsyslog.conf snippet: +------------------------------------------------------------------------------- +module(load="imzmq3" ioThreads="1") +input(type="imzmq3" action="CONNECT" socktype="SUB" description="tcp://*:7172" subscribe="foo,bar") + +------------------------------------------------------------------------------- +Note you can specify multiple subscriptions with a comma-delimited list, with +no spaces between values. + +The only global parameter for this plugin is ioThreads, which is optional and +probably best left to the zmq default unless you know exactly what you are +doing. + +The instance-level parameters are: + +Required +description +subscribe (required if the sockType is SUB) + +Optional +sockType (defaults to SUB) +action (defaults to BIND +sndHWM +rcvHWM +identity +sndBuf +rcvBuf +linger +backlog +sndTimeout +rcvTimeout +maxMsgSize +rate +recoveryIVL +multicastHops +reconnectIVL +reconnectIVLMax +ipv4Only +affinity + +These all correspond to zmq optional settings. Except where noted, the defaults +are the zmq defaults if not set. See http://api.zeromq.org/3-2:zmq-setsockopt +for info on these. diff --git a/plugins/imzmq3/imzmq3.c b/plugins/imzmq3/imzmq3.c new file mode 100644 index 00000000..08b1dbe4 --- /dev/null +++ b/plugins/imzmq3/imzmq3.c @@ -0,0 +1,876 @@ +/* imzmq3.c + * + * This input plugin enables rsyslog to read messages from a ZeroMQ + * queue. + * + * Copyright 2012 Talksum, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + * + * Authors: + * David Kelly <davidk@talksum.com> + * Hongfei Cheng <hongfeic@talksum.com> + */ + + +#include "config.h" +#include "rsyslog.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "cfsysline.h" +#include "dirty.h" +#include "errmsg.h" +#include "glbl.h" +#include "module-template.h" +#include "msg.h" +#include "net.h" +#include "parser.h" +#include "prop.h" +#include "ruleset.h" +#include "srUtils.h" +#include "unicode-helper.h" + +#include <czmq.h> + +MODULE_TYPE_INPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("imzmq3"); + +/* convienent symbols to denote a socket we want to bind + * vs one we want to just connect to + */ +#define ACTION_CONNECT 1 +#define ACTION_BIND 2 + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(ruleset) + + +/* ---------------------------------------------------------------------------- + * structs to describe sockets + */ +typedef struct _socket_type { + char* name; + int type; +} socket_type; + +/* more overkill, but seems nice to be consistent.*/ +typedef struct _socket_action { + char* name; + int action; +} socket_action; + +typedef struct _poller_data { + ruleset_t* ruleset; + thrdInfo_t* thread; +} poller_data; + + +/* a linked-list of subscription topics */ +typedef struct sublist_t { + char* subscribe; + struct sublist_t* next; +} sublist; + +struct instanceConf_s { + int type; + int action; + char* description; + int sndHWM; /* if you want more than 2^32 messages, */ + int rcvHWM; /* then pass in 0 (the default). */ + char* identity; + sublist* subscriptions; + int sndBuf; + int rcvBuf; + int linger; + int backlog; + int sndTimeout; + int rcvTimeout; + int maxMsgSize; + int rate; + int recoveryIVL; + int multicastHops; + int reconnectIVL; + int reconnectIVLMax; + int ipv4Only; + int affinity; + uchar* pszBindRuleset; + ruleset_t* pBindRuleset; + struct instanceConf_s* next; + +}; + +struct modConfData_s { + rsconf_t* pConf; + instanceConf_t* root; + instanceConf_t* tail; + int io_threads; +}; +struct lstn_s { + struct lstn_s* next; + void* sock; + ruleset_t* pRuleset; +}; + +/* ---------------------------------------------------------------------------- + * Static definitions/initializations. + */ +static modConfData_t* runModConf = NULL; +static struct lstn_s* lcnfRoot = NULL; +static struct lstn_s* lcnfLast = NULL; +static prop_t* s_namep = NULL; +static zloop_t* s_zloop = NULL; +static zctx_t* s_context = NULL; +static socket_type socketTypes[] = { + {"SUB", ZMQ_SUB }, + {"PULL", ZMQ_PULL }, + {"ROUTER", ZMQ_ROUTER }, + {"XSUB", ZMQ_XSUB } +}; + +static socket_action socketActions[] = { + {"BIND", ACTION_BIND}, + {"CONNECT", ACTION_CONNECT}, +}; + +static struct cnfparamdescr modpdescr[] = { + { "ioThreads", eCmdHdlrInt, 0 }, +}; + +static struct cnfparamblk modpblk = { + CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr +}; + +static struct cnfparamdescr inppdescr[] = { + { "description", eCmdHdlrGetWord, 0 }, + { "sockType", eCmdHdlrGetWord, 0 }, + { "subscribe", eCmdHdlrGetWord, 0 }, + { "ruleset", eCmdHdlrGetWord, 0 }, + { "action", eCmdHdlrGetWord, 0 }, + { "sndHWM", eCmdHdlrInt, 0 }, + { "rcvHWM", eCmdHdlrInt, 0 }, + { "identity", eCmdHdlrGetWord, 0 }, + { "sndBuf", eCmdHdlrInt, 0 }, + { "rcvBuf", eCmdHdlrInt, 0 }, + { "linger", eCmdHdlrInt, 0 }, + { "backlog", eCmdHdlrInt, 0 }, + { "sndTimeout", eCmdHdlrInt, 0 }, + { "rcvTimeout", eCmdHdlrInt, 0 }, + { "maxMsgSize", eCmdHdlrInt, 0 }, + { "rate", eCmdHdlrInt, 0 }, + { "recoveryIVL", eCmdHdlrInt, 0 }, + { "multicastHops", eCmdHdlrInt, 0 }, + { "reconnectIVL", eCmdHdlrInt, 0 }, + { "reconnectIVLMax", eCmdHdlrInt, 0 }, + { "ipv4Only", eCmdHdlrInt, 0 }, + { "affinity", eCmdHdlrInt, 0 } +}; + +static struct cnfparamblk inppblk = { + CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr +}; + +#include "im-helper.h" /* must be included AFTER the type definitions! */ + +/* ---------------------------------------------------------------------------- + * Helper functions + */ + +/* get the name of a socket type, return the ZMQ_XXX type + or -1 if not a supported type (see above) +*/ +static int getSocketType(char* name) { + int type = -1; + uint i; + + /* match name with known socket type */ + for(i=0; i<sizeof(socketTypes)/sizeof(socket_type); ++i) { + if( !strcmp(socketTypes[i].name, name) ) { + type = socketTypes[i].type; + break; + } + } + + /* whine if no match was found. */ + if (type == -1) + errmsg.LogError(0, NO_ERRCODE, "unknown type %s",name); + + return type; +} + + +static int getSocketAction(char* name) { + int action = -1; + uint i; + + /* match name with known socket action */ + for(i=0; i < sizeof(socketActions)/sizeof(socket_action); ++i) { + if(!strcmp(socketActions[i].name, name)) { + action = socketActions[i].action; + break; + } + } + + /* whine if no matching action was found */ + if (action == -1) + errmsg.LogError(0, NO_ERRCODE, "unknown action %s",name); + + return action; +} + + +static void setDefaults(instanceConf_t* info) { + info->type = -1; + info->action = -1; + info->description = NULL; + info->sndHWM = -1; + info->rcvHWM = -1; + info->identity = NULL; + info->subscriptions = NULL; + info->pszBindRuleset = NULL; + info->pBindRuleset = NULL; + info->sndBuf = -1; + info->rcvBuf = -1; + info->linger = -1; + info->backlog = -1; + info->sndTimeout = -1; + info->rcvTimeout = -1; + info->maxMsgSize = -1; + info->rate = -1; + info->recoveryIVL = -1; + info->multicastHops = -1; + info->reconnectIVL = -1; + info->reconnectIVLMax = -1; + info->ipv4Only = -1; + info->affinity = -1; + info->next = NULL; +}; + +/* given a comma separated list of subscriptions, create a char* array of them + * to set later + */ +static rsRetVal parseSubscriptions(char* subscribes, sublist** subList){ + char* tok = strtok(subscribes, ","); + sublist* currentSub; + sublist* head; + DEFiRet; + + /* create empty list */ + CHKmalloc(*subList = (sublist*)MALLOC(sizeof(sublist))); + head = *subList; + head->next = NULL; + head->subscribe=NULL; + currentSub=head; + + if(tok) { + head->subscribe=strdup(tok); + for(tok=strtok(NULL, ","); tok!=NULL;tok=strtok(NULL, ",")) { + CHKmalloc(currentSub->next = (sublist*)MALLOC(sizeof(sublist))); + currentSub=currentSub->next; + currentSub->subscribe=strdup(tok); + currentSub->next=NULL; + } + } else { + /* make empty subscription ie subscribe="" */ + head->subscribe=strdup(""); + } + /* TODO: temporary logging */ + currentSub = head; + DBGPRINTF("imzmq3: Subscriptions:"); + for(currentSub = head; currentSub != NULL; currentSub=currentSub->next) { + DBGPRINTF("'%s'", currentSub->subscribe); + } + DBGPRINTF("\n"); +finalize_it: + RETiRet; +} + +static rsRetVal validateConfig(instanceConf_t* info) { + + if (info->type == -1) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "you entered an invalid type"); + return RS_RET_INVALID_PARAMS; + } + if (info->action == -1) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "you entered an invalid action"); + return RS_RET_INVALID_PARAMS; + } + if (info->description == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "you didn't enter a description"); + return RS_RET_INVALID_PARAMS; + } + if(info->type == ZMQ_SUB && info->subscriptions == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "SUB sockets need a subscription"); + return RS_RET_INVALID_PARAMS; + } + if(info->type != ZMQ_SUB && info->subscriptions != NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "only SUB sockets can have subscriptions"); + return RS_RET_INVALID_PARAMS; + } + return RS_RET_OK; +} + +static rsRetVal createContext() { + if (s_context == NULL) { + DBGPRINTF("imzmq3: creating zctx..."); + zsys_handler_set(NULL); + s_context = zctx_new(); + + if (s_context == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "zctx_new failed: %s", + zmq_strerror(errno)); + /* DK: really should do better than invalid params...*/ + return RS_RET_INVALID_PARAMS; + } + DBGPRINTF("success!\n"); + if (runModConf->io_threads > 1) { + DBGPRINTF("setting io worker threads to %d\n", runModConf->io_threads); + zctx_set_iothreads(s_context, runModConf->io_threads); + } + } + return RS_RET_OK; +} + +static rsRetVal createSocket(instanceConf_t* info, void** sock) { + int rv; + sublist* sub; + + *sock = zsocket_new(s_context, info->type); + if (!sock) { + errmsg.LogError(0, + RS_RET_INVALID_PARAMS, + "zsocket_new failed: %s, for type %d", + zmq_strerror(errno),info->type); + /* DK: invalid params seems right here */ + return RS_RET_INVALID_PARAMS; + } + DBGPRINTF("imzmq3: socket of type %d created successfully\n", info->type) + /* Set options *before* the connect/bind. */ + if (info->identity) zsocket_set_identity(*sock, info->identity); + if (info->sndBuf > -1) zsocket_set_sndbuf(*sock, info->sndBuf); + if (info->rcvBuf > -1) zsocket_set_rcvbuf(*sock, info->rcvBuf); + if (info->linger > -1) zsocket_set_linger(*sock, info->linger); + if (info->backlog > -1) zsocket_set_backlog(*sock, info->backlog); + if (info->sndTimeout > -1) zsocket_set_sndtimeo(*sock, info->sndTimeout); + if (info->rcvTimeout > -1) zsocket_set_rcvtimeo(*sock, info->rcvTimeout); + if (info->maxMsgSize > -1) zsocket_set_maxmsgsize(*sock, info->maxMsgSize); + if (info->rate > -1) zsocket_set_rate(*sock, info->rate); + if (info->recoveryIVL > -1) zsocket_set_recovery_ivl(*sock, info->recoveryIVL); + if (info->multicastHops > -1) zsocket_set_multicast_hops(*sock, info->multicastHops); + if (info->reconnectIVL > -1) zsocket_set_reconnect_ivl(*sock, info->reconnectIVL); + if (info->reconnectIVLMax > -1) zsocket_set_reconnect_ivl_max(*sock, info->reconnectIVLMax); + if (info->ipv4Only > -1) zsocket_set_ipv4only(*sock, info->ipv4Only); + if (info->affinity > -1) zsocket_set_affinity(*sock, info->affinity); + if (info->sndHWM > -1 ) zsocket_set_sndhwm(*sock, info->sndHWM); + if (info->rcvHWM > -1 ) zsocket_set_rcvhwm(*sock, info->rcvHWM); + /* Set subscriptions.*/ + if (info->type == ZMQ_SUB) { + for(sub = info->subscriptions; sub!=NULL; sub=sub->next) { + zsocket_set_subscribe(*sock, sub->subscribe); + } + } + + /* Do the bind/connect... */ + if (info->action==ACTION_CONNECT) { + rv = zsocket_connect(*sock, info->description); + if (rv == -1) { + errmsg.LogError(0, + RS_RET_INVALID_PARAMS, + "zmq_connect using %s failed: %s", + info->description, zmq_strerror(errno)); + return RS_RET_INVALID_PARAMS; + } + DBGPRINTF("imzmq3: connect for %s successful\n",info->description); + } else { + rv = zsocket_bind(*sock, info->description); + if (rv == -1) { + errmsg.LogError(0, + RS_RET_INVALID_PARAMS, + "zmq_bind using %s failed: %s", + info->description, zmq_strerror(errno)); + return RS_RET_INVALID_PARAMS; + } + DBGPRINTF("imzmq3: bind for %s successful\n",info->description); + } + return RS_RET_OK; +} + +/* ---------------------------------------------------------------------------- + * Module endpoints + */ + + +/* add an actual endpoint + */ +static rsRetVal createInstance(instanceConf_t** pinst) { + DEFiRet; + instanceConf_t* inst; + CHKmalloc(inst = MALLOC(sizeof(instanceConf_t))); + + /* set defaults into new instance config struct */ + setDefaults(inst); + + /* add this to the config */ + if (runModConf->root == NULL || runModConf->tail == NULL) { + runModConf->tail = runModConf->root = inst; + } else { + runModConf->tail->next = inst; + runModConf->tail = inst; + } + *pinst = inst; +finalize_it: + RETiRet; +} + +static rsRetVal createListener(struct cnfparamvals* pvals) { + instanceConf_t* inst; + int i; + DEFiRet; + + CHKiRet(createInstance(&inst)); + for(i = 0 ; i < inppblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(inppblk.descr[i].name, "ruleset")) { + inst->pszBindRuleset = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "description")) { + inst->description = es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "sockType")){ + inst->type = getSocketType(es_str2cstr(pvals[i].val.d.estr, NULL)); + } else if(!strcmp(inppblk.descr[i].name, "action")){ + inst->action = getSocketAction(es_str2cstr(pvals[i].val.d.estr, NULL)); + } else if(!strcmp(inppblk.descr[i].name, "sndHWM")) { + inst->sndHWM = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "rcvHWM")) { + inst->rcvHWM = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "subscribe")) { + CHKiRet(parseSubscriptions(es_str2cstr(pvals[i].val.d.estr, NULL), + &inst->subscriptions)); + } else if(!strcmp(inppblk.descr[i].name, "identity")){ + inst->identity = es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(inppblk.descr[i].name, "sndBuf")) { + inst->sndBuf = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "rcvBuf")) { + inst->rcvBuf = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "linger")) { + inst->linger = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "backlog")) { + inst->backlog = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "sndTimeout")) { + inst->sndTimeout = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "rcvTimeout")) { + inst->rcvTimeout = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "maxMsgSize")) { + inst->maxMsgSize = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "rate")) { + inst->rate = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "recoveryIVL")) { + inst->recoveryIVL = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "multicastHops")) { + inst->multicastHops = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "reconnectIVL")) { + inst->reconnectIVL = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "reconnectIVLMax")) { + inst->reconnectIVLMax = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "ipv4Only")) { + inst->ipv4Only = (int) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "affinity")) { + inst->affinity = (int) pvals[i].val.d.n; + } else { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: program error, non-handled " + "param '%s'\n", inppblk.descr[i].name); + } + + } +finalize_it: + RETiRet; +} + +static rsRetVal addListener(instanceConf_t* inst){ + /* create the socket */ + void* sock; + struct lstn_s* newcnfinfo; + DEFiRet; + + CHKiRet(createSocket(inst, &sock)); + + /* now create new lstn_s struct */ + CHKmalloc(newcnfinfo=(struct lstn_s*)MALLOC(sizeof(struct lstn_s))); + newcnfinfo->next = NULL; + newcnfinfo->sock = sock; + newcnfinfo->pRuleset = inst->pBindRuleset; + + /* add this struct to the global */ + if(lcnfRoot == NULL) { + lcnfRoot = newcnfinfo; + } + if(lcnfLast == NULL) { + lcnfLast = newcnfinfo; + } else { + lcnfLast->next = newcnfinfo; + lcnfLast = newcnfinfo; + } + +finalize_it: + RETiRet; +} + +static int handlePoll(zloop_t __attribute__((unused)) * loop, zmq_pollitem_t *poller, void* pd) { + msg_t* pMsg; + poller_data* pollerData = (poller_data*)pd; + + char* buf = zstr_recv(poller->socket); + if (msgConstruct(&pMsg) == RS_RET_OK) { + MsgSetRawMsg(pMsg, buf, strlen(buf)); + MsgSetInputName(pMsg, s_namep); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); + MsgSetRcvFromIP(pMsg, glbl.GetLocalHostIP()); + MsgSetMSGoffs(pMsg, 0); + MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY); + MsgSetRuleset(pMsg, pollerData->ruleset); + pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME; + submitMsg2(pMsg); + } + + /* gotta free the string returned from zstr_recv() */ + free(buf); + + if( pollerData->thread->bShallStop == TRUE) { + /* a handler that returns -1 will terminate the + czmq reactor loop + */ + return -1; + } + + return 0; +} + +/* called when runInput is called by rsyslog + */ +static rsRetVal rcv_loop(thrdInfo_t* pThrd){ + size_t n_items = 0; + size_t i; + int rv; + zmq_pollitem_t* items = NULL; + poller_data* pollerData = NULL; + struct lstn_s* current; + instanceConf_t* inst; + DEFiRet; + + /* now add listeners. This actually creates the sockets, etc... */ + for (inst = runModConf->root; inst != NULL; inst=inst->next) { + addListener(inst); + } + if (lcnfRoot == NULL) { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: no listeners were " + "started, input not activated.\n"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + + /* count the # of items first */ + for(current=lcnfRoot;current!=NULL;current=current->next) + n_items++; + + /* make arrays of pollitems, pollerdata so they are easy to delete later */ + + /* create the poll items*/ + CHKmalloc(items = (zmq_pollitem_t*)MALLOC(sizeof(zmq_pollitem_t)*n_items)); + + /* create poller data (stuff to pass into the zmq closure called when we get a message)*/ + CHKmalloc(pollerData = (poller_data*)MALLOC(sizeof(poller_data)*n_items)); + + /* loop through and initialize the poll items and poller_data arrays...*/ + for(i=0, current = lcnfRoot; current != NULL; current = current->next, i++) { + /* create the socket, update items.*/ + items[i].socket=current->sock; + items[i].events = ZMQ_POLLIN; + + /* now update the poller_data for this item */ + pollerData[i].thread = pThrd; + pollerData[i].ruleset = current->pRuleset; + } + + s_zloop = zloop_new(); + for(i=0; i<n_items; ++i) { + + rv = zloop_poller(s_zloop, &items[i], handlePoll, &pollerData[i]); + if (rv) { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: zloop_poller failed for item %zu: %s", i, zmq_strerror(errno)); + } + } + DBGPRINTF("imzmq3: zloop_poller starting..."); + zloop_start(s_zloop); + zloop_destroy(&s_zloop); + DBGPRINTF("imzmq3: zloop_poller stopped."); +finalize_it: + zctx_destroy(&s_context); + + free(items); + free(pollerData); + RETiRet; +} + +/* ---------------------------------------------------------------------------- + * input module functions + */ + +BEGINrunInput +CODESTARTrunInput + CHKiRet(rcv_loop(pThrd)); +finalize_it: + RETiRet; +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* we need to create the inputName property (only once during our + lifetime) */ + CHKiRet(prop.Construct(&s_namep)); + CHKiRet(prop.SetString(s_namep, + UCHAR_CONSTANT("imzmq3"), + sizeof("imzmq3") - 1)); + CHKiRet(prop.ConstructFinalize(s_namep)); + +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + if (s_namep != NULL) + prop.Destruct(&s_namep); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDmodExit + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if (eFeat == sFEATURENonCancelInputTermination) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + /* After endCnfLoad() (BEGINendCnfLoad...ENDendCnfLoad) is called, + * the pModConf pointer must not be used to change the in-memory + * config object. It's safe to use the same pointer for accessing + * the config object until freeCnf() (BEGINfreeCnf...ENDfreeCnf). */ + runModConf = pModConf; + runModConf->pConf = pConf; + /* init module config */ + runModConf->io_threads = 0; /* 0 means don't set it */ +ENDbeginCnfLoad + + +BEGINsetModCnf + struct cnfparamvals* pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if (NULL == pvals) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imzmq3: error processing module " + " config parameters ['module(...)']"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + for (i=0; i < modpblk.nParams; ++i) { + if (!pvals[i].bUsed) + continue; + if (!strcmp(modpblk.descr[i].name, "ioThreads")) { + runModConf->io_threads = (int)pvals[i].val.d.n; + } else { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, + "imzmq3: config error, unknown " + "param %s in setModCnf\n", + modpblk.descr[i].name); + } + } + +finalize_it: + if (pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + + +BEGINendCnfLoad +CODESTARTendCnfLoad + /* Last chance to make changes to the in-memory config object for this + * input module. After this call, the config object must no longer be + * changed. */ + if (pModConf != runModConf) { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: pointer of in-memory config object has " + "changed - pModConf=%p, runModConf=%p", pModConf, runModConf); + } + assert(pModConf == runModConf); +ENDendCnfLoad + + +/* function to generate error message if framework does not find requested ruleset */ +static inline void +std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) +{ + errmsg.LogError(0, NO_ERRCODE, "imzmq3: ruleset '%s' for socket %s not found - " + "using default ruleset instead", inst->pszBindRuleset, + inst->description); +} + + +BEGINcheckCnf +instanceConf_t* inst; +CODESTARTcheckCnf + for(inst = pModConf->root; inst!=NULL; inst=inst->next) { + std_checkRuleset(pModConf, inst); + /* now, validate the instanceConf */ + CHKiRet(validateConfig(inst)); + } +finalize_it: + RETiRet; +ENDcheckCnf + + +BEGINactivateCnfPrePrivDrop +CODESTARTactivateCnfPrePrivDrop + if (pModConf != runModConf) { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: pointer of in-memory config object has " + "changed - pModConf=%p, runModConf=%p", pModConf, runModConf); + } + assert(pModConf == runModConf); + + /* first create the context */ + createContext(); + + /* could setup context here, and set the global worker threads + and so on... */ +ENDactivateCnfPrePrivDrop + + +BEGINactivateCnf +CODESTARTactivateCnf + if (pModConf != runModConf) { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: pointer of in-memory config object has " + "changed - pModConf=%p, runModConf=%p", pModConf, runModConf); + } + assert(pModConf == runModConf); +ENDactivateCnf + + +BEGINfreeCnf + struct lstn_s *lstn, *lstn_r; + instanceConf_t *inst, *inst_r; + sublist *sub, *sub_r; +CODESTARTfreeCnf + DBGPRINTF("imzmq3: BEGINfreeCnf ...\n"); + if (pModConf != runModConf) { + errmsg.LogError(0, NO_ERRCODE, "imzmq3: pointer of in-memory config object has " + "changed - pModConf=%p, runModConf=%p", pModConf, runModConf); + } + for (lstn = lcnfRoot; lstn != NULL; ) { + lstn_r = lstn; + lstn = lstn_r->next; + free(lstn_r); + } + for (inst = pModConf->root ; inst != NULL ; ) { + for (sub = inst->subscriptions; sub != NULL; ) { + free(sub->subscribe); + sub_r = sub; + sub = sub_r->next; + free(sub_r); + } + free(inst->pszBindRuleset); + inst_r = inst; + inst = inst->next; + free(inst_r); + } +ENDfreeCnf + + +BEGINnewInpInst + struct cnfparamvals* pvals; +CODESTARTnewInpInst + + DBGPRINTF("newInpInst (imzmq3)\n"); + pvals = nvlstGetParams(lst, &inppblk, NULL); + if(NULL==pvals) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, + "imzmq3: required parameters are missing\n"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + DBGPRINTF("imzmq3: input param blk:\n"); + cnfparamsPrint(&inppblk, pvals); + + /* now, parse the config params and so on... */ + CHKiRet(createListener(pvals)); + +finalize_it: +CODE_STD_FINALIZERnewInpInst + cnfparamvalsDestruct(pvals, &inppblk); +ENDnewInpInst + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES +CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + /* we only support the current interface specification */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); +ENDmodInit + + diff --git a/plugins/mmanon/Makefile.am b/plugins/mmanon/Makefile.am new file mode 100644 index 00000000..98f0da24 --- /dev/null +++ b/plugins/mmanon/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmanon.la + +mmanon_la_SOURCES = mmanon.c +mmanon_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +mmanon_la_LDFLAGS = -module -avoid-version +mmanon_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmanon/mmanon.c b/plugins/mmanon/mmanon.c new file mode 100644 index 00000000..a1c99d09 --- /dev/null +++ b/plugins/mmanon/mmanon.c @@ -0,0 +1,401 @@ +/* mmanon.c + * anonnymize IP addresses inside the syslog message part + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <stdint.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmanon") + + +DEFobjCurrIf(errmsg); +DEF_OMOD_STATIC_DATA + +/* config variables */ + +/* precomputed table of IPv4 anonymization masks */ +static const uint32_t ipv4masks[33] = { + 0xffffffff, 0xfffffffe, 0xfffffffc, 0xfffffff8, + 0xfffffff0, 0xffffffe0, 0xffffffc0, 0xffffff80, + 0xffffff00, 0xfffffe00, 0xfffffc00, 0xfffff800, + 0xfffff000, 0xffffe000, 0xffffc000, 0xffff8000, + 0xffff0000, 0xfffe0000, 0xfffc0000, 0xfff80000, + 0xfff00000, 0xffe00000, 0xffc00000, 0xff800000, + 0xff000000, 0xfe000000, 0xfc000000, 0xf8000000, + 0xf0000000, 0xe0000000, 0xc0000000, 0x80000000, + 0x00000000 + }; + +/* define operation modes we have */ +#define SIMPLE_MODE 0 /* just overwrite */ +#define REWRITE_MODE 1 /* rewrite IP address, canoninized */ +typedef struct _instanceData { + char replChar; + int8_t mode; + struct { + int8_t bits; + } ipv4; +} instanceData; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "mode", eCmdHdlrGetWord, 0 }, + { "replacementchar", eCmdHdlrGetChar, 0 }, + { "ipv4.bits", eCmdHdlrInt, 0 }, +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; +ENDbeginCnfLoad + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->mode = REWRITE_MODE; + pData->replChar = 'x'; + pData->ipv4.bits = 16; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; + sbool bHadBitsErr; +CODESTARTnewActInst + DBGPRINTF("newActInst (mmanon)\n"); + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CODE_STD_STRING_REQUESTnewActInst(1) + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "mode")) { + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"simple", + sizeof("simple")-1)) { + pData->mode = SIMPLE_MODE; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"rewrite", + sizeof("rewrite")-1)) { + pData->mode = REWRITE_MODE; + } else { + char *cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_INVLD_MODE, + "mmanon: invalid anonymization mode '%s' - ignored", + cstr); + free(cstr); + } + pData->replChar = es_getBufAddr(pvals[i].val.d.estr)[0]; + } else if(!strcmp(actpblk.descr[i].name, "replacementchar")) { + pData->replChar = es_getBufAddr(pvals[i].val.d.estr)[0]; + } else if(!strcmp(actpblk.descr[i].name, "ipv4.bits")) { + pData->ipv4.bits = (int8_t) pvals[i].val.d.n; + } else { + dbgprintf("mmanon: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->mode == SIMPLE_MODE) { + bHadBitsErr = 0; + if(pData->ipv4.bits < 8) { + pData->ipv4.bits = 8; + bHadBitsErr = 1; + } else if(pData->ipv4.bits < 16) { + pData->ipv4.bits = 16; + bHadBitsErr = 1; + } else if(pData->ipv4.bits < 24) { + pData->ipv4.bits = 24; + bHadBitsErr = 1; + } else if(pData->ipv4.bits != 32) { + pData->ipv4.bits = 32; + bHadBitsErr = 1; + } + if(bHadBitsErr) + errmsg.LogError(0, RS_RET_INVLD_ANON_BITS, + "mmanon: invalid number of ipv4 bits " + "in simple mode, corrected to %d", + pData->ipv4.bits); + } else { /* REWRITE_MODE */ + if(pData->ipv4.bits < 1 || pData->ipv4.bits > 32) { + pData->ipv4.bits = 32; + errmsg.LogError(0, RS_RET_INVLD_ANON_BITS, + "mmanon: invalid number of ipv4 bits " + "in rewrite mode, corrected to %d", + pData->ipv4.bits); + } + if(pData->replChar != 'x') { + errmsg.LogError(0, RS_RET_REPLCHAR_IGNORED, + "mmanon: replacementChar parameter is ignored " + "in rewrite mode"); + } + } + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + + +static int +getnum(uchar *msg, int lenMsg, int *idx) +{ + int num = 0; + int i = *idx; + + while(i < lenMsg && msg[i] >= '0' && msg[i] <= '9') { + num = num * 10 + msg[i] - '0'; + ++i; + } + + *idx = i; + return num; +} + + +/* write an IP address octet to the output position */ +static int +writeOctet(uchar *msg, int idx, int *nxtidx, uint8_t octet) +{ + if(octet > 99) { + msg[idx++] = '0' + octet / 100; + octet = octet % 100; + } + if(octet > 9) { + msg[idx++] = '0' + octet / 10; + octet = octet % 10; + } + msg[idx++] = '0' + octet; + + if(nxtidx != NULL) { + if(idx + 1 != *nxtidx) { + /* we got shorter, fix it! */ + msg[idx] = '.'; + *nxtidx = idx + 1; + } + } + return idx; +} + +/* currently works for IPv4 only! */ +void +anonip(instanceData *pData, uchar *msg, int *pLenMsg, int *idx) +{ + int i = *idx; + int octet; + uint32_t ipv4addr; + int ipstart[4]; + int j; + int endpos; + int lenMsg = *pLenMsg; + + while(i < lenMsg && (msg[i] <= '0' || msg[i] >= '9')) { + ++i; /* skip to first number */ + } + if(i >= lenMsg) + goto done; + + /* got digit, let's see if ip */ + ipstart[0] = i; + octet = getnum(msg, lenMsg, &i); + if(octet > 255 || msg[i] != '.') goto done; + ipv4addr = octet << 24; + ++i; + ipstart[1] = i; + octet = getnum(msg, lenMsg, &i); + if(octet > 255 || msg[i] != '.') goto done; + ipv4addr |= octet << 16; + ++i; + ipstart[2] = i; + octet = getnum(msg, lenMsg, &i); + if(octet > 255 || msg[i] != '.') goto done; + ipv4addr |= octet << 8; + ++i; + ipstart[3] = i; + octet = getnum(msg, lenMsg, &i); + if(octet > 255 || !(msg[i] == ' ' || msg[i] == ':')) goto done; + ipv4addr |= octet; + + /* OK, we now found an ip address */ + if(pData->mode == SIMPLE_MODE) { + if(pData->ipv4.bits == 8) + j = ipstart[3]; + else if(pData->ipv4.bits == 16) + j = ipstart[2]; + else if(pData->ipv4.bits == 24) + j = ipstart[1]; + else /* due to our checks, this *must* be 32 */ + j = ipstart[0]; + while(j < i) { + if(msg[j] != '.') + msg[j] = pData->replChar; + ++j; + } + } else { /* REWRITE_MODE */ + ipv4addr &= ipv4masks[pData->ipv4.bits]; + if(pData->ipv4.bits > 24) + writeOctet(msg, ipstart[0], &(ipstart[1]), ipv4addr >> 24); + if(pData->ipv4.bits > 16) + writeOctet(msg, ipstart[1], &(ipstart[2]), (ipv4addr >> 16) & 0xff); + if(pData->ipv4.bits > 8) + writeOctet(msg, ipstart[2], &(ipstart[3]), (ipv4addr >> 8) & 0xff); + endpos = writeOctet(msg, ipstart[3], NULL, ipv4addr & 0xff); + /* if we had truncation, we need to shrink the msg */ + dbgprintf("existing i %d, endpos %d\n", i, endpos); + if(i - endpos > 0) { + *pLenMsg = lenMsg - (i - endpos); + memmove(msg+endpos, msg+i, lenMsg - i + 1); + } + } + +done: *idx = i; + return; +} + + +BEGINdoAction + msg_t *pMsg; + uchar *msg; + int lenMsg; + int i; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + lenMsg = getMSGLen(pMsg); + msg = getMSG(pMsg); + for(i = 0 ; i < lenMsg ; ++i) { + anonip(pData, msg, &lenMsg, &i); + } + if(lenMsg != getMSGLen(pMsg)) + setMSGLen(pMsg, lenMsg); +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(strncmp((char*) p, ":mmanon:", sizeof(":mmanon:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "mmanon supports only v6+ config format, use: " + "action(type=\"mmanon\" ...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +ENDqueryEtryPt + + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("mmanon: module compiled with rsyslog version %s.\n", VERSION); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit diff --git a/plugins/mmaudit/Makefile.am b/plugins/mmaudit/Makefile.am new file mode 100644 index 00000000..77b2e85f --- /dev/null +++ b/plugins/mmaudit/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmaudit.la + +mmaudit_la_SOURCES = mmaudit.c +mmaudit_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +mmaudit_la_LDFLAGS = -module -avoid-version +mmaudit_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmaudit/mmaudit.c b/plugins/mmaudit/mmaudit.c new file mode 100644 index 00000000..6b6b804c --- /dev/null +++ b/plugins/mmaudit/mmaudit.c @@ -0,0 +1,354 @@ +/* mmaudit.c + * This is a message modification module supporting Linux audit format + * in various settings. The module tries to identify the provided + * message as being a Linux audit record and, if so, converts it into + * cee-enhanced syslog format. + * + * NOTE WELL: + * Right now, we do not do any trust checks. So it is possible that a + * malicous user emits something that looks like an audit record and + * tries to fool the system with that. Solving this trust issue is NOT + * an easy thing to do. This will be worked on, as the lumberjack effort + * continues. Please consider the module in its current state as a proof + * of concept. + * + * File begun on 2012-02-23 by RGerhards + * + * Copyright 2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> +#include <json/json.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "dirty.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmaudit") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* static data */ +DEFobjCurrIf(errmsg); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +typedef struct _instanceData { + int dummy; /* remove when the first real parameter is needed */ +} instanceData; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + resetConfigVariables(NULL, NULL); +ENDinitConfVars + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("mmaudit\n"); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + + +static inline void +skipWhitespace(uchar **buf) +{ + while(**buf && isspace(**buf)) + ++(*buf); +} + + +static inline rsRetVal +parseName(uchar **buf, char *name, unsigned lenName) +{ + unsigned i; + skipWhitespace(buf); + --lenName; /* reserve space for '\0' */ + i = 0; + while(**buf && **buf != '=' && lenName) { +//dbgprintf("parseNAme, buf: %s\n", *buf); + name[i++] = **buf; + ++(*buf), --lenName; + } + name[i] = '\0'; + return RS_RET_OK; +} + + +static inline rsRetVal +parseValue(uchar **buf, char *val, unsigned lenval) +{ + char termc; + unsigned i; + DEFiRet; + + --lenval; /* reserve space for '\0' */ + i = 0; + if(**buf == '\0') { + FINALIZE; + } else if(**buf == '\'') { + termc = '\''; + ++(*buf); + } else if(**buf == '"') { + termc = '"'; + ++(*buf); + } else { + termc = ' '; + } + + while(**buf && **buf != termc && lenval) { +//dbgprintf("parseValue, termc '%c', buf: %s\n", termc, *buf); + val[i++] = **buf; + ++(*buf), --lenval; + } + val[i] = '\0'; + +finalize_it: + RETiRet; +} + + +/* parse the audit record and create libee structure + */ +static rsRetVal +audit_parse(uchar *buf, struct json_object **jsonRoot) +{ + struct json_object *json; + struct json_object *jval; + char name[1024]; + char val[1024]; + DEFiRet; + + *jsonRoot = json_object_new_object(); + if(*jsonRoot == NULL) { + ABORT_FINALIZE(RS_RET_ERR); + } + json = json_object_new_object(); + json_object_object_add(*jsonRoot, "data", json); + + while(*buf) { +//dbgprintf("audit_parse, buf: '%s'\n", buf); + CHKiRet(parseName(&buf, name, sizeof(name))); + if(*buf != '=') { + ABORT_FINALIZE(RS_RET_ERR); + } + ++buf; + CHKiRet(parseValue(&buf, val, sizeof(val))); + jval = json_object_new_string(val); + json_object_object_add(json, name, jval); +dbgprintf("mmaudit: parsed %s=%s\n", name, val); + } + + +finalize_it: + RETiRet; +} + + +BEGINdoAction + msg_t *pMsg; + uchar *buf; + int typeID; + struct json_object *jsonRoot; + struct json_object *json; + struct json_object *jval; + int i; + char auditID[1024]; + int bSuccess = 0; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + /* note that we can performance-optimize the interface, but this also + * requires changes to the libraries. For now, we accept message + * duplication. -- rgerhards, 2010-12-01 + */ + buf = getMSG(pMsg); + +dbgprintf("mmaudit: msg is '%s'\n", buf); + while(*buf && isspace(*buf)) { + ++buf; + } + + if(*buf == '\0' || strncmp((char*)buf, "type=", 5)) { + DBGPRINTF("mmaudit: type= undetected: '%s'\n", buf); + FINALIZE; + } + buf += 5; + + typeID = 0; + while(*buf && isdigit(*buf)) { + typeID = typeID * 10 + *buf - '0'; + ++buf; + } + + if(*buf == '\0' || strncmp((char*)buf, " audit(", sizeof(" audit(")-1)) { + DBGPRINTF("mmaudit: audit( header not found: %s'\n", buf); + FINALIZE; + } + buf += sizeof(" audit("); + + for(i = 0 ; i < (int) (sizeof(auditID)-2) && *buf && *buf != ')' ; ++i) { + auditID[i] = *buf++; + } + auditID[i] = '\0'; + if(*buf != ')' || *(buf+1) != ':') { + DBGPRINTF("mmaudit: trailer '):' not found, no audit record: %s'\n", buf); + FINALIZE; + } + buf += 2; + + audit_parse(buf, &jsonRoot); + if(jsonRoot == NULL) { + DBGPRINTF("mmaudit: audit parse error, assuming no " + "audit message: '%s'\n", buf); + FINALIZE; + } + + /* we now need to shuffle the "outer" properties into that stream */ + json = json_object_new_object(); + json_object_object_add(jsonRoot, "hdr", json); + jval = json_object_new_string(auditID); + json_object_object_add(json, "auditid", jval); + jval = json_object_new_int(typeID); + json_object_object_add(json, "type", jval); + + msgAddJSON(pMsg, (uchar*)"!audit", jsonRoot); + bSuccess = 1; + +finalize_it: + MsgSetParseSuccess(pMsg, bSuccess); +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":mmaudit:", sizeof(":mmaudit:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":mmaudit:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + /* we call the function below because we need to call it via our interface definition. However, + * the format specified (if any) is always ignored. + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar*) "RSYSLOG_FileFormat")); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + RETiRet; +} + + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bMsgPassingSupported; +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; + /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* check if the rsyslog core supports parameter passing code */ + bMsgPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", + &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports msg passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_MSG) + bMsgPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */ + } + + if(!bMsgPassingSupported) { + DBGPRINTF("mmaudit: msg-passing is not supported by rsyslog core, " + "can not continue.\n"); + ABORT_FINALIZE(RS_RET_NO_MSG_PASSING); + } + + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/mmcount/Makefile.am b/plugins/mmcount/Makefile.am new file mode 100644 index 00000000..9c8c99db --- /dev/null +++ b/plugins/mmcount/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmcount.la + +mmcount_la_SOURCES = mmcount.c +mmcount_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +mmcount_la_LDFLAGS = -module -avoid-version +mmcount_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmcount/mmcount.c b/plugins/mmcount/mmcount.c new file mode 100644 index 00000000..56a4de55 --- /dev/null +++ b/plugins/mmcount/mmcount.c @@ -0,0 +1,342 @@ +/* mmcount.c + * count messages by priority or json property of given app-name. + * + * Copyright 2013 Red Hat Inc. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <stdint.h> +#include <json/json.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "hashtable.h" + +#define JSON_COUNT_NAME "!mmcount" +#define SEVERITY_COUNT 8 + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmcount") + + +DEFobjCurrIf(errmsg); +DEF_OMOD_STATIC_DATA + +/* config variables */ + +typedef struct _instanceData { + char *pszAppName; + int severity[SEVERITY_COUNT]; + char *pszKey; + char *pszValue; + int valueCounter; + struct hashtable *ht; +} instanceData; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "appname", eCmdHdlrGetWord, 0 }, + { "key", eCmdHdlrGetWord, 0 }, + { "value", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; +ENDbeginCnfLoad + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + int i; + + pData->pszAppName = NULL; + for (i = 0; i < SEVERITY_COUNT; i++) + pData->severity[i] = 0; + pData->pszKey = NULL; + pData->pszValue = NULL; + pData->valueCounter = 0; + pData->ht = NULL; +} + +static unsigned int +hash_from_key_fn(void *k) +{ + return *(unsigned int *)k; +} + +static int +key_equals_fn(void *k1, void *k2) +{ + return (*(unsigned int *)k1 == *(unsigned int *)k2); +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + DBGPRINTF("newActInst (mmcount)\n"); + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CODE_STD_STRING_REQUESTnewActInst(1) + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "appname")) { + pData->pszAppName = es_str2cstr(pvals[i].val.d.estr, NULL); + continue; + } + if(!strcmp(actpblk.descr[i].name, "key")) { + pData->pszKey = es_str2cstr(pvals[i].val.d.estr, NULL); + continue; + } + if(!strcmp(actpblk.descr[i].name, "value")) { + pData->pszValue = es_str2cstr(pvals[i].val.d.estr, NULL); + continue; + } + dbgprintf("mmcount: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + + if(pData->pszAppName == NULL) { + dbgprintf("mmcount: action requires a appname"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(pData->pszKey != NULL && pData->pszValue == NULL) { + if(NULL == (pData->ht = create_hashtable(100, hash_from_key_fn, key_equals_fn, NULL))) { + DBGPRINTF("mmcount: error creating hash table!\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + } +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +static int * +getCounter(struct hashtable *ht, char *str) { + unsigned int key; + int *pCounter; + unsigned int *pKey; + + /* we dont store str as key, instead we store hash of the str + as key to reduce memory usage */ + key = hash_from_string(str); + pCounter = hashtable_search(ht, &key); + if(pCounter) { + return pCounter; + } + + /* counter is not found for the str, so add new entry and + return the counter */ + if(NULL == (pKey = (unsigned int*)malloc(sizeof(unsigned int)))) { + DBGPRINTF("mmcount: memory allocation for key failed\n"); + return NULL; + } + *pKey = key; + + if(NULL == (pCounter = (int*)malloc(sizeof(int)))) { + DBGPRINTF("mmcount: memory allocation for value failed\n"); + free(pKey); + return NULL; + } + *pCounter = 0; + + if(!hashtable_insert(ht, pKey, pCounter)) { + DBGPRINTF("mmcount: inserting element into hashtable failed\n"); + free(pKey); + free(pCounter); + return NULL; + } + return pCounter; +} + +BEGINdoAction + msg_t *pMsg; + char *appname; + struct json_object *json = NULL; + es_str_t *estr = NULL; + struct json_object *keyjson = NULL; + char *pszValue; + int *pCounter; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + appname = getAPPNAME(pMsg, LOCK_MUTEX); + + if(0 != strcmp(appname, pData->pszAppName)) { + /* we are not working for this appname. nothing to do */ + ABORT_FINALIZE(RS_RET_OK); + } + + if(!pData->pszKey) { + /* no key given for count, so we count severity */ + if(pMsg->iSeverity <= SEVERITY_COUNT) { + pData->severity[pMsg->iSeverity]++; + json = json_object_new_int(pData->severity[pMsg->iSeverity]); + } + ABORT_FINALIZE(RS_RET_OK); + } + + /* key is given, so get the property json */ + estr = es_newStrFromBuf(pData->pszKey, strlen(pData->pszKey)); + if(msgGetCEEPropJSON(pMsg, estr, &keyjson) != RS_RET_OK) { + /* key not found in the message. nothing to do */ + ABORT_FINALIZE(RS_RET_OK); + } + + /* key found, so get the value */ + pszValue = (char*)json_object_get_string(keyjson); + + if(pData->pszValue) { + /* value also given for count */ + if(!strcmp(pszValue, pData->pszValue)) { + /* count for (value and key and appname) matched */ + pData->valueCounter++; + json = json_object_new_int(pData->valueCounter); + } + ABORT_FINALIZE(RS_RET_OK); + } + + /* value is not given, so we count for each value of given key */ + pCounter = getCounter(pData->ht, pszValue); + if(pCounter) { + (*pCounter)++; + json = json_object_new_int(*pCounter); + } +finalize_it: + if(estr) { + es_deleteStr(estr); + } + + if(json) { + msgAddJSON(pMsg, (uchar *)JSON_COUNT_NAME, json); + } +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(strncmp((char*) p, ":mmcount:", sizeof(":mmcount:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "mmcount supports only v6+ config format, use: " + "action(type=\"mmcount\" ...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +ENDqueryEtryPt + + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("mmcount: module compiled with rsyslog version %s.\n", VERSION); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit diff --git a/plugins/mmfields/Makefile.am b/plugins/mmfields/Makefile.am new file mode 100644 index 00000000..08170d52 --- /dev/null +++ b/plugins/mmfields/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmfields.la + +mmfields_la_SOURCES = mmfields.c +mmfields_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +mmfields_la_LDFLAGS = -module -avoid-version +mmfields_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmfields/mmfields.c b/plugins/mmfields/mmfields.c new file mode 100644 index 00000000..99c78916 --- /dev/null +++ b/plugins/mmfields/mmfields.c @@ -0,0 +1,265 @@ +/* mmfields.c + * Parse all fields of the message into structured data inside the + * JSON tree. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <stdint.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmfields") + + +DEFobjCurrIf(errmsg); +DEF_OMOD_STATIC_DATA + +/* config variables */ + +/* define operation modes we have */ +#define SIMPLE_MODE 0 /* just overwrite */ +#define REWRITE_MODE 1 /* rewrite IP address, canoninized */ +typedef struct _instanceData { + char separator; +} instanceData; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "separator", eCmdHdlrGetChar, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; +ENDbeginCnfLoad + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->separator = ','; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + DBGPRINTF("newActInst (mmfields)\n"); + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CODE_STD_STRING_REQUESTnewActInst(1) + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "separator")) { + pData->separator = es_getBufAddr(pvals[i].val.d.estr)[0]; + } else { + dbgprintf("mmfields: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + + +static inline rsRetVal +extractField(instanceData *pData, uchar *msgtext, int lenMsg, int *curridx, uchar *fieldbuf) +{ + int i, j; + DEFiRet; + i = *curridx; + j = 0; + while(i < lenMsg && msgtext[i] != pData->separator) { + fieldbuf[j++] = msgtext[i++]; + } + fieldbuf[j] = '\0'; + if(i < lenMsg) + ++i; + *curridx = i; + + RETiRet; +} + + +static inline rsRetVal +parse_fields(instanceData *pData, msg_t *pMsg, uchar *msgtext, int lenMsg) +{ + uchar fieldbuf[32*1024]; + uchar fieldname[512]; + struct json_object *json; + struct json_object *jval; + int field; + uchar *buf; + int currIdx = 0; + DEFiRet; + + if(lenMsg < (int) sizeof(fieldbuf)) { + buf = fieldbuf; + } else { + CHKmalloc(buf = malloc(lenMsg+1)); + } + + json = json_object_new_object(); + if(json == NULL) { + ABORT_FINALIZE(RS_RET_ERR); + } + field = 1; + while(currIdx < lenMsg) { + CHKiRet(extractField(pData, msgtext, lenMsg, &currIdx, buf)); + DBGPRINTF("mmfields: field %d: '%s'\n", field, buf); + snprintf(fieldname, sizeof(fieldname), "f%d", (char*)field); + fieldname[sizeof(fieldname)-1] = '\0'; + jval = json_object_new_string((char*)fieldbuf); + json_object_object_add(json, (char*)fieldname, jval); + field++; + } + msgAddJSON(pMsg, (uchar*)"!", json); +finalize_it: + RETiRet; +} + + +BEGINdoAction + msg_t *pMsg; + uchar *msg; + int lenMsg; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + lenMsg = getMSGLen(pMsg); + msg = getMSG(pMsg); + CHKiRet(parse_fields(pData, pMsg, msg, lenMsg)); +finalize_it: +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(strncmp((char*) p, ":mmfields:", sizeof(":mmfields:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "mmfields supports only v6+ config format, use: " + "action(type=\"mmfields\" ...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +ENDqueryEtryPt + + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("mmfields: module compiled with rsyslog version %s.\n", VERSION); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit diff --git a/plugins/mmjsonparse/Makefile.am b/plugins/mmjsonparse/Makefile.am new file mode 100644 index 00000000..ef39163e --- /dev/null +++ b/plugins/mmjsonparse/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmjsonparse.la + +mmjsonparse_la_SOURCES = mmjsonparse.c +mmjsonparse_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +mmjsonparse_la_LDFLAGS = -module -avoid-version +mmjsonparse_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmjsonparse/mmjsonparse.c b/plugins/mmjsonparse/mmjsonparse.c new file mode 100644 index 00000000..35f69aab --- /dev/null +++ b/plugins/mmjsonparse/mmjsonparse.c @@ -0,0 +1,311 @@ +/* mmjsonparse.c + * This is a message modification module. If give, it extracts JSON data + * and populates the EE event structure with it. + * + * NOTE: read comments in module-template.h for details on the calling interface! + * + * File begun on 2012-02-20 by RGerhards + * + * Copyright 2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> +#include <json/json.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "dirty.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmjsonparse") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* static data */ +DEFobjCurrIf(errmsg); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +typedef struct _instanceData { + struct json_tokener *tokener; +} instanceData; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; +ENDbeginCnfLoad + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance + pData->tokener = json_tokener_new(); + if(pData->tokener == NULL) { + errmsg.LogError(0, RS_RET_ERR, "error: could not create json " + "tokener, cannot activate action"); + ABORT_FINALIZE(RS_RET_ERR); + } +finalize_it: +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->tokener != NULL) + json_tokener_free(pData->tokener); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + DBGPRINTF("mmjsonparse\n"); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + + +static rsRetVal +processJSON(instanceData *pData, msg_t *pMsg, char *buf, size_t lenBuf) +{ + struct json_object *json; + const char *errMsg; + DEFiRet; + + assert(pData->tokener != NULL); + DBGPRINTF("mmjsonparse: toParse: '%s'\n", buf); + json_tokener_reset(pData->tokener); + + json = json_tokener_parse_ex(pData->tokener, buf, lenBuf); + if(Debug) { + errMsg = NULL; + if(json == NULL) { + enum json_tokener_error err; + + err = pData->tokener->err; + if(err != json_tokener_continue) + errMsg = json_tokener_errors[err]; + else + errMsg = "Unterminated input"; + } else if((size_t)pData->tokener->char_offset < lenBuf) + errMsg = "Extra characters after JSON object"; + else if(!json_object_is_type(json, json_type_object)) + errMsg = "JSON value is not an object"; + if(errMsg != NULL) { + DBGPRINTF("mmjsonparse: Error parsing JSON '%s': %s\n", + buf, errMsg); + } + } + if(json == NULL + || ((size_t)pData->tokener->char_offset < lenBuf) + || (!json_object_is_type(json, json_type_object))) { + ABORT_FINALIZE(RS_RET_NO_CEE_MSG); + } + + msgAddJSON(pMsg, (uchar*)"!", json); +finalize_it: + RETiRet; +} + +#define COOKIE "@cee:" +#define LEN_COOKIE (sizeof(COOKIE)-1) +BEGINdoAction + msg_t *pMsg; + uchar *buf; + int bSuccess = 0; + struct json_object *jval; + struct json_object *json; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + /* note that we can performance-optimize the interface, but this also + * requires changes to the libraries. For now, we accept message + * duplication. -- rgerhards, 2010-12-01 + */ + buf = getMSG(pMsg); + + while(*buf && isspace(*buf)) { + ++buf; + } + + if(*buf == '\0' || strncmp((char*)buf, COOKIE, LEN_COOKIE)) { + DBGPRINTF("mmjsonparse: no JSON cookie: '%s'\n", buf); + ABORT_FINALIZE(RS_RET_NO_CEE_MSG); + } + buf += LEN_COOKIE; + CHKiRet(processJSON(pData, pMsg, (char*) buf, strlen((char*)buf))); + bSuccess = 1; +finalize_it: + if(iRet == RS_RET_NO_CEE_MSG) { + /* add buf as msg */ + json = json_object_new_object(); + jval = json_object_new_string((char*)buf); + json_object_object_add(json, "msg", jval); + msgAddJSON(pMsg, (uchar*)"!", json); + iRet = RS_RET_OK; + } + MsgSetParseSuccess(pMsg, bSuccess); +ENDdoAction + +BEGINnewActInst +CODESTARTnewActInst + /* Note: we currently do not have any parameters, so we do not need + * the lst ptr. However, we will most probably need params in the + * future. + */ + DBGPRINTF("newActInst (mmjsonparse)\n"); + + CODE_STD_STRING_REQUESTnewActInst(1) + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + CHKiRet(createInstance(&pData)); + /*setInstParamDefaults(pData);*/ + +CODE_STD_FINALIZERnewActInst +/* cnfparamvalsDestruct(pvals, &actpblk);*/ +ENDnewActInst + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":mmjsonparse:", sizeof(":mmjsonparse:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":mmjsonparse:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + /* we call the function below because we need to call it via our interface definition. However, + * the format specified (if any) is always ignored. + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar*) "RSYSLOG_FileFormat")); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + RETiRet; +} + + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bMsgPassingSupported; +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; + /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("mmjsonparse: module compiled with rsyslog version %s.\n", VERSION); + /* check if the rsyslog core supports parameter passing code */ + bMsgPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", + &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports msg passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_MSG) + bMsgPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */ + } + + if(!bMsgPassingSupported) { + DBGPRINTF("mmjsonparse: msg-passing is not supported by rsyslog core, " + "can not continue.\n"); + ABORT_FINALIZE(RS_RET_NO_MSG_PASSING); + } + + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/mmnormalize/Makefile.am b/plugins/mmnormalize/Makefile.am new file mode 100644 index 00000000..0a3b5ba5 --- /dev/null +++ b/plugins/mmnormalize/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmnormalize.la + +mmnormalize_la_SOURCES = mmnormalize.c +mmnormalize_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(LIBLOGNORM_CFLAGS) $(LIBEE_CFLAGS) +mmnormalize_la_LDFLAGS = -module -avoid-version $(LIBLOGNORM_LIBS) $(LIBEE_LIBS) +mmnormalize_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmnormalize/mmnormalize.c b/plugins/mmnormalize/mmnormalize.c new file mode 100644 index 00000000..fcadc328 --- /dev/null +++ b/plugins/mmnormalize/mmnormalize.c @@ -0,0 +1,407 @@ +/* mmnormalize.c + * This is a message modification module. It normalizes the input message with + * the help of liblognorm. The messages EE event structure is updated. + * + * NOTE: read comments in module-template.h for details on the calling interface! + * + * TODO: check if we can replace libee via JSON system - currently that part + * is pretty inefficient... rgerhards, 2012-08-27 + * + * File begun on 2010-01-01 by RGerhards + * + * Copyright 2010-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <libestr.h> +#include <libee/libee.h> +#include <json/json.h> +#include <liblognorm.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "dirty.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmnormalize") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* static data */ +DEFobjCurrIf(errmsg); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +typedef struct _instanceData { + sbool bUseRawMsg; /**< use %rawmsg% instead of %msg% */ + uchar *rulebase; /**< name of rulebase to use */ + ln_ctx ctxln; /**< context to be used for liblognorm */ + ee_ctx ctxee; /**< context to be used for libee */ +} instanceData; + +typedef struct configSettings_s { + uchar *rulebase; /**< name of normalization rulebase to use */ + int bUseRawMsg; /**< use %rawmsg% instead of %msg% */ +} configSettings_t; +static configSettings_t cs; + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "rulebase", eCmdHdlrGetWord, 1 }, + { "userawmsg", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + +/* to be called to build the libee part of the instance ONCE ALL PARAMETERS ARE CORRECT + * (and set within pData!). + */ +static rsRetVal +buildInstance(instanceData *pData) +{ + DEFiRet; + if((pData->ctxee = ee_initCtx()) == NULL) { + errmsg.LogError(0, RS_RET_ERR_LIBEE_INIT, "error: could not initialize libee " + "ctx, cannot activate action"); + ABORT_FINALIZE(RS_RET_ERR_LIBEE_INIT); + } + + if((pData->ctxln = ln_initCtx()) == NULL) { + errmsg.LogError(0, RS_RET_ERR_LIBLOGNORM_INIT, "error: could not initialize " + "liblognorm ctx, cannot activate action"); + ee_exitCtx(pData->ctxee); + ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_INIT); + } + ln_setEECtx(pData->ctxln, pData->ctxee); + if(ln_loadSamples(pData->ctxln, (char*) pData->rulebase) != 0) { + errmsg.LogError(0, RS_RET_NO_RULEBASE, "error: normalization rulebase '%s' " + "could not be loaded cannot activate action", cs.rulebase); + ee_exitCtx(pData->ctxee); + ln_exitCtx(pData->ctxln); + ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_SAMPDB_LOAD); + } +finalize_it: + RETiRet; +} + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + resetConfigVariables(NULL, NULL); +ENDinitConfVars + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; +ENDbeginCnfLoad + + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.rulebase); + cs.rulebase = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + free(pData->rulebase); + ee_exitCtx(pData->ctxee); + ln_exitCtx(pData->ctxln); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("mmnormalize\n"); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction + msg_t *pMsg; + es_str_t *str; + uchar *buf; + char *cstrJSON; + int len; + int r; + struct ee_event *event = NULL; + struct json_tokener *tokener; + struct json_object *json; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + /* note that we can performance-optimize the interface, but this also + * requires changes to the libraries. For now, we accept message + * duplication. -- rgerhards, 2010-12-01 + */ + if(pData->bUseRawMsg) { + getRawMsg(pMsg, &buf, &len); + } else { + buf = getMSG(pMsg); + len = getMSGLen(pMsg); + } + str = es_newStrFromCStr((char*)buf, len); + r = ln_normalize(pData->ctxln, str, &event); + if(r != 0) { + DBGPRINTF("error %d during ln_normalize\n", r); + MsgSetParseSuccess(pMsg, 0); + } else { + MsgSetParseSuccess(pMsg, 1); + } + es_deleteStr(str); + + /* reformat to our json data struct */ + /* TODO: this is all extremly ineffcient! */ + ee_fmtEventToJSON(event, &str); + cstrJSON = es_str2cstr(str, NULL); + dbgprintf("mmnormalize generated: %s\n", cstrJSON); + + tokener = json_tokener_new(); + json = json_tokener_parse_ex(tokener, cstrJSON, strlen((char*)cstrJSON)); + json_tokener_free(tokener); + msgAddJSON(pMsg, (uchar*)"!", json); + + free(cstrJSON); + es_deleteStr(str); +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->rulebase = NULL; + pData->bUseRawMsg = 0; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; + int bDestructPValsOnExit; +CODESTARTnewActInst + DBGPRINTF("newActInst (mmnormalize)\n"); + + bDestructPValsOnExit = 0; + pvals = nvlstGetParams(lst, &actpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "mmnormalize: error reading " + "config parameters"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + bDestructPValsOnExit = 1; + + if(Debug) { + dbgprintf("action param blk in mmnormalize:\n"); + cnfparamsPrint(&actpblk, pvals); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "rulebase")) { + pData->rulebase = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "userawmsg")) { + pData->bUseRawMsg = (int) pvals[i].val.d.n; + } else { + DBGPRINTF("mmnormalize: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + CODE_STD_STRING_REQUESTnewActInst(1) + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + + iRet = buildInstance(pData); +CODE_STD_FINALIZERnewActInst + if(bDestructPValsOnExit) + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":mmnormalize:", sizeof(":mmnormalize:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + if(cs.rulebase == NULL) { + errmsg.LogError(0, RS_RET_NO_RULEBASE, "error: no normalization rulebase was specified, use " + "$MMNormalizeSampleDB directive first!"); + ABORT_FINALIZE(RS_RET_NO_RULEBASE); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":mmnormalize:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + pData->rulebase = cs.rulebase; + pData->bUseRawMsg = cs.bUseRawMsg; + /* all config vars auto-reset! */ + cs.bUseRawMsg = 0; + cs.rulebase = NULL; /* we used it up! */ + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + /* we call the function below because we need to call it via our interface definition. However, + * the format specified (if any) is always ignored. + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar*) "RSYSLOG_FileFormat")); + CHKiRet(buildInstance(pData)); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + cs.rulebase = NULL; + cs.bUseRawMsg = 0; + RETiRet; +} + +/* set the rulebase name */ +static rsRetVal +setRuleBase(void __attribute__((unused)) *pVal, uchar *pszName) +{ + DEFiRet; + cs.rulebase = pszName; + pszName = NULL; + RETiRet; +} + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bMsgPassingSupported; +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; + /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("mmnormalize: module compiled with rsyslog version %s.\n", VERSION); + /* check if the rsyslog core supports parameter passing code */ + bMsgPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", + &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports msg passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_MSG) + bMsgPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */ + } + + if(!bMsgPassingSupported) { + DBGPRINTF("mmnormalize: msg-passing is not supported by rsyslog core, " + "can not continue.\n"); + ABORT_FINALIZE(RS_RET_NO_MSG_PASSING); + } + + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmnormalizerulebase", 0, eCmdHdlrGetWord, + setRuleBase, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmnormalizeuserawmsg", 0, eCmdHdlrBinary, + NULL, &cs.bUseRawMsg, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/mmsnmptrapd/Makefile.am b/plugins/mmsnmptrapd/Makefile.am new file mode 100644 index 00000000..ca027ca7 --- /dev/null +++ b/plugins/mmsnmptrapd/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = mmsnmptrapd.la + +mmsnmptrapd_la_SOURCES = mmsnmptrapd.c +mmsnmptrapd_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +mmsnmptrapd_la_LDFLAGS = -module -avoid-version +mmsnmptrapd_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/mmsnmptrapd/mmsnmptrapd.c b/plugins/mmsnmptrapd/mmsnmptrapd.c new file mode 100644 index 00000000..b79a311b --- /dev/null +++ b/plugins/mmsnmptrapd/mmsnmptrapd.c @@ -0,0 +1,427 @@ +/* mmsnmptrapd.c + * This is a message modification module. It takes messages generated + * from snmptrapd and modifies them so that the look like they + * originated from the real originator. + * + * NOTE: read comments in module-template.h for details on the calling interface! + * + * File begun on 2011-05-05 by RGerhards + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> +#include "conf.h" +#include "msg.h" +#include "syslogd-types.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "unicode-helper.h" +#include "dirty.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("mmsnmptrapd") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* static data */ +DEFobjCurrIf(errmsg); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +struct severMap_s { + uchar *name; + int code; + struct severMap_s *next; +}; + +typedef struct _instanceData { + uchar *pszTagName; + uchar *pszTagID; /* chaced: name plus trailing shlash (for compares) */ + int lenTagID; /* cached length of tag ID, for performance reasons */ + struct severMap_s *severMap; +} instanceData; + +typedef struct configSettings_s { + uchar *pszTagName; /**< name of tag start value that indicates snmptrapd initiated message */ + uchar *pszSeverityMapping; /**< severitystring to numerical code mapping for snmptrapd string */ +} configSettings_t; +static configSettings_t cs; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.pszTagName = NULL; + cs.pszSeverityMapping = NULL; + resetConfigVariables(NULL, NULL); +ENDinitConfVars + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance + struct severMap_s *node, *nodeDel; +CODESTARTfreeInstance + for(node = pData->severMap ; node != NULL ; ) { + nodeDel = node; + node = node->next; + free(nodeDel->name); + free(nodeDel); + } + free(pData->pszTagName); + free(pData->pszTagID); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("mmsnmptrapd\n"); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + + +/* check if a string is numeric (int) */ +static inline int +isNumeric(uchar *str) +{ + int r = 1; + if(*str == '-' || *str == '+') + ++str; + while(*str) { + if(!isdigit(*str)) { + r = 0; + goto done; + } + ++str; + } +done: + return r; +} + +/* get a substring delimited by a character (or end of string). The + * string is trimmed, that is leading and trailing spaces are removed. + * The caller must provide a buffer which shall receive the substring. + * String length is returned as result. The input string is updated + * on exit, so that it may be used for another query starting at that + * position. + */ +static int +getSubstring(uchar **psrc, uchar delim, uchar *dst, int lenDst) +{ + uchar *dstwrk = dst; + uchar *src = *psrc; + while(*src && isspace(*src)) { + ++src; /* trim leading spaces */ + } + while(*src && *src != delim && --lenDst > 0) { + *dstwrk++ = *src++; + } + dstwrk--; + while(dstwrk > dst && isspace(*dst)) + --dstwrk; /* trim trailing spaces */ + *++dstwrk = '\0'; + + /* final results */ + if(*src == delim) + ++src; + *psrc = src; + return(dstwrk - dst); +} + + +/* get string up to the next SP or '/'. Stops at max size. + * dst, lenDst (receive buffer) must be given. lenDst is + * max length on entry and actual length on exit. + */ +static int +getTagComponent(uchar *tag, uchar *dst, int *lenDst) +{ + int end = *lenDst - 1; /* -1 for NUL-char! */ + int i; + + i = 0; + if(tag[i] != '/') + goto done; + ++tag; + while(i < end && tag[i] != '\0' && tag[i] != ' ' && tag[i] != '/') { + dst[i] = tag[i]; + ++i; + } + dst[i] = '\0'; +dbgprintf("XXXX: getTagComponent dst on output: '%s', len %d\n", dst, i); + *lenDst = i; +done: + return i; +} + + +/* lookup severity code based on provided severity + * returns -1 if severity could not be found. + */ +static inline int +lookupSeverityCode(instanceData *pData, uchar *sever) +{ + struct severMap_s *node; + int sevCode = -1; + + for(node = pData->severMap ; node != NULL ; node = node->next) { + if(!ustrcmp(node->name, sever)) { + sevCode = node->code; + break; + } + } + return sevCode; +} + + +BEGINdoAction + int lenTAG; + int lenSever; + int lenHost; + int sevCode; + msg_t *pMsg; + uchar *pszTag; + uchar pszSever[512]; + uchar pszHost[512]; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + dbgprintf("XXXX: mmsnmptrapd called with pMsg %p\n", pMsg); + getTAG(pMsg, &pszTag, &lenTAG); + if(strncmp((char*)pszTag, (char*)pData->pszTagID, pData->lenTagID)) { + DBGPRINTF("tag '%s' not matching, mmsnmptrapd ignoring this message\n", + pszTag); + FINALIZE; + } + + lenSever = sizeof(pszSever); +dbgprintf("XXXX: pszTag: '%s', lenID %d\n", pszTag, pData->lenTagID); + getTagComponent(pszTag+pData->lenTagID-1, pszSever, &lenSever); + lenHost = sizeof(pszHost); + getTagComponent(pszTag+pData->lenTagID+lenSever, pszHost, &lenHost); + dbgprintf("XXXX: mmsnmptrapd sever '%s'(%d), host '%s'(%d)\n", pszSever, lenSever, pszHost,lenHost); + + if(pszHost[lenHost-1] == ':') { + pszHost[lenHost-1] = '\0'; + --lenHost; + } + sevCode = lookupSeverityCode(pData, pszSever); +dbgprintf("XXXX: severity for message is %d\n", sevCode); + /* now apply new settings */ + MsgSetTAG(pMsg, pData->pszTagName, pData->lenTagID); + MsgSetHOSTNAME(pMsg, pszHost, lenHost); + if(sevCode != -1) + pMsg->iSeverity = sevCode; /* we update like the parser does! */ +finalize_it: +ENDdoAction + + +/* Build the severity mapping table based on user-provided configuration + * settings. + */ +static inline rsRetVal +buildSeverityMapping(instanceData *pData) +{ + uchar pszSev[512]; + uchar pszSevCode[512]; + int sevCode; + uchar *mapping; + struct severMap_s *node; + DEFiRet; + + mapping = cs.pszSeverityMapping; + + while(1) { /* broken inside when all entries are processed */ + if(getSubstring(&mapping, '/', pszSev, sizeof(pszSev)) == 0) { + FINALIZE; + } + if(getSubstring(&mapping, ',', pszSevCode, sizeof(pszSevCode)) == 0) { + errmsg.LogError(0, RS_RET_ERR, "error: invalid severity mapping, cannot " + "extract code. given: '%s'\n", cs.pszSeverityMapping); + ABORT_FINALIZE(RS_RET_ERR); + } + sevCode = atoi((char*) pszSevCode); + if(!isNumeric(pszSevCode)) + sevCode = -1; + if(sevCode < 0 || sevCode > 7) { + errmsg.LogError(0, RS_RET_ERR, "error: severity code %d outside of valid " + "range 0..7 (was string '%s')\n", sevCode, pszSevCode); + ABORT_FINALIZE(RS_RET_ERR); + } + CHKmalloc(node = MALLOC(sizeof(struct severMap_s))); + CHKmalloc(node->name = ustrdup(pszSev)); + node->code = sevCode; + /* we enqueue at the top, so the two lines below do all we need! */ + node->next = pData->severMap; + pData->severMap = node; + DBGPRINTF("mmsnmptrapd: severity string '%s' mapped to code %d\n", + pszSev, sevCode); + } + +finalize_it: + RETiRet; +} + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":mmsnmptrapd:", sizeof(":mmsnmptrapd:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":mmsnmptrapd:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + /* we call the function below because we need to call it via our interface definition. However, + * the format specified (if any) is always ignored. + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar*) "RSYSLOG_FileFormat")); + + /* finally build the instance */ + if(cs.pszTagName == NULL) { + pData->pszTagName = (uchar*) strdup("snmptrapd:"); + pData->pszTagID = (uchar*) strdup("snmptrapd/"); + } else { + int lenTag = ustrlen(cs.pszTagName); + /* new tag value (with colon at the end) */ + CHKmalloc(pData->pszTagName = MALLOC(lenTag + 2)); + memcpy(pData->pszTagName, cs.pszTagName, lenTag); + memcpy(pData->pszTagName+lenTag, ":", 2); + /* tag ID for comparisions */ + CHKmalloc(pData->pszTagID = MALLOC(lenTag + 2)); + memcpy(pData->pszTagID, cs.pszTagName, lenTag); + memcpy(pData->pszTagID+lenTag, "/", 2); + free(cs.pszTagName); /* no longer needed */ + } + pData->lenTagID = ustrlen(pData->pszTagID); + if(cs.pszSeverityMapping != NULL) { + CHKiRet(buildSeverityMapping(pData)); + } + + /* all config vars auto-reset! */ + cs.pszTagName = NULL; + free(cs.pszSeverityMapping); + cs.pszSeverityMapping = NULL; +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + free(cs.pszTagName); + cs.pszTagName = NULL; + free(cs.pszSeverityMapping); + cs.pszSeverityMapping = NULL; + RETiRet; +} + + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bMsgPassingSupported; +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; + /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* check if the rsyslog core supports parameter passing code */ + bMsgPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", + &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports msg passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_MSG) + bMsgPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */ + } + + if(!bMsgPassingSupported) { + DBGPRINTF("mmsnmptrapd: msg-passing is not supported by rsyslog core, " + "can not continue.\n"); + ABORT_FINALIZE(RS_RET_NO_MSG_PASSING); + } + + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* TODO: config vars ininit can be replaced by commented-out code above in v6 */ + cs.pszTagName = NULL; + cs.pszSeverityMapping = NULL; + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmsnmptrapdtag", 0, eCmdHdlrGetWord, + NULL, &cs.pszTagName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmsnmptrapdseveritymapping", 0, eCmdHdlrGetWord, + NULL, &cs.pszSeverityMapping, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/omelasticsearch/Makefile.am b/plugins/omelasticsearch/Makefile.am new file mode 100644 index 00000000..ba85a896 --- /dev/null +++ b/plugins/omelasticsearch/Makefile.am @@ -0,0 +1,9 @@ +pkglib_LTLIBRARIES = omelasticsearch.la + +# TODO: replace cJSON +omelasticsearch_la_SOURCES = omelasticsearch.c cJSON/cjson.c cJSON/cjson.h +omelasticsearch_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +omelasticsearch_la_LDFLAGS = -module -avoid-version +omelasticsearch_la_LIBADD = $(CURL_LIBS) $(LIBM) + +EXTRA_DIST = diff --git a/plugins/omelasticsearch/README b/plugins/omelasticsearch/README new file mode 100644 index 00000000..9021bc0e --- /dev/null +++ b/plugins/omelasticsearch/README @@ -0,0 +1,17 @@ +How to produce an error: +======================== +It's quite easy to get 400, if you put a wrong mapping to your +index. That would be easy to reproduce in "normal" omelasticsearch usage +conditions, by only altering the ES configuration: + +1. Make your index first. Let's call it "testindex": +$ curl -XPUT localhost:9200/testindex/ + +2. Put your mapping for a search type called "mytype", where you specify +that date property should be an integer: +$ curl -XPUT localhost:9200/testindex/mytype/_mapping -d '{"mytype":{"properties": {"timegenerated":{"type":"integer"}}}}' + +3. Now try to insert something where date is not an integer: +$ curl -XPOST localhost:9200/testindex/mytype/ -d '{"timegenerated":"bla"}' +{"error":"MapperParsingException[Failed to parse [date]]; nested: NumberFormatException[For input string: \"bla\"]; ","status":400} + diff --git a/plugins/omelasticsearch/cJSON/README b/plugins/omelasticsearch/cJSON/README new file mode 100644 index 00000000..7531c049 --- /dev/null +++ b/plugins/omelasticsearch/cJSON/README @@ -0,0 +1,247 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +Welcome to cJSON. + +cJSON aims to be the dumbest possible parser that you can get your job done with. +It's a single file of C, and a single header file. + +JSON is described best here: http://www.json.org/ +It's like XML, but fat-free. You use it to move data around, store things, or just +generally represent your program's state. + + +First up, how do I build? +Add cJSON.c to your project, and put cJSON.h somewhere in the header search path. +For example, to build the test app: + +gcc cJSON.c test.c -o test -lm +./test + + +As a library, cJSON exists to take away as much legwork as it can, but not get in your way. +As a point of pragmatism (i.e. ignoring the truth), I'm going to say that you can use it +in one of two modes: Auto and Manual. Let's have a quick run-through. + + +I lifted some JSON from this page: http://www.json.org/fatfree.html +That page inspired me to write cJSON, which is a parser that tries to share the same +philosophy as JSON itself. Simple, dumb, out of the way. + +Some JSON: +{ + "name": "Jack (\"Bee\") Nimble", + "format": { + "type": "rect", + "width": 1920, + "height": 1080, + "interlace": false, + "frame rate": 24 + } +} + +Assume that you got this from a file, a webserver, or magic JSON elves, whatever, +you have a char * to it. Everything is a cJSON struct. +Get it parsed: + cJSON *root = cJSON_Parse(my_json_string); + +This is an object. We're in C. We don't have objects. But we do have structs. +What's the framerate? + + cJSON *format = cJSON_GetObjectItem(root,"format"); + int framerate = cJSON_GetObjectItem(format,"frame rate")->valueint; + + +Want to change the framerate? + cJSON_GetObjectItem(format,"frame rate")->valueint=25; + +Back to disk? + char *rendered=cJSON_Print(root); + +Finished? Delete the root (this takes care of everything else). + cJSON_Delete(root); + +That's AUTO mode. If you're going to use Auto mode, you really ought to check pointers +before you dereference them. If you want to see how you'd build this struct in code? + cJSON *root,*fmt; + root=cJSON_CreateObject(); + cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble")); + cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject()); + cJSON_AddStringToObject(fmt,"type", "rect"); + cJSON_AddNumberToObject(fmt,"width", 1920); + cJSON_AddNumberToObject(fmt,"height", 1080); + cJSON_AddFalseToObject (fmt,"interlace"); + cJSON_AddNumberToObject(fmt,"frame rate", 24); + +Hopefully we can agree that's not a lot of code? There's no overhead, no unnecessary setup. +Look at test.c for a bunch of nice examples, mostly all ripped off the json.org site, and +a few from elsewhere. + +What about manual mode? First up you need some detail. +Let's cover how the cJSON objects represent the JSON data. +cJSON doesn't distinguish arrays from objects in handling; just type. +Each cJSON has, potentially, a child, siblings, value, a name. + +The root object has: Object Type and a Child +The Child has name "name", with value "Jack ("Bee") Nimble", and a sibling: +Sibling has type Object, name "format", and a child. +That child has type String, name "type", value "rect", and a sibling: +Sibling has type Number, name "width", value 1920, and a sibling: +Sibling has type Number, name "height", value 1080, and a sibling: +Sibling hs type False, name "interlace", and a sibling: +Sibling has type Number, name "frame rate", value 24 + +Here's the structure: +typedef struct cJSON { + struct cJSON *next,*prev; + struct cJSON *child; + + int type; + + char *valuestring; + int valueint; + double valuedouble; + + char *string; +} cJSON; + +By default all values are 0 unless set by virtue of being meaningful. + +next/prev is a doubly linked list of siblings. next takes you to your sibling, +prev takes you back from your sibling to you. +Only objects and arrays have a "child", and it's the head of the doubly linked list. +A "child" entry will have prev==0, but next potentially points on. The last sibling has next=0. +The type expresses Null/True/False/Number/String/Array/Object, all of which are #defined in +cJSON.h + +A Number has valueint and valuedouble. If you're expecting an int, read valueint, if not read +valuedouble. + +Any entry which is in the linked list which is the child of an object will have a "string" +which is the "name" of the entry. When I said "name" in the above example, that's "string". +"string" is the JSON name for the 'variable name' if you will. + +Now you can trivially walk the lists, recursively, and parse as you please. +You can invoke cJSON_Parse to get cJSON to parse for you, and then you can take +the root object, and traverse the structure (which is, formally, an N-tree), +and tokenise as you please. If you wanted to build a callback style parser, this is how +you'd do it (just an example, since these things are very specific): + +void parse_and_callback(cJSON *item,const char *prefix) +{ + while (item) + { + char *newprefix=malloc(strlen(prefix)+strlen(item->name)+2); + sprintf(newprefix,"%s/%s",prefix,item->name); + int dorecurse=callback(newprefix, item->type, item); + if (item->child && dorecurse) parse_and_callback(item->child,newprefix); + item=item->next; + free(newprefix); + } +} + +The prefix process will build you a separated list, to simplify your callback handling. +The 'dorecurse' flag would let the callback decide to handle sub-arrays on it's own, or +let you invoke it per-item. For the item above, your callback might look like this: + +int callback(const char *name,int type,cJSON *item) +{ + if (!strcmp(name,"name")) { /* populate name */ } + else if (!strcmp(name,"format/type") { /* handle "rect" */ } + else if (!strcmp(name,"format/width") { /* 800 */ } + else if (!strcmp(name,"format/height") { /* 600 */ } + else if (!strcmp(name,"format/interlace") { /* false */ } + else if (!strcmp(name,"format/frame rate") { /* 24 */ } + return 1; +} + +Alternatively, you might like to parse iteratively. +You'd use: + +void parse_object(cJSON *item) +{ + int i; for (i=0;i<cJSON_GetArraySize(item);i++) + { + cJSON *subitem=cJSON_GetArrayItem(item,i); + // handle subitem. + } +} + +Or, for PROPER manual mode: + +void parse_object(cJSON *item) +{ + cJSON *subitem=item->child; + while (subitem) + { + // handle subitem + if (subitem->child) parse_object(subitem->child); + + subitem=subitem->next; + } +} + +Of course, this should look familiar, since this is just a stripped-down version +of the callback-parser. + +This should cover most uses you'll find for parsing. The rest should be possible +to infer.. and if in doubt, read the source! There's not a lot of it! ;) + + +In terms of constructing JSON data, the example code above is the right way to do it. +You can, of course, hand your sub-objects to other functions to populate. +Also, if you find a use for it, you can manually build the objects. +For instance, suppose you wanted to build an array of objects? + +cJSON *objects[24]; + +cJSON *Create_array_of_anything(cJSON **items,int num) +{ + int i;cJSON *prev, *root=cJSON_CreateArray(); + for (i=0;i<24;i++) + { + if (!i) root->child=objects[i]; + else prev->next=objects[i], objects[i]->prev=prev; + prev=objects[i]; + } + return root; +} + +and simply: Create_array_of_anything(objects,24); + +cJSON doesn't make any assumptions about what order you create things in. +You can attach the objects, as above, and later add children to each +of those objects. + +As soon as you call cJSON_Print, it renders the structure to text. + + + +The test.c code shows how to handle a bunch of typical cases. If you uncomment +the code, it'll load, parse and print a bunch of test files, also from json.org, +which are more complex than I'd care to try and stash into a const char array[]. + + +Enjoy cJSON! + + +- Dave Gamble, Aug 2009 diff --git a/plugins/omelasticsearch/cJSON/cjson.c b/plugins/omelasticsearch/cJSON/cjson.c new file mode 100644 index 00000000..99a831e9 --- /dev/null +++ b/plugins/omelasticsearch/cJSON/cjson.c @@ -0,0 +1,514 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <float.h> +#include <limits.h> +#include <ctype.h> +#include "cjson.h" + +static const char *ep; + +const char *cJSON_GetErrorPtr() {return ep;} + +static int cJSON_strcasecmp(const char *s1,const char *s2) +{ + if (!s1) return (s1==s2)?0:1;if (!s2) return 1; + for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; + return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); +} + +static void *(*cJSON_malloc)(size_t sz) = malloc; +static void (*cJSON_free)(void *ptr) = free; + +static char* cJSON_strdup(const char* str) +{ + size_t len; + char* copy; + + len = strlen(str) + 1; + if (!(copy = (char*)cJSON_malloc(len))) return 0; + memcpy(copy,str,len); + return copy; +} + +void cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (!hooks) { /* Reset hooks */ + cJSON_malloc = malloc; + cJSON_free = free; + return; + } + + cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; + cJSON_free = (hooks->free_fn)?hooks->free_fn:free; +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item() +{ + cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); + if (node) memset(node,0,sizeof(cJSON)); + return node; +} + +/* Delete a cJSON structure. */ +void cJSON_Delete(cJSON *c) +{ + cJSON *next; + while (c) + { + next=c->next; + if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); + if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); + if (c->string) cJSON_free(c->string); + cJSON_free(c); + c=next; + } +} + +/* Parse the input text to generate a number, and populate the result into item. */ +static const char *parse_number(cJSON *item,const char *num) +{ + double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; + + /* Could use sscanf for this? */ + if (*num=='-') sign=-1,num++; /* Has sign? */ + if (*num=='0') num++; /* is zero */ + if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ + if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ + if (*num=='e' || *num=='E') /* Exponent? */ + { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ + while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ + } + + n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ + + item->valuedouble=n; + item->valueint=(int)n; + item->type=cJSON_Number; + return num; +} + +/* Render the number nicely from the given item into a string. */ +char *cJSON_print_number(cJSON *item) +{ + char *str; + double d=item->valuedouble; + if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) + { + str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ + if (str) sprintf(str,"%d",item->valueint); + } + else + { + str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */ + if (str) + { + if (fabs(floor(d)-d)<=DBL_EPSILON) sprintf(str,"%.0f",d); + else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); + else sprintf(str,"%f",d); + } + } + return str; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ +static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; +static const char *parse_string(cJSON *item,const char *str) +{ + const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2; + if (*str!='\"') {ep=str;return 0;} /* not a string! */ + + while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ + + out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ + if (!out) return 0; + + ptr=str+1;ptr2=out; + while (*ptr!='\"' && *ptr) + { + if (*ptr!='\\') *ptr2++=*ptr++; + else + { + ptr++; + switch (*ptr) + { + case 'b': *ptr2++='\b'; break; + case 'f': *ptr2++='\f'; break; + case 'n': *ptr2++='\n'; break; + case 'r': *ptr2++='\r'; break; + case 't': *ptr2++='\t'; break; + case 'u': /* transcode utf16 to utf8. */ + sscanf(ptr+1,"%4x",&uc);ptr+=4; /* get the unicode char. */ + + if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; // check for invalid. + + if (uc>=0xD800 && uc<=0xDBFF) // UTF16 surrogate pairs. + { + if (ptr[1]!='\\' || ptr[2]!='u') break; // missing second-half of surrogate. + sscanf(ptr+3,"%4x",&uc2);ptr+=6; + if (uc2<0xDC00 || uc2>0xDFFF) break; // invalid second-half of surrogate. + uc=0x10000 | ((uc&0x3FF)<<10) | (uc2&0x3FF); + } + + len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; + + switch (len) { + case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 =(uc | firstByteMark[len]); + } + ptr2+=len; + break; + default: *ptr2++=*ptr; break; + } + ptr++; + } + } + *ptr2=0; + if (*ptr=='\"') ptr++; + item->valuestring=out; + item->type=cJSON_String; + return ptr; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static char *print_string_ptr(const char *str) +{ + const char *ptr;char *ptr2,*out;int len=0;unsigned char token; + + if (!str) return cJSON_strdup(""); + ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} + + out=(char*)cJSON_malloc(len+3); + if (!out) return 0; + + ptr2=out;ptr=str; + *ptr2++='\"'; + while (*ptr) + { + if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; + else + { + *ptr2++='\\'; + switch (token=*ptr++) + { + case '\\': *ptr2++='\\'; break; + case '\"': *ptr2++='\"'; break; + case '\b': *ptr2++='b'; break; + case '\f': *ptr2++='f'; break; + case '\n': *ptr2++='n'; break; + case '\r': *ptr2++='r'; break; + case '\t': *ptr2++='t'; break; + default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ + } + } + } + *ptr2++='\"';*ptr2++=0; + return out; +} +/* Invote print_string_ptr (which is useful) on an item. */ +static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} + +/* Predeclare these prototypes. */ +static const char *parse_value(cJSON *item,const char *value); +static char *print_value(cJSON *item,int depth,int fmt); +static const char *parse_array(cJSON *item,const char *value); +static char *print_array(cJSON *item,int depth,int fmt); +static const char *parse_object(cJSON *item,const char *value); +static char *print_object(cJSON *item,int depth,int fmt); + +/* Utility to jump whitespace and cr/lf */ +static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} + +/* Parse an object - create a new root, and populate. */ +cJSON *cJSON_Parse(const char *value) +{ + cJSON *c=cJSON_New_Item(); + ep=0; + if (!c) return 0; /* memory fail */ + + if (!parse_value(c,skip(value))) {cJSON_Delete(c);return 0;} + return c; +} + +/* Render a cJSON item/entity/structure to text. */ +char *cJSON_Print(cJSON *item) {return print_value(item,0,1);} +char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} + +/* Parser core - when encountering text, process appropriately. */ +static const char *parse_value(cJSON *item,const char *value) +{ + if (!value) return 0; /* Fail on null. */ + if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } + if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } + if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } + if (*value=='\"') { return parse_string(item,value); } + if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } + if (*value=='[') { return parse_array(item,value); } + if (*value=='{') { return parse_object(item,value); } + + ep=value;return 0; /* failure. */ +} + +/* Render a value to text. */ +static char *print_value(cJSON *item,int depth,int fmt) +{ + char *out=0; + if (!item) return 0; + switch ((item->type)&255) + { + case cJSON_NULL: out=cJSON_strdup("null"); break; + case cJSON_False: out=cJSON_strdup("false");break; + case cJSON_True: out=cJSON_strdup("true"); break; + case cJSON_Number: out=cJSON_print_number(item);break; + case cJSON_String: out=print_string(item);break; + case cJSON_Array: out=print_array(item,depth,fmt);break; + case cJSON_Object: out=print_object(item,depth,fmt);break; + } + return out; +} + +/* Build an array from input text. */ +static const char *parse_array(cJSON *item,const char *value) +{ + cJSON *child; + if (*value!='[') {ep=value;return 0;} /* not an array! */ + + item->type=cJSON_Array; + value=skip(value+1); + if (*value==']') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; /* memory fail */ + value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_value(child,skip(value+1))); + if (!value) return 0; /* memory fail */ + } + + if (*value==']') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an array to text */ +static char *print_array(cJSON *item,int depth,int fmt) +{ + char **entries; + char *out=0,*ptr,*ret;int len=5; + cJSON *child=item->child; + int numentries=0,i=0,fail=0; + + /* How many entries in the array? */ + while (child) numentries++,child=child->next; + /* Allocate an array to hold the values for each */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + memset(entries,0,numentries*sizeof(char*)); + /* Retrieve all the results: */ + child=item->child; + while (child && !fail) + { + ret=print_value(child,depth+1,fmt); + entries[i++]=ret; + if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; + child=child->next; + } + + /* If we didn't fail, try to malloc the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + /* If that fails, we fail. */ + if (!out) fail=1; + + /* Handle failure. */ + if (fail) + { + for (i=0;i<numentries;i++) if (entries[i]) cJSON_free(entries[i]); + cJSON_free(entries); + return 0; + } + + /* Compose the output array. */ + *out='['; + ptr=out+1;*ptr=0; + for (i=0;i<numentries;i++) + { + strcpy(ptr,entries[i]);ptr+=strlen(entries[i]); + if (i!=numentries-1) {*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;} + cJSON_free(entries[i]); + } + cJSON_free(entries); + *ptr++=']';*ptr++=0; + return out; +} + +/* Build an object from the text. */ +static const char *parse_object(cJSON *item,const char *value) +{ + cJSON *child; + if (*value!='{') {ep=value;return 0;} /* not an object! */ + + item->type=cJSON_Object; + value=skip(value+1); + if (*value=='}') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; + value=skip(parse_string(child,skip(value))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_string(child,skip(value+1))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + } + + if (*value=='}') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an object to text. */ +static char *print_object(cJSON *item,int depth,int fmt) +{ + char **entries=0,**names=0; + char *out=0,*ptr,*ret,*str;int len=7,i=0,j; + cJSON *child=item->child; + int numentries=0,fail=0; + /* Count the number of entries. */ + while (child) numentries++,child=child->next; + /* Allocate space for the names and the objects */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + names=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!names) {cJSON_free(entries);return 0;} + memset(entries,0,sizeof(char*)*numentries); + memset(names,0,sizeof(char*)*numentries); + + /* Collect all the results into our arrays: */ + child=item->child;depth++;if (fmt) len+=depth; + while (child) + { + names[i]=str=print_string_ptr(child->string); + entries[i++]=ret=print_value(child,depth,fmt); + if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; + child=child->next; + } + + /* Try to allocate the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + if (!out) fail=1; + + /* Handle failure */ + if (fail) + { + for (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);} + cJSON_free(names);cJSON_free(entries); + return 0; + } + + /* Compose the output: */ + *out='{';ptr=out+1;if (fmt)*ptr++='\n';*ptr=0; + for (i=0;i<numentries;i++) + { + if (fmt) for (j=0;j<depth;j++) *ptr++='\t'; + strcpy(ptr,names[i]);ptr+=strlen(names[i]); + *ptr++=':';if (fmt) *ptr++='\t'; + strcpy(ptr,entries[i]);ptr+=strlen(entries[i]); + if (i!=numentries-1) *ptr++=','; + if (fmt) *ptr++='\n';*ptr=0; + cJSON_free(names[i]);cJSON_free(entries[i]); + } + + cJSON_free(names);cJSON_free(entries); + if (fmt) for (i=0;i<depth-1;i++) *ptr++='\t'; + *ptr++='}';*ptr++=0; + return out; +} + +/* Get Array size/item / object item. */ +int cJSON_GetArraySize(cJSON *array) {cJSON *c=array->child;int i=0;while(c)i++,c=c->next;return i;} +cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} +cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} +/* Utility for handling references. */ +static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} + +/* Add item to array/object. */ +void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} +void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} +void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} +void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} + +cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; + if (c->prev) c->prev->next=c->next;if (c->next) c->next->prev=c->prev;if (c==array->child) array->child=c->next;c->prev=c->next=0;return c;} +void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} +cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} +void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} + +/* Replace array/object items with new ones. */ +void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; + newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; + if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} +void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} + +/* Create basic types: */ +cJSON *cJSON_CreateNull() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} +cJSON *cJSON_CreateTrue() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} +cJSON *cJSON_CreateFalse() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} +cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} +cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} +cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} +cJSON *cJSON_CreateArray() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} +cJSON *cJSON_CreateObject() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} + +/* Create Arrays: */ +cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateFloatArray(float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateDoubleArray(double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateString(strings[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;} diff --git a/plugins/omelasticsearch/cJSON/cjson.h b/plugins/omelasticsearch/cJSON/cjson.h new file mode 100644 index 00000000..a621720c --- /dev/null +++ b/plugins/omelasticsearch/cJSON/cjson.h @@ -0,0 +1,130 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* cJSON Types: */ +#define cJSON_False 0 +#define cJSON_True 1 +#define cJSON_NULL 2 +#define cJSON_Number 3 +#define cJSON_String 4 +#define cJSON_Array 5 +#define cJSON_Object 6 + +#define cJSON_IsReference 256 + +/* The cJSON structure: */ +typedef struct cJSON { + struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int type; /* The type of the item, as above. */ + + char *valuestring; /* The item's string, if type==cJSON_String */ + int valueint; /* The item's number, if type==cJSON_Number */ + double valuedouble; /* The item's number, if type==cJSON_Number */ + + char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ +} cJSON; + +typedef struct cJSON_Hooks { + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} cJSON_Hooks; + +/* Supply malloc, realloc and free functions to cJSON */ +extern void cJSON_InitHooks(cJSON_Hooks* hooks); + + +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ +extern cJSON *cJSON_Parse(const char *value); +/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ +extern char *cJSON_Print(cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ +extern char *cJSON_PrintUnformatted(cJSON *item); +/* Delete a cJSON entity and all subentities. */ +extern void cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +extern int cJSON_GetArraySize(cJSON *array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ +extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); +/* Get item "string" from object. Case insensitive. */ +extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); + +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +extern const char *cJSON_GetErrorPtr(); + +/* These calls create a cJSON item of the appropriate type. */ +extern cJSON *cJSON_CreateNull(); +extern cJSON *cJSON_CreateTrue(); +extern cJSON *cJSON_CreateFalse(); +extern cJSON *cJSON_CreateBool(int b); +extern cJSON *cJSON_CreateNumber(double num); +extern cJSON *cJSON_CreateString(const char *string); +extern cJSON *cJSON_CreateArray(); +extern cJSON *cJSON_CreateObject(); + +/* These utilities create an Array of count items. */ +extern cJSON *cJSON_CreateIntArray(int *numbers,int count); +extern cJSON *cJSON_CreateFloatArray(float *numbers,int count); +extern cJSON *cJSON_CreateDoubleArray(double *numbers,int count); +extern cJSON *cJSON_CreateStringArray(const char **strings,int count); + +/* Append item to the specified array/object. */ +extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); +extern void cJSON_DeleteItemFromArray(cJSON *array,int which); +extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); +extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); + +/* Update array items. */ +extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); +extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); + +/* rger: added helpers */ + +char *cJSON_print_number(cJSON *item); +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/omelasticsearch/cJSON/test.c b/plugins/omelasticsearch/cJSON/test.c new file mode 100644 index 00000000..2cab632a --- /dev/null +++ b/plugins/omelasticsearch/cJSON/test.c @@ -0,0 +1,156 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include "cJSON.h" + +/* Parse text to JSON, then render back to text, and print! */ +void doit(char *text) +{ + char *out;cJSON *json; + + json=cJSON_Parse(text); + if (!json) {printf("Error before: [%s]\n",cJSON_GetErrorPtr());} + else + { + out=cJSON_Print(json); + cJSON_Delete(json); + printf("%s\n",out); + free(out); + } +} + +/* Read a file, parse, render back, etc. */ +void dofile(char *filename) +{ + FILE *f=fopen(filename,"rb");fseek(f,0,SEEK_END);long len=ftell(f);fseek(f,0,SEEK_SET); + char *data=malloc(len+1);fread(data,1,len,f);fclose(f); + doit(data); + free(data); +} + +/* Used by some code below as an example datatype. */ +struct record {const char *precision;double lat,lon;const char *address,*city,*state,*zip,*country; }; + +/* Create a bunch of objects as demonstration. */ +void create_objects() +{ + cJSON *root,*fmt,*img,*thm,*fld;char *out;int i; /* declare a few. */ + + /* Here we construct some JSON standards, from the JSON site. */ + + /* Our "Video" datatype: */ + root=cJSON_CreateObject(); + cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble")); + cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject()); + cJSON_AddStringToObject(fmt,"type", "rect"); + cJSON_AddNumberToObject(fmt,"width", 1920); + cJSON_AddNumberToObject(fmt,"height", 1080); + cJSON_AddFalseToObject (fmt,"interlace"); + cJSON_AddNumberToObject(fmt,"frame rate", 24); + + out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); /* Print to text, Delete the cJSON, print it, release the string. + + /* Our "days of the week" array: */ + const char *strings[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; + root=cJSON_CreateStringArray(strings,7); + + out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); + + /* Our matrix: */ + int numbers[3][3]={{0,-1,0},{1,0,0},{0,0,1}}; + root=cJSON_CreateArray(); + for (i=0;i<3;i++) cJSON_AddItemToArray(root,cJSON_CreateIntArray(numbers[i],3)); + +/* cJSON_ReplaceItemInArray(root,1,cJSON_CreateString("Replacement")); */ + + out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); + + + /* Our "gallery" item: */ + int ids[4]={116,943,234,38793}; + root=cJSON_CreateObject(); + cJSON_AddItemToObject(root, "Image", img=cJSON_CreateObject()); + cJSON_AddNumberToObject(img,"Width",800); + cJSON_AddNumberToObject(img,"Height",600); + cJSON_AddStringToObject(img,"Title","View from 15th Floor"); + cJSON_AddItemToObject(img, "Thumbnail", thm=cJSON_CreateObject()); + cJSON_AddStringToObject(thm, "Url", "http:/*www.example.com/image/481989943"); + cJSON_AddNumberToObject(thm,"Height",125); + cJSON_AddStringToObject(thm,"Width","100"); + cJSON_AddItemToObject(img,"IDs", cJSON_CreateIntArray(ids,4)); + + out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); + + /* Our array of "records": */ + struct record fields[2]={ + {"zip",37.7668,-1.223959e+2,"","SAN FRANCISCO","CA","94107","US"}, + {"zip",37.371991,-1.22026e+2,"","SUNNYVALE","CA","94085","US"}}; + + root=cJSON_CreateArray(); + for (i=0;i<2;i++) + { + cJSON_AddItemToArray(root,fld=cJSON_CreateObject()); + cJSON_AddStringToObject(fld, "precision", fields[i].precision); + cJSON_AddNumberToObject(fld, "Latitude", fields[i].lat); + cJSON_AddNumberToObject(fld, "Longitude", fields[i].lon); + cJSON_AddStringToObject(fld, "Address", fields[i].address); + cJSON_AddStringToObject(fld, "City", fields[i].city); + cJSON_AddStringToObject(fld, "State", fields[i].state); + cJSON_AddStringToObject(fld, "Zip", fields[i].zip); + cJSON_AddStringToObject(fld, "Country", fields[i].country); + } + +/* cJSON_ReplaceItemInObject(cJSON_GetArrayItem(root,1),"City",cJSON_CreateIntArray(ids,4)); */ + + out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); + +} + +int main (int argc, const char * argv[]) { + /* a bunch of json: */ + char text1[]="{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\": \"rect\", \n\"width\": 1920, \n\"height\": 1080, \n\"interlace\": false,\"frame rate\": 24\n}\n}"; + char text2[]="[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]"; + char text3[]="[\n [0, -1, 0],\n [1, 0, 0],\n [0, 0, 1]\n ]\n"; + char text4[]="{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http:/*www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": \"100\"\n },\n \"IDs\": [116, 943, 234, 38793]\n }\n }"; + char text5[]="[\n {\n \"precision\": \"zip\",\n \"Latitude\": 37.7668,\n \"Longitude\": -122.3959,\n \"Address\": \"\",\n \"City\": \"SAN FRANCISCO\",\n \"State\": \"CA\",\n \"Zip\": \"94107\",\n \"Country\": \"US\"\n },\n {\n \"precision\": \"zip\",\n \"Latitude\": 37.371991,\n \"Longitude\": -122.026020,\n \"Address\": \"\",\n \"City\": \"SUNNYVALE\",\n \"State\": \"CA\",\n \"Zip\": \"94085\",\n \"Country\": \"US\"\n }\n ]"; + + /* Process each json textblock by parsing, then rebuilding: */ + doit(text1); + doit(text2); + doit(text3); + doit(text4); + doit(text5); + + /* Parse standard testfiles: +/* dofile("../../tests/test1"); */ +/* dofile("../../tests/test2"); */ +/* dofile("../../tests/test3"); */ +/* dofile("../../tests/test4"); */ +/* dofile("../../tests/test5"); */ + + /* Now some samplecode for building objects concisely: */ + create_objects(); + + return 0; +} diff --git a/plugins/omelasticsearch/cJSON/tests/test1 b/plugins/omelasticsearch/cJSON/tests/test1 new file mode 100644 index 00000000..eacfbf5e --- /dev/null +++ b/plugins/omelasticsearch/cJSON/tests/test1 @@ -0,0 +1,22 @@ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} diff --git a/plugins/omelasticsearch/cJSON/tests/test2 b/plugins/omelasticsearch/cJSON/tests/test2 new file mode 100644 index 00000000..5600991a --- /dev/null +++ b/plugins/omelasticsearch/cJSON/tests/test2 @@ -0,0 +1,11 @@ +{"menu": { + "id": "file", + "value": "File", + "popup": { + "menuitem": [ + {"value": "New", "onclick": "CreateNewDoc()"}, + {"value": "Open", "onclick": "OpenDoc()"}, + {"value": "Close", "onclick": "CloseDoc()"} + ] + } +}} diff --git a/plugins/omelasticsearch/cJSON/tests/test3 b/plugins/omelasticsearch/cJSON/tests/test3 new file mode 100644 index 00000000..5662b377 --- /dev/null +++ b/plugins/omelasticsearch/cJSON/tests/test3 @@ -0,0 +1,26 @@ +{"widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } +}}
\ No newline at end of file diff --git a/plugins/omelasticsearch/cJSON/tests/test4 b/plugins/omelasticsearch/cJSON/tests/test4 new file mode 100644 index 00000000..d540b57f --- /dev/null +++ b/plugins/omelasticsearch/cJSON/tests/test4 @@ -0,0 +1,88 @@ +{"web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500}}, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2"}}, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet"}, + + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet"}, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true}}], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*"}, + + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
\ No newline at end of file diff --git a/plugins/omelasticsearch/cJSON/tests/test5 b/plugins/omelasticsearch/cJSON/tests/test5 new file mode 100644 index 00000000..49980ca2 --- /dev/null +++ b/plugins/omelasticsearch/cJSON/tests/test5 @@ -0,0 +1,27 @@ +{"menu": { + "header": "SVG Viewer", + "items": [ + {"id": "Open"}, + {"id": "OpenNew", "label": "Open New"}, + null, + {"id": "ZoomIn", "label": "Zoom In"}, + {"id": "ZoomOut", "label": "Zoom Out"}, + {"id": "OriginalView", "label": "Original View"}, + null, + {"id": "Quality"}, + {"id": "Pause"}, + {"id": "Mute"}, + null, + {"id": "Find", "label": "Find..."}, + {"id": "FindAgain", "label": "Find Again"}, + {"id": "Copy"}, + {"id": "CopyAgain", "label": "Copy Again"}, + {"id": "CopySVG", "label": "Copy SVG"}, + {"id": "ViewSVG", "label": "View SVG"}, + {"id": "ViewSource", "label": "View Source"}, + {"id": "SaveAs", "label": "Save As"}, + null, + {"id": "Help"}, + {"id": "About", "label": "About Adobe CVG Viewer..."} + ] +}} diff --git a/plugins/omelasticsearch/omelasticsearch.c b/plugins/omelasticsearch/omelasticsearch.c new file mode 100644 index 00000000..33e58c1a --- /dev/null +++ b/plugins/omelasticsearch/omelasticsearch.c @@ -0,0 +1,997 @@ +/* omelasticsearch.c + * This is the http://www.elasticsearch.org/ output module. + * + * NOTE: read comments in module-template.h for more specifics! + * + * Copyright 2011 Nathan Scott. + * Copyright 2009-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <curl/curl.h> +#include <curl/easy.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "cJSON/cjson.h" +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "statsobj.h" +#include "cfsysline.h" +#include "unicode-helper.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omelasticsearch") + +/* internal structures */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(statsobj) + +statsobj_t *indexStats; +STATSCOUNTER_DEF(indexConFail, mutIndexConFail) +STATSCOUNTER_DEF(indexSubmit, mutIndexSubmit) +STATSCOUNTER_DEF(indexFailed, mutIndexFailed) +STATSCOUNTER_DEF(indexSuccess, mutIndexSuccess) + +/* REST API for elasticsearch hits this URL: + * http://<hostName>:<restPort>/<searchIndex>/<searchType> + */ +typedef struct curl_slist HEADER; +typedef struct _instanceData { + int port; + int replyLen; + int fdErrFile; /* error file fd or -1 if not open */ + uchar *server; + uchar *uid; + uchar *pwd; + uchar *searchIndex; + uchar *searchType; + uchar *parent; + uchar *tplName; + uchar *timeout; + uchar *bulkId; + uchar *restURL; /* last used URL for error reporting */ + uchar *errorFile; + char *reply; + sbool dynSrchIdx; + sbool dynSrchType; + sbool dynParent; + sbool dynBulkId; + sbool bulkmode; + sbool asyncRepl; + struct { + es_str_t *data; + uchar *currTpl1; + uchar *currTpl2; + } batch; + CURL *curlHandle; /* libcurl session handle */ + HEADER *postHeader; /* json POST request info */ +} instanceData; + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "server", eCmdHdlrGetWord, 0 }, + { "serverport", eCmdHdlrInt, 0 }, + { "uid", eCmdHdlrGetWord, 0 }, + { "pwd", eCmdHdlrGetWord, 0 }, + { "searchindex", eCmdHdlrGetWord, 0 }, + { "searchtype", eCmdHdlrGetWord, 0 }, + { "parent", eCmdHdlrGetWord, 0 }, + { "dynsearchindex", eCmdHdlrBinary, 0 }, + { "dynsearchtype", eCmdHdlrBinary, 0 }, + { "dynparent", eCmdHdlrBinary, 0 }, + { "bulkmode", eCmdHdlrBinary, 0 }, + { "asyncrepl", eCmdHdlrBinary, 0 }, + { "timeout", eCmdHdlrGetWord, 0 }, + { "errorfile", eCmdHdlrGetWord, 0 }, + { "template", eCmdHdlrGetWord, 1 }, + { "dynbulkid", eCmdHdlrBinary, 0 }, + { "bulkid", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINcreateInstance +CODESTARTcreateInstance + pData->restURL = NULL; + pData->fdErrFile = -1; +ENDcreateInstance + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + +BEGINfreeInstance +CODESTARTfreeInstance + if (pData->postHeader) { + curl_slist_free_all(pData->postHeader); + pData->postHeader = NULL; + } + if (pData->curlHandle) { + curl_easy_cleanup(pData->curlHandle); + pData->curlHandle = NULL; + } + if(pData->fdErrFile != -1) + close(pData->fdErrFile); + free(pData->server); + free(pData->uid); + free(pData->pwd); + free(pData->searchIndex); + free(pData->searchType); + free(pData->parent); + free(pData->tplName); + free(pData->timeout); + free(pData->restURL); + free(pData->errorFile); + free(pData->bulkId); +ENDfreeInstance + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("omelasticsearch\n"); + dbgprintf("\ttemplate='%s'\n", pData->tplName); + dbgprintf("\tserver='%s'\n", pData->server); + dbgprintf("\tserverport=%d\n", pData->port); + dbgprintf("\tuid='%s'\n", pData->uid == NULL ? (uchar*)"(not configured)" : pData->uid); + dbgprintf("\tpwd=(%sconfigured)\n", pData->pwd == NULL ? "not " : ""); + dbgprintf("\tsearch index='%s'\n", pData->searchIndex); + dbgprintf("\tsearch index='%s'\n", pData->searchType); + dbgprintf("\tparent='%s'\n", pData->parent); + dbgprintf("\ttimeout='%s'\n", pData->timeout); + dbgprintf("\tdynamic search index=%d\n", pData->dynSrchIdx); + dbgprintf("\tdynamic search type=%d\n", pData->dynSrchType); + dbgprintf("\tdynamic parent=%d\n", pData->dynParent); + dbgprintf("\tasync replication=%d\n", pData->asyncRepl); + dbgprintf("\tbulkmode=%d\n", pData->bulkmode); + dbgprintf("\terrorfile='%s'\n", pData->errorFile == NULL ? + (uchar*)"(not configured)" : pData->errorFile); + dbgprintf("\tdynbulkid=%d\n", pData->dynBulkId); + dbgprintf("\tbulkid='%s'\n", pData->bulkId); +ENDdbgPrintInstInfo + + +/* Build basic URL part, which includes hostname and port as follows: + * http://hostname:port/ + * Newly creates an estr for this purpose. + */ +static rsRetVal +setBaseURL(instanceData *pData, es_str_t **url) +{ + char portBuf[64]; + int r; + DEFiRet; + + *url = es_newStr(128); + snprintf(portBuf, sizeof(portBuf), "%d", pData->port); + r = es_addBuf(url, "http://", sizeof("http://")-1); + if(r == 0) r = es_addBuf(url, (char*)pData->server, strlen((char*)pData->server)); + if(r == 0) r = es_addChar(url, ':'); + if(r == 0) r = es_addBuf(url, portBuf, strlen(portBuf)); + if(r == 0) r = es_addChar(url, '/'); + RETiRet; +} + + +static inline rsRetVal +checkConn(instanceData *pData) +{ + es_str_t *url; + CURL *curl = NULL; + CURLcode res; + char *cstr; + DEFiRet; + + setBaseURL(pData, &url); + curl = curl_easy_init(); + if(curl == NULL) { + DBGPRINTF("omelasticsearch: checkConn() curl_easy_init() failed\n"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + cstr = es_str2cstr(url, NULL); + curl_easy_setopt(curl, CURLOPT_URL, cstr); + free(cstr); + + pData->reply = NULL; + pData->replyLen = 0; + curl_easy_setopt(curl, CURLOPT_WRITEDATA, pData); + res = curl_easy_perform(curl); + if(res != CURLE_OK) { + DBGPRINTF("omelasticsearch: checkConn() curl_easy_perform() " + "failed: %s\n", curl_easy_strerror(res)); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + free(pData->reply); + DBGPRINTF("omelasticsearch: checkConn() completed with success\n"); + +finalize_it: + if(curl != NULL) + curl_easy_cleanup(curl); + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + DBGPRINTF("omelasticsearch: tryResume called\n"); + iRet = checkConn(pData); +ENDtryResume + + +/* get the current index and type for this message */ +static inline void +getIndexTypeAndParent(instanceData *pData, uchar **tpls, + uchar **srchIndex, uchar **srchType, uchar **parent, + uchar **bulkId) +{ + if(pData->dynSrchIdx) { + *srchIndex = tpls[1]; + if(pData->dynSrchType) { + *srchType = tpls[2]; + if(pData->dynParent) { + *parent = tpls[3]; + if(pData->dynBulkId) { + *bulkId = tpls[4]; + } + } else { + *parent = pData->parent; + if(pData->dynBulkId) { + *bulkId = tpls[3]; + } + } + } else { + *srchType = pData->searchType; + if(pData->dynParent) { + *parent = tpls[2]; + if(pData->dynBulkId) { + *bulkId = tpls[3]; + } + } else { + *parent = pData->parent; + if(pData->dynBulkId) { + *bulkId = tpls[2]; + } + } + } + } else { + *srchIndex = pData->searchIndex; + if(pData->dynSrchType) { + *srchType = tpls[1]; + if(pData->dynParent) { + *parent = tpls[2]; + if(pData->dynBulkId) { + *bulkId = tpls[3]; + } + } else { + *parent = pData->parent; + if(pData->dynBulkId) { + *bulkId = tpls[2]; + } + } + } else { + *srchType = pData->searchType; + if(pData->dynParent) { + *parent = tpls[1]; + if(pData->dynBulkId) { + *bulkId = tpls[2]; + } + } else { + *parent = pData->parent; + if(pData->dynBulkId) { + *bulkId = tpls[1]; + } + } + } + } +} + + +static rsRetVal +setCurlURL(instanceData *pData, uchar **tpls) +{ + char authBuf[1024]; + uchar *searchIndex; + uchar *searchType; + uchar *parent; + uchar *bulkId; + es_str_t *url; + int rLocal; + int r; + DEFiRet; + + setBaseURL(pData, &url); + + if(pData->bulkmode) { + r = es_addBuf(&url, "_bulk", sizeof("_bulk")-1); + parent = NULL; + } else { + getIndexTypeAndParent(pData, tpls, &searchIndex, &searchType, &parent, &bulkId); + r = es_addBuf(&url, (char*)searchIndex, ustrlen(searchIndex)); + if(r == 0) r = es_addChar(&url, '/'); + if(r == 0) r = es_addBuf(&url, (char*)searchType, ustrlen(searchType)); + } + if(r == 0) r = es_addChar(&url, '?'); + if(pData->asyncRepl) { + if(r == 0) r = es_addBuf(&url, "replication=async&", + sizeof("replication=async&")-1); + } + if(pData->timeout != NULL) { + if(r == 0) r = es_addBuf(&url, "timeout=", sizeof("timeout=")-1); + if(r == 0) r = es_addBuf(&url, (char*)pData->timeout, ustrlen(pData->timeout)); + if(r == 0) r = es_addChar(&url, '&'); + } + if(parent != NULL) { + if(r == 0) r = es_addBuf(&url, "parent=", sizeof("parent=")-1); + if(r == 0) r = es_addBuf(&url, (char*)parent, ustrlen(parent)); + } + + free(pData->restURL); + pData->restURL = (uchar*)es_str2cstr(url, NULL); + curl_easy_setopt(pData->curlHandle, CURLOPT_URL, pData->restURL); + es_deleteStr(url); + DBGPRINTF("omelasticsearch: using REST URL: '%s'\n", pData->restURL); + + if(pData->uid != NULL) { + rLocal = snprintf(authBuf, sizeof(authBuf), "%s:%s", pData->uid, + (pData->pwd == NULL) ? "" : (char*)pData->pwd); + if(rLocal < 1) { + errmsg.LogError(0, RS_RET_ERR, "omelasticsearch: snprintf failed " + "when trying to build auth string (return %d)\n", + rLocal); + ABORT_FINALIZE(RS_RET_ERR); + } + curl_easy_setopt(pData->curlHandle, CURLOPT_USERPWD, authBuf); + curl_easy_setopt(pData->curlHandle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + } +finalize_it: + RETiRet; +} + + +/* this method does not directly submit but builds a batch instead. It + * may submit, if we have dynamic index/type and the current type or + * index changes. + */ +static rsRetVal +buildBatch(instanceData *pData, uchar *message, uchar **tpls) +{ + int length = strlen((char *)message); + int r; + uchar *searchIndex; + uchar *searchType; + uchar *parent; + uchar *bulkId = NULL; + DEFiRet; +# define META_STRT "{\"index\":{\"_index\": \"" +# define META_TYPE "\",\"_type\":\"" +# define META_PARENT "\",\"_parent\":\"" +# define META_ID "\", \"_id\":\"" +# define META_END "\"}}\n" + + getIndexTypeAndParent(pData, tpls, &searchIndex, &searchType, &parent, &bulkId); + r = es_addBuf(&pData->batch.data, META_STRT, sizeof(META_STRT)-1); + if(r == 0) r = es_addBuf(&pData->batch.data, (char*)searchIndex, + ustrlen(searchIndex)); + if(r == 0) r = es_addBuf(&pData->batch.data, META_TYPE, sizeof(META_TYPE)-1); + if(r == 0) r = es_addBuf(&pData->batch.data, (char*)searchType, + ustrlen(searchType)); + if(parent != NULL) { + if(r == 0) r = es_addBuf(&pData->batch.data, META_PARENT, sizeof(META_PARENT)-1); + if(r == 0) r = es_addBuf(&pData->batch.data, (char*)parent, ustrlen(parent)); + } + if(bulkId != NULL) { + if(r == 0) r = es_addBuf(&pData->batch.data, META_ID, sizeof(META_ID)-1); + if(r == 0) r = es_addBuf(&pData->batch.data, (char*)bulkId, ustrlen(bulkId)); + } + if(r == 0) r = es_addBuf(&pData->batch.data, META_END, sizeof(META_END)-1); + if(r == 0) r = es_addBuf(&pData->batch.data, (char*)message, length); + if(r == 0) r = es_addBuf(&pData->batch.data, "\n", sizeof("\n")-1); + if(r != 0) { + DBGPRINTF("omelasticsearch: growing batch failed with code %d\n", r); + ABORT_FINALIZE(RS_RET_ERR); + } + iRet = RS_RET_DEFER_COMMIT; + +finalize_it: + RETiRet; +} + + +/* write data error request/replies to separate error file + * Note: we open the file but never close it before exit. If it + * needs to be closed, HUP must be sent. + */ +static inline rsRetVal +writeDataError(instanceData *pData, cJSON **pReplyRoot, uchar *reqmsg) +{ + char *rendered = NULL; + cJSON *errRoot; + cJSON *req; + cJSON *replyRoot = *pReplyRoot; + size_t toWrite; + ssize_t wrRet; + char errStr[1024]; + DEFiRet; + + if(pData->errorFile == NULL) { + DBGPRINTF("omelasticsearch: no local error logger defined - " + "ignoring ES error information\n"); + FINALIZE; + } + + if(pData->fdErrFile == -1) { + pData->fdErrFile = open((char*)pData->errorFile, + O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE|O_CLOEXEC, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); + if(pData->fdErrFile == -1) { + rs_strerror_r(errno, errStr, sizeof(errStr)); + DBGPRINTF("omelasticsearch: error opening error file: %s\n", errStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } + if((req=cJSON_CreateObject()) == NULL) ABORT_FINALIZE(RS_RET_ERR); + cJSON_AddItemToObject(req, "url", cJSON_CreateString((char*)pData->restURL)); + cJSON_AddItemToObject(req, "postdata", cJSON_CreateString((char*)reqmsg)); + + if((errRoot=cJSON_CreateObject()) == NULL) ABORT_FINALIZE(RS_RET_ERR); + cJSON_AddItemToObject(errRoot, "request", req); + cJSON_AddItemToObject(errRoot, "reply", replyRoot); + rendered = cJSON_Print(errRoot); + /* we do not do real error-handling on the err file, as this finally complicates + * things way to much. + */ + DBGPRINTF("omelasticsearch: error record: '%s'\n", rendered); + toWrite = strlen(rendered); + wrRet = write(pData->fdErrFile, rendered, toWrite); + if(wrRet != (ssize_t) toWrite) { + DBGPRINTF("omelasticsearch: error %d writing error file, write returns %lld\n", + errno, (long long) wrRet); + } + free(rendered); + cJSON_Delete(errRoot); + *pReplyRoot = NULL; /* tell caller not to delete once again! */ + +finalize_it: + if(rendered != NULL) + free(rendered); + RETiRet; +} + + +static inline rsRetVal +checkResultBulkmode(instanceData *pData, cJSON *root) +{ + int i; + int numitems; + cJSON *items; + cJSON *item; + cJSON *create; + cJSON *ok; + DEFiRet; + + items = cJSON_GetObjectItem(root, "items"); + if(items == NULL || items->type != cJSON_Array) { + DBGPRINTF("omelasticsearch: error in elasticsearch reply: " + "bulkmode insert does not return array, reply is: %s\n", + pData->reply); + ABORT_FINALIZE(RS_RET_DATAFAIL); + } + numitems = cJSON_GetArraySize(items); +DBGPRINTF("omelasticsearch: %d items in reply\n", numitems); + for(i = 0 ; i < numitems ; ++i) { + item = cJSON_GetArrayItem(items, i); + if(item == NULL) { + DBGPRINTF("omelasticsearch: error in elasticsearch reply: " + "cannot obtain reply array item %d\n", i); + ABORT_FINALIZE(RS_RET_DATAFAIL); + } + create = cJSON_GetObjectItem(item, "create"); + if(create == NULL || create->type != cJSON_Object) { + DBGPRINTF("omelasticsearch: error in elasticsearch reply: " + "cannot obtain 'create' item for #%d\n", i); + ABORT_FINALIZE(RS_RET_DATAFAIL); + } + ok = cJSON_GetObjectItem(create, "ok"); + if(ok == NULL || ok->type != cJSON_True) { + DBGPRINTF("omelasticsearch: error in elasticsearch reply: " + "item %d, prop ok (%p) not ok\n", i, ok); + ABORT_FINALIZE(RS_RET_DATAFAIL); + } + } + +finalize_it: + RETiRet; +} + + +static inline rsRetVal +checkResult(instanceData *pData, uchar *reqmsg) +{ + cJSON *root; + cJSON *ok; + DEFiRet; + + root = cJSON_Parse(pData->reply); + if(root == NULL) { + DBGPRINTF("omelasticsearch: could not parse JSON result \n"); + ABORT_FINALIZE(RS_RET_ERR); + } + + if(pData->bulkmode) { + iRet = checkResultBulkmode(pData, root); + } else { + ok = cJSON_GetObjectItem(root, "ok"); + if(ok == NULL || ok->type != cJSON_True) { + iRet = RS_RET_DATAFAIL; + } + } + + /* Note: we ignore errors writing the error file, as we cannot handle + * these in any case. + */ + if(iRet == RS_RET_DATAFAIL) { + writeDataError(pData, &root, reqmsg); + iRet = RS_RET_OK; /* we have handled the problem! */ + } + +finalize_it: + if(root != NULL) + cJSON_Delete(root); + RETiRet; +} + + +static rsRetVal +curlPost(instanceData *pData, uchar *message, int msglen, uchar **tpls) +{ + CURLcode code; + CURL *curl = pData->curlHandle; + DEFiRet; + + pData->reply = NULL; + pData->replyLen = 0; + + if(pData->dynSrchIdx || pData->dynSrchType || pData->dynParent) + CHKiRet(setCurlURL(pData, tpls)); + + curl_easy_setopt(curl, CURLOPT_WRITEDATA, pData); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *)message); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, msglen); + code = curl_easy_perform(curl); + switch (code) { + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_CONNECT: + case CURLE_WRITE_ERROR: + STATSCOUNTER_INC(indexConFail, mutIndexConFail); + DBGPRINTF("omelasticsearch: we are suspending ourselfs due " + "to failure %lld of curl_easy_perform()\n", + (long long) code); + ABORT_FINALIZE(RS_RET_SUSPENDED); + default: + STATSCOUNTER_INC(indexSubmit, mutIndexSubmit); + break; + } + + pData->reply[pData->replyLen] = '\0'; /* byte has been reserved in malloc */ + DBGPRINTF("omelasticsearch: es reply: '%s'\n", pData->reply); + + CHKiRet(checkResult(pData, message)); +finalize_it: + free(pData->reply); + RETiRet; +} + +BEGINbeginTransaction +CODESTARTbeginTransaction +dbgprintf("omelasticsearch: beginTransaction\n"); + if(!pData->bulkmode) { + FINALIZE; + } + + es_emptyStr(pData->batch.data); +finalize_it: +ENDbeginTransaction + + +BEGINdoAction +CODESTARTdoAction + if(pData->bulkmode) { + CHKiRet(buildBatch(pData, ppString[0], ppString)); + } else { + CHKiRet(curlPost(pData, ppString[0], strlen((char*)ppString[0]), + ppString)); + } +finalize_it: +dbgprintf("omelasticsearch: result doAction: %d (bulkmode %d)\n", iRet, pData->bulkmode); +ENDdoAction + + +BEGINendTransaction + char *cstr; +CODESTARTendTransaction +dbgprintf("omelasticsearch: endTransaction init\n"); + cstr = es_str2cstr(pData->batch.data, NULL); + dbgprintf("omelasticsearch: endTransaction, batch: '%s'\n", cstr); + CHKiRet(curlPost(pData, (uchar*) cstr, strlen(cstr), NULL)); +finalize_it: + free(cstr); +dbgprintf("omelasticsearch: endTransaction done with %d\n", iRet); +ENDendTransaction + +/* elasticsearch POST result string ... useful for debugging */ +size_t +curlResult(void *ptr, size_t size, size_t nmemb, void *userdata) +{ + char *p = (char *)ptr; + instanceData *pData = (instanceData*) userdata; + char *buf; + size_t newlen; + + newlen = pData->replyLen + size*nmemb; + if((buf = realloc(pData->reply, newlen + 1)) == NULL) { + DBGPRINTF("omelasticsearch: realloc failed in curlResult\n"); + return 0; /* abort due to failure */ + } + memcpy(buf+pData->replyLen, p, size*nmemb); + pData->replyLen = newlen; + pData->reply = buf; + return size*nmemb; +} + + +static rsRetVal +curlSetup(instanceData *pData) +{ + HEADER *header; + CURL *handle; + + handle = curl_easy_init(); + if (handle == NULL) { + return RS_RET_OBJ_CREATION_FAILED; + } + + header = curl_slist_append(NULL, "Content-Type: text/json; charset=utf-8"); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlResult); + curl_easy_setopt(handle, CURLOPT_POST, 1); + + pData->curlHandle = handle; + pData->postHeader = header; + + if( pData->bulkmode + || (pData->dynSrchIdx == 0 && pData->dynSrchType == 0 && pData->dynParent == 0)) { + /* in this case, we know no tpls are involved in the request-->NULL OK! */ + setCurlURL(pData, NULL); + } + + if(Debug) { + if(pData->dynSrchIdx == 0 && pData->dynSrchType == 0 && pData->dynParent == 0) + dbgprintf("omelasticsearch setup, using static REST URL\n"); + else + dbgprintf("omelasticsearch setup, we have a dynamic REST URL\n"); + } + return RS_RET_OK; +} + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->server = NULL; + pData->port = 9200; + pData->uid = NULL; + pData->pwd = NULL; + pData->searchIndex = NULL; + pData->searchType = NULL; + pData->parent = NULL; + pData->timeout = NULL; + pData->dynSrchIdx = 0; + pData->dynSrchType = 0; + pData->dynParent = 0; + pData->asyncRepl = 0; + pData->bulkmode = 0; + pData->tplName = NULL; + pData->errorFile = NULL; + pData->dynBulkId= 0; + pData->bulkId = NULL; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; + int iNumTpls; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "server")) { + pData->server = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "errorfile")) { + pData->errorFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "serverport")) { + pData->port = (int) pvals[i].val.d.n, NULL; + } else if(!strcmp(actpblk.descr[i].name, "uid")) { + pData->uid = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "pwd")) { + pData->pwd = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "searchindex")) { + pData->searchIndex = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "searchtype")) { + pData->searchType = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "parent")) { + pData->parent = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "dynsearchindex")) { + pData->dynSrchIdx = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "dynsearchtype")) { + pData->dynSrchType = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "dynparent")) { + pData->dynParent = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "bulkmode")) { + pData->bulkmode = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "timeout")) { + pData->timeout = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "asyncrepl")) { + pData->asyncRepl = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "dynbulkid")) { + pData->dynBulkId = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "bulkid")) { + pData->bulkId = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omelasticsearch: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->pwd != NULL && pData->uid == NULL) { + errmsg.LogError(0, RS_RET_UID_MISSING, + "omelasticsearch: password is provided, but no uid " + "- action definition invalid"); + ABORT_FINALIZE(RS_RET_UID_MISSING); + } + if(pData->dynSrchIdx && pData->searchIndex == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, + "omelasticsearch: requested dynamic search index, but no " + "name for index template given - action definition invalid"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if(pData->dynSrchType && pData->searchType == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, + "omelasticsearch: requested dynamic search type, but no " + "name for type template given - action definition invalid"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if(pData->dynParent && pData->parent == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, + "omelasticsearch: requested dynamic parent, but no " + "name for parent template given - action definition invalid"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if(pData->dynBulkId && pData->bulkId == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, + "omelasticsearch: requested dynamic bulkid, but no " + "name for bulkid template given - action definition invalid"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + + if(pData->bulkmode) { + pData->batch.currTpl1 = NULL; + pData->batch.currTpl2 = NULL; + if((pData->batch.data = es_newStr(1024)) == NULL) { + DBGPRINTF("omelasticsearch: error creating batch string " + "turned off bulk mode\n"); + pData->bulkmode = 0; /* at least it works */ + } + } + + iNumTpls = 1; + if(pData->dynSrchIdx) ++iNumTpls; + if(pData->dynSrchType) ++iNumTpls; + if(pData->dynParent) ++iNumTpls; + if(pData->dynBulkId) ++iNumTpls; + DBGPRINTF("omelasticsearch: requesting %d templates\n", iNumTpls); + CODE_STD_STRING_REQUESTnewActInst(iNumTpls) + + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*)strdup((pData->tplName == NULL) ? + " StdJSONFmt" : (char*)pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + + + /* we need to request additional templates. If we have a dynamic search index, + * it will always be string 1. Type may be 1 or 2, depending on whether search + * index is dynamic as well. Rule needs to be followed throughout the module. + */ + if(pData->dynSrchIdx) { + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->searchIndex), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynSrchType) { + CHKiRet(OMSRsetEntry(*ppOMSR, 2, ustrdup(pData->searchType), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynParent) { + CHKiRet(OMSRsetEntry(*ppOMSR, 3, ustrdup(pData->parent), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 4, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } else { + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 3, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } + } else { + if(pData->dynParent) { + CHKiRet(OMSRsetEntry(*ppOMSR, 2, ustrdup(pData->parent), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 3, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } else { + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 2, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } + } + } else { + if(pData->dynSrchType) { + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->searchType), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynParent) { + CHKiRet(OMSRsetEntry(*ppOMSR, 2, ustrdup(pData->parent), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 3, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } else { + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 2, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } + } else { + if(pData->dynParent) { + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->parent), + OMSR_NO_RQD_TPL_OPTS)); + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 2, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } else { + if(pData->dynBulkId) { + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->bulkId), + OMSR_NO_RQD_TPL_OPTS)); + } + } + } + } + + if(pData->server == NULL) + pData->server = (uchar*) strdup("localhost"); + if(pData->searchIndex == NULL) + pData->searchIndex = (uchar*) strdup("system"); + if(pData->searchType == NULL) + pData->searchType = (uchar*) strdup("events"); + + CHKiRet(curlSetup(pData)); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omelasticsearch:", sizeof(":omelasticsearch:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "omelasticsearch supports only v6 config format, use: " + "action(type=\"omelasticsearch\" server=...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + +BEGINdoHUP +CODESTARTdoHUP + if(pData->fdErrFile != -1) { + close(pData->fdErrFile); + pData->fdErrFile = -1; + } +ENDdoHUP + + +BEGINmodExit +CODESTARTmodExit + curl_global_cleanup(); + statsobj.Destruct(&indexStats); + objRelease(errmsg, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); +ENDmodExit + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_doHUP +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + + if (curl_global_init(CURL_GLOBAL_ALL) != 0) { + errmsg.LogError(0, RS_RET_OBJ_CREATION_FAILED, "CURL fail. -elasticsearch indexing disabled"); + ABORT_FINALIZE(RS_RET_OBJ_CREATION_FAILED); + } + + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&indexStats)); + CHKiRet(statsobj.SetName(indexStats, (uchar *)"elasticsearch")); + CHKiRet(statsobj.AddCounter(indexStats, (uchar *)"connfail", + ctrType_IntCtr, &indexConFail)); + CHKiRet(statsobj.AddCounter(indexStats, (uchar *)"submits", + ctrType_IntCtr, &indexSubmit)); + CHKiRet(statsobj.AddCounter(indexStats, (uchar *)"failed", + ctrType_IntCtr, &indexFailed)); + CHKiRet(statsobj.AddCounter(indexStats, (uchar *)"success", + ctrType_IntCtr, &indexSuccess)); + CHKiRet(statsobj.ConstructFinalize(indexStats)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/omgssapi/Makefile.am b/plugins/omgssapi/Makefile.am new file mode 100644 index 00000000..a57a64b3 --- /dev/null +++ b/plugins/omgssapi/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omgssapi.la + +omgssapi_la_SOURCES = omgssapi.c +omgssapi_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omgssapi_la_LDFLAGS = -module -avoid-version +omgssapi_la_LIBADD = $(GSS_LIBS) diff --git a/plugins/omgssapi/omgssapi.c b/plugins/omgssapi/omgssapi.c new file mode 100644 index 00000000..818a7cfd --- /dev/null +++ b/plugins/omgssapi/omgssapi.c @@ -0,0 +1,711 @@ +/* omgssapi.c + * This is the implementation of the build-in forwarding output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#ifdef USE_GSSAPI +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fnmatch.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#ifdef USE_NETZIP +#include <zlib.h> +#endif +#include <pthread.h> +#include <gssapi/gssapi.h> +#include "dirty.h" +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "net.h" +#include "template.h" +#include "msg.h" +#include "cfsysline.h" +#include "module-template.h" +#include "gss-misc.h" +#include "tcpclt.h" +#include "glbl.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omgssapi") + + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(gssutil) +DEFobjCurrIf(tcpclt) + +typedef struct _instanceData { + char *f_hname; + short sock; /* file descriptor */ + enum { /* TODO: we shoud revisit these definitions */ + eDestFORW, + eDestFORW_SUSP, + eDestFORW_UNKN + } eDestState; + struct addrinfo *f_addr; + int compressionLevel; /* 0 - no compression, else level for zlib */ + char *port; + tcpclt_t *pTCPClt; /* our tcpclt object */ + gss_ctx_id_t gss_context; + OM_uint32 gss_flags; +} instanceData; + +/* config data */ + +typedef enum gss_mode_e { + GSSMODE_MIC, + GSSMODE_ENC +} gss_mode_t; + +static struct configSettings_s { + uchar *pszTplName; /* name of the default template to use */ + char *gss_base_service_name; + gss_mode_t gss_mode; +} cs; + + +/* get the syslog forward port from selector_t. The passed in + * struct must be one that is setup for forwarding. + * rgerhards, 2007-06-28 + * We may change the implementation to try to lookup the port + * if it is unspecified. So far, we use the IANA default auf 514. + */ +static char *getFwdSyslogPt(instanceData *pData) +{ + assert(pData != NULL); + if(pData->port == NULL) + return("514"); + else + return(pData->port); +} + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +OM_uint32 maj_stat, min_stat; +CODESTARTfreeInstance + switch (pData->eDestState) { + case eDestFORW: + case eDestFORW_SUSP: + freeaddrinfo(pData->f_addr); + /* fall through */ + case eDestFORW_UNKN: + if(pData->port != NULL) + free(pData->port); + break; + } + + if (pData->gss_context != GSS_C_NO_CONTEXT) { + maj_stat = gss_delete_sec_context(&min_stat, &pData->gss_context, GSS_C_NO_BUFFER); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("deleting context", maj_stat, min_stat); + } + /* this is meant to be done when module is unloaded, + but since this module is static... + */ + free(cs.gss_base_service_name); + cs.gss_base_service_name = NULL; + + /* final cleanup */ + tcpclt.Destruct(&pData->pTCPClt); + if(pData->sock >= 0) + close(pData->sock); + + if(pData->f_hname != NULL) + free(pData->f_hname); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("%s", pData->f_hname); +ENDdbgPrintInstInfo + + +/* This function is called immediately before a send retry is attempted. + * It shall clean up whatever makes sense. + * rgerhards, 2007-12-28 + */ +static rsRetVal TCPSendGSSPrepRetry(void __attribute__((unused)) *pData) +{ + /* in case of TCP/GSS, there is nothing to do */ + return RS_RET_OK; +} + + +static rsRetVal TCPSendGSSInit(void *pvData) +{ + DEFiRet; + int s = -1; + char *base; + OM_uint32 maj_stat, min_stat, init_sec_min_stat, *sess_flags, ret_flags; + gss_buffer_desc out_tok, in_tok; + gss_buffer_t tok_ptr; + gss_name_t target_name; + gss_ctx_id_t *context; + instanceData *pData = (instanceData *) pvData; + + assert(pData != NULL); + + /* if the socket is already initialized, we are done */ + if(pData->sock > 0) + ABORT_FINALIZE(RS_RET_OK); + + base = (cs.gss_base_service_name == NULL) ? "host" : cs.gss_base_service_name; + out_tok.length = strlen(pData->f_hname) + strlen(base) + 2; + CHKmalloc(out_tok.value = MALLOC(out_tok.length)); + strcpy(out_tok.value, base); + strcat(out_tok.value, "@"); + strcat(out_tok.value, pData->f_hname); + dbgprintf("GSS-API service name: %s\n", (char*) out_tok.value); + + tok_ptr = GSS_C_NO_BUFFER; + context = &pData->gss_context; + *context = GSS_C_NO_CONTEXT; + + maj_stat = gss_import_name(&min_stat, &out_tok, GSS_C_NT_HOSTBASED_SERVICE, &target_name); + free(out_tok.value); + out_tok.value = NULL; + out_tok.length = 0; + + if (maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("parsing name", maj_stat, min_stat); + goto fail; + } + + sess_flags = &pData->gss_flags; + *sess_flags = GSS_C_MUTUAL_FLAG; + if (cs.gss_mode == GSSMODE_MIC) { + *sess_flags |= GSS_C_INTEG_FLAG; + } + if (cs.gss_mode == GSSMODE_ENC) { + *sess_flags |= GSS_C_CONF_FLAG; + } + dbgprintf("GSS-API requested context flags:\n"); + gssutil.display_ctx_flags(*sess_flags); + + do { + maj_stat = gss_init_sec_context(&init_sec_min_stat, GSS_C_NO_CREDENTIAL, context, + target_name, GSS_C_NO_OID, *sess_flags, 0, NULL, + tok_ptr, NULL, &out_tok, &ret_flags, NULL); + if (tok_ptr != GSS_C_NO_BUFFER) + free(in_tok.value); + + if (maj_stat != GSS_S_COMPLETE + && maj_stat != GSS_S_CONTINUE_NEEDED) { + gssutil.display_status("initializing context", maj_stat, init_sec_min_stat); + goto fail; + } + + if (s == -1) + if ((s = pData->sock = tcpclt.CreateSocket(pData->f_addr)) == -1) + goto fail; + + if (out_tok.length != 0) { + dbgprintf("GSS-API Sending init_sec_context token (length: %ld)\n", (long) out_tok.length); + if (gssutil.send_token(s, &out_tok) < 0) { + goto fail; + } + } + gss_release_buffer(&min_stat, &out_tok); + + if (maj_stat == GSS_S_CONTINUE_NEEDED) { + dbgprintf("GSS-API Continue needed...\n"); + if (gssutil.recv_token(s, &in_tok) <= 0) { + goto fail; + } + tok_ptr = &in_tok; + } + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + dbgprintf("GSS-API Provided context flags:\n"); + *sess_flags = ret_flags; + gssutil.display_ctx_flags(*sess_flags); + + dbgprintf("GSS-API Context initialized\n"); + gss_release_name(&min_stat, &target_name); + +finalize_it: + RETiRet; + + fail: + errmsg.LogError(0, RS_RET_GSS_SENDINIT_ERROR, "GSS-API Context initialization failed\n"); + gss_release_name(&min_stat, &target_name); + gss_release_buffer(&min_stat, &out_tok); + if (*context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + *context = GSS_C_NO_CONTEXT; + } + if (s != -1) + close(s); + pData->sock = -1; + ABORT_FINALIZE(RS_RET_GSS_SENDINIT_ERROR); +} + + +static rsRetVal TCPSendGSSSend(void *pvData, char *msg, size_t len) +{ + int s; + gss_ctx_id_t *context; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc in_buf, out_buf; + instanceData *pData = (instanceData *) pvData; + + assert(pData != NULL); + assert(msg != NULL); + assert(len > 0); + + s = pData->sock; + context = &pData->gss_context; + in_buf.value = msg; + in_buf.length = len; + maj_stat = gss_wrap(&min_stat, *context, (cs.gss_mode == GSSMODE_ENC) ? 1 : 0, GSS_C_QOP_DEFAULT, + &in_buf, NULL, &out_buf); + if (maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("wrapping message", maj_stat, min_stat); + goto fail; + } + + if (gssutil.send_token(s, &out_buf) < 0) { + goto fail; + } + gss_release_buffer(&min_stat, &out_buf); + + return RS_RET_OK; + + fail: + close(s); + pData->sock = -1; + gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + *context = GSS_C_NO_CONTEXT; + gss_release_buffer(&min_stat, &out_buf); + dbgprintf("message not (GSS/tcp)send"); + return RS_RET_GSS_SEND_ERROR; +} + + +/* try to resume connection if it is not ready + * rgerhards, 2007-08-02 + */ +static rsRetVal doTryResume(instanceData *pData) +{ + DEFiRet; + struct addrinfo *res; + struct addrinfo hints; + unsigned e; + + switch (pData->eDestState) { + case eDestFORW_SUSP: + iRet = RS_RET_OK; /* the actual check happens during doAction() only */ + pData->eDestState = eDestFORW; + break; + + case eDestFORW_UNKN: + /* The remote address is not yet known and needs to be obtained */ + dbgprintf(" %s\n", pData->f_hname); + memset(&hints, 0, sizeof(hints)); + /* port must be numeric, because config file syntax requests this */ + /* TODO: this code is a duplicate from cfline() - we should later create + * a common function. + */ + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_STREAM; + if((e = getaddrinfo(pData->f_hname, + getFwdSyslogPt(pData), &hints, &res)) == 0) { + dbgprintf("%s found, resuming.\n", pData->f_hname); + pData->f_addr = res; + pData->eDestState = eDestFORW; + } else { + iRet = RS_RET_SUSPENDED; + } + break; + case eDestFORW: + /* NOOP */ + break; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doTryResume(pData); +ENDtryResume + +BEGINdoAction + char *psz = NULL; /* temporary buffering */ + register unsigned l; + int iMaxLine; +CODESTARTdoAction + switch (pData->eDestState) { + case eDestFORW_SUSP: + dbgprintf("internal error in omgssapi.c, eDestFORW_SUSP in doAction()!\n"); + iRet = RS_RET_SUSPENDED; + break; + + case eDestFORW_UNKN: + dbgprintf("doAction eDestFORW_UNKN\n"); + iRet = doTryResume(pData); + break; + + case eDestFORW: + dbgprintf(" %s:%s/%s\n", pData->f_hname, getFwdSyslogPt(pData), "tcp-gssapi"); + iMaxLine = glbl.GetMaxLine(); + psz = (char*) ppString[0]; + l = strlen((char*) psz); + if((int) l > iMaxLine) + l = iMaxLine; + +# ifdef USE_NETZIP + /* Check if we should compress and, if so, do it. We also + * check if the message is large enough to justify compression. + * The smaller the message, the less likely is a gain in compression. + * To save CPU cycles, we do not try to compress very small messages. + * What "very small" means needs to be configured. Currently, it is + * hard-coded but this may be changed to a config parameter. + * rgerhards, 2006-11-30 + */ + if(pData->compressionLevel && (l > CONF_MIN_SIZE_FOR_COMPRESS)) { + Bytef *out; + uLongf destLen = sizeof(out) / sizeof(Bytef); + uLong srcLen = l; + int ret; + /* TODO: optimize malloc sequence? -- rgerhards, 2008-09-02 */ + CHKmalloc(out = (Bytef*) MALLOC(iMaxLine + iMaxLine/100 + 12)); + out[0] = 'z'; + out[1] = '\0'; + ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz, + srcLen, pData->compressionLevel); + dbgprintf("Compressing message, length was %d now %d, return state %d.\n", + l, (int) destLen, ret); + if(ret != Z_OK) { + /* if we fail, we complain, but only in debug mode + * Otherwise, we are silent. In any case, we ignore the + * failed compression and just sent the uncompressed + * data, which is still valid. So this is probably the + * best course of action. + * rgerhards, 2006-11-30 + */ + dbgprintf("Compression failed, sending uncompressed message\n"); + free(out); + } else if(destLen+1 < l) { + /* only use compression if there is a gain in using it! */ + dbgprintf("there is gain in compression, so we do it\n"); + psz = (char*) out; + l = destLen + 1; /* take care for the "z" at message start! */ + } else { + free(out); + } + ++destLen; + } +# endif + + CHKiRet_Hdlr(tcpclt.Send(pData->pTCPClt, pData, psz, l)) { + /* error! */ + dbgprintf("error forwarding via tcp, suspending\n"); + pData->eDestState = eDestFORW_SUSP; + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + break; + } +finalize_it: +# ifdef USE_NETZIP + if((psz != NULL) && (psz != (char*) ppString[0])) { + /* we need to free temporary buffer, alloced above - Naoya Nakazawa, 2010-01-11 */ + free(psz); + } +# endif +ENDdoAction + + +BEGINparseSelectorAct + uchar *q; + int i; + int error; + int bErr; + struct addrinfo hints, *res; + TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us + * The first test [*p == '>'] can be skipped if a module shall only + * support the newer slection syntax [:modname:]. This is in fact + * recommended for new modules. Please note that over time this part + * will be handled by rsyslogd itself, but for the time being it is + * a good compromise to do it at the module level. + * rgerhards, 2007-10-15 + */ + + if(!strncmp((char*) p, ":omgssapi:", sizeof(":omgssapi:") - 1)) { + p += sizeof(":omgssapi:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + + /* we are now after the protocol indicator. Now check if we should + * use compression. We begin to use a new option format for this: + * @(option,option)host:port + * The first option defined is "z[0..9]" where the digit indicates + * the compression level. If it is not given, 9 (best compression) is + * assumed. An example action statement might be: + * @@(z5,o)127.0.0.1:1400 + * Which means send via TCP with medium (5) compresion (z) to the local + * host on port 1400. The '0' option means that octet-couting (as in + * IETF I-D syslog-transport-tls) is to be used for framing (this option + * applies to TCP-based syslog only and is ignored when specified with UDP). + * That is not yet implemented. + * rgerhards, 2006-12-07 + */ + if(*p == '(') { + /* at this position, it *must* be an option indicator */ + do { + ++p; /* eat '(' or ',' (depending on when called) */ + /* check options */ + if(*p == 'z') { /* compression */ +# ifdef USE_NETZIP + ++p; /* eat */ + if(isdigit((int) *p)) { + int iLevel; + iLevel = *p - '0'; + ++p; /* eat */ + pData->compressionLevel = iLevel; + } else { + errmsg.LogError(0, NO_ERRCODE, "Invalid compression level '%c' specified in " + "forwardig action - NOT turning on compression.", + *p); + } +# else + errmsg.LogError(0, NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " + "with compression support - request ignored."); +# endif /* #ifdef USE_NETZIP */ + } else if(*p == 'o') { /* octet-couting based TCP framing? */ + ++p; /* eat */ + /* no further options settable */ + tcp_framing = TCP_FRAMING_OCTET_COUNTING; + } else { /* invalid option! Just skip it... */ + errmsg.LogError(0, NO_ERRCODE, "Invalid option %c in forwarding action - ignoring.", *p); + ++p; /* eat invalid option */ + } + /* the option processing is done. We now do a generic skip + * to either the next option or the end of the option + * block. + */ + while(*p && *p != ')' && *p != ',') + ++p; /* just skip it */ + } while(*p && *p == ','); /* Attention: do.. while() */ + if(*p == ')') + ++p; /* eat terminator, on to next */ + else + /* we probably have end of string - leave it for the rest + * of the code to handle it (but warn the user) + */ + errmsg.LogError(0, NO_ERRCODE, "Option block not terminated in gssapi forward action."); + } + /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0') + * now skip to port and then template name. rgerhards 2005-07-06 + */ + for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p) + /* JUST SKIP */; + + pData->port = NULL; + if(*p == ':') { /* process port */ + uchar * tmp; + + *p = '\0'; /* trick to obtain hostname (later)! */ + tmp = ++p; + for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i) + /* SKIP AND COUNT */; + pData->port = MALLOC(i + 1); + if(pData->port == NULL) { + errmsg.LogError(0, NO_ERRCODE, "Could not get memory to store syslog forwarding port, " + "using default port, results may not be what you intend\n"); + /* we leave f_forw.port set to NULL, this is then handled by + * getFwdSyslogPt(). + */ + } else { + memcpy(pData->port, tmp, i); + *(pData->port + i) = '\0'; + } + } + + + /* now skip to template */ + bErr = 0; + while(*p && *p != ';') { + if(*p && *p != ';' && !isspace((int) *p)) { + if(bErr == 0) { /* only 1 error msg! */ + bErr = 1; + errno = 0; + errmsg.LogError(0, NO_ERRCODE, "invalid selector line (port), probably not doing " + "what was intended"); + } + } + ++p; + } + + /* TODO: make this if go away! */ + if(*p == ';' || *p == '#' || isspace(*p)) { + uchar cTmp = *p; + *p = '\0'; /* trick to obtain hostname (later)! */ + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = cTmp; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (cs.pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : cs.pszTplName)); + + /* first set the pData->eDestState */ + memset(&hints, 0, sizeof(hints)); + /* port must be numeric, because config file syntax requests this */ + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_STREAM; + if( (error = getaddrinfo(pData->f_hname, getFwdSyslogPt(pData), &hints, &res)) != 0) { + pData->eDestState = eDestFORW_UNKN; + } else { + pData->eDestState = eDestFORW; + pData->f_addr = res; + } + + /* now create our tcpclt */ + CHKiRet(tcpclt.Construct(&pData->pTCPClt)); + /* and set callbacks */ + CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendGSSInit)); + CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendGSSSend)); + CHKiRet(tcpclt.SetSendPrepRetry(pData->pTCPClt, TCPSendGSSPrepRetry)); + CHKiRet(tcpclt.SetFraming(pData->pTCPClt, tcp_framing)); + + /* TODO: do we need to call freeInstance if we failed - this is a general question for + * all output modules. I'll address it lates as the interface evolves. rgerhards, 2007-07-25 + */ +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(gssutil, LM_GSSUTIL_FILENAME); + objRelease(tcpclt, LM_TCPCLT_FILENAME); + + if(cs.pszTplName != NULL) { + free(cs.pszTplName); + cs.pszTplName = NULL; + } +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +/* set a new GSSMODE based on config directive */ +static rsRetVal setGSSMode(void __attribute__((unused)) *pVal, uchar *mode) +{ + DEFiRet; + + if (!strcmp((char *) mode, "integrity")) { + cs.gss_mode = GSSMODE_MIC; + dbgprintf("GSS-API gssmode set to GSSMODE_MIC\n"); + } else if (!strcmp((char *) mode, "encryption")) { + cs.gss_mode = GSSMODE_ENC; + dbgprintf("GSS-API gssmode set to GSSMODE_ENC\n"); + } else { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "unknown gssmode parameter: %s", (char *) mode); + iRet = RS_RET_INVALID_PARAMS; + } + free(mode); + + RETiRet; +} + + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.gss_mode = GSSMODE_ENC; + free(cs.gss_base_service_name); + cs.gss_base_service_name = NULL; + free(cs.pszTplName); + cs.pszTplName = NULL; + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(gssutil, LM_GSSUTIL_FILENAME)); + CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"gssforwardservicename", 0, eCmdHdlrGetWord, NULL, &cs.gss_base_service_name, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"gssmode", 0, eCmdHdlrGetWord, setGSSMode, &cs.gss_mode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actiongssforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &cs.pszTplName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +#endif /* #ifdef USE_GSSAPI */ +/* vi:set ai: + */ diff --git a/plugins/omhdfs/Makefile.am b/plugins/omhdfs/Makefile.am new file mode 100644 index 00000000..95e6b102 --- /dev/null +++ b/plugins/omhdfs/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omhdfs.la + +omhdfs_la_SOURCES = omhdfs.c +omhdfs_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(JAVA_INCLUDES) +omhdfs_la_LDFLAGS = -module -avoid-version -lhdfs $(JAVA_LIBS) +omhdfs_la_LIBADD = $(RSRT_LIBS) diff --git a/plugins/omhdfs/javaenv.sh b/plugins/omhdfs/javaenv.sh new file mode 100644 index 00000000..d07a8473 --- /dev/null +++ b/plugins/omhdfs/javaenv.sh @@ -0,0 +1,14 @@ +# This is a sample file for environment settings on Fedora 13 +# that made me compile & run omhdfs. I really *hate* the way +# java uses environment variables... Hopefully this file will +# help building and testing omhdfs in the future (there is also +# some more information in the rsyslog wiki). +# rgerhards, 2011-03-11 +# this now works, but don't ask my why ;) +#export JAVA_HOME=/usr/java/jdk1.6.0_21/bin/java +export PATH=/usr/java/jdk1.6.0_21/bin:$PATH +export JAVA_INCLUDES="-I/usr/java/jdk1.6.0_21/include -I/usr/java/jdk1.6.0_21/include/linux" +export JAVA_LIBS="-L/usr/java/jdk1.6.0_21/jre/lib/i386 -ljava -ljvm -lverify" +export HADOOP_HOME=/usr/lib/hadoop +export CLASSPATH=/usr/lib/jvm/java-6-sun/lib:/usr/lib/hadoop/lib:/usr/lib/hadoop/hadoop-ant-0.20.2+320.jar:/usr/lib/hadoop/hadoop-core-0.20.2+320.jar:/usr/lib/hadoop/hadoop-examples-0.20.2+320.jar:/usr/lib/hadoop/hadoop-test-0.20.2+320.jar:/usr/lib/hadoop/hadoop-tools-0.20.2+320.jar/usr/lib/hadoop/lib/commons-cli-1.2.jar:/usr/lib/hadoop/lib/commons-codec-1.3.jar:/usr/lib/hadoop/lib/commons-el-1.0.jar:/usr/lib/hadoop/lib/commons-httpclient-3.0.1.jar:/usr/lib/hadoop/lib/commons-logging-1.0.4.jar:/usr/lib/hadoop/lib/commons-logging-api-1.0.4.jar:/usr/lib/hadoop/lib/commons-net-1.4.1.jar:/usr/lib/hadoop/lib/core-3.1.1.jar:/usr/lib/hadoop/lib/hadoop-fairscheduler-0.20.2+320.jar:/usr/lib/hadoop/lib/hadoop-scribe-log4j-0.20.2+320.jar:/usr/lib/hadoop/lib/hsqldb-1.8.0.10.jar:/usr/lib/hadoop/lib/hsqldb.jar:/usr/lib/hadoop/lib/jackson-core-asl-1.0.1.jar:/usr/lib/hadoop/lib/jackson-mapper-asl-1.0.1.jar:/usr/lib/hadoop/lib/jasper-compiler-5.5.12.jar:/usr/lib/hadoop/lib/jasper-runtime-5.5.12.jar:/usr/lib/hadoop/lib/jets3t-0.6.1.jar:/usr/lib/hadoop/lib/jetty-6.1.14.jar:/usr/lib/hadoop/lib/jetty-util-6.1.14.jar:/usr/lib/hadoop/lib/junit-4.5.jar:/usr/lib/hadoop/lib/kfs-0.2.2.jar:/usr/lib/hadoop/lib/libfb303.jar:/usr/lib/hadoop/lib/libthrift.jar:/usr/lib/hadoop/lib/log4j-1.2.15.jar:/usr/lib/hadoop/lib/mockito-all-1.8.2.jar:/usr/lib/hadoop/lib/mysql-connector-java-5.0.8-bin.jar:/usr/lib/hadoop/lib/oro-2.0.8.jar:/usr/lib/hadoop/lib/servlet-api-2.5-6.1.14.jar:/usr/lib/hadoop/lib/slf4j-api-1.4.3.jar:/usr/lib/hadoop/lib/slf4j-log4j12-1.4.3.jar:/usr/lib/hadoop/lib/xmlenc-0.52.jar:/etc/hadoop/conf +###export CLASSPATH="/usr/lib/hadoop/hadoop-0.20.2+320-ant.jar: /usr/lib/hadoop/hadoop-0.20.2+320-core.jar: /usr/lib/hadoop/hadoop-0.20.2+320-examples.jar: /usr/lib/hadoop/hadoop-0.20.2+320-test.jar: /usr/lib/hadoop/hadoop-0.20.2+320-tools.jar: /usr/lib/hadoop/hadoop-ant-0.20.2+320.jar: /usr/lib/hadoop/hadoop-core-0.20.2+320.jar: /usr/lib/hadoop/hadoop-examples-0.20.2+320.jar: /usr/lib/hadoop/hadoop-test-0.20.2+320.jar: /usr/lib/hadoop/hadoop-tools-0.20.2+320.jar:/usr/lib/hadoop/lib: /usr/lib/hadoop/lib/commons-cli-1.2.jar: /usr/lib/hadoop/lib/commons-codec-1.3.jar: /usr/lib/hadoop/lib/commons-el-1.0.jar: /usr/lib/hadoop/lib/commons-httpclient-3.0.1.jar: /usr/lib/hadoop/lib/commons-logging-1.0.4.jar: /usr/lib/hadoop/lib/commons-logging-api-1.0.4.jar: /usr/lib/hadoop/lib/commons-net-1.4.1.jar: /usr/lib/hadoop/lib/core-3.1.1.jar: /usr/lib/hadoop/lib/hadoop-fairscheduler-0.20.2+320.jar: /usr/lib/hadoop/lib/hadoop-scribe-log4j-0.20.2+320.jar: /usr/lib/hadoop/lib/hsqldb-1.8.0.10.jar: /usr/lib/hadoop/lib/hsqldb.jar: /usr/lib/hadoop/lib/jackson-core-asl-1.0.1.jar: /usr/lib/hadoop/lib/jackson-mapper-asl-1.0.1.jar: /usr/lib/hadoop/lib/jasper-compiler-5.5.12.jar: /usr/lib/hadoop/lib/jasper-runtime-5.5.12.jar: /usr/lib/hadoop/lib/jets3t-0.6.1.jar: /usr/lib/hadoop/lib/jetty-6.1.14.jar: /usr/lib/hadoop/lib/jetty-util-6.1.14.jar: /usr/lib/hadoop/lib/junit-4.5.jar: /usr/lib/hadoop/lib/kfs-0.2.2.jar: /usr/lib/hadoop/lib/libfb303.jar: /usr/lib/hadoop/lib/libthrift.jar: /usr/lib/hadoop/lib/log4j-1.2.15.jar: /usr/lib/hadoop/lib/mockito-all-1.8.2.jar: /usr/lib/hadoop/lib/mysql-connector-java-5.0.8-bin.jar: /usr/lib/hadoop/lib/oro-2.0.8.jar: /usr/lib/hadoop/lib/servlet-api-2.5-6.1.14.jar: /usr/lib/hadoop/lib/slf4j-api-1.4.3.jar: /usr/lib/hadoop/lib/slf4j-log4j12-1.4.3.jar: /usr/lib/hadoop/lib/xmlenc-0.52.jar:/etc/hadoop/conf:/usr/lib/hadoop/lib" diff --git a/plugins/omhdfs/omhdfs.c b/plugins/omhdfs/omhdfs.c new file mode 100644 index 00000000..f8a7e739 --- /dev/null +++ b/plugins/omhdfs/omhdfs.c @@ -0,0 +1,549 @@ +/* omhdfs.c + * This is an output module to support Hadoop's HDFS. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * 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. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/file.h> +#include <pthread.h> +#include <hdfs.h> + +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "conf.h" +#include "cfsysline.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "errmsg.h" +#include "hashtable.h" +#include "hashtable_itr.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omhdfs") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* global data */ +static struct hashtable *files; /* holds all file objects that we know */ + +typedef struct configSettings_s { + uchar *fileName; + uchar *hdfsHost; + uchar *dfltTplName; /* default template name to use */ + int hdfsPort; +} configSettings_t; +static configSettings_t cs; + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars +ENDinitConfVars + +typedef struct { + uchar *name; + hdfsFS fs; + hdfsFile fh; + const char *hdfsHost; + tPort hdfsPort; + int nUsers; + pthread_mutex_t mut; +} file_t; + + +typedef struct _instanceData { + file_t *pFile; + uchar ioBuf[64*1024]; + unsigned offsBuf; +} instanceData; + +/* forward definitions (down here, need data types) */ +static inline rsRetVal fileClose(file_t *pFile); + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("omhdfs: file:%s", pData->pFile->name); +ENDdbgPrintInstInfo + + +/* note that hdfsFileExists() does not work, so we did our + * own function to see if a pathname exists. Returns 0 if the + * file does not exists, something else otherwise. Note that + * we can also check a directroy (if that matters...) + */ +static int +HDFSFileExists(hdfsFS fs, uchar *name) +{ + int r; + hdfsFileInfo *info; + + info = hdfsGetPathInfo(fs, (char*)name); + /* if things go wrong, we assume it is because the file + * does not exist. We do not get too much information... + */ + if(info == NULL) { + r = 0; + } else { + r = 1; + hdfsFreeFileInfo(info, 1); + } + return r; +} + +static inline rsRetVal +HDFSmkdir(hdfsFS fs, uchar *name) +{ + DEFiRet; + if(hdfsCreateDirectory(fs, (char*)name) == -1) + ABORT_FINALIZE(RS_RET_ERR); + +finalize_it: + RETiRet; +} + + +/* ---BEGIN FILE OBJECT---------------------------------------------------- */ +/* This code handles the "file object". This is split from the actual + * instance data, because several instances may write into the same file. + * If so, we need to use a single object, and also synchronize their writes. + * So we keep the file object separately, and just stick a reference into + * the instance data. + */ + +static inline rsRetVal +fileObjConstruct(file_t **ppFile) +{ + file_t *pFile; + DEFiRet; + + CHKmalloc(pFile = malloc(sizeof(file_t))); + pFile->name = NULL; + pFile->hdfsHost = NULL; + pFile->fh = NULL; + pFile->nUsers = 0; + + *ppFile = pFile; +finalize_it: + RETiRet; +} + +static inline void +fileObjAddUser(file_t *pFile) +{ + /* init mutex only when second user is added */ + ++pFile->nUsers; + if(pFile->nUsers == 2) + pthread_mutex_init(&pFile->mut, NULL); + DBGPRINTF("omhdfs: file %s now being used by %d actions\n", pFile->name, pFile->nUsers); +} + +static rsRetVal +fileObjDestruct(file_t **ppFile) +{ + file_t *pFile = *ppFile; + if(pFile->nUsers > 1) + pthread_mutex_destroy(&pFile->mut); + fileClose(pFile); + free(pFile->name); + free((char*)pFile->hdfsHost); + free(pFile->fh); + + return RS_RET_OK; +} + + +/* check, and potentially create, all names inside a path */ +static rsRetVal +filePrepare(file_t *pFile) +{ + uchar *p; + uchar *pszWork; + size_t len; + DEFiRet; + + if(HDFSFileExists(pFile->fs, pFile->name)) + FINALIZE; + + /* file does not exist, create it (and eventually parent directories */ + if(1) { // check if bCreateDirs + len = ustrlen(pFile->name) + 1; + CHKmalloc(pszWork = MALLOC(sizeof(uchar) * len)); + memcpy(pszWork, pFile->name, len); + for(p = pszWork+1 ; *p ; p++) + if(*p == '/') { + /* temporarily terminate string, create dir and go on */ + *p = '\0'; + if(!HDFSFileExists(pFile->fs, pszWork)) { + CHKiRet(HDFSmkdir(pFile->fs, pszWork)); + } + *p = '/'; + } + free(pszWork); + return 0; + } + +finalize_it: + RETiRet; +} + + +/* this function is to be used as destructor for the + * hash table code. + */ +static void +fileObjDestruct4Hashtable(void *ptr) +{ + file_t *pFile = (file_t*) ptr; + fileObjDestruct(&pFile); +} + + +static inline rsRetVal +fileOpen(file_t *pFile) +{ + DEFiRet; + + assert(pFile->fh == NULL); + if(pFile->nUsers > 1) + d_pthread_mutex_lock(&pFile->mut); + + DBGPRINTF("omhdfs: try to connect to HDFS at host '%s', port %d\n", + pFile->hdfsHost, pFile->hdfsPort); + pFile->fs = hdfsConnect(pFile->hdfsHost, pFile->hdfsPort); + if(pFile->fs == NULL) { + DBGPRINTF("omhdfs: error can not connect to hdfs\n"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + + CHKiRet(filePrepare(pFile)); + + pFile->fh = hdfsOpenFile(pFile->fs, (char*)pFile->name, O_WRONLY|O_APPEND, 0, 0, 0); + if(pFile->fh == NULL) { + /* maybe the file does not exist, so we try to create it now. + * Note that we can not use hdfsExists() because of a deficit in + * it: https://issues.apache.org/jira/browse/HDFS-1154 + * As of my testing, libhdfs at least seems to return ENOENT if + * the file does not exist. + */ + if(errno == ENOENT) { + DBGPRINTF("omhdfs: ENOENT trying to append to '%s', now trying create\n", + pFile->name); + pFile->fh = hdfsOpenFile(pFile->fs, + (char*)pFile->name, O_WRONLY|O_CREAT, 0, 0, 0); + } + } + if(pFile->fh == NULL) { + DBGPRINTF("omhdfs: failed to open %s for writing!\n", pFile->name); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + if(pFile->nUsers > 1) + d_pthread_mutex_unlock(&pFile->mut); + RETiRet; +} + + +/* Note: lenWrite is reset to zero on successful write! */ +static inline rsRetVal +fileWrite(file_t *pFile, uchar *buf, size_t *lenWrite) +{ + DEFiRet; + + if(*lenWrite == 0) + FINALIZE; + + if(pFile->nUsers > 1) + d_pthread_mutex_lock(&pFile->mut); + + /* open file if not open. This must be done *here* and while mutex-protected + * because of HUP handling (which is async to normal processing!). + */ + if(pFile->fh == NULL) { + fileOpen(pFile); + if(pFile->fh == NULL) { + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + } + +dbgprintf("XXXXX: omhdfs writing %u bytes\n", *lenWrite); + tSize num_written_bytes = hdfsWrite(pFile->fs, pFile->fh, buf, *lenWrite); + if((unsigned) num_written_bytes != *lenWrite) { + errmsg.LogError(errno, RS_RET_ERR_HDFS_WRITE, + "omhdfs: failed to write %s, expected %lu bytes, " + "written %lu\n", pFile->name, (unsigned long) *lenWrite, + (unsigned long) num_written_bytes); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + *lenWrite = 0; + +finalize_it: + RETiRet; +} + + +static inline rsRetVal +fileClose(file_t *pFile) +{ + DEFiRet; + + if(pFile->fh == NULL) + FINALIZE; + + if(pFile->nUsers > 1) + d_pthread_mutex_lock(&pFile->mut); + + hdfsCloseFile(pFile->fs, pFile->fh); + pFile->fh = NULL; + + if(pFile->nUsers > 1) + d_pthread_mutex_unlock(&pFile->mut); + +finalize_it: + RETiRet; +} + +/* ---END FILE OBJECT---------------------------------------------------- */ + +/* This adds data to the output buffer and performs an actual write + * if the new data does not fit into the buffer. Note that we never write + * partial data records. Other actions may write into the same file, and if + * we would write partial records, data could become severely mixed up. + * Note that we must check of some new data arrived is large than our + * buffer. In that case, the new data will written with its own + * write operation. + */ +static inline rsRetVal +addData(instanceData *pData, uchar *buf) +{ + unsigned len; + DEFiRet; + + len = strlen((char*)buf); + if(pData->offsBuf + len < sizeof(pData->ioBuf)) { + /* new data fits into remaining buffer */ + memcpy((char*) pData->ioBuf + pData->offsBuf, buf, len); + pData->offsBuf += len; + } else { +dbgprintf("XXXXX: not enough room, need to flush\n"); + CHKiRet(fileWrite(pData->pFile, pData->ioBuf, &pData->offsBuf)); + if(len >= sizeof(pData->ioBuf)) { + CHKiRet(fileWrite(pData->pFile, buf, &len)); + } else { + memcpy((char*) pData->ioBuf + pData->offsBuf, buf, len); + pData->offsBuf += len; + } + } + + iRet = RS_RET_DEFER_COMMIT; +finalize_it: + RETiRet; +} + +BEGINcreateInstance +CODESTARTcreateInstance + pData->pFile = NULL; +ENDcreateInstance + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->pFile != NULL) + fileObjDestruct(&pData->pFile); +ENDfreeInstance + + +BEGINtryResume +CODESTARTtryResume + fileClose(pData->pFile); + fileOpen(pData->pFile); + if(pData->pFile->fh == NULL){ + dbgprintf("omhdfs: tried to resume file %s, but still no luck...\n", + pData->pFile->name); + iRet = RS_RET_SUSPENDED; + } +ENDtryResume + + +BEGINbeginTransaction +CODESTARTbeginTransaction +dbgprintf("omhdfs: beginTransaction\n"); +ENDbeginTransaction + + +BEGINdoAction +CODESTARTdoAction + DBGPRINTF("omhdfs: action to to write to %s\n", pData->pFile->name); + iRet = addData(pData, ppString[0]); +dbgprintf("omhdfs: done doAction\n"); +ENDdoAction + + +BEGINendTransaction +CODESTARTendTransaction +dbgprintf("omhdfs: endTransaction\n"); + if(pData->offsBuf != 0) { + DBGPRINTF("omhdfs: data unwritten at end of transaction, persisting...\n"); + iRet = fileWrite(pData->pFile, pData->ioBuf, &pData->offsBuf); + } +ENDendTransaction + + +BEGINparseSelectorAct + file_t *pFile; + int r; + uchar *keybuf; +CODESTARTparseSelectorAct + + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":omhdfs:", sizeof(":omhdfs:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":omhdfs:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + CODE_STD_STRING_REQUESTparseSelectorAct(1) + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, 0, + (cs.dfltTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : cs.dfltTplName)); + + if(cs.fileName == NULL) { + errmsg.LogError(0, RS_RET_ERR_HDFS_OPEN, "omhdfs: no file name specified, can not continue"); + ABORT_FINALIZE(RS_RET_FILE_NOT_SPECIFIED); + } + + pFile = hashtable_search(files, cs.fileName); + if(pFile == NULL) { + /* we need a new file object, this one not seen before */ + CHKiRet(fileObjConstruct(&pFile)); + CHKmalloc(pFile->name = cs.fileName); + CHKmalloc(keybuf = ustrdup(cs.fileName)); + cs.fileName = NULL; /* re-set, data passed to file object */ + CHKmalloc(pFile->hdfsHost = strdup((cs.hdfsHost == NULL) ? "default" : (char*) cs.hdfsHost)); + pFile->hdfsPort = cs.hdfsPort; + fileOpen(pFile); + if(pFile->fh == NULL){ + errmsg.LogError(0, RS_RET_ERR_HDFS_OPEN, "omhdfs: failed to open %s - " + "retrying later", pFile->name); + iRet = RS_RET_SUSPENDED; + } + r = hashtable_insert(files, keybuf, pFile); + if(r == 0) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + fileObjAddUser(pFile); + pData->pFile = pFile; + pData->offsBuf = 0; + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINdoHUP + file_t *pFile; + struct hashtable_itr *itr; +CODESTARTdoHUP + DBGPRINTF("omhdfs: HUP received (file count %d)\n", hashtable_count(files)); + /* Iterator constructor only returns a valid iterator if + * the hashtable is not empty */ + itr = hashtable_iterator(files); + if(hashtable_count(files) > 0) + { + do { + pFile = (file_t *) hashtable_iterator_value(itr); + fileClose(pFile); + DBGPRINTF("omhdfs: HUP, closing file %s\n", pFile->name); + } while (hashtable_iterator_advance(itr)); + } +ENDdoHUP + + +/* Reset config variables for this module to default values. + * rgerhards, 2007-07-17 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.hdfsHost = NULL; + cs.hdfsPort = 0; + free(cs.fileName); + cs.fileName = NULL; + free(cs.dfltTplName); + cs.dfltTplName = NULL; + return RS_RET_OK; +} + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); + if(files != NULL) + hashtable_destroy(files, 1); /* 1 => free all values automatically */ +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +CODEqueryEtryPt_doHUP +ENDqueryEtryPt + + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKmalloc(files = create_hashtable(20, hash_from_string, key_equals_string, + fileObjDestruct4Hashtable)); + + CHKiRet(regCfSysLineHdlr((uchar *)"omhdfsfilename", 0, eCmdHdlrGetWord, NULL, &cs.fileName, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"omhdfshost", 0, eCmdHdlrGetWord, NULL, &cs.hdfsHost, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"omhdfsport", 0, eCmdHdlrInt, NULL, &cs.hdfsPort, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"omhdfsdefaulttemplate", 0, eCmdHdlrGetWord, NULL, &cs.dfltTplName, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + DBGPRINTF("omhdfs: module compiled with rsyslog version %s.\n", VERSION); +CODEmodInit_QueryRegCFSLineHdlr +ENDmodInit diff --git a/plugins/omhiredis/COPYING b/plugins/omhiredis/COPYING new file mode 100644 index 00000000..f44bd493 --- /dev/null +++ b/plugins/omhiredis/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/plugins/omhiredis/COPYING_LESSER b/plugins/omhiredis/COPYING_LESSER new file mode 100644 index 00000000..ddae3e79 --- /dev/null +++ b/plugins/omhiredis/COPYING_LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/plugins/omhiredis/Makefile.am b/plugins/omhiredis/Makefile.am new file mode 100644 index 00000000..2332be4b --- /dev/null +++ b/plugins/omhiredis/Makefile.am @@ -0,0 +1,7 @@ +pkglib_LTLIBRARIES = omhiredis.la +omhiredis_la_SOURCES = omhiredis.c +omhiredis_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(HIREDIS_CFLAGS) +omhiredis_la_LDFLAGS = -module -avoid-version +omhiredis_la_LIBADD = $(HIREDIS_LIBS) + +EXTRA_DIST = diff --git a/plugins/omhiredis/README b/plugins/omhiredis/README new file mode 100644 index 00000000..3b2bf9de --- /dev/null +++ b/plugins/omhiredis/README @@ -0,0 +1,22 @@ +Redis Outplug Plugin using hiredis library + +tested in Centos 6.2 and Archlinux + +BUILDING THIS PLUGIN +Requires the hiredis C client library: https://github.com/redis/hiredis/ + +in your /etc/rsyslog.conf, together with other modules: + +Brian Knox <briank@talksum.com> + +--------------------------------------------------------------------------------------------- +module(load="omhiredis") + +template(name="simple_count" type="string" string="HINCRBY progcount %programname% 1") + +action(name="simple_count_redis" type="omhiredis" queue.type="FixedArray" queue.size="10000" queue.dequeuebatchsize="100" template="simple_count") +--------------------------------------------------------------------------------------------- + +Note: dequeuebatchsize now sets the pipeline size for hiredis, allowing pipelining commands. +Note: this plugin will NOT handle full rsyslog messages properly yet. spaces in a property will + cause the redis command to be constructed improperly. a fix for this is in the works! diff --git a/plugins/omhiredis/omhiredis.c b/plugins/omhiredis/omhiredis.c new file mode 100644 index 00000000..051ac0bf --- /dev/null +++ b/plugins/omhiredis/omhiredis.c @@ -0,0 +1,301 @@ +/* omhiredis.c + * Copyright 2012 Talksum, Inc +* +* This program is free software: you can redistribute it and/or +* modify it under the terms of the GNU Lesser 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this program. If not, see +* <http://www.gnu.org/licenses/>. +* +* Author: Brian Knox +* <briank@talksum.com> +*/ + + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <time.h> +#include <hiredis/hiredis.h> + +#include "rsyslog.h" +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omhiredis") +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* our instance data. + * this will be accessable + * via pData */ +typedef struct _instanceData { + redisContext *conn; /* redis connection */ + uchar *server; /* redis server address */ + int port; /* redis port */ + uchar *tplName; /* template name */ + redisReply **replies; /* array to hold replies from redis */ + int count; /* count of command sent for current batch */ +} instanceData; + + +static struct cnfparamdescr actpdescr[] = { + { "server", eCmdHdlrGetWord, 0 }, + { "serverport", eCmdHdlrInt, 0 }, + { "template", eCmdHdlrGetWord, 1 } +}; +static struct cnfparamblk actpblk = { + CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr +}; + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + +/* called when closing */ +static void closeHiredis(instanceData *pData) +{ + if(pData->conn != NULL) { + redisFree(pData->conn); + pData->conn = NULL; + } +} + +/* Free our instance data. + * TODO: free **replies */ +BEGINfreeInstance +CODESTARTfreeInstance + closeHiredis(pData); + free(pData->server); + free(pData->tplName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* nothing special here */ +ENDdbgPrintInstInfo + +/* establish our connection to redis */ +static rsRetVal initHiredis(instanceData *pData, int bSilent) +{ + char *server; + DEFiRet; + + server = (pData->server == NULL) ? "127.0.0.1" : (char*) pData->server; + DBGPRINTF("omhiredis: trying connect to '%s' at port %d\n", server, pData->port); + + struct timeval timeout = { 1, 500000 }; /* 1.5 seconds */ + pData->conn = redisConnectWithTimeout(server, pData->port, timeout); + if (pData->conn->err) { + if(!bSilent) + errmsg.LogError(0, RS_RET_SUSPENDED, + "can not initialize redis handle"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } +finalize_it: + RETiRet; +} + +rsRetVal writeHiredis(uchar *message, instanceData *pData) +{ + DEFiRet; + + /* if we do not have a redis connection, call + * initHiredis and try to establish one */ + if(pData->conn == NULL) + CHKiRet(initHiredis(pData, 0)); + + /* try to append the command to the pipeline. + * REDIS_ERR reply indicates something bad + * happened, in which case abort. otherwise + * increase our current pipeline count + * by 1 and continue. */ + int rc; + rc = redisAppendCommand(pData->conn, (char*)message); + if (rc == REDIS_ERR) { + errmsg.LogError(0, NO_ERRCODE, "omhiredis: %s", pData->conn->errstr); + dbgprintf("omhiredis: %s\n", pData->conn->errstr); + ABORT_FINALIZE(RS_RET_ERR); + } else { + pData->count++; + } + +finalize_it: + RETiRet; +} + +/* called when resuming from suspended state. + * try to restablish our connection to redis */ +BEGINtryResume +CODESTARTtryResume + if(pData->conn == NULL) + iRet = initHiredis(pData, 0); +ENDtryResume + +/* begin a transaction. for now does nothing. + * if I decide to use MULTI ... EXEC in the + * fture, this block should send the + * MULTI command to redis. */ +BEGINbeginTransaction +CODESTARTbeginTransaction + dbgprintf("omhiredis: beginTransaction called\n"); +ENDbeginTransaction + +/* call writeHiredis for this log line, + * which appends it as a command to the + * current pipeline */ +BEGINdoAction +CODESTARTdoAction + CHKiRet(writeHiredis(ppString[0], pData)); + iRet = RS_RET_DEFER_COMMIT; +finalize_it: +ENDdoAction + +/* called when we have reached the end of a + * batch (queue.dequeuebatchsize). this + * iterates over the replies, putting them + * into the pData->replies buffer. we currently + * don't really bother to check for errors + * which should be fixed */ +BEGINendTransaction +CODESTARTendTransaction + dbgprintf("omhiredis: endTransaction called\n"); + int i; + pData->replies = malloc ( sizeof ( redisReply* ) * pData->count ); + for ( i = 0; i < pData->count; i++ ) { + redisGetReply ( pData->conn, (void *)&pData->replies[i] ); + /* TODO: add error checking here! */ + free ( pData->replies[i] ); + } + free ( pData->replies ); + pData->count = 0; +ENDendTransaction + +/* set defaults. note server is set to NULL + * and is set to a default in initHiredis if + * it is still null when it's called - I should + * probable just set the default here instead */ +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->server = NULL; + pData->port = 6379; + pData->tplName = NULL; + pData->count = 0; +} + +/* here is where the work to set up a new instance + * is done. this reads the config options from + * the rsyslog conf and takes appropriate setup + * actions. */ +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + + if(!strcmp(actpblk.descr[i].name, "server")) { + pData->server = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "serverport")) { + pData->port = (int) pvals[i].val.d.n, NULL; + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omhiredis: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + dbgprintf("omhiredis: action requires a template name"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + /* template string 0 is just a regular string */ + OMSRsetEntry(*ppOMSR, 0,(uchar*)pData->tplName, OMSR_NO_RQD_TPL_OPTS); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + +/* tell the engine we only want one template string */ +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omhiredis:", sizeof(":omhiredis:") - 1)) + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "omhiredis supports only v6 config format, use: " + "action(type=\"omhiredis\" server=...)"); + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + +/* register our plugin entry points + * with the rsyslog core engine */ +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* supports transaction interface */ +ENDqueryEtryPt + +/* note we do not support rsyslog v5 syntax */ +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* only supports rsyslog 6 configs */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); + if (!bCoreSupportsBatching) { + errmsg.LogError(0, NO_ERRCODE, "omhiredis: rsyslog core does not support batching - abort"); + ABORT_FINALIZE(RS_RET_ERR); + } + DBGPRINTF("omhiredis: module compiled with rsyslog version %s.\n", VERSION); +ENDmodInit diff --git a/plugins/omjournal/Makefile.am b/plugins/omjournal/Makefile.am new file mode 100644 index 00000000..4cfbbd96 --- /dev/null +++ b/plugins/omjournal/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omjournal.la + +omjournal_la_SOURCES = omjournal.c +omjournal_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(LIBSYSTEMD_JOURNAL_CFLAGS) +omjournal_la_LDFLAGS = -module -avoid-version +omjournal_la_LIBADD = $(LIBSYSTEMD_JOURNAL_LIBS) + +EXTRA_DIST = diff --git a/plugins/omjournal/omjournal.c b/plugins/omjournal/omjournal.c new file mode 100644 index 00000000..160c369d --- /dev/null +++ b/plugins/omjournal/omjournal.c @@ -0,0 +1,187 @@ +/* omjournal.c + * send messages to the Linux Journal. This is meant to be used + * in cases where journal serves as the whole system log database. + * Note that we may get into a loop if journald re-injects messages + * into the syslog stream and we read that via imuxsock. Thus there + * is an option in imuxsock to ignore messages from ourselves + * (actually from our pid). So there are some module-interdependencies. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include <systemd/sd-journal.h> + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omjournal") + + +DEFobjCurrIf(errmsg); +DEF_OMOD_STATIC_DATA + +/* config variables */ + + +typedef struct _instanceData { +} instanceData; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ +}; +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; +ENDbeginCnfLoad + +BEGINendCnfLoad +CODESTARTendCnfLoad +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +BEGINnewActInst +CODESTARTnewActInst + /* Note: we currently do not have any parameters, so we do not need + * the lst ptr. However, we will most probably need params in the + * future. + */ + (void) lst; /* prevent compiler warning */ + DBGPRINTF("newActInst (mmjournal)\n"); + CODE_STD_STRING_REQUESTnewActInst(1) + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + CHKiRet(createInstance(&pData)); + /*setInstParamDefaults(pData);*/ +CODE_STD_FINALIZERnewActInst +/* cnfparamvalsDestruct(pvals, &actpblk);*/ +ENDnewActInst + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction + msg_t *pMsg; + uchar *tag; + int lenTag; + int sev; + int r; +CODESTARTdoAction + pMsg = (msg_t*) ppString[0]; + MsgGetSeverity(pMsg, &sev); + getTAG(pMsg, &tag, &lenTag); + /* we can use more properties here, but let's see if there + * is some real user interest. We can always add later... + */ + r = sd_journal_send("MESSAGE=%s", getMSG(pMsg), + "PRIORITY=%d", sev, + "SYSLOG_FACILITY=%d", pMsg->iFacility, + "SYSLOG_IDENTIFIER=%s", tag, + NULL); + /* FIXME: think about what to do with errors ;) */ + (void) r; /* prevent compiler warning */ +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(strncmp((char*) p, ":omjournal:", sizeof(":omjournal:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "omjournal supports only v6+ config format, use: " + "action(type=\"omjournal\" ...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +ENDqueryEtryPt + + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + DBGPRINTF("omjournal: module compiled with rsyslog version %s.\n", VERSION); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit diff --git a/plugins/omlibdbi/Makefile.am b/plugins/omlibdbi/Makefile.am new file mode 100644 index 00000000..6a26f807 --- /dev/null +++ b/plugins/omlibdbi/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omlibdbi.la + +omlibdbi_la_SOURCES = omlibdbi.c +omlibdbi_la_CPPFLAGS = -I$(top_srcdir) $(LIBDBI_CFLAGS) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omlibdbi_la_LDFLAGS = -module -avoid-version +omlibdbi_la_LIBADD = $(LIBDBI_LIBS) diff --git a/plugins/omlibdbi/omlibdbi.c b/plugins/omlibdbi/omlibdbi.c new file mode 100644 index 00000000..6e27ad22 --- /dev/null +++ b/plugins/omlibdbi/omlibdbi.c @@ -0,0 +1,596 @@ +/* omlibdbi.c + * This is the implementation of the dbi output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * This depends on libdbi being present with the proper settings. Older + * versions do not necessarily have them. Please visit this bug tracker + * for details: http://bugzilla.adiscon.com/show_bug.cgi?id=31 + * + * File begun on 2008-02-14 by RGerhards (extracted from syslogd.c) + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <time.h> +#include <dbi/dbi.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "cfsysline.h" +#include "conf.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "debug.h" +#include "errmsg.h" +#include "conf.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omlibdbi") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +static int bDbiInitialized = 0; /* dbi_initialize() can only be called one - this keeps track of it */ + +typedef struct _instanceData { + uchar *dbiDrvrDir; /* where do the dbi drivers reside? */ + dbi_conn conn; /* handle to database */ + uchar *drvrName; /* driver to use */ + uchar *host; /* host to connect to */ + uchar *usrName; /* user name for connect */ + uchar *pwd; /* password for connect */ + uchar *dbName; /* database to use */ + unsigned uLastDBErrno; /* last errno returned by libdbi or 0 if all is well */ + uchar *tplName; /* format template to use */ + int txSupport; /* transaction support */ +} instanceData; + +typedef struct configSettings_s { + uchar *dbiDrvrDir; /* global: where do the dbi drivers reside? */ + uchar *drvrName; /* driver to use */ + uchar *host; /* host to connect to */ + uchar *usrName; /* user name for connect */ + uchar *pwd; /* password for connect */ + uchar *dbName; /* database to use */ +} configSettings_t; +static configSettings_t cs; +uchar *pszFileDfltTplName; /* name of the default template to use */ + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + uchar *dbiDrvrDir; /* where do the dbi drivers reside? */ + uchar *tplName; /* default template */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ +static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ + + +/* tables for interfacing with the v6 config system */ +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "template", eCmdHdlrGetWord, 0 }, + { "driverdirectory", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "server", eCmdHdlrGetWord, 1 }, + { "db", eCmdHdlrGetWord, 1 }, + { "uid", eCmdHdlrGetWord, 1 }, + { "pwd", eCmdHdlrGetWord, 1 }, + { "driver", eCmdHdlrGetWord, 1 }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +/* this function gets the default template. It coordinates action between + * old-style and new-style configuration parts. + */ +static inline uchar* +getDfltTpl(void) +{ + if(loadModConf != NULL && loadModConf->tplName != NULL) + return loadModConf->tplName; + else if(pszFileDfltTplName == NULL) + return (uchar*)" StdDBFmt"; + else + return pszFileDfltTplName; +} + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.dbiDrvrDir = NULL; + cs.drvrName = NULL; + cs.host = NULL; + cs.usrName = NULL; + cs.pwd = NULL; + cs.dbName = NULL; +ENDinitConfVars + + +/* config settings */ +#ifdef HAVE_DBI_R +static dbi_inst dbiInst; +#endif + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we do not like repeated message reduction inside the database */ +ENDisCompatibleWithFeature + + +/* The following function is responsible for closing a + * database connection. + */ +static void closeConn(instanceData *pData) +{ + ASSERT(pData != NULL); + + if(pData->conn != NULL) { /* just to be on the safe side... */ + dbi_conn_close(pData->conn); + pData->conn = NULL; + } +} + +BEGINfreeInstance +CODESTARTfreeInstance + closeConn(pData); + free(pData->drvrName); + free(pData->host); + free(pData->usrName); + free(pData->pwd); + free(pData->dbName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* nothing special here */ +ENDdbgPrintInstInfo + + +/* log a database error with descriptive message. + * We check if we have a valid database handle. If not, we simply + * report an error, but can not be specific. RGerhards, 2007-01-30 + */ +static void +reportDBError(instanceData *pData, int bSilent) +{ + unsigned uDBErrno; + char errMsg[1024]; + const char *pszDbiErr; + + BEGINfunc + ASSERT(pData != NULL); + + /* output log message */ + errno = 0; + if(pData->conn == NULL) { + errmsg.LogError(0, NO_ERRCODE, "unknown DB error occured - could not obtain connection handle"); + } else { /* we can ask dbi for the error description... */ + uDBErrno = dbi_conn_error(pData->conn, &pszDbiErr); + snprintf(errMsg, sizeof(errMsg)/sizeof(char), "db error (%d): %s\n", uDBErrno, pszDbiErr); + if(bSilent || uDBErrno == pData->uLastDBErrno) + dbgprintf("libdbi, DBError(silent): %s\n", errMsg); + else { + pData->uLastDBErrno = uDBErrno; + errmsg.LogError(0, NO_ERRCODE, "%s", errMsg); + } + } + + ENDfunc +} + + +/* The following function is responsible for initializing a connection + */ +static rsRetVal initConn(instanceData *pData, int bSilent) +{ + DEFiRet; + int iDrvrsLoaded; + + ASSERT(pData != NULL); + ASSERT(pData->conn == NULL); + + if(bDbiInitialized == 0) { + /* we need to init libdbi first */ +# ifdef HAVE_DBI_R + iDrvrsLoaded = dbi_initialize_r((char*) pData->dbiDrvrDir, &dbiInst); +# else + iDrvrsLoaded = dbi_initialize((char*) pData->dbiDrvrDir); +# endif + if(iDrvrsLoaded == 0) { + errmsg.LogError(0, RS_RET_SUSPENDED, "libdbi error: libdbi or libdbi drivers not present on this system - suspending."); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } else if(iDrvrsLoaded < 0) { + errmsg.LogError(0, RS_RET_SUSPENDED, "libdbi error: libdbi could not be " + "initialized (do you have any dbi drivers installed?) - suspending."); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + bDbiInitialized = 1; /* we are done for the rest of our existence... */ + } + +# ifdef HAVE_DBI_R + pData->conn = dbi_conn_new_r((char*)pData->drvrName, dbiInst); +# else + pData->conn = dbi_conn_new((char*)pData->drvrName); +# endif + if(pData->conn == NULL) { + errmsg.LogError(0, RS_RET_SUSPENDED, "can not initialize libdbi connection"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } else { /* we could get the handle, now on with work... */ + /* Connect to database */ + dbi_conn_set_option(pData->conn, "host", (char*) pData->host); + dbi_conn_set_option(pData->conn, "username", (char*) pData->usrName); + dbi_conn_set_option(pData->conn, "dbname", (char*) pData->dbName); + if(pData->pwd != NULL) + dbi_conn_set_option(pData->conn, "password", (char*) pData->pwd); + if(dbi_conn_connect(pData->conn) < 0) { + reportDBError(pData, bSilent); + closeConn(pData); /* ignore any error we may get */ + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + pData->txSupport = dbi_conn_cap_get(pData->conn, "transaction_support"); + } + +finalize_it: + RETiRet; +} + + +/* The following function writes the current log entry + * to an established database connection. + */ +rsRetVal writeDB(uchar *psz, instanceData *pData) +{ + DEFiRet; + dbi_result dbiRes = NULL; + + ASSERT(psz != NULL); + ASSERT(pData != NULL); + + /* see if we are ready to proceed */ + if(pData->conn == NULL) { + CHKiRet(initConn(pData, 0)); + } + + /* try insert */ + if((dbiRes = dbi_conn_query(pData->conn, (const char*)psz)) == NULL) { + /* error occured, try to re-init connection and retry */ + closeConn(pData); /* close the current handle */ + CHKiRet(initConn(pData, 0)); /* try to re-open */ + if((dbiRes = dbi_conn_query(pData->conn, (const char*)psz)) == NULL) { /* re-try insert */ + /* we failed, giving up for now */ + reportDBError(pData, 0); + closeConn(pData); /* free ressources */ + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + } + +finalize_it: + if(iRet == RS_RET_OK) { + pData->uLastDBErrno = 0; /* reset error for error supression */ + } + + if(dbiRes != NULL) + dbi_result_free(dbiRes); + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + if(pData->conn == NULL) { + iRet = initConn(pData, 1); + } +ENDtryResume + +/* transaction support 2013-03 */ +BEGINbeginTransaction +CODESTARTbeginTransaction + if(pData->conn == NULL) { + CHKiRet(initConn(pData, 0)); + } +# if HAVE_DBI_TXSUPP + if (pData->txSupport == 1) { + if (dbi_conn_transaction_begin(pData->conn) != 0) { + dbgprintf("libdbi server error: begin transaction not successful\n"); + iRet = RS_RET_SUSPENDED; + } + } +# endif +finalize_it: +ENDbeginTransaction +/* end transaction */ + +BEGINdoAction +CODESTARTdoAction + CHKiRet(writeDB(ppString[0], pData)); +# if HAVE_DBI_TXSUPP + if (pData->txSupport == 1) { + iRet = RS_RET_DEFER_COMMIT; + } +# endif +finalize_it: +ENDdoAction + +/* transaction support 2013-03 */ +BEGINendTransaction +CODESTARTendTransaction +# if HAVE_DBI_TXSUPP + if (dbi_conn_transaction_commit(pData->conn) != 0) { + dbgprintf("libdbi server error: transaction not committed\n"); + iRet = RS_RET_SUSPENDED; + } +# endif +ENDendTransaction +/* end transaction */ + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->tplName = NULL; + bLegacyCnfModGlobalsPermitted = 1; +ENDbeginCnfLoad + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "omlibdbi: error processing " + "module config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for omlibdbi:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "template")) { + loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + if(pszFileDfltTplName != NULL) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "omlibdbi: warning: default template " + "was already set via legacy directive - may lead to inconsistent " + "results."); + } + } else if(!strcmp(modpblk.descr[i].name, "driverdirectory")) { + loadModConf->dbiDrvrDir = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omlibdbi: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } + bLegacyCnfModGlobalsPermitted = 0; +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(pszFileDfltTplName); + pszFileDfltTplName = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf + free(pModConf->tplName); + free(pModConf->dbiDrvrDir); +ENDfreeCnf + + + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->tplName = NULL; +} + + +BEGINnewActInst + struct cnfparamvals *pvals; + uchar *tplToUse; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "server")) { + pData->host = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "db")) { + pData->dbName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "uid")) { + pData->usrName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "pwd")) { + pData->pwd = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "driver")) { + pData->drvrName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omlibdbi: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + tplToUse = (pData->tplName == NULL) ? (uchar*)strdup((char*)getDfltTpl()) : pData->tplName; + CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_RQD_TPL_OPT_SQL)); +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omlibdbi:", sizeof(":omlibdbi:") - 1)) { + p += sizeof(":omlibdbi:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + CHKiRet(createInstance(&pData)); + /* no create the instance based on what we currently have */ + if(cs.drvrName == NULL) { + errmsg.LogError(0, RS_RET_NO_DRIVERNAME, "omlibdbi: no db driver name given - action can not be created"); + ABORT_FINALIZE(RS_RET_NO_DRIVERNAME); + } + + if((pData->drvrName = (uchar*) strdup((char*)cs.drvrName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + /* NULL values are supported because drivers have different needs. + * They will err out on connect. -- rgerhards, 2008-02-15 + */ + if(cs.host != NULL) + CHKmalloc(pData->host = (uchar*) strdup((char*)cs.host)); + if(cs.usrName != NULL) + CHKmalloc(pData->usrName = (uchar*) strdup((char*)cs.usrName)); + if(cs.dbName != NULL) + CHKmalloc(pData->dbName = (uchar*) strdup((char*)cs.dbName)); + if(cs.pwd != NULL) + CHKmalloc(pData->pwd = (uchar*) strdup((char*)cs.pwd)); + if(cs.dbiDrvrDir != NULL) + CHKmalloc(loadModConf->dbiDrvrDir = (uchar*) strdup((char*)cs.dbiDrvrDir)); + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_RQD_TPL_OPT_SQL, getDfltTpl())); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + /* if we initialized libdbi, we now need to cleanup */ + if(bDbiInitialized) { +# ifdef HAVE_DBI_R + dbi_shutdown_r(dbiInst); +# else + dbi_shutdown(); +# endif + } +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + free(cs.dbiDrvrDir); + cs.dbiDrvrDir = NULL; + free(cs.drvrName); + cs.drvrName = NULL; + free(cs.host); + cs.host = NULL; + free(cs.usrName); + cs.usrName = NULL; + free(cs.pwd); + cs.pwd = NULL; + free(cs.dbName); + cs.dbName = NULL; + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr +# ifndef HAVE_DBI_TXSUPP + DBGPRINTF("omlibdbi: no transaction support in libdbi\n"); +# warning libdbi too old - transactions are not enabled (use 0.9 or later) +# endif + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(regCfSysLineHdlr2((uchar *)"actionlibdbidriverdirectory", 0, eCmdHdlrGetWord, NULL, &cs.dbiDrvrDir, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbidriver", 0, eCmdHdlrGetWord, NULL, &cs.drvrName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbihost", 0, eCmdHdlrGetWord, NULL, &cs.host, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbiusername", 0, eCmdHdlrGetWord, NULL, &cs.usrName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbipassword", 0, eCmdHdlrGetWord, NULL, &cs.pwd, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbidbname", 0, eCmdHdlrGetWord, NULL, &cs.dbName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + DBGPRINTF("omlibdbi compiled with version %s loaded, libdbi version %s\n", VERSION, dbi_version()); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/ommail/Makefile.am b/plugins/ommail/Makefile.am new file mode 100644 index 00000000..97c9296a --- /dev/null +++ b/plugins/ommail/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = ommail.la + +ommail_la_SOURCES = ommail.c +ommail_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +ommail_la_LDFLAGS = -module -avoid-version +ommail_la_LIBADD = diff --git a/plugins/ommail/ommail.c b/plugins/ommail/ommail.c new file mode 100644 index 00000000..6044d2e9 --- /dev/null +++ b/plugins/ommail/ommail.c @@ -0,0 +1,729 @@ +/* ommail.c + * + * This is an implementation of a mail sending output module. So far, we + * only support direct SMTP, that is talking to a SMTP server. In the long + * term, support for using sendmail should also be implemented. Please note + * that the SMTP protocol implementation is a very bare one. We support + * RFC821/822 messages, without any authentication and any other nice + * features (no MIME, no nothing). It is assumed that proper firewalling + * and/or STMP server configuration is used together with this module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2008-04-04 by RGerhards + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <errno.h> +#include <netdb.h> +#include <time.h> +#include <sys/socket.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" +#include "datetime.h" +#include "glbl.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("ommail") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(datetime) + +/* we add a little support for multiple recipients. We do this via a + * singly-linked list, enqueued from the top. -- rgerhards, 2008-08-04 + */ +typedef struct toRcpt_s toRcpt_t; +struct toRcpt_s { + uchar *pszTo; + toRcpt_t *pNext; +}; + +typedef struct _instanceData { + int iMode; /* 0 - smtp, 1 - sendmail */ + int bHaveSubject; /* is a subject configured? (if so, it is the second string provided by rsyslog core) */ + int bEnableBody; /* is a body configured? (if so, it is the second string provided by rsyslog core) */ + union { + struct { + uchar *pszSrv; + uchar *pszSrvPort; + uchar *pszFrom; + toRcpt_t *lstRcpt; + char RcvBuf[1024]; /* buffer for receiving server responses */ + size_t lenRcvBuf; + size_t iRcvBuf; /* current index into the rcvBuf (buf empty if iRcvBuf == lenRcvBuf) */ + int sock; /* socket to this server (most important when we do multiple msgs per mail) */ + } smtp; + } md; /* mode-specific data */ +} instanceData; + +typedef struct configSettings_s { + toRcpt_t *lstRcpt; + uchar *pszSrv; + uchar *pszSrvPort; + uchar *pszFrom; + uchar *pszSubject; + int bEnableBody; /* should a mail body be generated? (set to 0 eg for SMS gateways) */ +} configSettings_t; +static configSettings_t cs; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.lstRcpt = NULL; + cs.pszSrv = NULL; + cs.pszSrvPort = NULL; + cs.pszFrom = NULL; + cs.pszSubject = NULL; + cs.bEnableBody = 1; /* should a mail body be generated? (set to 0 eg for SMS gateways) */ +ENDinitConfVars + +/* forward definitions (as few as possible) */ +static rsRetVal Send(int sock, char *msg, size_t len); +static rsRetVal readResponse(instanceData *pData, int *piState, int iExpected); + + +/* helpers for handling the recipient lists */ + +/* destroy a complete recipient list */ +static void lstRcptDestruct(toRcpt_t *pRoot) +{ + toRcpt_t *pDel; + + while(pRoot != NULL) { + pDel = pRoot; + pRoot = pRoot->pNext; + /* ready to disalloc */ + free(pDel->pszTo); + free(pDel); + } +} + +/* This function is called when a new recipient email address is to be + * added. rgerhards, 2008-08-04 + */ +static rsRetVal +addRcpt(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + toRcpt_t *pNew = NULL; + + CHKmalloc(pNew = calloc(1, sizeof(toRcpt_t))); + + pNew->pszTo = pNewVal; + pNew->pNext = cs.lstRcpt; + cs.lstRcpt = pNew; + + dbgprintf("ommail::addRcpt adds recipient %s\n", pNewVal); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + free(pNew); + free(pNewVal); /* in any case, this is no longer needed */ + } + + RETiRet; +} + + +/* output the recipient list to the mail server + * iStatusToCheck < 0 means no checking should happen + */ +static rsRetVal +WriteRcpts(instanceData *pData, uchar *pszOp, size_t lenOp, int iStatusToCheck) +{ + toRcpt_t *pRcpt; + int iState; + DEFiRet; + + assert(pData != NULL); + assert(pszOp != NULL); + assert(lenOp != 0); + + for(pRcpt = pData->md.smtp.lstRcpt ; pRcpt != NULL ; pRcpt = pRcpt->pNext) { + dbgprintf("Sending '%s: <%s>'\n", pszOp, pRcpt->pszTo); + CHKiRet(Send(pData->md.smtp.sock, (char*)pszOp, lenOp)); + CHKiRet(Send(pData->md.smtp.sock, ": <", sizeof(": <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pRcpt->pszTo, strlen((char*)pRcpt->pszTo))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + if(iStatusToCheck >= 0) + CHKiRet(readResponse(pData, &iState, iStatusToCheck)); + } + +finalize_it: + RETiRet; +} +/* end helpers for handling the recipient lists */ + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->iMode == 0) { + if(pData->md.smtp.pszSrv != NULL) + free(pData->md.smtp.pszSrv); + if(pData->md.smtp.pszSrvPort != NULL) + free(pData->md.smtp.pszSrvPort); + if(pData->md.smtp.pszFrom != NULL) + free(pData->md.smtp.pszFrom); + lstRcptDestruct(pData->md.smtp.lstRcpt); + } +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("mail"); /* TODO: extend! */ +ENDdbgPrintInstInfo + + +/* TCP support code, should probably be moved to net.c or some place else... -- rgerhards, 2008-04-04 */ + +/* "receive" a character from the remote server. A single character + * is returned. Returns RS_RET_NO_MORE_DATA if the server has closed + * the connection and RS_RET_IO_ERROR if something goes wrong. This + * is a blocking read. + * rgerhards, 2008-04-04 + */ +static rsRetVal +getRcvChar(instanceData *pData, char *pC) +{ + DEFiRet; + ssize_t lenBuf; + assert(pData != NULL); + + if(pData->md.smtp.iRcvBuf == pData->md.smtp.lenRcvBuf) { /* buffer empty? */ + /* yes, we need to read the next server response */ + do { + lenBuf = recv(pData->md.smtp.sock, pData->md.smtp.RcvBuf, sizeof(pData->md.smtp.RcvBuf), 0); + if(lenBuf == 0) { + ABORT_FINALIZE(RS_RET_NO_MORE_DATA); + } else if(lenBuf < 0) { + if(errno != EAGAIN) { + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } else { + /* good read */ + pData->md.smtp.iRcvBuf = 0; + pData->md.smtp.lenRcvBuf = lenBuf; + } + + } while(lenBuf < 1); + } + + /* when we reach this point, we have a non-empty buffer */ + *pC = pData->md.smtp.RcvBuf[pData->md.smtp.iRcvBuf++]; + +finalize_it: + RETiRet; +} + + +/* close the mail server connection + * rgerhards, 2008-04-08 + */ +static rsRetVal +serverDisconnect(instanceData *pData) +{ + DEFiRet; + assert(pData != NULL); + + if(pData->md.smtp.sock != -1) { + close(pData->md.smtp.sock); + pData->md.smtp.sock = -1; + } + + RETiRet; +} + + +/* open a connection to the mail server + * rgerhards, 2008-04-04 + */ +static rsRetVal +serverConnect(instanceData *pData) +{ + struct addrinfo *res = NULL; + struct addrinfo hints; + char *smtpPort; + char *smtpSrv; + char errStr[1024]; + + DEFiRet; + assert(pData != NULL); + + if(pData->md.smtp.pszSrv == NULL) + smtpSrv = "127.0.0.1"; + else + smtpSrv = (char*)pData->md.smtp.pszSrv; + + if(pData->md.smtp.pszSrvPort == NULL) + smtpPort = "25"; + else + smtpPort = (char*)pData->md.smtp.pszSrvPort; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* TODO: make configurable! */ + hints.ai_socktype = SOCK_STREAM; + if(getaddrinfo(smtpSrv, smtpPort, &hints, &res) != 0) { + dbgprintf("error %d in getaddrinfo\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if((pData->md.smtp.sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { + dbgprintf("couldn't create send socket, reason %s", rs_strerror_r(errno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if(connect(pData->md.smtp.sock, res->ai_addr, res->ai_addrlen) != 0) { + dbgprintf("create tcp connection failed, reason %s", rs_strerror_r(errno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + if(pData->md.smtp.sock != -1) { + close(pData->md.smtp.sock); + pData->md.smtp.sock = -1; + } + } + + RETiRet; +} + + +/* send text to the server, blocking send */ +static rsRetVal +Send(int sock, char *msg, size_t len) +{ + DEFiRet; + size_t offsBuf = 0; + ssize_t lenSend; + + assert(msg != NULL); + + if(len == 0) /* it's valid, but does not make much sense ;) */ + FINALIZE; + + do { + lenSend = send(sock, msg + offsBuf, len - offsBuf, 0); + if(lenSend == -1) { + if(errno != EAGAIN) { + dbgprintf("message not (tcp)send, errno %d", errno); + ABORT_FINALIZE(RS_RET_TCP_SEND_ERROR); + } + } else if(lenSend != (ssize_t) len) { + offsBuf += len; /* on to next round... */ + } else { + FINALIZE; + } + } while(1); + +finalize_it: + RETiRet; +} + + +/* send body text to the server, blocking send + * The body is special in that we must escape a leading dot inside a line + */ +static rsRetVal +bodySend(instanceData *pData, char *msg, size_t len) +{ + DEFiRet; + char szBuf[2048]; + size_t iSrc; + size_t iBuf = 0; + int bHadCR = 0; + int bInStartOfLine = 1; + + assert(pData != NULL); + assert(msg != NULL); + + for(iSrc = 0 ; iSrc < len ; ++iSrc) { + if(iBuf >= sizeof(szBuf) - 1) { /* one is reserved for our extra dot */ + CHKiRet(Send(pData->md.smtp.sock, szBuf, iBuf)); + iBuf = 0; + } + szBuf[iBuf++] = msg[iSrc]; + switch(msg[iSrc]) { + case '\r': + bHadCR = 1; + break; + case '\n': + if(bHadCR) + bInStartOfLine = 1; + bHadCR = 0; + break; + case '.': + if(bInStartOfLine) + szBuf[iBuf++] = '.'; /* space is always reserved for this! */ + /*FALLTHROUGH*/ + default: + bInStartOfLine = 0; + bHadCR = 0; + break; + } + } + + if(iBuf > 0) { /* incomplete buffer to send (the *usual* case)? */ + CHKiRet(Send(pData->md.smtp.sock, szBuf, iBuf)); + } + +finalize_it: + RETiRet; +} + + +/* read response line from server + */ +static rsRetVal +readResponseLn(instanceData *pData, char *pLn, size_t lenLn) +{ + DEFiRet; + size_t i = 0; + char c; + + assert(pData != NULL); + assert(pLn != NULL); + + do { + CHKiRet(getRcvChar(pData, &c)); + if(c == '\n') + break; + if(i < (lenLn - 1)) /* if line is too long, we simply discard the rest */ + pLn[i++] = c; + } while(1); + pLn[i] = '\0'; + dbgprintf("smtp server response: %s\n", pLn); /* do not remove, this is helpful in troubleshooting SMTP probs! */ + +finalize_it: + RETiRet; +} + + +/* read numerical response code from server and compare it to requried response code. + * If they two don't match, return RS_RET_SMTP_ERROR. + * rgerhards, 2008-04-07 + */ +static rsRetVal +readResponse(instanceData *pData, int *piState, int iExpected) +{ + DEFiRet; + int bCont; + char buf[128]; + + assert(pData != NULL); + assert(piState != NULL); + + bCont = 1; + do { + CHKiRet(readResponseLn(pData, buf, sizeof(buf))); + /* note: the code below is not 100% clean as we may have received less than 4 characters. + * However, as we have a fixed size this will not create a vulnerability. An error will + * also most likely be generated, so it is quite acceptable IMHO -- rgerhards, 2008-04-08 + */ + if(buf[3] != '-') { /* last or only response line? */ + bCont = 0; + *piState = buf[0] - '0'; + *piState = *piState * 10 + buf[1] - '0'; + *piState = *piState * 10 + buf[2] - '0'; + if(*piState != iExpected) + ABORT_FINALIZE(RS_RET_SMTP_ERROR); + } + } while(bCont); + +finalize_it: + RETiRet; +} + + +/* create a timestamp suitable for use with the Date: SMTP body header + * rgerhards, 2008-04-08 + */ +static void +mkSMTPTimestamp(uchar *pszBuf, size_t lenBuf) +{ + time_t tCurr; + struct tm tmCurr; + static const char szDay[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static const char szMonth[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + datetime.GetTime(&tCurr); + gmtime_r(&tCurr, &tmCurr); + snprintf((char*)pszBuf, lenBuf, "Date: %s, %2d %s %4d %2d:%02d:%02d UT\r\n", szDay[tmCurr.tm_wday], tmCurr.tm_mday, + szMonth[tmCurr.tm_mon], 1900 + tmCurr.tm_year, tmCurr.tm_hour, tmCurr.tm_min, tmCurr.tm_sec); +} + + +/* send a message via SMTP + * rgerhards, 2008-04-04 + */ +static rsRetVal +sendSMTP(instanceData *pData, uchar *body, uchar *subject) +{ + DEFiRet; + int iState; /* SMTP state */ + uchar szDateBuf[64]; + + assert(pData != NULL); + + CHKiRet(serverConnect(pData)); + CHKiRet(readResponse(pData, &iState, 220)); + + CHKiRet(Send(pData->md.smtp.sock, "HELO ", 5)); + CHKiRet(Send(pData->md.smtp.sock, (char*)glbl.GetLocalHostName(), strlen((char*)glbl.GetLocalHostName()))); + CHKiRet(Send(pData->md.smtp.sock, "\r\n", sizeof("\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 250)); + + CHKiRet(Send(pData->md.smtp.sock, "MAIL FROM: <", sizeof("MAIL FROM: <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pData->md.smtp.pszFrom, strlen((char*)pData->md.smtp.pszFrom))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 250)); + + CHKiRet(WriteRcpts(pData, (uchar*)"RCPT TO", sizeof("RCPT TO") - 1, 250)); + + CHKiRet(Send(pData->md.smtp.sock, "DATA\r\n", sizeof("DATA\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 354)); + + /* now come the data part */ + /* header */ + mkSMTPTimestamp(szDateBuf, sizeof(szDateBuf)); + CHKiRet(Send(pData->md.smtp.sock, (char*)szDateBuf, strlen((char*)szDateBuf))); + + CHKiRet(Send(pData->md.smtp.sock, "From: <", sizeof("From: <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pData->md.smtp.pszFrom, strlen((char*)pData->md.smtp.pszFrom))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + + CHKiRet(WriteRcpts(pData, (uchar*)"To", sizeof("To") - 1, -1)); + + CHKiRet(Send(pData->md.smtp.sock, "Subject: ", sizeof("Subject: ") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)subject, strlen((char*)subject))); + CHKiRet(Send(pData->md.smtp.sock, "\r\n", sizeof("\r\n") - 1)); + + CHKiRet(Send(pData->md.smtp.sock, "X-Mailer: rsyslog-immail\r\n", sizeof("x-mailer: rsyslog-immail\r\n") - 1)); + + CHKiRet(Send(pData->md.smtp.sock, "\r\n", sizeof("\r\n") - 1)); /* indicate end of header */ + + /* body */ + if(pData->bEnableBody) + CHKiRet(bodySend(pData, (char*)body, strlen((char*) body))); + + /* end of data, back to envelope transaction */ + CHKiRet(Send(pData->md.smtp.sock, "\r\n.\r\n", sizeof("\r\n.\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 250)); + + CHKiRet(Send(pData->md.smtp.sock, "QUIT\r\n", sizeof("QUIT\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 221)); + + /* we are finished, a new connection is created for each request, so let's close it now */ + CHKiRet(serverDisconnect(pData)); + +finalize_it: + RETiRet; +} + + +/* in tryResume we check if we can connect to the server in question. If that is OK, + * we close the connection without doing any actual SMTP transaction. It will be + * reopened during the actual send process. This may not be the best way to do it if + * there is a problem inside the SMTP transaction. However, we can't find that out without + * actually initiating something, and that would be bad. The logic here helps us + * correctly recover from an unreachable/down mail server, which is probably the majority + * of problem cases. For SMTP transaction problems, we will do lots of retries, but if it + * is a temporary problem, it will be fixed anyhow. So I consider this implementation to + * be clean enough, especially as I think other approaches have other weaknesses. + * rgerhards, 2008-04-08 + */ +BEGINtryResume +CODESTARTtryResume + CHKiRet(serverConnect(pData)); + CHKiRet(serverDisconnect(pData)); /* if we fail, we will never reach this line */ +finalize_it: + if(iRet == RS_RET_IO_ERROR) + iRet = RS_RET_SUSPENDED; +ENDtryResume + + +BEGINdoAction +CODESTARTdoAction + dbgprintf(" Mail\n"); + + /* forward */ + if(pData->bHaveSubject) + iRet = sendSMTP(pData, ppString[0], ppString[1]); + else + iRet = sendSMTP(pData, ppString[0], (uchar*)"message from rsyslog"); + + if(iRet != RS_RET_OK) { + /* error! */ + dbgprintf("error sending mail, suspending\n"); + iRet = RS_RET_SUSPENDED; + } +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + if(!strncmp((char*) p, ":ommail:", sizeof(":ommail:") - 1)) { + p += sizeof(":ommail:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + FINALIZE; + + /* TODO: check strdup() result */ + + if(cs.pszFrom == NULL) { + errmsg.LogError(0, RS_RET_MAIL_NO_FROM, "no sender address given - specify $ActionMailFrom"); + ABORT_FINALIZE(RS_RET_MAIL_NO_FROM); + } + if(cs.lstRcpt == NULL) { + errmsg.LogError(0, RS_RET_MAIL_NO_TO, "no recipient address given - specify $ActionMailTo"); + ABORT_FINALIZE(RS_RET_MAIL_NO_TO); + } + + pData->md.smtp.pszFrom = (uchar*) strdup((char*)cs.pszFrom); + pData->md.smtp.lstRcpt = cs.lstRcpt; /* we "hand over" this memory */ + cs.lstRcpt = NULL; /* note: this is different from pre-3.21.2 versions! */ + + if(cs.pszSubject == NULL) { + /* if no subject is configured, we need just one template string */ + CODE_STD_STRING_REQUESTparseSelectorAct(1) + } else { + CODE_STD_STRING_REQUESTparseSelectorAct(2) + pData->bHaveSubject = 1; + CHKiRet(OMSRsetEntry(*ppOMSR, 1, (uchar*)strdup((char*) cs.pszSubject), OMSR_NO_RQD_TPL_OPTS)); + } + if(cs.pszSrv != NULL) + pData->md.smtp.pszSrv = (uchar*) strdup((char*)cs.pszSrv); + if(cs.pszSrvPort != NULL) + pData->md.smtp.pszSrvPort = (uchar*) strdup((char*)cs.pszSrvPort); + pData->bEnableBody = cs.bEnableBody; + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_FileFormat")); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* Free string config variables and reset them to NULL (not necessarily the default!) */ +static rsRetVal freeConfigVariables(void) +{ + DEFiRet; + + free(cs.pszSrv); + cs.pszSrv = NULL; + free(cs.pszSrvPort); + cs.pszSrvPort = NULL; + free(cs.pszFrom); + cs.pszFrom = NULL; + lstRcptDestruct(cs.lstRcpt); + cs.lstRcpt = NULL; + + RETiRet; +} + + +BEGINmodExit +CODESTARTmodExit + /* cleanup our allocations */ + freeConfigVariables(); + + /* release what we no longer need */ + objRelease(datetime, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + cs.bEnableBody = 1; + iRet = freeConfigVariables(); + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* tell which objects we need */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + dbgprintf("ommail version %s initializing\n", VERSION); + + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsmtpserver", 0, eCmdHdlrGetWord, NULL, &cs.pszSrv, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsmtpport", 0, eCmdHdlrGetWord, NULL, &cs.pszSrvPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailfrom", 0, eCmdHdlrGetWord, NULL, &cs.pszFrom, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailto", 0, eCmdHdlrGetWord, addRcpt, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsubject", 0, eCmdHdlrGetWord, NULL, &cs.pszSubject, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailenablebody", 0, eCmdHdlrBinary, NULL, &cs.bEnableBody, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/ommongodb/Makefile.am b/plugins/ommongodb/Makefile.am new file mode 100644 index 00000000..3a05c435 --- /dev/null +++ b/plugins/ommongodb/Makefile.am @@ -0,0 +1,7 @@ +pkglib_LTLIBRARIES = ommongodb.la +ommongodb_la_SOURCES = ommongodb.c +ommongodb_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(LIBMONGO_CLIENT_CFLAGS) +ommongodb_la_LDFLAGS = -module -avoid-version +ommongodb_la_LIBADD = $(LIBMONGO_CLIENT_LIBS) + +EXTRA_DIST = diff --git a/plugins/ommongodb/README b/plugins/ommongodb/README new file mode 100644 index 00000000..ad4a8ea2 --- /dev/null +++ b/plugins/ommongodb/README @@ -0,0 +1,18 @@ +plugin to use MongoDB as backend. + +tested in ubuntu 10.04 and ubuntu 10.10 + +configuration: + +in your /etc/rsyslog.conf, together with other modules: +$ModLoad ommongodb # provides mongodb support +*.* action(type="ommongodb" db="..." collection="..." template="...") + +Note: if no template is specified, a default schema will be used. That schema +contains proper data types. However, if a template is specified, only strings +are supported. This is a restriction of the rsyslog v6 core engine. This +changed in v7. + +If templates are used, it is suggested to use list-based templates. Constants +can ONLY be inserted with list-based templates, as only these provide the +capability to specify a field name (outname parameter). diff --git a/plugins/ommongodb/ommongodb.c b/plugins/ommongodb/ommongodb.c new file mode 100644 index 00000000..dd997410 --- /dev/null +++ b/plugins/ommongodb/ommongodb.c @@ -0,0 +1,587 @@ +/* ommongodb.c + * Output module for mongodb. + * Note: this module uses the libmongo-client library. The original 10gen + * mongodb C interface is crap. Obtain the library here: + * https://github.com/algernon/libmongo-client + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <stdint.h> +#include <time.h> +#include <mongo.h> +#include <json/json.h> +/* For struct json_object_iter, should not be necessary in future versions */ +#include <json/json_object_private.h> + +#include "rsyslog.h" +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "datetime.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "unicode-helper.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("ommongodb") +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(datetime) + +typedef struct _instanceData { + mongo_sync_connection *conn; + struct json_tokener *json_tokener; /* only if (tplName != NULL) */ + uchar *server; + int port; + uchar *db; + uchar *collection; + uchar *uid; + uchar *pwd; + uchar *dbNcoll; + uchar *tplName; + int bErrMsgPermitted; /* only one errmsg permitted per connection */ +} instanceData; + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "server", eCmdHdlrGetWord, 0 }, + { "serverport", eCmdHdlrInt, 0 }, + { "db", eCmdHdlrGetWord, 0 }, + { "collection", eCmdHdlrGetWord, 0 }, + { "uid", eCmdHdlrGetWord, 0 }, + { "pwd", eCmdHdlrGetWord, 0 }, + { "template", eCmdHdlrGetWord, 1 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* use this to specify if select features are supported by this + * plugin. If not, the framework will handle that. Currently, only + * RepeatedMsgReduction ("last message repeated n times") is optional. + */ + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + +static void closeMongoDB(instanceData *pData) +{ + if(pData->conn != NULL) { + mongo_sync_disconnect(pData->conn); + pData->conn = NULL; + } +} + + +BEGINfreeInstance +CODESTARTfreeInstance + closeMongoDB(pData); + if (pData->json_tokener != NULL) + json_tokener_free(pData->json_tokener); + free(pData->server); + free(pData->db); + free(pData->collection); + free(pData->uid); + free(pData->pwd); + free(pData->dbNcoll); + free(pData->tplName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* nothing special here */ + (void)pData; +ENDdbgPrintInstInfo + + +/* report error that occured during *last* operation + */ +static void +reportMongoError(instanceData *pData) +{ + char errStr[1024]; + gchar *err; + int eno; + + if(pData->bErrMsgPermitted) { + eno = errno; + if(mongo_sync_cmd_get_last_error(pData->conn, (gchar*)pData->db, &err) == TRUE) { + errmsg.LogError(0, RS_RET_ERR, "ommongodb: error: %s", err); + } else { + DBGPRINTF("ommongodb: we had an error, but can not obtain specifics, " + "using plain old errno error message generator\n"); + errmsg.LogError(0, RS_RET_ERR, "ommongodb: error: %s", + rs_strerror_r(eno, errStr, sizeof(errStr))); + } + pData->bErrMsgPermitted = 0; + } +} + + +/* The following function is responsible for initializing a + * MySQL connection. + * Initially added 2004-10-28 mmeckelein + */ +static rsRetVal initMongoDB(instanceData *pData, int bSilent) +{ + char *server; + DEFiRet; + + server = (pData->server == NULL) ? "127.0.0.1" : (char*) pData->server; + DBGPRINTF("ommongodb: trying connect to '%s' at port %d\n", server, pData->port); + + pData->conn = mongo_sync_connect(server, pData->port, TRUE); + if(pData->conn == NULL) { + if(!bSilent) { + reportMongoError(pData); + dbgprintf("ommongodb: can not initialize MongoDB handle"); + } + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + RETiRet; +} + + +/* map syslog severity to lumberjack level + * TODO: consider moving this to msg.c - make some dirty "friend" references... + * rgerhards, 2012-03-19 + */ +static inline char * +getLumberjackLevel(short severity) +{ + switch(severity) { + case 0: return "FATAL"; + case 1: + case 2: + case 3: return "ERROR"; + case 4: return "WARN"; + case 5: + case 6: return "INFO"; + case 7: return "DEBUG"; + default:DBGPRINTF("ommongodb: invalid syslog severity %u\n", severity); + return "INVLD"; + } +} + + +/* small helper: get integer power of 10 */ +static inline int +i10pow(int exp) +{ + int r = 1; + while(exp > 0) { + r *= 10; + exp--; + } + return r; +} +/* Return a BSON document when an user hasn't specified a template. + * In this mode, we use the standard document format, which is somewhat + * aligned to cee (as described in project lumberjack). Note that this is + * a moving target, so we may run out of sync (and stay so to retain + * backward compatibility, which we consider pretty important). + */ +static bson * +getDefaultBSON(msg_t *pMsg) +{ + bson *doc = NULL; + uchar *procid; short unsigned procid_free; rs_size_t procid_len; + uchar *tag; short unsigned tag_free; rs_size_t tag_len; + uchar *pid; short unsigned pid_free; rs_size_t pid_len; + uchar *sys; short unsigned sys_free; rs_size_t sys_len; + uchar *msg; short unsigned msg_free; rs_size_t msg_len; + int severity, facil; + gint64 ts_gen, ts_rcv; /* timestamps: generated, received */ + int secfrac; + + procid = MsgGetProp(pMsg, NULL, PROP_PROGRAMNAME, NULL, &procid_len, &procid_free, NULL); + tag = MsgGetProp(pMsg, NULL, PROP_SYSLOGTAG, NULL, &tag_len, &tag_free, NULL); + pid = MsgGetProp(pMsg, NULL, PROP_PROCID, NULL, &pid_len, &pid_free, NULL); + sys = MsgGetProp(pMsg, NULL, PROP_HOSTNAME, NULL, &sys_len, &sys_free, NULL); + msg = MsgGetProp(pMsg, NULL, PROP_MSG, NULL, &msg_len, &msg_free, NULL); + + // TODO: move to datetime? Refactor in any case! rgerhards, 2012-03-30 + ts_gen = (gint64) datetime.syslogTime2time_t(&pMsg->tTIMESTAMP) * 1000; /* ms! */ +dbgprintf("ommongodb: ts_gen is %lld\n", (long long) ts_gen); +dbgprintf("ommongodb: secfrac is %d, precision %d\n", pMsg->tTIMESTAMP.secfrac, pMsg->tTIMESTAMP.secfracPrecision); + if(pMsg->tTIMESTAMP.secfracPrecision > 3) { + secfrac = pMsg->tTIMESTAMP.secfrac / i10pow(pMsg->tTIMESTAMP.secfracPrecision - 3); + } else if(pMsg->tTIMESTAMP.secfracPrecision < 3) { + secfrac = pMsg->tTIMESTAMP.secfrac * i10pow(3 - pMsg->tTIMESTAMP.secfracPrecision); + } else { + secfrac = pMsg->tTIMESTAMP.secfrac; + } + ts_gen += secfrac; + ts_rcv = (gint64) datetime.syslogTime2time_t(&pMsg->tRcvdAt) * 1000; /* ms! */ + if(pMsg->tRcvdAt.secfracPrecision > 3) { + secfrac = pMsg->tRcvdAt.secfrac / i10pow(pMsg->tRcvdAt.secfracPrecision - 3); + } else if(pMsg->tRcvdAt.secfracPrecision < 3) { + secfrac = pMsg->tRcvdAt.secfrac * i10pow(3 - pMsg->tRcvdAt.secfracPrecision); + } else { + secfrac = pMsg->tRcvdAt.secfrac; + } + ts_rcv += secfrac; + + /* the following need to be int, but are short, so we need to xlat */ + severity = pMsg->iSeverity; + facil = pMsg->iFacility; + + doc = bson_build(BSON_TYPE_STRING, "sys", sys, sys_len, + BSON_TYPE_UTC_DATETIME, "time", ts_gen, + BSON_TYPE_UTC_DATETIME, "time_rcvd", ts_rcv, + BSON_TYPE_STRING, "msg", msg, msg_len, + BSON_TYPE_INT32, "syslog_fac", facil, + BSON_TYPE_INT32, "syslog_sever", severity, + BSON_TYPE_STRING, "syslog_tag", tag, tag_len, + BSON_TYPE_STRING, "procid", procid, procid_len, + BSON_TYPE_STRING, "pid", pid, pid_len, + BSON_TYPE_STRING, "level", getLumberjackLevel(pMsg->iSeverity), -1, + BSON_TYPE_NONE); + + if(procid_free) free(procid); + if(tag_free) free(tag); + if(pid_free) free(pid); + if(sys_free) free(sys); + if(msg_free) free(msg); + + if(doc == NULL) + return doc; + bson_finish(doc); + return doc; +} + +static bson *BSONFromJSONArray(struct json_object *json); +static bson *BSONFromJSONObject(struct json_object *json); + +/* Append a BSON variant of json to doc using name. Return TRUE on success */ +static gboolean +BSONAppendJSONObject(bson *doc, const gchar *name, struct json_object *json) +{ + switch(json != NULL ? json_object_get_type(json) : json_type_null) { + case json_type_null: + return bson_append_null(doc, name); + case json_type_boolean: + return bson_append_boolean(doc, name, + json_object_get_boolean(json)); + case json_type_double: + return bson_append_double(doc, name, + json_object_get_double(json)); + case json_type_int: { + int64_t i; + + /* FIXME: the future version will have get_int64 */ + i = json_object_get_int(json); + if (i >= INT32_MIN && i <= INT32_MAX) + return bson_append_int32(doc, name, i); + else + return bson_append_int64(doc, name, i); + } + case json_type_object: { + bson *sub; + gboolean ok; + + sub = BSONFromJSONObject(json); + if (sub == NULL) + return FALSE; + ok = bson_append_document(doc, name, sub); + bson_free(sub); + return ok; + } + case json_type_array: { + bson *sub; + gboolean ok; + + sub = BSONFromJSONArray(json); + if (sub == NULL) + return FALSE; + ok = bson_append_document(doc, name, sub); + bson_free(sub); + return ok; + } + case json_type_string: + return bson_append_string(doc, name, + json_object_get_string(json), -1); + + default: + return FALSE; + } +} + +/* Return a BSON variant of json, which must be a json_type_array */ +static bson * +BSONFromJSONArray(struct json_object *json) +{ + /* Way more than necessary */ + bson *doc = NULL; + size_t i, array_len; + + doc = bson_new(); + if(doc == NULL) + goto error; + + array_len = json_object_array_length(json); + for (i = 0; i < array_len; i++) { + char buf[sizeof(size_t) * CHAR_BIT + 1]; + + if ((size_t)snprintf(buf, sizeof(buf), "%zu", i) >= sizeof(buf)) + goto error; + if (BSONAppendJSONObject(doc, buf, + json_object_array_get_idx(json, i)) + == FALSE) + goto error; + } + + if(bson_finish(doc) == FALSE) + goto error; + + return doc; + +error: + if(doc != NULL) + bson_free(doc); + return NULL; +} + +/* Return a BSON variant of json, which must be a json_type_object */ +static bson * +BSONFromJSONObject(struct json_object *json) +{ + bson *doc = NULL; + struct json_object_iter it; + + doc = bson_new(); + if(doc == NULL) + goto error; + + json_object_object_foreachC(json, it) { + if (BSONAppendJSONObject(doc, it.key, it.val) == FALSE) + goto error; + } + + if(bson_finish(doc) == FALSE) + goto error; + + return doc; + +error: + if(doc != NULL) + bson_free(doc); + return NULL; +} + +BEGINtryResume +CODESTARTtryResume + if(pData->conn == NULL) { + iRet = initMongoDB(pData, 1); + } +ENDtryResume + +BEGINdoAction + bson *doc = NULL; +CODESTARTdoAction + /* see if we are ready to proceed */ + if(pData->conn == NULL) { + CHKiRet(initMongoDB(pData, 0)); + } + + if(pData->tplName == NULL) { + doc = getDefaultBSON((msg_t*)ppString[0]); + } else { + doc = BSONFromJSONObject((struct json_object *)ppString[0]); + } + if(doc == NULL) { + dbgprintf("ommongodb: error creating BSON doc\n"); + /* FIXME: is this a correct return code? */ + ABORT_FINALIZE(RS_RET_ERR); + } + if(mongo_sync_cmd_insert(pData->conn, (char*)pData->dbNcoll, doc, NULL)) { + pData->bErrMsgPermitted = 1; + } else { + dbgprintf("ommongodb: insert error\n"); + reportMongoError(pData); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + if(doc != NULL) + bson_free(doc); +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->server = NULL; + pData->port = 27017; + pData->db = NULL; + pData->collection= NULL; + pData->uid = NULL; + pData->pwd = NULL; + pData->tplName = NULL; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; + unsigned lendb, lencoll; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "server")) { + pData->server = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "serverport")) { + pData->port = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "db")) { + pData->db = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "collection")) { + pData->collection = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "uid")) { + pData->uid = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "pwd")) { + pData->pwd = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("ommongodb: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, ustrdup(pData->tplName), + OMSR_TPL_AS_JSON)); + CHKmalloc(pData->json_tokener = json_tokener_new()); + } + + if(pData->db == NULL) + pData->db = (uchar*)strdup("syslog"); + if(pData->collection == NULL) + pData->collection = (uchar*)strdup("log"); + + /* we now create a db+collection string as we need to pass this + * into the API and we do not want to generate it each time ;) + * +2 ==> dot as delimiter and \0 + */ + lendb = strlen((char*)pData->db); + lencoll = strlen((char*)pData->collection); + CHKmalloc(pData->dbNcoll = malloc(lendb+lencoll+2)); + memcpy(pData->dbNcoll, pData->db, lendb); + pData->dbNcoll[lendb] = '.'; + /* lencoll+1 => copy \0! */ + memcpy(pData->dbNcoll+lendb+1, pData->collection, lencoll+1); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":ommongodb:", sizeof(":ommongodb:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "ommongodb supports only v6 config format, use: " + "action(type=\"ommongodb\" server=...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bJSONPassingSupported; +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); + DBGPRINTF("ommongodb: module compiled with rsyslog version %s.\n", VERSION); + + /* check if the rsyslog core supports parameter passing code */ + bJSONPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", + &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports msg passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_JSON) + bJSONPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */ + } + if(!bJSONPassingSupported) { + DBGPRINTF("ommongodb: JSON-passing is not supported by rsyslog core, " + "can not continue.\n"); + ABORT_FINALIZE(RS_RET_NO_JSON_PASSING); + } +ENDmodInit diff --git a/plugins/ommysql/Makefile.am b/plugins/ommysql/Makefile.am new file mode 100644 index 00000000..e253b9da --- /dev/null +++ b/plugins/ommysql/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = ommysql.la + +ommysql_la_SOURCES = ommysql.c ommysql.h +ommysql_la_CPPFLAGS = $(RSRT_CFLAGS) $(MYSQL_CFLAGS) $(PTHREADS_CFLAGS) +ommysql_la_LDFLAGS = -module -avoid-version +ommysql_la_LIBADD = $(MYSQL_LIBS) + +EXTRA_DIST = createDB.sql contrib/delete_mysql diff --git a/plugins/ommysql/contrib/delete_mysql b/plugins/ommysql/contrib/delete_mysql new file mode 100644 index 00000000..3ed84d17 --- /dev/null +++ b/plugins/ommysql/contrib/delete_mysql @@ -0,0 +1,52 @@ +#!/bin/bash + +# Database maintance script which can be used for rsyslog +# and phplogcon default database schema. +# Michael Mansour suggested it to be included - thx! + +# This program was original part of of PHPloghost +# Copyright (C) 2004 Tuatha de Dana +# some modifications for rsyslog by mmeckelein at 2007-08-08 +# 2007-08-13 mmeckelein: added dbhost and some other improvements +# suggested by Michael Mansour - thx a lot! + +# 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. + +# Change these variables to reflect your situation. +database=sqlrsyslogd +dbhost="localhost" +export table=systemevents +sqluser="" +password="" + +# Location of the mysql daemon: +mysqld=/usr/bin/mysql + +# A couple of steps should be taken to maintain your database. +# If not, the number of messages will fill your database. +# By default, logs are deleted after they're 30 days old. +# Change this to meet your requirements. +# rsyslog's default database template use two date columns +# ReceivedAt and DeviceReportedTime. You can use either of +# the two and in most cases it doesn't make a huge difference. +# See the property replacer doc at http://www.rsyslog.com/doc +# for details on the two dates. +SQL_DELETE="DELETE FROM $table WHERE ReceivedAt < CURDATE() - INTERVAL 30 DAY;" + +# After a large amount of rows have been deleted, we should # optimize the table. +SQL_OPT="OPTIMIZE TABLE $table;"; + +$mysqld -u$sqluser -p$password -h$dbhost -e"$SQL_DELETE" -D$database +$mysqld -u$sqluser -p$password -h$dbhost -e"$SQL_OPT" -D$database diff --git a/plugins/ommysql/createDB.sql b/plugins/ommysql/createDB.sql new file mode 100644 index 00000000..211cfb0e --- /dev/null +++ b/plugins/ommysql/createDB.sql @@ -0,0 +1,37 @@ +CREATE DATABASE Syslog; +USE Syslog; +CREATE TABLE SystemEvents +( + ID int unsigned not null auto_increment primary key, + CustomerID bigint, + ReceivedAt datetime NULL, + DeviceReportedTime datetime NULL, + Facility smallint NULL, + Priority smallint NULL, + FromHost varchar(60) NULL, + Message text, + NTSeverity int NULL, + Importance int NULL, + EventSource varchar(60), + EventUser varchar(60) NULL, + EventCategory int NULL, + EventID int NULL, + EventBinaryData text NULL, + MaxAvailable int NULL, + CurrUsage int NULL, + MinUsage int NULL, + MaxUsage int NULL, + InfoUnitID int NULL , + SysLogTag varchar(60), + EventLogType varchar(60), + GenericFileName VarChar(60), + SystemID int NULL +); + +CREATE TABLE SystemEventsProperties +( + ID int unsigned not null auto_increment primary key, + SystemEventID int NULL , + ParamName varchar(255) NULL , + ParamValue text NULL +); diff --git a/plugins/ommysql/ommysql.c b/plugins/ommysql/ommysql.c new file mode 100644 index 00000000..2dfa29de --- /dev/null +++ b/plugins/ommysql/ommysql.c @@ -0,0 +1,508 @@ +/* ommysql.c + * This is the implementation of the build-in output module for MySQL. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <time.h> +#include <mysql.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "ommysql.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("ommysql") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + MYSQL *f_hmysql; /* handle to MySQL */ + char f_dbsrv[MAXHOSTNAMELEN+1]; /* IP or hostname of DB server*/ + unsigned int f_dbsrvPort; /* port of MySQL server */ + char f_dbname[_DB_MAXDBLEN+1]; /* DB name */ + char f_dbuid[_DB_MAXUNAMELEN+1]; /* DB user */ + char f_dbpwd[_DB_MAXPWDLEN+1]; /* DB user's password */ + unsigned uLastMySQLErrno; /* last errno returned by MySQL or 0 if all is well */ + uchar * f_configfile; /* MySQL Client Configuration File */ + uchar * f_configsection; /* MySQL Client Configuration Section */ + uchar *tplName; /* format template to use */ +} instanceData; + +typedef struct configSettings_s { + int iSrvPort; /* database server port */ + uchar *pszMySQLConfigFile; /* MySQL Client Configuration File */ + uchar *pszMySQLConfigSection; /* MySQL Client Configuration Section */ +} configSettings_t; +static configSettings_t cs; + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "server", eCmdHdlrGetWord, 1 }, + { "db", eCmdHdlrGetWord, 1 }, + { "uid", eCmdHdlrGetWord, 1 }, + { "pwd", eCmdHdlrGetWord, 1 }, + { "serverport", eCmdHdlrInt, 0 }, + { "mysqlconfig.file", eCmdHdlrGetWord, 0 }, + { "mysqlconfig.section", eCmdHdlrGetWord, 0 }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + resetConfigVariables(NULL, NULL); +ENDinitConfVars + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +/* The following function is responsible for closing a + * MySQL connection. + * Initially added 2004-10-28 + */ +static void closeMySQL(instanceData *pData) +{ + ASSERT(pData != NULL); + + if(pData->f_hmysql != NULL) { /* just to be on the safe side... */ + mysql_close(pData->f_hmysql); + pData->f_hmysql = NULL; + } + if(pData->f_configfile!=NULL){ + free(pData->f_configfile); + pData->f_configfile=NULL; + } + if(pData->f_configsection!=NULL){ + free(pData->f_configsection); + pData->f_configsection=NULL; + } +} + +BEGINfreeInstance +CODESTARTfreeInstance + free(pData->f_configfile); + free(pData->f_configsection); + free(pData->tplName); + closeMySQL(pData); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* nothing special here */ +ENDdbgPrintInstInfo + + +/* log a database error with descriptive message. + * We check if we have a valid MySQL handle. If not, we simply + * report an error, but can not be specific. RGerhards, 2007-01-30 + */ +static void reportDBError(instanceData *pData, int bSilent) +{ + char errMsg[512]; + unsigned uMySQLErrno; + + ASSERT(pData != NULL); + + /* output log message */ + errno = 0; + if(pData->f_hmysql == NULL) { + errmsg.LogError(0, NO_ERRCODE, "unknown DB error occured - could not obtain MySQL handle"); + } else { /* we can ask mysql for the error description... */ + uMySQLErrno = mysql_errno(pData->f_hmysql); + snprintf(errMsg, sizeof(errMsg)/sizeof(char), "db error (%d): %s\n", uMySQLErrno, + mysql_error(pData->f_hmysql)); + if(bSilent || uMySQLErrno == pData->uLastMySQLErrno) + dbgprintf("mysql, DBError(silent): %s\n", errMsg); + else { + pData->uLastMySQLErrno = uMySQLErrno; + errmsg.LogError(0, NO_ERRCODE, "%s", errMsg); + } + } + + return; +} + + +/* The following function is responsible for initializing a + * MySQL connection. + * Initially added 2004-10-28 mmeckelein + */ +static rsRetVal initMySQL(instanceData *pData, int bSilent) +{ + DEFiRet; + + ASSERT(pData != NULL); + ASSERT(pData->f_hmysql == NULL); + pData->f_hmysql = mysql_init(NULL); + if(pData->f_hmysql == NULL) { + errmsg.LogError(0, RS_RET_SUSPENDED, "can not initialize MySQL handle"); + iRet = RS_RET_SUSPENDED; + } else { /* we could get the handle, now on with work... */ + mysql_options(pData->f_hmysql,MYSQL_READ_DEFAULT_GROUP,((pData->f_configsection!=NULL)?(char*)pData->f_configsection:"client")); + if(pData->f_configfile!=NULL){ + FILE * fp; + fp=fopen((char*)pData->f_configfile,"r"); + int err=errno; + if(fp==NULL){ + char msg[512]; + snprintf(msg,sizeof(msg)/sizeof(char),"Could not open '%s' for reading",pData->f_configfile); + if(bSilent) { + char errStr[512]; + rs_strerror_r(err, errStr, sizeof(errStr)); + dbgprintf("mysql configuration error(%d): %s - %s\n",err,msg,errStr); + } else + errmsg.LogError(err,NO_ERRCODE,"mysql configuration error: %s\n",msg); + } else { + fclose(fp); + mysql_options(pData->f_hmysql,MYSQL_READ_DEFAULT_FILE,pData->f_configfile); + } + } + /* Connect to database */ + if(mysql_real_connect(pData->f_hmysql, pData->f_dbsrv, pData->f_dbuid, + pData->f_dbpwd, pData->f_dbname, pData->f_dbsrvPort, NULL, 0) == NULL) { + reportDBError(pData, bSilent); + closeMySQL(pData); /* ignore any error we may get */ + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + mysql_autocommit(pData->f_hmysql, 0); + } + +finalize_it: + RETiRet; +} + + +/* The following function writes the current log entry + * to an established MySQL session. + * Initially added 2004-10-28 mmeckelein + */ +rsRetVal writeMySQL(uchar *psz, instanceData *pData) +{ + DEFiRet; + + ASSERT(psz != NULL); + ASSERT(pData != NULL); + + /* see if we are ready to proceed */ + if(pData->f_hmysql == NULL) { + CHKiRet(initMySQL(pData, 0)); + + } + + /* try insert */ + if(mysql_query(pData->f_hmysql, (char*)psz)) { + /* error occured, try to re-init connection and retry */ + closeMySQL(pData); /* close the current handle */ + CHKiRet(initMySQL(pData, 0)); /* try to re-open */ + if(mysql_query(pData->f_hmysql, (char*)psz)) { /* re-try insert */ + /* we failed, giving up for now */ + reportDBError(pData, 0); + closeMySQL(pData); /* free ressources */ + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + } + +finalize_it: + if(iRet == RS_RET_OK) { + pData->uLastMySQLErrno = 0; /* reset error for error supression */ + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + if(pData->f_hmysql == NULL) { + iRet = initMySQL(pData, 1); + } +ENDtryResume + +BEGINbeginTransaction +CODESTARTbeginTransaction + CHKiRet(writeMySQL((uchar*)"START TRANSACTION", pData)); +finalize_it: +ENDbeginTransaction + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + CHKiRet(writeMySQL(ppString[0], pData)); + iRet = RS_RET_DEFER_COMMIT; +finalize_it: +ENDdoAction + +BEGINendTransaction +CODESTARTendTransaction + if (mysql_commit(pData->f_hmysql) != 0) { + dbgprintf("mysql server error: transaction not committed\n"); + iRet = RS_RET_SUSPENDED; + } +ENDendTransaction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->f_dbsrvPort = 0; + pData->f_configfile = NULL; + pData->f_configsection = NULL; + pData->tplName = NULL; + pData->f_hmysql = NULL; /* initialize, but connect only on first message (important for queued mode!) */ +} + + +/* note: we use the fixed-size buffers inside the config object to avoid + * changing too much of the previous plumbing. rgerhards, 2012-02-02 + */ +BEGINnewActInst + struct cnfparamvals *pvals; + int i; + char *cstr; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTparseSelectorAct(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "server")) { + cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + strncpy(pData->f_dbsrv, cstr, sizeof(pData->f_dbsrv)); + free(cstr); + } else if(!strcmp(actpblk.descr[i].name, "serverport")) { + pData->f_dbsrvPort = (int) pvals[i].val.d.n, NULL; + } else if(!strcmp(actpblk.descr[i].name, "db")) { + cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + strncpy(pData->f_dbname, cstr, sizeof(pData->f_dbname)); + free(cstr); + } else if(!strcmp(actpblk.descr[i].name, "uid")) { + cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + strncpy(pData->f_dbuid, cstr, sizeof(pData->f_dbuid)); + free(cstr); + } else if(!strcmp(actpblk.descr[i].name, "pwd")) { + cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + strncpy(pData->f_dbpwd, cstr, sizeof(pData->f_dbpwd)); + free(cstr); + } else if(!strcmp(actpblk.descr[i].name, "mysqlconfig.file")) { + pData->f_configfile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "mysqlconfig.section")) { + pData->f_configsection = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("ommysql: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*) strdup(" StdDBFmt"), + OMSR_RQD_TPL_OPT_SQL)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, + (uchar*) strdup((char*) pData->tplName), + OMSR_RQD_TPL_OPT_SQL)); + } +CODE_STD_FINALIZERnewActInst +dbgprintf("XXXX: added param, iRet %d\n", iRet); + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct + int iMySQLPropErr = 0; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us + * The first test [*p == '>'] can be skipped if a module shall only + * support the newer slection syntax [:modname:]. This is in fact + * recommended for new modules. Please note that over time this part + * will be handled by rsyslogd itself, but for the time being it is + * a good compromise to do it at the module level. + * rgerhards, 2007-10-15 + */ + if(*p == '>') { + p++; /* eat '>' '*/ + } else if(!strncmp((char*) p, ":ommysql:", sizeof(":ommysql:") - 1)) { + p += sizeof(":ommysql:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + CHKiRet(createInstance(&pData)); + + /* rger 2004-10-28: added support for MySQL + * >server,dbname,userid,password + * Now we read the MySQL connection properties + * and verify that the properties are valid. + */ + if(getSubString(&p, pData->f_dbsrv, MAXHOSTNAMELEN+1, ',')) + iMySQLPropErr++; + if(*pData->f_dbsrv == '\0') + iMySQLPropErr++; + if(getSubString(&p, pData->f_dbname, _DB_MAXDBLEN+1, ',')) + iMySQLPropErr++; + if(*pData->f_dbname == '\0') + iMySQLPropErr++; + if(getSubString(&p, pData->f_dbuid, _DB_MAXUNAMELEN+1, ',')) + iMySQLPropErr++; + if(*pData->f_dbuid == '\0') + iMySQLPropErr++; + if(getSubString(&p, pData->f_dbpwd, _DB_MAXPWDLEN+1, ';')) + iMySQLPropErr++; + /* now check for template + * We specify that the SQL option must be present in the template. + * This is for your own protection (prevent sql injection). + */ + if(*(p-1) == ';') + --p; /* TODO: the whole parsing of the MySQL module needs to be re-thought - but this here + * is clean enough for the time being -- rgerhards, 2007-07-30 + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_RQD_TPL_OPT_SQL, (uchar*) " StdDBFmt")); + + /* If we detect invalid properties, we disable logging, + * because right properties are vital at this place. + * Retries make no sense. + */ + if (iMySQLPropErr) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "Trouble with MySQL connection properties. -MySQL logging disabled"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } else { + pData->f_dbsrvPort = (unsigned) cs.iSrvPort; /* set configured port */ + pData->f_configfile = cs.pszMySQLConfigFile; + pData->f_configsection = cs.pszMySQLConfigSection; + pData->f_hmysql = NULL; /* initialize, but connect only on first message (important for queued mode!) */ + } + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +# ifdef HAVE_MYSQL_LIBRARY_INIT + mysql_library_end(); +# else + mysql_server_end(); +# endif +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + cs.iSrvPort = 0; /* zero is the default port */ + free(cs.pszMySQLConfigFile); + cs.pszMySQLConfigFile = NULL; + free(cs.pszMySQLConfigSection); + cs.pszMySQLConfigSection = NULL; + RETiRet; +} + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); + if(!bCoreSupportsBatching) { + errmsg.LogError(0, NO_ERRCODE, "ommysql: rsyslog core too old"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* we need to init the MySQL library. If that fails, we cannot run */ + if( +# ifdef HAVE_MYSQL_LIBRARY_INIT + mysql_library_init(0, NULL, NULL) +# else + mysql_server_init(0, NULL, NULL) +# endif + ) { + errmsg.LogError(0, NO_ERRCODE, "ommysql: mysql_server_init() failed, plugin " + "can not run"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* register our config handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionommysqlserverport", 0, eCmdHdlrInt, NULL, &cs.iSrvPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"ommysqlconfigfile",0,eCmdHdlrGetWord,NULL,&cs.pszMySQLConfigFile,STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"ommysqlconfigsection",0,eCmdHdlrGetWord,NULL,&cs.pszMySQLConfigSection,STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/ommysql/ommysql.h b/plugins/ommysql/ommysql.h new file mode 100644 index 00000000..d8075785 --- /dev/null +++ b/plugins/ommysql/ommysql.h @@ -0,0 +1,31 @@ +/* omusrmsg.c + * These are the definitions for the build-in MySQL output module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMMYSQL_H_INCLUDED +#define OMMYSQL_H_INCLUDED 1 + +#endif /* #ifndef OMMYSQL_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/plugins/omoracle/Makefile.am b/plugins/omoracle/Makefile.am new file mode 100644 index 00000000..11257fb2 --- /dev/null +++ b/plugins/omoracle/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omoracle.la + +omoracle_la_SOURCES = omoracle.c omoracle.h +omoracle_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(ORACLE_CFLAGS) +omoracle_la_LDFLAGS = -module -avoid-version +omoracle_la_LIBADD = $(ORACLE_LIBS) + +#EXTRA_DIST = diff --git a/plugins/omoracle/omoracle.c b/plugins/omoracle/omoracle.c new file mode 100644 index 00000000..736629a6 --- /dev/null +++ b/plugins/omoracle/omoracle.c @@ -0,0 +1,637 @@ +/** omoracle.c + + This is an output module feeding directly to an Oracle + database. It uses Oracle Call Interface, a propietary module + provided by Oracle. + + Selector lines to be used are of this form: + + :omoracle:;TemplateName + + The module gets its configuration via rsyslog $... directives, + namely: + + $OmoracleDBUser: user name to log in on the database. + + $OmoracleDBPassword: password to log in on the database. + + $OmoracleDB: connection string (an Oracle easy connect or a db + name as specified by tnsnames.ora) + + $OmoracleBatchSize: Number of elements to send to the DB on each + transaction. + + $OmoracleBatchItemSize: Number of characters each property may + have. Make it as big as the longest value you expect for *any* + property in the sentence. For instance, if you expect 5 arguments + to the statement, 4 have 10 bytes and the 5th may be up to 3KB, + then specify $OmoracleBatchItemSize 3072. Please, remember to + leave space to the trailing \0!! + + $OmoracleStatementTemplate: Name of the template containing the + statement to be prepared and executed in batches. Please note that + Oracle's prepared statements have their placeholders as + ':identifier', and this module uses the colon to guess how many + placeholders there will be. + + All these directives are mandatory. The dbstring can be an Oracle + easystring or a DB name, as present in the tnsnames.ora file. + + The form of the template is just a list of strings you want + inserted to the DB, for instance: + + $template TestStmt,"%hostname%%msg%" + + Will provide the arguments to a statement like + + $OmoracleStatement \ + insert into foo(hostname,message)values(:host,:message) + + Also note that identifiers to placeholders are arbitrary. You need + to define the properties on the template in the correct order you + want them passed to the statement! + + This file is licensed under the terms of the GPL version 3 or, at + your choice, any later version. Exceptionally (perhaps), you are + allowed to link to the Oracle Call Interface in your derived work + + Author: Luis Fernando Muñoz MejÃas + <Luis.Fernando.Munoz.Mejias@cern.ch> + + This file is part of rsyslog. +*/ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <oci.h> +#include <errno.h> +#include <stdarg.h> +#include <signal.h> +#include <time.h> +#include <assert.h> +#include <ctype.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "omoracle.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omoracle") + +/** */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/** Structure defining a batch of items to be sent to the database in + * the same statement execution. */ +struct oracle_batch +{ + /* Batch size */ + int size; + /* Last element inserted in the buffer. The batch will be + * executed when n == size */ + int n; + /* Number of arguments the statement takes */ + int arguments; + /** Maximum size of each parameter */ + int param_size; + /* Parameters to pass to the statement on this transaction */ + char*** parameters; + /* Binding parameters */ + OCIBind** bindings; +}; + +typedef struct _instanceData { + /* Environment handler, the base for any OCI work. */ + OCIEnv* environment; + /* Session handler, the actual DB connection object. */ + OCISession* session; + /* Error handler for OCI calls. */ + OCIError* error; + /* Prepared statement. */ + OCIStmt* statement; + /* Service handler. */ + OCISvcCtx* service; + /* Credentials object for the connection. */ + OCIAuthInfo* authinfo; + /* Connection string, kept here for possible retries. */ + char* connection; + /* Statement to be prepared. */ + char* txt_statement; + /* Batch */ + struct oracle_batch batch; +} instanceData; + +/* To be honest, strlcpy is faster than strncpy and makes very easy to + * detect if a message has been truncated. */ +#ifndef strlcpy +#define strlcpy(dst,src,sz) snprintf((dst), (sz), "%s", (src)) +#endif + + +/** Database name, to be filled by the $OmoracleDB directive */ +static char* db_name; +/** Database user name, to be filled by the $OmoracleDBUser + * directive */ +static char* db_user; +/** Database password, to be filled by the $OmoracleDBPassword */ +static char* db_password; +/** Batch size. */ +static int batch_size; +/** Size of each element in the batch. */ +static int batch_item_size; +/** Statement to prepare and execute */ +static char* db_statement; + +/** Generic function for handling errors from OCI. + + It will be called only inside CHECKERR and CHECKENV macros. + + Arguments: handle The error or environment handle to check. + htype: OCI_HTYPE_* constant, usually OCI_HTYPE_ERROR or + OCI_HTYPE_ENV + status: status code to check, usually the return value of an OCI + function. + + Returns OCI_SUCCESS on success, OCI_ERROR otherwise. +*/ +static int oci_errors(void* handle, ub4 htype, sword status) +{ + sb4 errcode; + unsigned char buf[MAX_BUFSIZE]; + + switch (status) { + case OCI_SUCCESS: + return OCI_SUCCESS; + break; + case OCI_SUCCESS_WITH_INFO: + OCIErrorGet(handle, 1, NULL, &errcode, buf, sizeof buf, htype); + errmsg.LogError(0, NO_ERRCODE, "OCI SUCCESS - With info: %s", + buf); + return OCI_SUCCESS_WITH_INFO; + case OCI_NEED_DATA: + errmsg.LogError(0, NO_ERRCODE, "OCI NEEDS MORE DATA\n"); + break; + case OCI_ERROR: + dbgprintf ("OCI GENERAL ERROR\n"); + if (handle) { + OCIErrorGet(handle, 1, NULL, &errcode, buf, + sizeof buf, htype); + errmsg.LogError(0, NO_ERRCODE, "Error message: %s", buf); + } else + errmsg.LogError(0, NO_ERRCODE, "NULL handle\n" + "Unable to extract further " + "information"); + break; + case OCI_INVALID_HANDLE: + errmsg.LogError(0, NO_ERRCODE, "OCI INVALID HANDLE\n"); + /* In this case we may have to trigger a call to + * tryResume(). */ + return RS_RET_SUSPENDED; + break; + case OCI_STILL_EXECUTING: + errmsg.LogError(0, NO_ERRCODE, "Still executing...\n"); + break; + case OCI_CONTINUE: + errmsg.LogError(0, NO_ERRCODE, "OCI CONTINUE\n"); + break; + } + return OCI_ERROR; +} + +/** Callback for OCIBindDynamic. + * + * OCI doesn't insert an array of char* by itself (although it can + * handle arrays of int), so we must either run in batches of size one + * (no way) or bind all parameters with OCI_DATA_AT_EXEC instead of + * OCI_DEFAULT, and then give this function as an argument to + * OCIBindDynamic so that it is able to handle all strings in a single + * server trip. + * + * See the documentation of OCIBindDynamic + * (http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28395/oci16rel003.htm#i444015) + * for more details. + */ +static int bind_dynamic (char** in, OCIBind __attribute__((unused))* bind, + int iter, int __attribute__((unused)) idx, + char** out, int* buflen, unsigned char* piece, + void** bd) +{ + *out = in[iter]; + *buflen = strlen(*out) + 1; + dbgprintf ("omoracle bound line %d, length %d: %s\n", iter, *buflen, + *out); + *piece = OCI_ONE_PIECE; + *bd = NULL; + return OCI_CONTINUE; +} + + +/** Returns the number of bind parameters for the statement given as + * an argument. It counts the number of appearances of ':', as in + * + * insert into foo(bar, baz) values(:bar, :baz) + * + * while taking in account that string literals must not be parsed. */ +static int count_bind_parameters(char* p) +{ + int n = 0; + int enable = 1; + + for (; *p; p++) + if (enable && *p == BIND_MARK ) + n++; + else if (*p == '\'') + enable ^= 1; + dbgprintf ("omoracle statement has %d parameters\n", n); + return n; +} + +/** Prepares the statement, binding all its positional parameters */ +static int prepare_statement(instanceData* pData) +{ + int i; + DEFiRet; + + CHECKERR(pData->error, + OCIStmtPrepare(pData->statement, + pData->error, + pData->txt_statement, + strlen(pData->txt_statement), + OCI_NTV_SYNTAX, OCI_DEFAULT)); + for (i = 0; i < pData->batch.arguments; i++) { + CHECKERR(pData->error, + OCIBindByPos(pData->statement, + pData->batch.bindings+i, + pData->error, i+1, NULL, + pData->batch.param_size, + SQLT_STR, NULL, NULL, NULL, + 0, 0, OCI_DATA_AT_EXEC)); + CHECKERR(pData->error, + OCIBindDynamic(pData->batch.bindings[i], + pData->error, + pData->batch.parameters[i], + bind_dynamic, NULL, NULL)); + } + +finalize_it: + RETiRet; +} + + +/* Resource allocation */ +BEGINcreateInstance + int i, j; + struct template* tpl; +CODESTARTcreateInstance + + ASSERT(pData != NULL); + + CHECKENV(pData->environment, + OCIEnvCreate((void*) &(pData->environment), OCI_DEFAULT, + NULL, NULL, NULL, NULL, 0, NULL)); + CHECKENV(pData->environment, + OCIHandleAlloc(pData->environment, (void*) &(pData->error), + OCI_HTYPE_ERROR, 0, NULL)); + CHECKENV(pData->environment, + OCIHandleAlloc(pData->environment, (void*) &(pData->authinfo), + OCI_HTYPE_AUTHINFO, 0, NULL)); + CHECKENV(pData->environment, + OCIHandleAlloc(pData->environment, (void*) &(pData->statement), + OCI_HTYPE_STMT, 0, NULL)); + tpl = tplFind(db_statement, strlen(db_statement)); + pData->txt_statement = strdup(tpl->pEntryRoot->data.constant.pConstant); + CHKmalloc(pData->txt_statement); + dbgprintf("omoracle will run stored statement: %s\n", + pData->txt_statement); + + pData->batch.n = 0; + pData->batch.size = batch_size; + pData->batch.param_size = batch_item_size * + sizeof ***pData->batch.parameters; + pData->batch.arguments = count_bind_parameters(pData->txt_statement); + + /* I know, this can be done with a single malloc() call but this is + * easier to read. :) */ + pData->batch.parameters = calloc(pData->batch.arguments, + sizeof *pData->batch.parameters); + CHKmalloc(pData->batch.parameters); + for (i = 0; i < pData->batch.arguments; i++) { + pData->batch.parameters[i] = calloc(pData->batch.size, + sizeof **pData->batch.parameters); + CHKmalloc(pData->batch.parameters[i]); + for (j = 0; j < pData->batch.size; j++) { + /* Each entry has at most + * pData->batch.param_size bytes because OCI + * doesn't like null-terminated strings when + * operating with batches, and the maximum + * size of each entry must be provided when + * binding parameters. pData->batch.param_size + * is long enough for usual entries. */ + pData->batch.parameters[i][j] = malloc(pData->batch.param_size); + CHKmalloc(pData->batch.parameters[i][j]); + } + } + + pData->batch.bindings = calloc(pData->batch.arguments, + sizeof *pData->batch.bindings); + CHKmalloc(pData->batch.bindings); + +finalize_it: +ENDcreateInstance + +/* Analyses the errors during a batch statement execution, and logs + * all the corresponding ORA-MESSAGES, together with some useful + * information. */ +static void log_detailed_err(instanceData* pData) +{ + DEFiRet; + int errs, i, row, code, j; + OCIError *er = NULL, *er2 = NULL; + unsigned char buf[MAX_BUFSIZE]; + + OCIAttrGet(pData->statement, OCI_HTYPE_STMT, &errs, 0, + OCI_ATTR_NUM_DML_ERRORS, pData->error); + errmsg.LogError(0, NO_ERRCODE, "OCI: %d errors in execution of " + "statement: %s", errs, pData->txt_statement); + + CHECKENV(pData->environment, + OCIHandleAlloc(pData->environment, &er, OCI_HTYPE_ERROR, + 0, NULL)); + CHECKENV(pData->environment, + OCIHandleAlloc(pData->environment, &er2, OCI_HTYPE_ERROR, + 0, NULL)); + + for (i = 0; i < errs; i++) { + OCIParamGet(pData->error, OCI_HTYPE_ERROR, + er2, &er, i); + OCIAttrGet(er, OCI_HTYPE_ERROR, &row, 0, + OCI_ATTR_DML_ROW_OFFSET, er2); + errmsg.LogError(0, NO_ERRCODE, "OCI failure in row %d:", row); + for (j = 0; j < pData->batch.arguments; j++) + errmsg.LogError(0, NO_ERRCODE, "%s", + pData->batch.parameters[j][row]); + OCIErrorGet(er, 1, NULL, &code, buf, sizeof buf, + OCI_HTYPE_ERROR); + errmsg.LogError(0, NO_ERRCODE, "FAILURE DETAILS: %s", buf); + } + +finalize_it: + OCIHandleFree(er, OCI_HTYPE_ERROR); + OCIHandleFree(er2, OCI_HTYPE_ERROR); +} + + +/* Inserts all stored statements into the database, releasing any + * allocated memory. */ +static int insert_to_db(instanceData* pData) +{ + DEFiRet; + + CHECKERR(pData->error, + OCIStmtExecute(pData->service, + pData->statement, + pData->error, + pData->batch.n, 0, NULL, NULL, + OCI_BATCH_ERRORS)); + +finalize_it: + if (iRet == OCI_SUCCESS_WITH_INFO) { + log_detailed_err(pData); + iRet = RS_RET_OK; + } + pData->batch.n = 0; + OCITransCommit(pData->service, pData->error, 0); + dbgprintf ("omoracle insertion to DB %s\n", iRet == RS_RET_OK ? + "succeeded" : "did not succeed"); + RETiRet; +} + +/** Close the session and free anything allocated by + createInstance. */ +BEGINfreeInstance + int i, j; +CODESTARTfreeInstance + +/* Before actually releasing our resources, let's try to commit + * anything pending so that we don't lose any messages. */ + insert_to_db(pData); + OCISessionRelease(pData->service, pData->error, NULL, 0, OCI_DEFAULT); + OCIHandleFree(pData->environment, OCI_HTYPE_ENV); + OCIHandleFree(pData->error, OCI_HTYPE_ERROR); + OCIHandleFree(pData->service, OCI_HTYPE_SVCCTX); + OCIHandleFree(pData->authinfo, OCI_HTYPE_AUTHINFO); + OCIHandleFree(pData->statement, OCI_HTYPE_STMT); + free(pData->connection); + free(pData->txt_statement); + for (i = 0; i < pData->batch.arguments; i++) { + for (j = 0; j < pData->batch.size; j++) + free(pData->batch.parameters[i][j]); + free(pData->batch.parameters[i]); + } + free(pData->batch.parameters); + free(pData->batch.bindings); + dbgprintf ("omoracle freed all its resources\n"); + +ENDfreeInstance + +BEGINtryResume +CODESTARTtryResume + /* Here usually only a reconnect is done. The rsyslog core will call + * this entry point from time to time when the action suspended itself. + * Note that the rsyslog core expects that if the plugin suspended itself + * the action was not carried out during that invocation. Thus, rsyslog + * will call the action with *the same* data item again AFTER a resume + * was successful. As such, tryResume should NOT write the failed data + * item. If it needs to for some reason, it must delete the item again, + * otherwise, it will get duplicated. + * This handling inside the rsyslog core is important to be able to + * preserve data over rsyslog restarts. With it, the core can ensure that + * it queues all not-yet-processed messages without the plugin needing + * to take care about that. + * So in essence, it is recommended that just a reconnet is tried, but + * the last action not restarted. Note that it is not a real problem + * (but causes a slight performance degradation) if tryResume returns + * successfully but the next call to doAction() immediately returns + * RS_RET_SUSPENDED. So it is OK to do the actual restart inside doAction(). + * ... of course I don't know why Oracle might need a full restart... + * rgerhards, 2009-03-26 + */ + dbgprintf("omoracle attempting to reconnect to DB server\n"); + OCISessionRelease(pData->service, pData->error, NULL, 0, OCI_DEFAULT); + OCIHandleFree(pData->service, OCI_HTYPE_SVCCTX); + CHECKERR(pData->error, OCISessionGet(pData->environment, pData->error, + &pData->service, pData->authinfo, + pData->connection, + strlen(pData->connection), NULL, 0, + NULL, NULL, NULL, OCI_DEFAULT)); + CHKiRet(prepare_statement(pData)); + +finalize_it: +ENDtryResume + +static rsRetVal startSession(instanceData* pData, char* connection, char* user, + char * password) +{ + DEFiRet; + CHECKERR(pData->error, + OCIAttrSet(pData->authinfo, OCI_HTYPE_AUTHINFO, user, + strlen(user), OCI_ATTR_USERNAME, pData->error)); + CHECKERR(pData->error, + OCIAttrSet(pData->authinfo, OCI_HTYPE_AUTHINFO, password, + strlen(password), OCI_ATTR_PASSWORD, pData->error)); + CHECKERR(pData->error, + OCISessionGet(pData->environment, pData->error, + &pData->service, pData->authinfo, connection, + strlen(connection), NULL, 0, NULL, NULL, NULL, + OCI_DEFAULT)); +finalize_it: + if (iRet != RS_RET_OK) + errmsg.LogError(0, NO_ERRCODE, + "Unable to start Oracle session\n"); + RETiRet; +} + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* Right now, this module is compatible with nothing. */ + iRet = RS_RET_INCOMPATIBLE; +ENDisCompatibleWithFeature + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1); + + if (strncmp((char*) p, ":omoracle:", sizeof ":omoracle:" - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + p += sizeof ":omoracle:" - 1; + + if (*p == '\0' || *p == ',') { + errmsg.LogError(0, NO_ERRCODE, + "Wrong char processing module arguments: %c\n", + *p); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, + OMSR_TPL_AS_ARRAY, " StdFmt")); + CHKiRet(createInstance(&pData)); + CHKmalloc(pData->connection = strdup(db_name)); + CHKiRet(startSession(pData, db_name, db_user, db_password)); + + CHKiRet(prepare_statement(pData)); + + dbgprintf ("omoracle module got all its resources allocated " + "and connected to the DB\n"); + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + +BEGINdoAction + int i, sz; + char **params = (char**) ppString[0]; +CODESTARTdoAction + + if (pData->batch.n == pData->batch.size) { + dbgprintf("omoracle batch size limit hit, sending into DB\n"); + CHKiRet(insert_to_db(pData)); + } + + for (i = 0; i < pData->batch.arguments && params[i]; i++) { + dbgprintf("batch[%d][%d]=%s\n", i, pData->batch.n, params[i]); + sz = strlcpy(pData->batch.parameters[i][pData->batch.n], + params[i], pData->batch.param_size); + if (sz >= pData->batch.param_size) + errmsg.LogError(0, NO_ERRCODE, + "Possibly truncated %d column of '%s' " + "statement: %s", i, + pData->txt_statement, params[i]); + } + pData->batch.n++; + +finalize_it: +ENDdoAction + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, + void __attribute__((unused)) *pVal) +{ + int n; + DEFiRet; + if(db_user != NULL) + free(db_user); + if(db_name != NULL) + free(db_name); + if (db_password != NULL) { + n = strlen(db_password); + memset(db_password, 0, n); + free(db_password); + } + if (db_statement != NULL) + free(db_statement); + db_name = db_user = db_password = db_statement = NULL; + batch_size = batch_item_size = 0; + RETiRet; +} + +BEGINmodInit() + rsRetVal (*supported_options)(unsigned long *pOpts); + unsigned long opts; +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(omsdRegCFSLineHdlr((uchar*) "resetconfigvariables", 1, + eCmdHdlrCustomHandler, resetConfigVariables, + NULL, STD_LOADABLE_MODULE_ID)); + + CHKiRet(omsdRegCFSLineHdlr((uchar*) "omoracledbuser", 0, + eCmdHdlrGetWord, NULL, &db_user, + STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar*) "omoracledbpassword", 0, + eCmdHdlrGetWord, NULL, &db_password, + STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar*) "omoracledb", 0, + eCmdHdlrGetWord, NULL, &db_name, + STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar*) "omoraclebatchsize", 0, + eCmdHdlrInt, NULL, &batch_size, + STD_LOADABLE_MODULE_ID)); + CHKiRet(pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", &supported_options)); + CHKiRet((*supported_options)(&opts)); + if (!(opts & OMSR_TPL_AS_ARRAY)) + ABORT_FINALIZE(RS_RET_RSCORE_TOO_OLD); + + CHKiRet(omsdRegCFSLineHdlr((uchar*) "omoraclestatementtemplate", 0, + eCmdHdlrGetWord, NULL, + &db_statement, STD_LOADABLE_MODULE_ID)); + + CHKiRet(omsdRegCFSLineHdlr((uchar*) "omoraclebatchitemsize", 0, + eCmdHdlrInt, NULL, + &batch_item_size, STD_LOADABLE_MODULE_ID)); + +ENDmodInit diff --git a/plugins/omoracle/omoracle.h b/plugins/omoracle/omoracle.h new file mode 100644 index 00000000..0ff879b3 --- /dev/null +++ b/plugins/omoracle/omoracle.h @@ -0,0 +1,31 @@ +/** Definitions for the Oracle output module. + + This module needs OCI to be installed (on Red Hat-like systems + this is usually the oracle-instantclient-devel RPM). + + This file is part of rsyslog. + + This file is licensed under the terms of the GPL version 3 or, at + your choice, any later version. Exceptionally (perhaps), you are + allowed to link to the Oracle Call Interface in your derived work + + Author: Luis Fernando Muñoz MejÃas <Luis.Fernando.Munoz.Mejias@cern.ch> +*/ +#ifndef __OMORACLEH__ +#define __OMORACLEH__ + +/** Macros to make error handling easier. */ + +/** Checks for errors on the OCI handling. */ +#define CHECKERR(handle,status) CHKiRet(oci_errors((handle), \ + OCI_HTYPE_ERROR, (status))) + +/** Checks for errors when handling the environment of OCI. */ +#define CHECKENV(handle,status) CHKiRet(oci_errors((handle), \ + OCI_HTYPE_ENV, (status))) + +enum { MAX_BUFSIZE = 2048 }; + +#define BIND_MARK ':' + +#endif diff --git a/plugins/omoracle/omoracle.te b/plugins/omoracle/omoracle.te new file mode 100644 index 00000000..81eb6cf1 --- /dev/null +++ b/plugins/omoracle/omoracle.te @@ -0,0 +1,13 @@ + +module omoracle 1.0; + +require { + type syslogd_t; + type port_t; + class process { execstack execmem }; + class tcp_socket name_connect; +} + +#============= syslogd_t ============== +allow syslogd_t port_t:tcp_socket name_connect; +allow syslogd_t self:process { execstack execmem }; diff --git a/plugins/ompgsql/Makefile.am b/plugins/ompgsql/Makefile.am new file mode 100644 index 00000000..607239cd --- /dev/null +++ b/plugins/ompgsql/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = ompgsql.la + +ompgsql_la_SOURCES = ompgsql.c ompgsql.h +ompgsql_la_CPPFLAGS = -I$(top_srcdir) $(PGSQL_CFLAGS) $(RSRT_CFLAGS) +ompgsql_la_LDFLAGS = -module -avoid-version +ompgsql_la_LIBADD = $(PGSQL_LIBS) + +EXTRA_DIST = createDB.sql diff --git a/plugins/ompgsql/createDB.sql b/plugins/ompgsql/createDB.sql new file mode 100644 index 00000000..2f72a0a6 --- /dev/null +++ b/plugins/ompgsql/createDB.sql @@ -0,0 +1,37 @@ +CREATE DATABASE 'Syslog' WITH ENCODING 'SQL_ASCII'; +\c Syslog; +CREATE TABLE SystemEvents +( + ID serial not null primary key, + CustomerID bigint, + ReceivedAt timestamp without time zone NULL, + DeviceReportedTime timestamp without time zone NULL, + Facility smallint NULL, + Priority smallint NULL, + FromHost varchar(60) NULL, + Message text, + NTSeverity int NULL, + Importance int NULL, + EventSource varchar(60), + EventUser varchar(60) NULL, + EventCategory int NULL, + EventID int NULL, + EventBinaryData text NULL, + MaxAvailable int NULL, + CurrUsage int NULL, + MinUsage int NULL, + MaxUsage int NULL, + InfoUnitID int NULL , + SysLogTag varchar(60), + EventLogType varchar(60), + GenericFileName VarChar(60), + SystemID int NULL +); + +CREATE TABLE SystemEventsProperties +( + ID serial not null primary key, + SystemEventID int NULL , + ParamName varchar(255) NULL , + ParamValue text NULL +); diff --git a/plugins/ompgsql/ompgsql.c b/plugins/ompgsql/ompgsql.c new file mode 100644 index 00000000..11f346f6 --- /dev/null +++ b/plugins/ompgsql/ompgsql.c @@ -0,0 +1,379 @@ +/* ompgsql.c + * This is the implementation of the build-in output module for PgSQL. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-10-18 by sur5r (converted from ommysql.c) + * + * Copyright 2007, 2009 Rainer Gerhards and Adiscon GmbH. + * + * The following link my be useful for the not-so-postgres literate + * when setting up a test environment (on Fedora): + * http://www.jboss.org/community/wiki/InstallPostgreSQLonFedora + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <time.h> +#include <libpq-fe.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "ompgsql.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("ompgsql") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + PGconn *f_hpgsql; /* handle to PgSQL */ + char f_dbsrv[MAXHOSTNAMELEN+1]; /* IP or hostname of DB server*/ + char f_dbname[_DB_MAXDBLEN+1]; /* DB name */ + char f_dbuid[_DB_MAXUNAMELEN+1]; /* DB user */ + char f_dbpwd[_DB_MAXPWDLEN+1]; /* DB user's password */ + ConnStatusType eLastPgSQLStatus; /* last status from postgres */ +} instanceData; + +typedef struct configSettings_s { + EMPTY_STRUCT +} configSettings_t; +static configSettings_t __attribute__((unused)) cs; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars +ENDinitConfVars + + +static rsRetVal writePgSQL(uchar *psz, instanceData *pData); + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +/* The following function is responsible for closing a + * PgSQL connection. + */ +static void closePgSQL(instanceData *pData) +{ + assert(pData != NULL); + + if(pData->f_hpgsql != NULL) { /* just to be on the safe side... */ + PQfinish(pData->f_hpgsql); + pData->f_hpgsql = NULL; + } +} + +BEGINfreeInstance +CODESTARTfreeInstance + closePgSQL(pData); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* nothing special here */ +ENDdbgPrintInstInfo + + +/* log a database error with descriptive message. + * We check if we have a valid handle. If not, we simply + * report an error, but can not be specific. RGerhards, 2007-01-30 + */ +static void reportDBError(instanceData *pData, int bSilent) +{ + char errMsg[512]; + ConnStatusType ePgSQLStatus; + + assert(pData != NULL); + bSilent=0; + + /* output log message */ + errno = 0; + if(pData->f_hpgsql == NULL) { + errmsg.LogError(0, NO_ERRCODE, "unknown DB error occured - could not obtain PgSQL handle"); + } else { /* we can ask pgsql for the error description... */ + ePgSQLStatus = PQstatus(pData->f_hpgsql); + snprintf(errMsg, sizeof(errMsg)/sizeof(char), "db error (%d): %s\n", ePgSQLStatus, + PQerrorMessage(pData->f_hpgsql)); + if(bSilent || ePgSQLStatus == pData->eLastPgSQLStatus) + dbgprintf("pgsql, DBError(silent): %s\n", errMsg); + else { + pData->eLastPgSQLStatus = ePgSQLStatus; + errmsg.LogError(0, NO_ERRCODE, "%s", errMsg); + } + } + + return; +} + + +/* The following function is responsible for initializing a + * PgSQL connection. + */ +static rsRetVal initPgSQL(instanceData *pData, int bSilent) +{ + DEFiRet; + + assert(pData != NULL); + assert(pData->f_hpgsql == NULL); + + dbgprintf("host=%s dbname=%s uid=%s\n",pData->f_dbsrv,pData->f_dbname,pData->f_dbuid); + + /* Force PostgreSQL to use ANSI-SQL conforming strings, otherwise we may + * get all sorts of side effects (e.g.: backslash escapes) and warnings + */ + const char *PgConnectionOptions = "-c standard_conforming_strings=on"; + + /* Connect to database */ + if((pData->f_hpgsql=PQsetdbLogin(pData->f_dbsrv, NULL, PgConnectionOptions, NULL, + pData->f_dbname, pData->f_dbuid, pData->f_dbpwd)) == NULL) { + reportDBError(pData, bSilent); + closePgSQL(pData); /* ignore any error we may get */ + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +/* try the insert into postgres and return if that failed or not + * (1 = had error, 0=ok). We do not use the standard IRET calling convention + * rgerhards, 2009-04-17 + */ +static inline int +tryExec(uchar *pszCmd, instanceData *pData) +{ + PGresult *pgRet; + ExecStatusType execState; + int bHadError = 0; + + /* try insert */ + pgRet = PQexec(pData->f_hpgsql, (char*)pszCmd); + execState = PQresultStatus(pgRet); + if(execState != PGRES_COMMAND_OK && execState != PGRES_TUPLES_OK) { + dbgprintf("postgres query execution failed: %s\n", PQresStatus(PQresultStatus(pgRet))); + bHadError = 1; + } + PQclear(pgRet); + + return(bHadError); +} + + +/* The following function writes the current log entry + * to an established PgSQL session. + * Enhanced function to take care of the returned error + * value (if there is such). Note that this may happen due to + * a sql format error - connection aborts were properly handled + * before my patch. -- rgerhards, 2009-04-17 + */ +static rsRetVal +writePgSQL(uchar *psz, instanceData *pData) +{ + int bHadError = 0; + DEFiRet; + + assert(psz != NULL); + assert(pData != NULL); + + dbgprintf("writePgSQL: %s\n", psz); + + bHadError = tryExec(psz, pData); /* try insert */ + + if(bHadError || (PQstatus(pData->f_hpgsql) != CONNECTION_OK)) { + /* error occured, try to re-init connection and retry */ + closePgSQL(pData); /* close the current handle */ + CHKiRet(initPgSQL(pData, 0)); /* try to re-open */ + bHadError = tryExec(psz, pData); /* retry */ + if(bHadError || (PQstatus(pData->f_hpgsql) != CONNECTION_OK)) { + /* we failed, giving up for now */ + reportDBError(pData, 0); + closePgSQL(pData); /* free ressources */ + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + } + +finalize_it: + if(iRet == RS_RET_OK) { + pData->eLastPgSQLStatus = CONNECTION_OK; /* reset error for error supression */ + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + if(pData->f_hpgsql == NULL) { + iRet = initPgSQL(pData, 1); + if(iRet == RS_RET_OK) { + /* the code above seems not to actually connect to the database. As such, we do a + * dummy statement (a pointless select...) to verify the connection and return + * success only when that statemetn succeeds. Note that I am far from being a + * PostgreSQL expert, so any patch that does the desired result in a more + * intelligent way is highly welcome. -- rgerhards, 2009-12-16 + */ + iRet = writePgSQL((uchar*)"select 'a' as a", pData); + } + + } +ENDtryResume + + +BEGINbeginTransaction +CODESTARTbeginTransaction +dbgprintf("ompgsql: beginTransaction\n"); + iRet = writePgSQL((uchar*) "begin", pData); /* TODO: make user-configurable */ +ENDbeginTransaction + + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + CHKiRet(writePgSQL(ppString[0], pData)); + if(bCoreSupportsBatching) + iRet = RS_RET_DEFER_COMMIT; +finalize_it: +ENDdoAction + + +BEGINendTransaction +CODESTARTendTransaction + iRet = writePgSQL((uchar*) "commit;", pData); /* TODO: make user-configurable */ +dbgprintf("ompgsql: endTransaction\n"); +ENDendTransaction + + +BEGINparseSelectorAct + int iPgSQLPropErr = 0; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us + * The first test [*p == '>'] can be skipped if a module shall only + * support the newer slection syntax [:modname:]. This is in fact + * recommended for new modules. Please note that over time this part + * will be handled by rsyslogd itself, but for the time being it is + * a good compromise to do it at the module level. + * rgerhards, 2007-10-15 + */ + + if(!strncmp((char*) p, ":ompgsql:", sizeof(":ompgsql:") - 1)) { + p += sizeof(":ompgsql:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + + + /* sur5r 2007-10-18: added support for PgSQL + * :ompgsql:server,dbname,userid,password + * Now we read the PgSQL connection properties + * and verify that the properties are valid. + */ + if(getSubString(&p, pData->f_dbsrv, MAXHOSTNAMELEN+1, ',')) + iPgSQLPropErr++; + dbgprintf("%p:%s\n",p,p); + if(*pData->f_dbsrv == '\0') + iPgSQLPropErr++; + if(getSubString(&p, pData->f_dbname, _DB_MAXDBLEN+1, ',')) + iPgSQLPropErr++; + if(*pData->f_dbname == '\0') + iPgSQLPropErr++; + if(getSubString(&p, pData->f_dbuid, _DB_MAXUNAMELEN+1, ',')) + iPgSQLPropErr++; + if(*pData->f_dbuid == '\0') + iPgSQLPropErr++; + if(getSubString(&p, pData->f_dbpwd, _DB_MAXPWDLEN+1, ';')) + iPgSQLPropErr++; + /* now check for template + * We specify that the SQL option must be present in the template. + * This is for your own protection (prevent sql injection). + */ + if(*(p-1) == ';') + --p; /* TODO: the whole parsing of the MySQL module needs to be re-thought - but this here + * is clean enough for the time being -- rgerhards, 2007-07-30 + * kept it for pgsql -- sur5r, 2007-10-19 + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_RQD_TPL_OPT_SQL, (uchar*) " StdPgSQLFmt")); + + /* If we detect invalid properties, we disable logging, + * because right properties are vital at this place. + * Retries make no sense. + */ + if (iPgSQLPropErr) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "Trouble with PgSQL connection properties. -PgSQL logging disabled"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } else { + CHKiRet(initPgSQL(pData, 0)); + } + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); + DBGPRINTF("ompgsql: module compiled with rsyslog version %s.\n", VERSION); + DBGPRINTF("ompgsql: %susing transactional output interface.\n", bCoreSupportsBatching ? "" : "not "); +ENDmodInit +/* vi:set ai: + */ diff --git a/plugins/ompgsql/ompgsql.h b/plugins/ompgsql/ompgsql.h new file mode 100644 index 00000000..495291f4 --- /dev/null +++ b/plugins/ompgsql/ompgsql.h @@ -0,0 +1,31 @@ +/* ompgsql.h + * These are the definitions for the build-in PgSQL output module. + * + * File begun on 2007-10-18 by sur5r (converted from ompgsql.h) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMPGSQL_H_INCLUDED +#define OMPGSQL_H_INCLUDED 1 + +#endif /* #ifndef OMPGSQL_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/plugins/omprog/Makefile.am b/plugins/omprog/Makefile.am new file mode 100644 index 00000000..63fe09b8 --- /dev/null +++ b/plugins/omprog/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omprog.la + +omprog_la_SOURCES = omprog.c +omprog_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +omprog_la_LDFLAGS = -module -avoid-version +omprog_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/omprog/omprog.c b/plugins/omprog/omprog.c new file mode 100644 index 00000000..e425b428 --- /dev/null +++ b/plugins/omprog/omprog.c @@ -0,0 +1,439 @@ +/* omprog.c + * This output plugin enables rsyslog to execute a program and + * feed it the message stream as standard input. + * + * NOTE: read comments in module-template.h for more specifics! + * + * File begun on 2009-04-01 by RGerhards + * + * Copyright 2009-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <wait.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omprog") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + uchar *szBinary; /* name of binary to call */ + uchar *tplName; /* assigned output template */ + pid_t pid; /* pid of currently running process */ + int fdPipe; /* file descriptor to write to */ + int bIsRunning; /* is binary currently running? 0-no, 1-yes */ +} instanceData; + +typedef struct configSettings_s { + uchar *szBinary; /* name of binary to call */ +} configSettings_t; +static configSettings_t cs; + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "binary", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.szBinary = NULL; /* name of binary to call */ +ENDinitConfVars + +/* config settings */ + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->szBinary != NULL) + free(pData->szBinary); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + + +/* execute the child process (must be called in child context + * after fork). + */ + +static void execBinary(instanceData *pData, int fdStdin) +{ + int i; + struct sigaction sigAct; + char *newargv[] = { NULL }; + char *newenviron[] = { NULL }; + + assert(pData != NULL); + + fclose(stdin); + if(dup(fdStdin) == -1) { + DBGPRINTF("omprog: dup() failed\n"); + /* do some more error handling here? Maybe if the module + * gets some more widespread use... + */ + } + //fclose(stdout); + + /* we close all file handles as we fork soon + * Is there a better way to do this? - mail me! rgerhards@adiscon.com + */ +# ifndef VALGRIND /* we can not use this with valgrind - too many errors... */ + for(i = 3 ; i <= 65535 ; ++i) + close(i); +# endif + + /* reset signal handlers to default */ + memset(&sigAct, 0, sizeof(sigAct)); + sigfillset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + for(i = 1 ; i < NSIG ; ++i) + sigaction(i, &sigAct, NULL); + + alarm(0); + + /* finally exec child */ + execve((char*)pData->szBinary, newargv, newenviron); + /* switch to? + execlp((char*)program, (char*) program, (char*)arg, NULL); + */ + + /* we should never reach this point, but if we do, we terminate */ + exit(1); +} + + +/* creates a pipe and starts program, uses pipe as stdin for program. + * rgerhards, 2009-04-01 + */ +static rsRetVal +openPipe(instanceData *pData) +{ + int pipefd[2]; + pid_t cpid; + DEFiRet; + + assert(pData != NULL); + + if(pipe(pipefd) == -1) { + ABORT_FINALIZE(RS_RET_ERR_CREAT_PIPE); + } + + DBGPRINTF("executing program '%s'\n", pData->szBinary); + + /* NO OUTPUT AFTER FORK! */ + + cpid = fork(); + if(cpid == -1) { + ABORT_FINALIZE(RS_RET_ERR_FORK); + } + + if(cpid == 0) { + /* we are now the child, just set the right selectors and + * exec the binary. If that fails, there is not much we can do. + */ + close(pipefd[1]); + execBinary(pData, pipefd[0]); + /*NO CODE HERE - WILL NEVER BE REACHED!*/ + } + + DBGPRINTF("child has pid %d\n", (int) cpid); + pData->fdPipe = pipefd[1]; + pData->pid = cpid; + close(pipefd[0]); + pData->bIsRunning = 1; +finalize_it: + RETiRet; +} + + +/* clean up after a terminated child + */ +static inline rsRetVal +cleanup(instanceData *pData) +{ + int status; + int ret; + char errStr[1024]; + DEFiRet; + + assert(pData != NULL); + assert(pData->bIsRunning == 1); + ret = waitpid(pData->pid, &status, 0); + if(ret != pData->pid) { + /* if waitpid() fails, we can not do much - try to ignore it... */ + DBGPRINTF("waitpid() returned state %d[%s], future malfunction may happen\n", ret, + rs_strerror_r(errno, errStr, sizeof(errStr))); + } else { + /* check if we should print out some diagnostic information */ + DBGPRINTF("waitpid status return for program '%s': %2.2x\n", + pData->szBinary, status); + if(WIFEXITED(status)) { + errmsg.LogError(0, NO_ERRCODE, "program '%s' exited normally, state %d", + pData->szBinary, WEXITSTATUS(status)); + } else if(WIFSIGNALED(status)) { + errmsg.LogError(0, NO_ERRCODE, "program '%s' terminated by signal %d.", + pData->szBinary, WTERMSIG(status)); + } + } + + pData->bIsRunning = 0; + RETiRet; +} + + +/* try to restart the binary when it has stopped. + */ +static inline rsRetVal +tryRestart(instanceData *pData) +{ + DEFiRet; + assert(pData != NULL); + assert(pData->bIsRunning == 0); + + iRet = openPipe(pData); + RETiRet; +} + + +/* write to pipe + * note that we do not try to run block-free. If the users fears something + * may block (and this not be acceptable), the action should be run on its + * own action queue. + */ +static rsRetVal +writePipe(instanceData *pData, uchar *szMsg) +{ + int lenWritten; + int lenWrite; + int writeOffset; + char errStr[1024]; + DEFiRet; + + assert(pData != NULL); + + lenWrite = strlen((char*)szMsg); + writeOffset = 0; + + do + { + lenWritten = write(pData->fdPipe, ((char*)szMsg)+writeOffset, lenWrite); + if(lenWritten == -1) { + switch(errno) { + case EPIPE: + DBGPRINTF("Program '%s' terminated, trying to restart\n", + pData->szBinary); + CHKiRet(cleanup(pData)); + CHKiRet(tryRestart(pData)); + break; + default: + DBGPRINTF("error %d writing to pipe: %s\n", errno, + rs_strerror_r(errno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_ERR_WRITE_PIPE); + break; + } + } else { + writeOffset += lenWritten; + } + } while(lenWritten != lenWrite); + + +finalize_it: + RETiRet; +} + + +BEGINdoAction +CODESTARTdoAction + if(pData->bIsRunning == 0) { + openPipe(pData); + } + + iRet = writePipe(pData, ppString[0]); + + if(iRet != RS_RET_OK) + iRet = RS_RET_SUSPENDED; +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->szBinary = NULL; + pData->fdPipe = -1; + pData->bIsRunning = 0; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "binary")) { + pData->szBinary = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omprog: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*) "RSYSLOG_FileFormat", + OMSR_NO_RQD_TPL_OPTS)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, + (uchar*) strdup((char*) pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + } +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":omprog:", sizeof(":omprog:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":omprog:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + if(cs.szBinary == NULL) { + errmsg.LogError(0, RS_RET_CONF_RQRD_PARAM_MISSING, + "no binary to execute specified"); + ABORT_FINALIZE(RS_RET_CONF_RQRD_PARAM_MISSING); + } + + CHKiRet(createInstance(&pData)); + + if(cs.szBinary == NULL) { + errmsg.LogError(0, RS_RET_CONF_RQRD_PARAM_MISSING, + "no binary to execute specified"); + ABORT_FINALIZE(RS_RET_CONF_RQRD_PARAM_MISSING); + } + + CHKmalloc(pData->szBinary = (uchar*) strdup((char*)cs.szBinary)); + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, 0, (uchar*) "RSYSLOG_FileFormat")); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + free(cs.szBinary); + cs.szBinary = NULL; + CHKiRet(objRelease(errmsg, CORE_COMPONENT)); +finalize_it: +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + free(cs.szBinary); + cs.szBinary = NULL; + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionomprogbinary", 0, eCmdHdlrGetWord, NULL, &cs.szBinary, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +CODEmodInit_QueryRegCFSLineHdlr +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/omrabbitmq/Makefile.am b/plugins/omrabbitmq/Makefile.am new file mode 100644 index 00000000..de374081 --- /dev/null +++ b/plugins/omrabbitmq/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omrabbitmq.la + +omrabbitmq_la_SOURCES = omrabbitmq.c +omrabbitmq_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +omrabbitmq_la_LDFLAGS = -module -avoid-version +omrabbitmq_la_LIBADD = $(RABBITMQ_LIBS) + +EXTRA_DIST = diff --git a/plugins/omrabbitmq/README.md b/plugins/omrabbitmq/README.md new file mode 100644 index 00000000..7aa60206 --- /dev/null +++ b/plugins/omrabbitmq/README.md @@ -0,0 +1,56 @@ + +# rsyslog output module for RabbitMQ + +This module sends syslog messages into RabbitMQ server. + +Only v6 configuration syntax is supported. + +**omrabbitmq is tested only with 6.6.0 version of rsyslog.** + + +## Compile +To successfully compile omrabbitmq module you need [rabbitmq-c](https://github.com/alanxz/rabbitmq-c) library. + + ./configure --enable-omrabbitmq ... + + +---- +## Configure + +omrabbitmq output module supports only v6 configuration syntax. + +Parameters: + +* host=<hostname> – server +* virtual_host=<virtual\_host> – virtual message broker +* user=<user> – user name +* password=<password> – password +* exchange=<name> – exchange name +* routing_key=<name> – name of routing key + + +Example: + + $ModLoad omrabbitmq + + *.* action(type="omrabbitmq" + host="localhost" + virtual_host="/" + user="guest" + password="guest" + exchange="syslog" + routing_key="syslog.all" + template="RSYSLOG_ForwardFormat" + queue.type="linkedlist" + queue.timeoutenqueue="0" + queue.filename="rabbitmq" + queue.highwatermark="500000" + queue.lowwatermark="400000" + queue.discardmark="5000000" + queue.timeoutenqueue="0" + queue.maxdiskspace="5g" + queue.size="2000000" + queue.saveonshutdown="on" + action.resumeretrycount="-1") + + diff --git a/plugins/omrabbitmq/omrabbitmq.c b/plugins/omrabbitmq/omrabbitmq.c new file mode 100644 index 00000000..7ea7793d --- /dev/null +++ b/plugins/omrabbitmq/omrabbitmq.c @@ -0,0 +1,466 @@ +/* omrabbitmq.c + * + * This output plugin enables rsyslog to send messages to the RabbitMQ. + * + * Copyright 2012-2013 Vaclav Tomec + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Vaclav Tomec + * <vaclav.tomec@gmail.com> + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <time.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +#include <amqp.h> + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omrabbitmq") + + +/* + * internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + + +typedef struct _instanceData { + /* here you need to define all action-specific data. A record of type + * instanceData will be handed over to each instance of the action. Keep + * in mind that there may be several invocations of the same type of action + * inside rsyslog.conf, and this is what keeps them apart. Do NOT use + * static data for this! + */ + amqp_connection_state_t conn; + amqp_basic_properties_t props; + uchar *host; + int port; + uchar *vhost; + uchar *user; + uchar *password; + uchar *exchange; + uchar *routing_key; + uchar *tplName; +} instanceData; + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "host", eCmdHdlrGetWord, 0 }, + { "port", eCmdHdlrInt, 0 }, + { "virtual_host", eCmdHdlrGetWord, 0 }, + { "user", eCmdHdlrGetWord, 0 }, + { "password", eCmdHdlrGetWord, 0 }, + { "exchange", eCmdHdlrGetWord, 0 }, + { "routing_key", eCmdHdlrGetWord, 0 }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { + CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + + +/* + * Report general error + */ +static int +die_on_error(int x, char const *context) +{ + int retVal = 0; // false + + if (x < 0) { + char *errstr = amqp_error_string(-x); + errmsg.LogError(0, RS_RET_ERR, "omrabbitmq: %s: %s", context, errstr); + free(errstr); + retVal = 1; // true + } + + return retVal; +} + + +/* + * Report AMQP specific error + */ +static int +die_on_amqp_error(amqp_rpc_reply_t x, char const *context) +{ + int retVal = 1; // true + + switch (x.reply_type) { + case AMQP_RESPONSE_NORMAL: + retVal = 0; // false + break; + + case AMQP_RESPONSE_NONE: + errmsg.LogError(0, RS_RET_ERR, "omrabbitmq: %s: missing RPC reply type!", context); + break; + + case AMQP_RESPONSE_LIBRARY_EXCEPTION: + errmsg.LogError(0, RS_RET_ERR, "omrabbitmq: %s: %s", context, amqp_error_string(x.library_error)); + break; + + case AMQP_RESPONSE_SERVER_EXCEPTION: + switch (x.reply.id) { + case AMQP_CONNECTION_CLOSE_METHOD: { + amqp_connection_close_t *m = (amqp_connection_close_t *) x.reply.decoded; + errmsg.LogError(0, RS_RET_ERR, "omrabbitmq: %s: server connection error %d, message: %.*s", + context, + m->reply_code, + (int) m->reply_text.len, (char *) m->reply_text.bytes); + break; + } + case AMQP_CHANNEL_CLOSE_METHOD: { + amqp_channel_close_t *m = (amqp_channel_close_t *) x.reply.decoded; + errmsg.LogError(0, RS_RET_ERR, "omrabbitmq: %s: server channel error %d, message: %.*s", + context, + m->reply_code, + (int) m->reply_text.len, (char *) m->reply_text.bytes); + break; + } + default: + errmsg.LogError(0, RS_RET_ERR, "omrabbitmq: %s: unknown server error, method id 0x%08X\n", context, x.reply.id); + break; + } + break; + + } + + return retVal; +} + + +static amqp_bytes_t +cstring_bytes(const char *str) +{ + return str ? amqp_cstring_bytes(str) : amqp_empty_bytes; +} + + +static void +closeAMQPConnection(instanceData *pData) +{ + if (pData->conn != NULL) { + die_on_amqp_error(amqp_channel_close(pData->conn, 1, AMQP_REPLY_SUCCESS), "amqp_channel_close"); + die_on_amqp_error(amqp_connection_close(pData->conn, AMQP_REPLY_SUCCESS), "amqp_connection_close"); + die_on_error(amqp_destroy_connection(pData->conn), "amqp_destroy_connection"); + + pData->conn = NULL; + } +} + + +/* + * Initialize RabbitMQ connection + */ +static rsRetVal +initRabbitMQ(instanceData *pData) +{ + int sockfd; + DEFiRet; + + DBGPRINTF("omrabbitmq: trying connect to '%s' at port %d\n", pData->host, pData->port); + + pData->conn = amqp_new_connection(); + + if (die_on_error(sockfd = amqp_open_socket((char*) pData->host, pData->port), "Opening socket")) { + pData->conn = NULL; + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + + amqp_set_sockfd(pData->conn, sockfd); + + if (die_on_amqp_error(amqp_login(pData->conn, (char*) pData->vhost, 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, pData->user, pData->password), + "Logging in")) { + pData->conn = NULL; + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + + amqp_channel_open(pData->conn, 1); + + if (die_on_amqp_error(amqp_get_rpc_reply(pData->conn), "Opening channel")) { + pData->conn = NULL; + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + RETiRet; +} + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* use this to specify if select features are supported by this + * plugin. If not, the framework will handle that. Currently, only + * RepeatedMsgReduction ("last message repeated n times") is optional. + */ + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + /* this is a cleanup callback. All dynamically-allocated resources + * in instance data must be cleaned up here. Prime examples are + * malloc()ed memory, file & database handles and the like. + */ + closeAMQPConnection(pData); + free(pData->host); + free(pData->vhost); + free(pData->user); + free(pData->password); + free(pData->exchange); + free(pData->routing_key); + free(pData->tplName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* permits to spit out some debug info */ + dbgprintf("omrabbitmq\n"); + dbgprintf("\thost='%s'\n", pData->host); + dbgprintf("\tport=%d\n", pData->port); + dbgprintf("\tvirtual_host='%s'\n", pData->vhost); + dbgprintf("\tuser='%s'\n", pData->user == NULL ? (uchar*)"(not configured)" : pData->user); + dbgprintf("\tpassword=(%sconfigured)\n", pData->password == NULL ? "not " : ""); + dbgprintf("\texchange='%s'\n", pData->exchange); + dbgprintf("\trouting_key='%s'\n", pData->routing_key); + dbgprintf("\ttemplate='%s'\n", pData->tplName); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume + /* this is called when an action has been suspended and the + * rsyslog core tries to resume it. The action must then + * retry (if possible) and report RS_RET_OK if it succeeded + * or RS_RET_SUSPENDED otherwise. + * Note that no data can be written in this callback, as it is + * not present. Prime examples of what can be retried are + * reconnects to remote hosts, reconnects to database, + * opening of files and the like. + * If there is no retry-type of operation, the action may + * return RS_RET_OK, so that it will get called on its doAction + * entry point (where it receives data), retries there, and + * immediately returns RS_RET_SUSPENDED if that does not work + * out. This disables some optimizations in the core's retry logic, + * but is a valid and expected behaviour. Note that it is also OK + * for the retry entry point to return OK but the immediately following + * doAction call to fail. In real life, for example, a buggy com line + * may cause such behaviour. + * Note that there is no guarantee that the core will very quickly + * call doAction after the retry succeeded. Today, it does, but that may + * not always be the case. + */ + + if (pData->conn == NULL) { + iRet = initRabbitMQ(pData); + } + +ENDtryResume + + +BEGINdoAction +CODESTARTdoAction + /* this is where you receive the message and need to carry out the + * action. Data is provided in ppString[i] where 0 <= i <= num of strings + * requested. + * Return RS_RET_OK if all goes well, RS_RET_SUSPENDED if the action can + * currently not complete, or an error code or RS_RET_DISABLED. The later + * two should only be returned if there is no hope that the action can be + * restored unless an rsyslog restart (prime example is an invalid config). + * Error code or RS_RET_DISABLED permanently disables the action, up to + * the next restart. + */ + + amqp_bytes_t body_bytes; + + if (pData->conn == NULL) { + CHKiRet(initRabbitMQ(pData)); + } + + body_bytes = amqp_cstring_bytes((char *)ppString[0]); + + if (die_on_error(amqp_basic_publish(pData->conn, 1, + cstring_bytes((char *) pData->exchange), + cstring_bytes((char *) pData->routing_key), + 0, 0, &pData->props, body_bytes), "amqp_basic_publish")) { + closeAMQPConnection(pData); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->host = NULL; + pData->port = 5672; + pData->vhost = NULL; + pData->user = NULL; + pData->password = NULL; + pData->exchange = NULL; + pData->routing_key = NULL; + pData->tplName = NULL; +} + + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTparseSelectorAct(1) + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if (!pvals[i].bUsed) + continue; + if (!strcmp(actpblk.descr[i].name, "host")) { + pData->host = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "port")) { + pData->port = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "virtual_host")) { + pData->vhost = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "user")) { + pData->user = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "password")) { + pData->password = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "exchange")) { + pData->exchange = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "routing_key")) { + pData->routing_key = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omrabbitmq: program error, non-handled param '%s'\n", actpblk.descr[i].name); + } + } + + if (pData->host == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "omrabbitmq module disabled: parameter host must be specified"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + if (pData->vhost == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "omrabbitmq module disabled: parameter virtual_host must be specified"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + if (pData->user == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "omrabbitmq module disabled: parameter user must be specified"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + if (pData->password == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "omrabbitmq module disabled: parameter password must be specified"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + if (pData->exchange == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "omrabbitmq module disabled: parameter exchange must be specified"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + if (pData->routing_key == NULL) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "omrabbitmq module disabled: parameter routing_key must be specified"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + // RabbitMQ properties initialization + memset(&pData->props, 0, sizeof pData->props); + pData->props._flags = AMQP_BASIC_DELIVERY_MODE_FLAG; + pData->props.delivery_mode = 2; /* persistent delivery mode */ + pData->props._flags |= AMQP_BASIC_CONTENT_TYPE_FLAG; + pData->props.content_type = amqp_cstring_bytes("application/json"); + + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*)strdup((pData->tplName == NULL) ? + " StdJSONFmt" : (char*)pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omrabbitmq:", sizeof(":omrabbitmq:") - 1)) { + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "omrabbitmq supports only v6 config format, use: " + "action(type=\"omrabbitmq\" host=...)"); + } + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt + CODEqueryEtryPt_STD_OMOD_QUERIES + CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit diff --git a/plugins/omrelp/Makefile.am b/plugins/omrelp/Makefile.am new file mode 100644 index 00000000..906aab43 --- /dev/null +++ b/plugins/omrelp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omrelp.la + +omrelp_la_SOURCES = omrelp.c +omrelp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RELP_CFLAGS) $(RSRT_CFLAGS) +omrelp_la_LDFLAGS = -module -avoid-version +omrelp_la_LIBADD = $(RELP_LIBS) diff --git a/plugins/omrelp/omrelp.c b/plugins/omrelp/omrelp.c new file mode 100644 index 00000000..e0650c62 --- /dev/null +++ b/plugins/omrelp/omrelp.c @@ -0,0 +1,469 @@ +/* omrelp.c + * + * This is the implementation of the RELP output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2008-03-13 by RGerhards + * + * Copyright 2008-2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <librelp.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "cfsysline.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "debug.h" +#include "unicode-helper.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omrelp") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +#define DFLT_ENABLE_TLS 0 +#define DFLT_ENABLE_TLSZIP 0 + +static relpEngine_t *pRelpEngine; /* our relp engine */ + +typedef struct _instanceData { + uchar *target; + uchar *port; + int bInitialConnect; /* is this the initial connection request of our module? (0-no, 1-yes) */ + int bIsConnected; /* currently connected to server? 0 - no, 1 - yes */ + unsigned timeout; + unsigned rebindInterval; + unsigned nSent; + relpClt_t *pRelpClt; /* relp client for this instance */ + sbool bEnableTLS; + sbool bEnableTLSZip; + uchar *pristring; /* GnuTLS priority string (NULL if not to be provided) */ + uchar *caCertFile; + uchar *myCertFile; + uchar *myPrivKeyFile; + uchar *tplName; +} instanceData; + +typedef struct configSettings_s { + EMPTY_STRUCT +} configSettings_t; +static configSettings_t __attribute__((unused)) cs; + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "target", eCmdHdlrGetWord, 1 }, + { "tls", eCmdHdlrBinary, 0 }, + { "tls.compression", eCmdHdlrBinary, 0 }, + { "tls.prioritystring", eCmdHdlrString, 0 }, + { "tls.cacert", eCmdHdlrString, 0 }, + { "tls.mycert", eCmdHdlrString, 0 }, + { "tls.myprivkey", eCmdHdlrString, 0 }, + { "port", eCmdHdlrGetWord, 0 }, + { "rebindinterval", eCmdHdlrInt, 0 }, + { "timeout", eCmdHdlrInt, 0 }, + { "template", eCmdHdlrGetWord, 1 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars +ENDinitConfVars + +/* We may change the implementation to try to lookup the port + * if it is unspecified. So far, we use 514 as default (what probably + * is not a really bright idea, but kept for backward compatibility). + */ +static uchar *getRelpPt(instanceData *pData) +{ + assert(pData != NULL); + if(pData->port == NULL) + return((uchar*)"514"); + else + return(pData->port); +} + +static inline rsRetVal +doCreateRelpClient(instanceData *pData) +{ + DEFiRet; + if(relpEngineCltConstruct(pRelpEngine, &pData->pRelpClt) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(relpCltSetTimeout(pData->pRelpClt, pData->timeout) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(pData->bEnableTLS) { + if(relpCltEnableTLS(pData->pRelpClt) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(pData->bEnableTLSZip) { + if(relpCltEnableTLSZip(pData->pRelpClt) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + } + if(relpCltSetGnuTLSPriString(pData->pRelpClt, (char*) pData->pristring) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(relpCltSetCACert(pData->pRelpClt, (char*) pData->caCertFile) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(relpCltSetOwnCert(pData->pRelpClt, (char*) pData->myCertFile) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + if(relpCltSetPrivKey(pData->pRelpClt, (char*) pData->myPrivKeyFile) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + } + if(glbl.GetSourceIPofLocalClient() == NULL) { /* ar Do we have a client IP set? */ + if(relpCltSetClientIP(pData->pRelpClt, glbl.GetSourceIPofLocalClient()) != RELP_RET_OK) + ABORT_FINALIZE(RS_RET_RELP_ERR); + } + pData->bInitialConnect = 1; + pData->nSent = 0; +finalize_it: + RETiRet; +} + + +BEGINcreateInstance +CODESTARTcreateInstance + pData->timeout = 90; + pData->rebindInterval = 0; + pData->bEnableTLS = DFLT_ENABLE_TLS; + pData->bEnableTLSZip = DFLT_ENABLE_TLSZIP; + pData->pristring = NULL; + pData->caCertFile = NULL; + pData->myCertFile = NULL; + pData->myPrivKeyFile = NULL; +ENDcreateInstance + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->pRelpClt != NULL) + relpEngineCltDestruct(pRelpEngine, &pData->pRelpClt); + free(pData->target); + free(pData->port); + free(pData->tplName); + free(pData->pristring); + free(pData->caCertFile); + free(pData->myCertFile); + free(pData->myPrivKeyFile); +ENDfreeInstance + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->target = NULL; + pData->port = NULL; + pData->tplName = NULL; + pData->timeout = 90; + pData->rebindInterval = 0; + pData->bEnableTLS = DFLT_ENABLE_TLS; + pData->bEnableTLSZip = DFLT_ENABLE_TLSZIP; + pData->pristring = NULL; + pData->caCertFile = NULL; + pData->myCertFile = NULL; + pData->myPrivKeyFile = NULL; +} + + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "target")) { + pData->target = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "port")) { + pData->port = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "timeout")) { + pData->timeout = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "rebindinterval")) { + pData->rebindInterval = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "tls")) { + pData->bEnableTLS = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "tls.compression")) { + pData->bEnableTLSZip = (unsigned) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "tls.prioritystring")) { + pData->pristring = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "tls.cacert")) { + pData->caCertFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "tls.mycert")) { + pData->myCertFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "tls.myprivkey")) { + pData->myPrivKeyFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omrelp: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + CODE_STD_STRING_REQUESTnewActInst(1) + + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*)strdup((pData->tplName == NULL) ? + "RSYSLOG_ForwardFormat" : (char*)pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + + CHKiRet(doCreateRelpClient(pData)); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + +BEGINSetShutdownImmdtPtr +CODESTARTSetShutdownImmdtPtr + relpEngineSetShutdownImmdtPtr(pRelpEngine, pPtr); + DBGPRINTF("omrelp: shutdownImmediate ptr now is %p\n", pPtr); +ENDSetShutdownImmdtPtr + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("RELP/%s", pData->target); +ENDdbgPrintInstInfo + + +/* try to connect to server + * rgerhards, 2008-03-21 + */ +static rsRetVal doConnect(instanceData *pData) +{ + DEFiRet; + + if(pData->bInitialConnect) { + iRet = relpCltConnect(pData->pRelpClt, glbl.GetDefPFFamily(), pData->port, pData->target); + if(iRet == RELP_RET_OK) + pData->bInitialConnect = 0; + } else { + iRet = relpCltReconnect(pData->pRelpClt); + } + + if(iRet == RELP_RET_OK) { + pData->bIsConnected = 1; + } else { + pData->bIsConnected = 0; + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doConnect(pData); +ENDtryResume + +static inline rsRetVal +doRebind(instanceData *pData) +{ + DEFiRet; + DBGPRINTF("omrelp: destructing relp client due to rebindInterval\n"); + CHKiRet(relpEngineCltDestruct(pRelpEngine, &pData->pRelpClt)); + pData->bIsConnected = 0; + CHKiRet(doCreateRelpClient(pData)); +finalize_it: + RETiRet; +} + +BEGINdoAction + uchar *pMsg; /* temporary buffering */ + size_t lenMsg; + relpRetVal ret; +CODESTARTdoAction + dbgprintf(" %s:%s/RELP\n", pData->target, getRelpPt(pData)); + + if(!pData->bIsConnected) { + CHKiRet(doConnect(pData)); + } + + pMsg = ppString[0]; + lenMsg = strlen((char*) pMsg); /* TODO: don't we get this? */ + + /* we need to truncate oversize msgs - no way around that... */ + if((int) lenMsg > glbl.GetMaxLine()) + lenMsg = glbl.GetMaxLine(); + + /* forward */ + ret = relpCltSendSyslog(pData->pRelpClt, (uchar*) pMsg, lenMsg); + if(ret != RELP_RET_OK) { + /* error! */ + dbgprintf("error forwarding via relp, suspending\n"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + + if(pData->rebindInterval != 0 && + (++pData->nSent >= pData->rebindInterval)) { + doRebind(pData); + } +finalize_it: +ENDdoAction + + +BEGINparseSelectorAct + uchar *q; + int i; + int bErr; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omrelp:", sizeof(":omrelp:") - 1)) { + p += sizeof(":omrelp:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + FINALIZE; + + /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0') + * now skip to port and then template name. rgerhards 2005-07-06 + */ + if(*p == '[') { /* everything is hostname upto ']' */ + ++p; /* skip '[' */ + for(q = p ; *p && *p != ']' ; ++p) + /* JUST SKIP */; + if(*p == ']') { + *p = '\0'; /* trick to obtain hostname (later)! */ + ++p; /* eat it */ + } + } else { /* traditional view of hostname */ + for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p) + /* JUST SKIP */; + } + + pData->port = NULL; + if(*p == ':') { /* process port */ + uchar * tmp; + + *p = '\0'; /* trick to obtain hostname (later)! */ + tmp = ++p; + for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i) + /* SKIP AND COUNT */; + pData->port = MALLOC(i + 1); + if(pData->port == NULL) { + errmsg.LogError(0, NO_ERRCODE, "Could not get memory to store relp port, " + "using default port, results may not be what you intend\n"); + /* we leave f_forw.port set to NULL, this is then handled by getRelpPt() */ + } else { + memcpy(pData->port, tmp, i); + *(pData->port + i) = '\0'; + } + } + + /* now skip to template */ + bErr = 0; + while(*p && *p != ';') { + if(*p && *p != ';' && !isspace((int) *p)) { + if(bErr == 0) { /* only 1 error msg! */ + bErr = 1; + errno = 0; + errmsg.LogError(0, NO_ERRCODE, "invalid selector line (port), probably not doing " + "what was intended"); + } + } + ++p; + } + + if(*p == ';') { + *p = '\0'; /* trick to obtain hostname (later)! */ + CHKmalloc(pData->target = ustrdup(q)); + *p = ';'; + } else { + CHKmalloc(pData->target = ustrdup(q)); + } + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_ForwardFormat")); + + CHKiRet(doCreateRelpClient(pData)); + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + relpEngineDestruct(&pRelpEngine); + + /* release what we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_SetShutdownImmdtPtr +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* create our relp engine */ + CHKiRet(relpEngineConstruct(&pRelpEngine)); + CHKiRet(relpEngineSetDbgprint(pRelpEngine, dbgprintf)); + CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar*) "syslog", eRelpCmdState_Required)); + + /* tell which objects we need */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/omruleset/Makefile.am b/plugins/omruleset/Makefile.am new file mode 100644 index 00000000..fdd91a6e --- /dev/null +++ b/plugins/omruleset/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omruleset.la + +omruleset_la_SOURCES = omruleset.c +omruleset_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +omruleset_la_LDFLAGS = -module -avoid-version +omruleset_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/omruleset/omruleset.c b/plugins/omruleset/omruleset.c new file mode 100644 index 00000000..11765507 --- /dev/null +++ b/plugins/omruleset/omruleset.c @@ -0,0 +1,258 @@ +/* omruleset.c + * This is a very special output module. It permits to pass a message object + * to another rule set. While this is a very simple action, it enables very + * complex configurations, e.g. it supports high-speed "and" conditions, sending + * data to the same file in a non-racy way, include functionality as well as + * some high-performance optimizations (in case the rule sets have the necessary + * queue definitions). So while this code is small, it is pretty important. + * + * NOTE: read comments in module-template.h for details on the calling interface! + * + * File begun on 2009-11-02 by RGerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "ruleset.h" +#include "cfsysline.h" +#include "dirty.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omruleset") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* static data */ +DEFobjCurrIf(ruleset); +DEFobjCurrIf(errmsg); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +/* config variables */ + + +typedef struct _instanceData { + ruleset_t *pRuleset; /* ruleset to enqueue to */ + uchar *pszRulesetName; /* primarily for debugging/display purposes */ +} instanceData; + +typedef struct configSettings_s { + ruleset_t *pRuleset; /* ruleset to enqueue message to (NULL = Default, not recommended) */ + uchar *pszRulesetName; +} configSettings_t; +static configSettings_t cs; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + resetConfigVariables(NULL, NULL); +ENDinitConfVars + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + free(pData->pszRulesetName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("omruleset target %s[%p]\n", (char*) pData->pszRulesetName, pData->pRuleset); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +/* Note that we change the flow control type to "no delay", because at this point in + * rsyslog procesing we can not really slow down the producer any longer, as we already + * work off a queue. So a delay would just block out execution for longer than needed. + */ +BEGINdoAction + msg_t *pMsg; +CODESTARTdoAction + CHKmalloc(pMsg = MsgDup((msg_t*) ppString[0])); + DBGPRINTF(":omruleset: forwarding message %p to ruleset %s[%p]\n", pMsg, + (char*) pData->pszRulesetName, pData->pRuleset); + MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY); + MsgSetRuleset(pMsg, pData->pRuleset); + /* Note: we intentionally use submitMsg2() here, as we process messages + * that were already run through the rate-limiter. So it is (at least) + * questionable if they were rate-limited again. + */ + submitMsg2(pMsg); +finalize_it: +ENDdoAction + +/* set the ruleset name */ +static rsRetVal +setRuleset(void __attribute__((unused)) *pVal, uchar *pszName) +{ + rsRetVal localRet; + DEFiRet; + + localRet = ruleset.GetRuleset(ourConf, &cs.pRuleset, pszName); + if(localRet == RS_RET_NOT_FOUND) { + errmsg.LogError(0, RS_RET_RULESET_NOT_FOUND, "error: ruleset '%s' not found - ignored", pszName); + } + CHKiRet(localRet); + cs.pszRulesetName = pszName; /* save for later display purposes */ + +finalize_it: + if(iRet != RS_RET_OK) { /* cleanup needed? */ + free(pszName); + } + RETiRet; +} + + +BEGINparseSelectorAct + int iTplOpts; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":omruleset:", sizeof(":omruleset:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + if(cs.pRuleset == NULL) { + errmsg.LogError(0, RS_RET_NO_RULESET, "error: no ruleset was specified, use " + "$ActionOmrulesetRulesetName directive first!"); + ABORT_FINALIZE(RS_RET_NO_RULESET); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":omruleset:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + errmsg.LogError(0, RS_RET_DEPRECATED, "warning: omruleset is deprecated, consider " + "using the 'call' statement instead"); + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + iTplOpts = OMSR_TPL_AS_MSG; + /* we call the message below because we need to call it via our interface definition. However, + * the format specified (if any) is always ignored. + */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, iTplOpts, (uchar*) "RSYSLOG_FileFormat")); + pData->pRuleset = cs.pRuleset; + pData->pszRulesetName = cs.pszRulesetName; + cs.pRuleset = NULL; /* re-set, because there is a high risk of unwanted behavior if we leave it in! */ + cs.pszRulesetName = NULL; /* note: we must not free, as we handed over this pointer to the instanceDat to the instanceDataa! */ +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + free(cs.pszRulesetName); + objRelease(errmsg, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + cs.pRuleset = NULL; + free(cs.pszRulesetName); + cs.pszRulesetName = NULL; + RETiRet; +} + + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bMsgPassingSupported; /* does core support template passing as an array? */ +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* check if the rsyslog core supports parameter passing code */ + bMsgPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports msg passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_MSG) + bMsgPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, what is not acceptable */ + } + + if(!bMsgPassingSupported) { + DBGPRINTF("omruleset: msg-passing is not supported by rsyslog core, can not continue.\n"); + ABORT_FINALIZE(RS_RET_NO_MSG_PASSING); + } + + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + errmsg.LogError(0, RS_RET_DEPRECATED, "warning: omruleset is deprecated, consider " + "using the 'call' statement instead"); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionomrulesetrulesetname", 0, eCmdHdlrGetWord, + setRuleset, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/omsnmp/Makefile.am b/plugins/omsnmp/Makefile.am new file mode 100644 index 00000000..f75fb091 --- /dev/null +++ b/plugins/omsnmp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omsnmp.la + +omsnmp_la_SOURCES = omsnmp.c omsnmp.h +omsnmp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omsnmp_la_LDFLAGS = -module -avoid-version +omsnmp_la_LIBADD = $(SNMP_LIBS) diff --git a/plugins/omsnmp/mibs/ADISCON-MIB.txt b/plugins/omsnmp/mibs/ADISCON-MIB.txt new file mode 100644 index 00000000..741ea84f --- /dev/null +++ b/plugins/omsnmp/mibs/ADISCON-MIB.txt @@ -0,0 +1,38 @@ +-- ***************************************************************** +-- ADISCON-RSYSLOG-MIB.txt: Adiscon RSyslog message MIB file +-- +-- March 2008, Andre Lorbach +-- +-- Copyright (c) 2008 by Adiscon GmbH +-- All rights reserved. +-- ***************************************************************** +-- +-- This is a basic MIB which defines our main enterprise OID + +ADISCON-MIB DEFINITIONS ::= BEGIN + +-- +-- Top-level infrastructure for the Adiscon enterprise MIB tree +-- + +IMPORTS + MODULE-IDENTITY, enterprises FROM SNMPv2-SMI; + +adiscon MODULE-IDENTITY + LAST-UPDATED "200803040000Z" + ORGANIZATION "www.adiscon.com" + CONTACT-INFO + "postal: Adiscon GmbH + Mozartstrasse 21 + D-97950 Großrinderfeld + Deutschland + + email: info@adiscon.com" + DESCRIPTION + "Top-level infrastructure for the Adiscon enterprise MIB tree" + REVISION "200803040000Z" + DESCRIPTION + "First draft" + ::= { enterprises 19406} + +END diff --git a/plugins/omsnmp/mibs/ADISCON-MONITORWARE-MIB.txt b/plugins/omsnmp/mibs/ADISCON-MONITORWARE-MIB.txt new file mode 100644 index 00000000..d26d7746 --- /dev/null +++ b/plugins/omsnmp/mibs/ADISCON-MONITORWARE-MIB.txt @@ -0,0 +1,362 @@ +-- ***************************************************************** +-- ADISCON-MONITORWARE-MIB.txt: Adiscon Monitorware message MIB file +-- +-- March 2008, Andre Lorbach +-- +-- Copyright (c) 2008 by Adiscon GmbH +-- All rights reserved. +-- ***************************************************************** +-- +-- This MIB defines traps and variables to wrap syslog messages into +-- snmp traps. + +ADISCON-MONITORWARE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + enterprises, + MODULE-IDENTITY, OBJECT-TYPE, Integer32, + NOTIFICATION-TYPE FROM SNMPv2-SMI, + adiscon FROM ADISCON-MIB +; + +monitorware MODULE-IDENTITY + LAST-UPDATED "200803050000Z" + ORGANIZATION "www.adiscon-com" + CONTACT-INFO + "postal: Adiscon GmbH + Mozartstrasse 21 + D-97950 Großrinderfeld + Deutschland + + email: info@adiscon.com" + DESCRIPTION + "This MIB defines traps and variables to wrap syslog messages into snmp traps." + REVISION "200803040000Z" + DESCRIPTION + "Added a few new variables for the representation of MonitorWare properties. Also added a few new traps." + REVISION "200803050000Z" + DESCRIPTION + "First draft" + ::= { adiscon 1 } + +-- Printable string, using the ISO 8859-1 character set. +DisplayString ::= OCTET STRING (SIZE (0..255)) +SmallString ::= OCTET STRING (SIZE (0..64)) +-- + +-- +-- top level structure +-- +-- adiscon OBJECT IDENTIFIER ::= { enterprises 19406 } +monitorware OBJECT IDENTIFIER ::= { adiscon 1 } +monitorwarevars OBJECT IDENTIFIER ::= { monitorware 1 } +monitorwaretraps OBJECT IDENTIFIER ::= { monitorware 2 } +genericvars OBJECT IDENTIFIER ::= { monitorwarevars 1 } +syslogvars OBJECT IDENTIFIER ::= { monitorwarevars 2 } +eventlogvars OBJECT IDENTIFIER ::= { monitorwarevars 3 } +filemonvars OBJECT IDENTIFIER ::= { monitorwarevars 4 } +ntservicemonvars OBJECT IDENTIFIER ::= { monitorwarevars 5 } + +-- ***************************************************************** +-- Trap variables +-- ***************************************************************** + +syslogMsg OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Syslog Message, this will contain the full + syslog message including the full syslog header" + ::= { syslogvars 1 } + +syslogSeverity OBJECT-TYPE + SYNTAX INTEGER { + emergency (0), + alert (1), + critical (2), + error (3), + warning (4), + notice (5), + info (6), + debug (7) + } + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Syslog severity(priority)." + DEFVAL { 5 } + ::= { syslogvars 2 } + +syslogFacility OBJECT-TYPE + SYNTAX INTEGER { + kern (0), + user (1), + mail (2), + daemon (3), + auth (4), + syslog (5), + lpr (6), + news (7), + uucp (8), + cron (9), + local0 (16), + local1 (17), + local2 (18), + local3 (19), + local4 (20), + local5 (21), + local6 (22), + local7 (23) + } + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Syslog facility." + DEFVAL { 16 } + ::= { syslogvars 3 } + +syslogTag OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Contains the SyslogTag Value." + ::= { syslogvars 4 } + +genCustomerID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Property CustomerID." + ::= { genericvars 1 } + +genSystemID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Property SystemID." + ::= { genericvars 2 } + +genSource OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Source Property." + ::= { genericvars 3 } + +genTimereported OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Timestamp of when the event was reported." + ::= { genericvars 4 } + +genTimegenerated OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Timestamp of when the event was generated." + ::= { genericvars 5 } + +genIut OBJECT-TYPE + SYNTAX INTEGER { + Unknown (0), + Syslog (1), + Heartbeat (2), + NTEventReport (3), + SNMPTrap (4), + FileMonitor (5), + PingProbe (8), + PortProbe (9), + NTServiceMonitor (10), + DiskSpaceMonitor (11), + DBMonitor (12), + SerialMonitor (13), + CPUMonitor (14), + AliveMonRequest (16), + SMTPProbe (17), + FTPProbe (18), + HTTPProbe (19), + POP3Probe (20), + IMAPProbe (21), + NNTPProbe (22), + WEVTMONV2 (23), + SMTPLISTENER (24), + SNMPMONITOR (25), + AliveMonECHO (1999998) + } + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "InfoUnit TypeID, defines from which Source the event is derived from." + DEFVAL { 0 } + ::= { genericvars 6 } + +genMsg OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Message for this event" + ::= { genericvars 7 } + +eventlogEventID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "EventID of the EventLog Entry" + ::= { eventlogvars 1 } + +eventlogEventType OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "EventLog Type of the EventLog Entry (Like Application, Security or System)" + ::= { eventlogvars 2 } + +eventlogEventSource OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "EventLog Source of the EventLog Entry" + ::= { eventlogvars 3 } + +eventlogEventCategoryID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Event Category number of the EventLog Entry" + ::= { eventlogvars 4 } + +eventlogEventCategoryName OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Event Category name of the EventLog Entry" + ::= { eventlogvars 5 } + +eventlogEventUser OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Event User of the EventLog Entry" + ::= { eventlogvars 6 } + +filemonGenericFilename OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Filename template used to create the filename" + ::= { filemonvars 1 } + +filemonGeneratedFilename OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generated Filename, the source file of this event." + ::= { filemonvars 2 } + +filemonMsgseperator OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The message seperator which was used." + ::= { filemonvars 3 } + +ntserviceServiceName OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Internal Name of the monitored service." + ::= { ntservicemonvars 1 } + +ntserviceServiceDisplayName OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Display Name of the monitored service." + ::= { ntservicemonvars 2 } + + +-- ***************************************************************** +-- Trap definitions +-- ***************************************************************** + +syslogtrap NOTIFICATION-TYPE + OBJECTS { syslogMsg, + syslogSeverity, + syslogFacility + } + STATUS current + DESCRIPTION + "Syslogmessage Trap." +::= { monitorwaretraps 1 } + +monitorwaretrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + genTimegenerated, + genIut + } + STATUS current + DESCRIPTION + "Generic Trap from monitorware events." +::= { monitorwaretraps 2 } + +eventmontrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + eventlogEventID, + eventlogEventType, + eventlogEventSource, + eventlogEventCategoryID, + eventlogEventCategoryName, + eventlogEventUser + } + STATUS current + DESCRIPTION + "Trap generated by the EventLog Monitor." +::= { monitorwaretraps 3 } + +filemontrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + genTimegenerated, + filemonGenericFilename, + filemonGeneratedFilename + } + STATUS current + DESCRIPTION + "Trap generated by the FileMonitor." +::= { monitorwaretraps 4 } + +ntservicetrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + genTimegenerated, + ntserviceServiceName, + ntserviceServiceDisplayName + } + STATUS current + DESCRIPTION + "Trap generated by the NT Service Monitor." +::= { monitorwaretraps 5 } + +END diff --git a/plugins/omsnmp/omsnmp.c b/plugins/omsnmp/omsnmp.c new file mode 100644 index 00000000..79e555b3 --- /dev/null +++ b/plugins/omsnmp/omsnmp.c @@ -0,0 +1,578 @@ +/* omsnmp.c + * + * This module sends an snmp trap. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netdb.h> +#include <ctype.h> +#include <assert.h> +#include "conf.h" +#include "syslogd-types.h" +#include "cfsysline.h" +#include "module-template.h" + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include "omsnmp.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omsnmp") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* Default static snmp OID's */ +/*unused +static oid objid_enterprise[] = { 1, 3, 6, 1, 4, 1, 3, 1, 1 }; +static oid objid_sysdescr[] = { 1, 3, 6, 1, 2, 1, 1, 1, 0 }; +*/ +static oid objid_snmptrap[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; +static oid objid_sysuptime[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 }; + + +typedef struct _instanceData { + uchar *szTransport; /* Transport - Can be udp, tcp, udp6, tcp6 and other types supported by NET-SNMP */ + uchar *szTarget; /* IP/hostname of Snmp Target*/ + uchar *szCommunity; /* Snmp Community */ + uchar *szEnterpriseOID;/* Snmp Enterprise OID - default is (1.3.6.1.4.1.3.1.1 = enterprises.cmu.1.1) */ + uchar *szSnmpTrapOID; /* Snmp Trap OID - default is (1.3.6.1.4.1.19406.1.2.1 = ADISCON-MONITORWARE-MIB::syslogtrap) */ + uchar *szSyslogMessageOID; /* Snmp OID used for the Syslog Message: + * default is 1.3.6.1.4.1.19406.1.1.2.1 - ADISCON-MONITORWARE-MIB::syslogMsg + * You will need the ADISCON-MONITORWARE-MIB and ADISCON-MIB mibs installed on the receiver + * side in order to decode this mib. + * Downloads of these mib files can be found here: + * http://www.adiscon.org/download/ADISCON-MONITORWARE-MIB.txt + * http://www.adiscon.org/download/ADISCON-MIB.txt + */ + int iPort; /* Target Port */ + int iSNMPVersion; /* SNMP Version to use */ + int iTrapType; /* Snmp TrapType or GenericType */ + int iSpecificType; /* Snmp Specific Type */ + + netsnmp_session *snmpsession; /* Holds to SNMP Session, NULL if not initialized */ + uchar *tplName; /* format template to use */ +} instanceData; + +typedef struct configSettings_s { + uchar* pszTransport; /* default transport */ + uchar* pszTarget; + /* note using an unsigned for a port number is not a good idea from an IPv6 point of view */ + int iPort; + int iSNMPVersion; /* 0 Means SNMPv1, 1 Means SNMPv2c */ + uchar* pszCommunity; + uchar* pszEnterpriseOID; + uchar* pszSnmpTrapOID; + uchar* pszSyslogMessageOID; + int iSpecificType; + int iTrapType; /*Default is SNMP_TRAP_ENTERPRISESPECIFIC */ + /* + Possible Values + SNMP_TRAP_COLDSTART (0) + SNMP_TRAP_WARMSTART (1) + SNMP_TRAP_LINKDOWN (2) + SNMP_TRAP_LINKUP (3) + SNMP_TRAP_AUTHFAIL (4) + SNMP_TRAP_EGPNEIGHBORLOSS (5) + SNMP_TRAP_ENTERPRISESPECIFIC (6) + */ +} configSettings_t; +static configSettings_t cs; + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "server", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "port", eCmdHdlrInt, CNFPARAM_REQUIRED }, + { "transport", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "version", eCmdHdlrInt, CNFPARAM_REQUIRED }, + { "community", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "enterpriseoid", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "trapoid", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "messageoid", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "traptype", eCmdHdlrInt, CNFPARAM_REQUIRED }, + { "specifictype", eCmdHdlrInt, CNFPARAM_REQUIRED }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.pszTransport = NULL; + cs.pszTarget = NULL; + cs.iPort = 0; + cs.iSNMPVersion = 1; + cs.pszCommunity = NULL; + cs.pszEnterpriseOID = NULL; + cs.pszSnmpTrapOID = NULL; + cs.pszSyslogMessageOID = NULL; + cs.iSpecificType = 0; + cs.iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; +ENDinitConfVars + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("SNMPTransport: %s\n", pData->szTransport); + dbgprintf("SNMPTarget: %s\n", pData->szTarget); + dbgprintf("SNMPPort: %d\n", pData->iPort); + dbgprintf("SNMPVersion (0=v1, 1=v2c): %d\n", pData->iSNMPVersion); + dbgprintf("Community: %s\n", pData->szCommunity); + dbgprintf("EnterpriseOID: %s\n", pData->szEnterpriseOID); + dbgprintf("SnmpTrapOID: %s\n", pData->szSnmpTrapOID); + dbgprintf("SyslogMessageOID: %s\n", pData->szSyslogMessageOID); + dbgprintf("TrapType: %d\n", pData->iTrapType); + dbgprintf("SpecificType: %d\n", pData->iSpecificType); +ENDdbgPrintInstInfo + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we are not compatible with repeated msg reduction feature, so do not allow it */ +ENDisCompatibleWithFeature + +/* Exit SNMP Session + * alorbach, 2008-02-12 + */ +static rsRetVal omsnmp_exitSession(instanceData *pData) +{ + DEFiRet; + + if(pData->snmpsession != NULL) { + dbgprintf( "omsnmp_exitSession: Clearing Session to '%s' on Port = '%d'\n", pData->szTarget, pData->iPort); + snmp_close(pData->snmpsession); + pData->snmpsession = NULL; + } + + RETiRet; +} + +/* Init SNMP Session + * alorbach, 2008-02-12 + */ +static rsRetVal omsnmp_initSession(instanceData *pData) +{ + netsnmp_session session; + char szTargetAndPort[MAXHOSTNAMELEN+128]; /* work buffer for specifying a full target and port string */ + DEFiRet; + + /* should not happen, but if session is not cleared yet - we do it now! */ + if (pData->snmpsession != NULL) + omsnmp_exitSession(pData); + + snprintf((char*)szTargetAndPort, sizeof(szTargetAndPort), "%s:%s:%d", + (pData->szTransport == NULL) ? "udp" : (char*)pData->szTransport, + pData->szTarget, pData->iPort == 0 ? 162 : pData->iPort); + + dbgprintf( "omsnmp_initSession: ENTER - Target = '%s' on Port = '%d'\n", pData->szTarget, pData->iPort); + + putenv(strdup("POSIXLY_CORRECT=1")); + + snmp_sess_init(&session); + session.version = pData->iSNMPVersion; + session.callback = NULL; /* NOT NEEDED */ + session.callback_magic = NULL; + session.peername = (char*) szTargetAndPort; + + /* Set SNMP Community */ + if (session.version == SNMP_VERSION_1 || session.version == SNMP_VERSION_2c) { + session.community = (unsigned char *) pData->szCommunity == NULL ? (uchar*)"public" : pData->szCommunity; + session.community_len = strlen((char*) session.community); + } + + pData->snmpsession = snmp_open(&session); + if (pData->snmpsession == NULL) { + errmsg.LogError(0, RS_RET_SUSPENDED, "omsnmp_initSession: snmp_open to host '%s' on Port '%d' failed\n", pData->szTarget, pData->iPort); + /* Stay suspended */ + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + +static rsRetVal omsnmp_sendsnmp(instanceData *pData, uchar *psz) +{ + DEFiRet; + + netsnmp_pdu *pdu = NULL; + oid enterpriseoid[MAX_OID_LEN]; + size_t enterpriseoidlen = MAX_OID_LEN; + oid oidSyslogMessage[MAX_OID_LEN]; + size_t oLen = MAX_OID_LEN; + int status; + char *trap = NULL; + const char *strErr = NULL; + + /* Init SNMP Session if necessary */ + if (pData->snmpsession == NULL) { + CHKiRet(omsnmp_initSession(pData)); + } + + /* String should not be NULL */ + ASSERT(psz != NULL); + dbgprintf( "omsnmp_sendsnmp: ENTER - Syslogmessage = '%s'\n", (char*)psz); + + /* If SNMP Version1 is configured !*/ + if(pData->snmpsession->version == SNMP_VERSION_1) { + pdu = snmp_pdu_create(SNMP_MSG_TRAP); + + /* Set enterprise */ + if(!snmp_parse_oid(pData->szEnterpriseOID == NULL ? "1.3.6.1.4.1.3.1.1" : (char*)pData->szEnterpriseOID, + enterpriseoid, &enterpriseoidlen )) { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Parsing EnterpriseOID " + "failed '%s' with error '%s' \n", pData->szSyslogMessageOID, strErr); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + pdu->enterprise = (oid *) MALLOC(enterpriseoidlen * sizeof(oid)); + memcpy(pdu->enterprise, enterpriseoid, enterpriseoidlen * sizeof(oid)); + pdu->enterprise_length = enterpriseoidlen; + + /* Set Traptype */ + pdu->trap_type = pData->iTrapType; + + /* Set SpecificType */ + pdu->specific_type = pData->iSpecificType; + + /* Set Updtime */ + pdu->time = get_uptime(); + } + /* If SNMP Version2c is configured !*/ + else if (pData->snmpsession->version == SNMP_VERSION_2c) + { + long sysuptime; + char csysuptime[20]; + + /* Create PDU */ + pdu = snmp_pdu_create(SNMP_MSG_TRAP2); + + /* Set uptime */ + sysuptime = get_uptime(); + snprintf( csysuptime, sizeof(csysuptime) , "%ld", sysuptime); + trap = csysuptime; + snmp_add_var(pdu, objid_sysuptime, sizeof(objid_sysuptime) / sizeof(oid), 't', trap); + + /* Now set the SyslogMessage Trap OID */ + if ( snmp_add_var(pdu, objid_snmptrap, sizeof(objid_snmptrap) / sizeof(oid), 'o', + pData->szSnmpTrapOID == NULL ? "1.3.6.1.4.1.19406.1.2.1" : (char*) pData->szSnmpTrapOID + ) != 0) { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Adding trap OID failed '%s' with error '%s' \n", pData->szSnmpTrapOID, strErr); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + } + + /* SET TRAP PARAMETER for SyslogMessage! */ +/* dbgprintf( "omsnmp_sendsnmp: SyslogMessage '%s'\n", psz );*/ + + /* First create new OID object */ + if (snmp_parse_oid(pData->szSyslogMessageOID == NULL ? + "1.3.6.1.4.1.19406.1.1.2.1" : (char*)pData->szSyslogMessageOID, + oidSyslogMessage, &oLen)) { + int iErrCode = snmp_add_var(pdu, oidSyslogMessage, oLen, 's', (char*) psz); + if (iErrCode) { + const char *str = snmp_api_errstring(iErrCode); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Invalid SyslogMessage OID, error code '%d' - '%s'\n", iErrCode, str ); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + } else { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Parsing SyslogMessageOID failed '%s' with error '%s' \n", pData->szSyslogMessageOID, strErr); + + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + + /* Send the TRAP */ + status = snmp_send(pData->snmpsession, pdu) == 0; + if (status) + { + /* Debug Output! */ + int iErrorCode = pData->snmpsession->s_snmp_errno; + errmsg.LogError(0, RS_RET_SUSPENDED, "omsnmp_sendsnmp: snmp_send failed error '%d', Description='%s'\n", iErrorCode*(-1), api_errors[iErrorCode*(-1)]); + + /* Clear Session */ + omsnmp_exitSession(pData); + + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(pdu != NULL) { + snmp_free_pdu(pdu); + } + } + + dbgprintf( "omsnmp_sendsnmp: LEAVE\n"); + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = omsnmp_initSession(pData); +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + /* Abort if the STRING is not set, should never happen */ + if (ppString[0] == NULL) { + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + /* This will generate and send the SNMP Trap */ + iRet = omsnmp_sendsnmp(pData, ppString[0]); +finalize_it: +ENDdoAction + +BEGINfreeInstance +CODESTARTfreeInstance + /* free snmp Session here */ + omsnmp_exitSession(pData); + + free(pData->tplName); + free(pData->szTarget); +ENDfreeInstance + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->tplName = NULL; + pData->szCommunity = NULL; + pData->szEnterpriseOID = NULL; + pData->szSnmpTrapOID = NULL; + pData->szSyslogMessageOID = NULL; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "server")) { + pData->szTarget = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "port")) { + pData->iPort = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "transport")) { + pData->szTransport = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "version")) { + pData->iSNMPVersion = pvals[i].val.d.n; + if(pData->iSNMPVersion < 0 || cs.iSNMPVersion > 1) + pData->iSNMPVersion = 1; + } else if(!strcmp(actpblk.descr[i].name, "community")) { + pData->szCommunity = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "enterpriseoid")) { + pData->szEnterpriseOID = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "trapoid")) { + pData->szSnmpTrapOID = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "messageoid")) { + pData->szSyslogMessageOID = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "traptype")) { + pData->iTrapType = pvals[i].val.d.n; + if(cs.iTrapType < 0 && cs.iTrapType >= 6) + pData->iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + } else if(!strcmp(actpblk.descr[i].name, "specifictype")) { + pData->iSpecificType = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("ompipe: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*) "RSYSLOG_FileFormat", + OMSR_NO_RQD_TPL_OPTS)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, + (uchar*) strdup((char*) pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + } +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omsnmp:", sizeof(":omsnmp:") - 1)) { + p += sizeof(":omsnmp:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + FINALIZE; + + /* Check Target */ + if(cs.pszTarget == NULL) { + ABORT_FINALIZE( RS_RET_PARAM_ERROR ); + } else { + CHKmalloc(pData->szTarget = (uchar*) strdup((char*)cs.pszTarget)); + } + + /* copy config params */ + pData->szTransport = (uchar*) ((cs.pszTransport == NULL) ? NULL : strdup((char*)cs.pszTransport)); + pData->szCommunity = (uchar*) ((cs.pszCommunity == NULL) ? NULL : strdup((char*)cs.pszCommunity)); + pData->szEnterpriseOID = (uchar*) ((cs.pszEnterpriseOID == NULL) ? NULL : strdup((char*)cs.pszEnterpriseOID)); + pData->szSnmpTrapOID = (uchar*) ((cs.pszSnmpTrapOID == NULL) ? NULL : strdup((char*)cs.pszSnmpTrapOID)); + pData->szSyslogMessageOID = (uchar*) ((cs.pszSyslogMessageOID == NULL) ? NULL : strdup((char*)cs.pszSyslogMessageOID)); + pData->iPort = cs.iPort; + pData->iSpecificType = cs.iSpecificType; + + /* Set SNMPVersion */ + if ( cs.iSNMPVersion < 0 || cs.iSNMPVersion > 1) /* Set default to 1 if out of range */ + pData->iSNMPVersion = 1; + else + pData->iSNMPVersion = cs.iSNMPVersion; + + /* Copy TrapType */ + if ( cs.iTrapType < 0 && cs.iTrapType >= 6) /* Only allow values from 0 to 6 !*/ + pData->iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + else + pData->iTrapType = cs.iTrapType; + + /* Print Debug info */ + dbgprintf("SNMPTransport: %s\n", pData->szTransport); + dbgprintf("SNMPTarget: %s\n", pData->szTarget); + dbgprintf("SNMPPort: %d\n", pData->iPort); + dbgprintf("SNMPVersion (0=v1, 1=v2c): %d\n", pData->iSNMPVersion); + dbgprintf("Community: %s\n", pData->szCommunity); + dbgprintf("EnterpriseOID: %s\n", pData->szEnterpriseOID); + dbgprintf("SnmpTrapOID: %s\n", pData->szSnmpTrapOID); + dbgprintf("SyslogMessageOID: %s\n", pData->szSyslogMessageOID); + dbgprintf("TrapType: %d\n", pData->iTrapType); + dbgprintf("SpecificType: %d\n", pData->iSpecificType); + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_TraditionalForwardFormat")); + + /* Init NetSNMP library and read in MIB database */ + init_snmp("rsyslog"); + + /* Set some defaults in the NetSNMP library */ + netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DEFAULT_PORT, pData->iPort ); + + /* Init Session Pointer */ + pData->snmpsession = NULL; +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + free(cs.pszTarget); + cs.pszTarget = NULL; + free(cs.pszCommunity); + cs.pszCommunity = NULL; + free(cs.pszEnterpriseOID); + cs.pszEnterpriseOID = NULL; + free(cs.pszSnmpTrapOID); + cs.pszSnmpTrapOID = NULL; + free(cs.pszSyslogMessageOID); + cs.pszSyslogMessageOID = NULL; + cs.iPort = 0; + cs.iSNMPVersion = 1; + cs.iSpecificType = 0; + cs.iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + RETiRet; +} + + +BEGINmodExit +CODESTARTmodExit + free(cs.pszTarget); + free(cs.pszCommunity); + free(cs.pszEnterpriseOID); + free(cs.pszSnmpTrapOID); + free(cs.pszSyslogMessageOID); + + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + initConfVars(); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmptransport", 0, eCmdHdlrGetWord, NULL, &cs.pszTransport, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmptarget", 0, eCmdHdlrGetWord, NULL, &cs.pszTarget, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmptargetport", 0, eCmdHdlrInt, NULL, &cs.iPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmpversion", 0, eCmdHdlrInt, NULL, &cs.iSNMPVersion, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmpcommunity", 0, eCmdHdlrGetWord, NULL, &cs.pszCommunity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmpenterpriseoid", 0, eCmdHdlrGetWord, NULL, &cs.pszEnterpriseOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmptrapoid", 0, eCmdHdlrGetWord, NULL, &cs.pszSnmpTrapOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmpsyslogmessageoid", 0, eCmdHdlrGetWord, NULL, &cs.pszSyslogMessageOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmpspecifictype", 0, eCmdHdlrInt, NULL, &cs.iSpecificType, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionsnmptraptype", 0, eCmdHdlrInt, NULL, &cs.iTrapType, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/omsnmp/omsnmp.h b/plugins/omsnmp/omsnmp.h new file mode 100644 index 00000000..f685a236 --- /dev/null +++ b/plugins/omsnmp/omsnmp.h @@ -0,0 +1,103 @@ +/* omsnmp.h + * These are the definitions for the build-in MySQL output module. + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OMSNMP_H_INCLUDED +#define OMSNMP_H_INCLUDED 1 + +#define OMSNMP_MAXTRANSPORLENGTH 10 +#define OMSNMP_MAXPORTLENGHT 5 +#define OMSNMP_MAXCOMMUNITYLENGHT 255 +#define OMSNMP_MAXOIDLENGHT 255 + + +#endif /* #ifndef OMMYSQL_H_INCLUDED */ +/* + * vi:set ai: + */ + +#include <net-snmp/library/snmp_api.h> + +static const char *api_errors[-SNMPERR_MAX + 1] = { + "No error", /* SNMPERR_SUCCESS */ + "Generic error", /* SNMPERR_GENERR */ + "Invalid local port", /* SNMPERR_BAD_LOCPORT */ + "Unknown host", /* SNMPERR_BAD_ADDRESS */ + "Unknown session", /* SNMPERR_BAD_SESSION */ + "Too long", /* SNMPERR_TOO_LONG */ + "No socket", /* SNMPERR_NO_SOCKET */ + "Cannot send V2 PDU on V1 session", /* SNMPERR_V2_IN_V1 */ + "Cannot send V1 PDU on V2 session", /* SNMPERR_V1_IN_V2 */ + "Bad value for non-repeaters", /* SNMPERR_BAD_REPEATERS */ + "Bad value for max-repetitions", /* SNMPERR_BAD_REPETITIONS */ + "Error building ASN.1 representation", /* SNMPERR_BAD_ASN1_BUILD */ + "Failure in sendto", /* SNMPERR_BAD_SENDTO */ + "Bad parse of ASN.1 type", /* SNMPERR_BAD_PARSE */ + "Bad version specified", /* SNMPERR_BAD_VERSION */ + "Bad source party specified", /* SNMPERR_BAD_SRC_PARTY */ + "Bad destination party specified", /* SNMPERR_BAD_DST_PARTY */ + "Bad context specified", /* SNMPERR_BAD_CONTEXT */ + "Bad community specified", /* SNMPERR_BAD_COMMUNITY */ + "Cannot send noAuth/Priv", /* SNMPERR_NOAUTH_DESPRIV */ + "Bad ACL definition", /* SNMPERR_BAD_ACL */ + "Bad Party definition", /* SNMPERR_BAD_PARTY */ + "Session abort failure", /* SNMPERR_ABORT */ + "Unknown PDU type", /* SNMPERR_UNKNOWN_PDU */ + "Timeout", /* SNMPERR_TIMEOUT */ + "Failure in recvfrom", /* SNMPERR_BAD_RECVFROM */ + "Unable to determine contextEngineID", /* SNMPERR_BAD_ENG_ID */ + "No securityName specified", /* SNMPERR_BAD_SEC_NAME */ + "Unable to determine securityLevel", /* SNMPERR_BAD_SEC_LEVEL */ + "ASN.1 parse error in message", /* SNMPERR_ASN_PARSE_ERR */ + "Unknown security model in message", /* SNMPERR_UNKNOWN_SEC_MODEL */ + "Invalid message (e.g. msgFlags)", /* SNMPERR_INVALID_MSG */ + "Unknown engine ID", /* SNMPERR_UNKNOWN_ENG_ID */ + "Unknown user name", /* SNMPERR_UNKNOWN_USER_NAME */ + "Unsupported security level", /* SNMPERR_UNSUPPORTED_SEC_LEVEL */ + "Authentication failure (incorrect password, community or key)", /* SNMPERR_AUTHENTICATION_FAILURE */ + "Not in time window", /* SNMPERR_NOT_IN_TIME_WINDOW */ + "Decryption error", /* SNMPERR_DECRYPTION_ERR */ + "SCAPI general failure", /* SNMPERR_SC_GENERAL_FAILURE */ + "SCAPI sub-system not configured", /* SNMPERR_SC_NOT_CONFIGURED */ + "Key tools not available", /* SNMPERR_KT_NOT_AVAILABLE */ + "Unknown Report message", /* SNMPERR_UNKNOWN_REPORT */ + "USM generic error", /* SNMPERR_USM_GENERICERROR */ + "USM unknown security name (no such user exists)", /* SNMPERR_USM_UNKNOWNSECURITYNAME */ + "USM unsupported security level (this user has not been configured for that level of security)", /* SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL */ + "USM encryption error", /* SNMPERR_USM_ENCRYPTIONERROR */ + "USM authentication failure (incorrect password or key)", /* SNMPERR_USM_AUTHENTICATIONFAILURE */ + "USM parse error", /* SNMPERR_USM_PARSEERROR */ + "USM unknown engineID", /* SNMPERR_USM_UNKNOWNENGINEID */ + "USM not in time window", /* SNMPERR_USM_NOTINTIMEWINDOW */ + "USM decryption error", /* SNMPERR_USM_DECRYPTIONERROR */ + "MIB not initialized", /* SNMPERR_NOMIB */ + "Value out of range", /* SNMPERR_RANGE */ + "Sub-id out of range", /* SNMPERR_MAX_SUBID */ + "Bad sub-id in object identifier", /* SNMPERR_BAD_SUBID */ + "Object identifier too long", /* SNMPERR_LONG_OID */ + "Bad value name", /* SNMPERR_BAD_NAME */ + "Bad value notation", /* SNMPERR_VALUE */ + "Unknown Object Identifier", /* SNMPERR_UNKNOWN_OBJID */ + "No PDU in snmp_send", /* SNMPERR_NULL_PDU */ + "Missing variables in PDU", /* SNMPERR_NO_VARS */ + "Bad variable type", /* SNMPERR_VAR_TYPE */ + "Out of memory (malloc failure)", /* SNMPERR_MALLOC */ + "Kerberos related error", /* SNMPERR_KRB5 */ +}; diff --git a/plugins/omstdout/Makefile.am b/plugins/omstdout/Makefile.am new file mode 100644 index 00000000..9f5d497f --- /dev/null +++ b/plugins/omstdout/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omstdout.la + +omstdout_la_SOURCES = omstdout.c +omstdout_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +omstdout_la_LDFLAGS = -module -avoid-version +omstdout_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/omstdout/omstdout.c b/plugins/omstdout/omstdout.c new file mode 100644 index 00000000..a84a7593 --- /dev/null +++ b/plugins/omstdout/omstdout.c @@ -0,0 +1,239 @@ +/* omstdout.c + * send all output to stdout - this is primarily a test driver (but may + * be used for weired use cases). Not tested for robustness! + * + * NOTE: read comments in module-template.h for more specifics! + * + * File begun on 2009-03-19 by RGerhards + * + * Copyright 2009-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omstdout") + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +/* config variables */ + + +typedef struct _instanceData { + int bUseArrayInterface; /* uses action use array instead of string template interface? */ + int bEnsureLFEnding; /* ensure that a linefeed is written at the end of EACH record (test aid for nettester) */ +} instanceData; + +typedef struct configSettings_s { + int bUseArrayInterface; /* shall action use array instead of string template interface? */ + int bEnsureLFEnding; /* shall action use array instead of string template interface? */ +} configSettings_t; +static configSettings_t cs; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + resetConfigVariables(NULL, NULL); +ENDinitConfVars + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction + char **szParams; + char *toWrite; + int iParamVal; + int iParam; + int iBuf; + char szBuf[65564]; + size_t len; + int r; +CODESTARTdoAction + if(pData->bUseArrayInterface) { + /* if we use array passing, we need to put together a string + * ourselves. At this point, please keep in mind that omstdout is + * primarily a testing aid. Other modules may do different processing + * if they would like to support downlevel versions which do not support + * array-passing, but also use that interface on cores who do... + * So this code here is also more or less an example of how to do that. + * rgerhards, 2009-04-03 + */ + szParams = (char**)(void*) (ppString[0]); + /* In array-passing mode, ppString[] contains a NULL-terminated array + * of char *pointers. + */ + iParam = 0; + iBuf = 0; + while(szParams[iParam] != NULL) { + if(iParam > 0) + szBuf[iBuf++] = ','; /* all but first need a delimiter */ + iParamVal = 0; + while(szParams[iParam][iParamVal] != '\0' && iBuf < (int) sizeof(szBuf)) { + szBuf[iBuf++] = szParams[iParam][iParamVal++]; + } + ++iParam; + } + szBuf[iBuf] = '\0'; + toWrite = szBuf; + } else { + toWrite = (char*) ppString[0]; + } + len = strlen(toWrite); + /* the following if's are just to silence compiler warnings. If someone + * actually intends to use this module in production (why???), this code + * needs to be more solid. -- rgerhards, 2012-11-28 + */ + if((r = write(1, toWrite, len)) != (int) len) { /* 1 is stdout! */ + DBGPRINTF("omstdout: error %d writing to stdout[%d]: %s\n", + r, len, toWrite); + } + if(pData->bEnsureLFEnding && toWrite[len-1] != '\n') { + if((r = write(1, "\n", 1)) != 1) { /* write missing LF */ + DBGPRINTF("omstdout: error %d writing \\n to stdout\n", + r); + } + } +ENDdoAction + + +BEGINparseSelectorAct + int iTplOpts; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":omstdout:", sizeof(":omstdout:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":omstdout:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + iTplOpts = (cs.bUseArrayInterface == 0) ? 0 : OMSR_TPL_AS_ARRAY; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, iTplOpts, (uchar*) "RSYSLOG_FileFormat")); + pData->bUseArrayInterface = cs.bUseArrayInterface; + pData->bEnsureLFEnding = cs.bEnsureLFEnding; +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +ENDqueryEtryPt + + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + cs.bUseArrayInterface = 0; + cs.bEnsureLFEnding = 1; + RETiRet; +} + + +BEGINmodInit() + rsRetVal localRet; + rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts); + unsigned long opts; + int bArrayPassingSupported; /* does core support template passing as an array? */ +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* check if the rsyslog core supports parameter passing code */ + bArrayPassingSupported = 0; + localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", &pomsrGetSupportedTplOpts); + if(localRet == RS_RET_OK) { + /* found entry point, so let's see if core supports array passing */ + CHKiRet((*pomsrGetSupportedTplOpts)(&opts)); + if(opts & OMSR_TPL_AS_ARRAY) + bArrayPassingSupported = 1; + } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) { + ABORT_FINALIZE(localRet); /* Something else went wrong, what is not acceptable */ + } + DBGPRINTF("omstdout: array-passing is %ssupported by rsyslog core.\n", bArrayPassingSupported ? "" : "not "); + + if(bArrayPassingSupported) { + /* enable config comand only if core supports it */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionomstdoutarrayinterface", 0, eCmdHdlrBinary, NULL, + &cs.bUseArrayInterface, STD_LOADABLE_MODULE_ID)); + } + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionomstdoutensurelfending", 0, eCmdHdlrBinary, NULL, + &cs.bEnsureLFEnding, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vi:set ai: + */ diff --git a/plugins/omtesting/Makefile.am b/plugins/omtesting/Makefile.am new file mode 100644 index 00000000..4700e1eb --- /dev/null +++ b/plugins/omtesting/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omtesting.la + +omtesting_la_SOURCES = omtesting.c +omtesting_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omtesting_la_LDFLAGS = -module -avoid-version +omtesting_la_LIBADD = diff --git a/plugins/omtesting/omtesting.c b/plugins/omtesting/omtesting.c new file mode 100644 index 00000000..c9f1e06b --- /dev/null +++ b/plugins/omtesting/omtesting.c @@ -0,0 +1,332 @@ +/* omtesting.c + * + * This module is a testing aid. It is not meant to be used in production. I have + * initially written it to introduce delays of custom length to action processing. + * This is needed for development of new message queueing methods. However, I think + * there are other uses for this module. For example, I can envision that it is a good + * thing to have an output module that requests a retry on every "n"th invocation + * and such things. I implement only what I need. But should further testing needs + * arise, it makes much sense to add them here. + * + * This module will become part of the CVS and the rsyslog project because I think + * it is a generally useful debugging, testing and development aid for everyone + * involved with rsyslog. + * + * CURRENT SUPPORTED COMMANDS: + * + * :omtesting:sleep <seconds> <milliseconds> + * + * Must be specified exactly as above. Keep in mind milliseconds are a millionth + * of a second! + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "module-template.h" +#include "conf.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omtesting") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + + +typedef struct _instanceData { + enum { MD_SLEEP, MD_FAIL, MD_RANDFAIL, MD_ALWAYS_SUSPEND } + mode; + int bEchoStdout; + int iWaitSeconds; + int iWaitUSeconds; /* milli-seconds (one million of a second, just to make sure...) */ + int iCurrCallNbr; + int iFailFrequency; + int iResumeAfter; + int iCurrRetries; +} instanceData; + +typedef struct configSettings_s { + int bEchoStdout; /* echo non-failed messages to stdout */ +} configSettings_t; +static configSettings_t cs; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.bEchoStdout = 0; +ENDinitConfVars + +BEGINcreateInstance +CODESTARTcreateInstance + pData->iWaitSeconds = 1; + pData->iWaitUSeconds = 0; +ENDcreateInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("Action delays rule by %d second(s) and %d millisecond(s)\n", + pData->iWaitSeconds, pData->iWaitUSeconds); + /* do nothing */ +ENDdbgPrintInstInfo + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we are not compatible with repeated msg reduction feature, so do not allow it */ +ENDisCompatibleWithFeature + + +/* implement "fail" command in retry processing */ +static rsRetVal doFailOnResume(instanceData *pData) +{ + DEFiRet; + + dbgprintf("fail retry curr %d, max %d\n", pData->iCurrRetries, pData->iResumeAfter); + if(++pData->iCurrRetries == pData->iResumeAfter) { + iRet = RS_RET_OK; + } else { + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +/* implement "fail" command */ +static rsRetVal doFail(instanceData *pData) +{ + DEFiRet; + + dbgprintf("fail curr %d, frquency %d\n", pData->iCurrCallNbr, pData->iFailFrequency); + if(pData->iCurrCallNbr++ % pData->iFailFrequency == 0) { + pData->iCurrRetries = 0; + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +/* implement "sleep" command */ +static rsRetVal doSleep(instanceData *pData) +{ + DEFiRet; + struct timeval tvSelectTimeout; + + dbgprintf("sleep(%d, %d)\n", pData->iWaitSeconds, pData->iWaitUSeconds); + tvSelectTimeout.tv_sec = pData->iWaitSeconds; + tvSelectTimeout.tv_usec = pData->iWaitUSeconds; /* milli seconds */ + select(0, NULL, NULL, NULL, &tvSelectTimeout); + RETiRet; +} + + +/* implement "randomfail" command */ +static rsRetVal doRandFail(void) +{ + DEFiRet; + if((rand() >> 4) < (RAND_MAX >> 5)) { /* rougly same probability */ + iRet = RS_RET_OK; + dbgprintf("omtesting randfail: succeeded this time\n"); + } else { + iRet = RS_RET_SUSPENDED; + dbgprintf("omtesting randfail: failed this time\n"); + } + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + dbgprintf("omtesting tryResume() called\n"); + switch(pData->mode) { + case MD_SLEEP: + break; + case MD_FAIL: + iRet = doFailOnResume(pData); + break; + case MD_RANDFAIL: + iRet = doRandFail(); + break; + case MD_ALWAYS_SUSPEND: + iRet = RS_RET_SUSPENDED; + } + dbgprintf("omtesting tryResume() returns iRet %d\n", iRet); +ENDtryResume + + +BEGINdoAction +CODESTARTdoAction + dbgprintf("omtesting received msg '%s'\n", ppString[0]); + switch(pData->mode) { + case MD_SLEEP: + iRet = doSleep(pData); + break; + case MD_FAIL: + iRet = doFail(pData); + break; + case MD_RANDFAIL: + iRet = doRandFail(); + break; + case MD_ALWAYS_SUSPEND: + iRet = RS_RET_SUSPENDED; + break; + } + + if(iRet == RS_RET_OK && pData->bEchoStdout) { + fprintf(stdout, "%s", ppString[0]); + fflush(stdout); + } + dbgprintf(":omtesting: end doAction(), iRet %d\n", iRet); +ENDdoAction + + +BEGINfreeInstance +CODESTARTfreeInstance + /* we do not have instance data, so we do not need to + * do anything here. -- rgerhards, 2007-07-25 + */ +ENDfreeInstance + + +BEGINparseSelectorAct + int i; + uchar szBuf[1024]; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* code here is quick and dirty - if you like, clean it up. But keep + * in mind it is just a testing aid ;) -- rgerhards, 2007-12-31 + */ + if(!strncmp((char*) p, ":omtesting:", sizeof(":omtesting:") - 1)) { + p += sizeof(":omtesting:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + + /* check mode */ + for(i = 0 ; *p && !isspace((char) *p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = (uchar) *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + + dbgprintf("omtesting command: '%s'\n", szBuf); + if(!strcmp((char*) szBuf, "sleep")) { + /* parse seconds */ + for(i = 0 ; *p && !isspace(*p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + pData->iWaitSeconds = atoi((char*) szBuf); + /* parse milliseconds */ + for(i = 0 ; *p && !isspace(*p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + pData->iWaitUSeconds = atoi((char*) szBuf); + pData->mode = MD_SLEEP; + } else if(!strcmp((char*) szBuf, "fail")) { + /* "fail fail-freqency resume-after" + * fail-frequency specifies how often doAction() fails + * resume-after speicifes how fast tryResume() should come back with success + * all numbers being "times called" + */ + /* parse fail-frequence */ + for(i = 0 ; *p && !isspace(*p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + pData->iFailFrequency = atoi((char*) szBuf); + /* parse resume-after */ + for(i = 0 ; *p && !isspace(*p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + pData->iResumeAfter = atoi((char*) szBuf); + pData->iCurrCallNbr = 1; + pData->mode = MD_FAIL; + } else if(!strcmp((char*) szBuf, "randfail")) { + pData->mode = MD_RANDFAIL; + } else if(!strcmp((char*) szBuf, "always_suspend")) { + pData->mode = MD_ALWAYS_SUSPEND; + } else { + dbgprintf("invalid mode '%s', doing 'sleep 1 0' - fix your config\n", szBuf); + } + + pData->bEchoStdout = cs.bEchoStdout; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (uchar*)"RSYSLOG_TraditionalForwardFormat")); + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionomtestingechostdout", 0, eCmdHdlrBinary, NULL, + &cs.bEchoStdout, STD_LOADABLE_MODULE_ID)); + /* we seed the random-number generator in any case... */ + srand(time(NULL)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/omudpspoof/Makefile.am b/plugins/omudpspoof/Makefile.am new file mode 100644 index 00000000..79c495a0 --- /dev/null +++ b/plugins/omudpspoof/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omudpspoof.la + +omudpspoof_la_SOURCES = omudpspoof.c +omudpspoof_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(UDPSPOOF_CFLAGS) +omudpspoof_la_LDFLAGS = -module -avoid-version +omudpspoof_la_LIBADD = $(UDPSPOOF_LIBS) + +EXTRA_DIST = diff --git a/plugins/omudpspoof/omudpspoof.c b/plugins/omudpspoof/omudpspoof.c new file mode 100644 index 00000000..c80f0e57 --- /dev/null +++ b/plugins/omudpspoof/omudpspoof.c @@ -0,0 +1,787 @@ +/* omudpspoof.c + * + * This is a udp-based output module that support spoofing. + * + * This file builds on UDP spoofing code contributed by + * David Lang <david@lang.hm>. I then created a "real" rsyslog module + * out of that code and omfwd. I decided to make it a separate module because + * omfwd already mixes up too many things (TCP & UDP & a different modes, + * this has historic reasons), it would not be a good idea to also add + * spoofing to it. And, looking at the requirements, there is little in + * common between omfwd and this module. + * + * Note: I have briefly checked libnet source code and I somewhat have the feeling + * that under some circumstances we may get into trouble with the lib. For + * example, it registers an atexit() handler, which should not play nicely + * with our dynamically loaded modules. Anyhow, I refrain from looking deeper + * at libnet code, especially as testing does not show any real issues. If some + * occur, it may be easier to modify libnet for dynamic load environments than + * using a work-around (as a side not, libnet looks somewhat unmaintained, the CVS + * I can see on sourceforge dates has no updates done less than 7 years ago). + * On the other hand, it looks like libnet is thread safe (at least is appropriately + * compiled, which I hope the standard packages are). So I do not guard calls to + * it with my own mutex calls. + * rgerhards, 2009-07-10 + * + * Copyright 2009 David Lang (spoofing code) + * Copyright 2009-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fnmatch.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#ifdef USE_NETZIP +#include <zlib.h> +#endif +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "net.h" +#include "template.h" +#include "msg.h" +#include "cfsysline.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "dirty.h" +#include "unicode-helper.h" +#include "debug.h" + + +#include <libnet.h> +#define _BSD_SOURCE 1 +#define __BSD_SOURCE 1 +#define __FAVOR_BSD 1 + + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omudpspoof") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) + +typedef struct _instanceData { + uchar *tplName; /* name of assigned template */ + uchar *host; + uchar *port; + uchar *sourceTpl; + int mtu; + int *pSockArray; /* sockets to use for UDP */ + struct addrinfo *f_addr; + u_short sourcePort; + u_short sourcePortStart; /* for sorce port iteration */ + u_short sourcePortEnd; + int bReportLibnetInitErr; /* help prevent multiple error messages on init err */ + libnet_t *libnet_handle; + char errbuf[LIBNET_ERRBUF_SIZE]; +} instanceData; + +#define DFLT_SOURCE_PORT_START 32000 +#define DFLT_SOURCE_PORT_END 42000 + +typedef struct configSettings_s { + uchar *tplName; /* name of the default template to use */ + uchar *pszSourceNameTemplate; /* name of the template containing the spoofing address */ + uchar *pszTargetHost; + uchar *pszTargetPort; + int iSourcePortStart; + int iSourcePortEnd; +} configSettings_t; +static configSettings_t cs; + +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "target", eCmdHdlrGetWord, 1 }, + { "port", eCmdHdlrGetWord, 0 }, + { "sourcetemplate", eCmdHdlrGetWord, 0 }, + { "sourceport.start", eCmdHdlrInt, 0 }, + { "sourceport.end", eCmdHdlrInt, 0 }, + { "mtu", eCmdHdlrInt, 0 }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "template", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + uchar *tplName; /* default template */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.tplName = NULL; + cs.pszSourceNameTemplate = NULL; + cs.pszTargetHost = NULL; + cs.pszTargetPort = NULL; + cs.iSourcePortStart = DFLT_SOURCE_PORT_START; + cs.iSourcePortEnd = DFLT_SOURCE_PORT_END; +ENDinitConfVars + + +/* add some variables needed for libnet */ +pthread_mutex_t mutLibnet; + +/* forward definitions */ +static rsRetVal doTryResume(instanceData *pData); + + +/* this function gets the default template. It coordinates action between + * old-style and new-style configuration parts. + */ +static inline uchar* +getDfltTpl(void) +{ + if(loadModConf != NULL && loadModConf->tplName != NULL) + return loadModConf->tplName; + else if(cs.tplName == NULL) + return (uchar*)"RSYSLOG_FileFormat"; + else + return cs.tplName; +} + + +/* set the default template to be used + * This is a module-global parameter, and as such needs special handling. It needs to + * be coordinated with values set via the v2 config system (rsyslog v6+). What we do + * is we do not permit this directive after the v2 config system has been used to set + * the parameter. + */ +rsRetVal +setLegacyDfltTpl(void __attribute__((unused)) *pVal, uchar* newVal) +{ + DEFiRet; + + if(loadModConf != NULL && loadModConf->tplName != NULL) { + free(newVal); + errmsg.LogError(0, RS_RET_ERR, "omudpspoof default template already set via module " + "global parameter - can no longer be changed"); + ABORT_FINALIZE(RS_RET_ERR); + } + free(cs.tplName); + cs.tplName = newVal; +finalize_it: + RETiRet; +} + +/* Close the UDP sockets. + * rgerhards, 2009-05-29 + */ +static rsRetVal +closeUDPSockets(instanceData *pData) +{ + DEFiRet; + assert(pData != NULL); + if(pData->pSockArray != NULL) { + net.closeUDPListenSockets(pData->pSockArray); + pData->pSockArray = NULL; + freeaddrinfo(pData->f_addr); + pData->f_addr = NULL; + } + RETiRet; +} + + +/* get the syslog forward port + * We may change the implementation to try to lookup the port + * if it is unspecified. So far, we use the IANA default auf 514. + * rgerhards, 2007-06-28 + */ +static inline uchar *getFwdPt(instanceData *pData) +{ + return (pData->port == NULL) ? UCHAR_CONSTANT("514") : pData->port; +} + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->tplName = NULL; +ENDbeginCnfLoad + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for omudpspoof:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "template")) { + loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + if(cs.tplName != NULL) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "omudpspoof: warning: default template " + "was already set via legacy directive - may lead to inconsistent " + "results."); + } + } else { + dbgprintf("omudpspoof: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.tplName); + cs.tplName = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf + free(pModConf->tplName); +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance + pData->libnet_handle = NULL; + pData->mtu = 1500; + pData->bReportLibnetInitErr = 1; +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + /* final cleanup */ + closeUDPSockets(pData); + free(pData->tplName); + free(pData->port); + free(pData->host); + free(pData->sourceTpl); + if(pData->libnet_handle != NULL) + libnet_destroy(pData->libnet_handle); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + DBGPRINTF("%s", pData->host); +ENDdbgPrintInstInfo + + +/* Send a message via UDP + * Note: libnet is not thread-safe, so we need to ensure that only one + * instance ever is calling libnet code. + * rgehards, 2007-12-20 + */ +static inline rsRetVal +UDPSend(instanceData *pData, uchar *pszSourcename, char *msg, size_t len) +{ + struct addrinfo *r; + int lsent = 0; + int bSendSuccess; + struct sockaddr_in *tempaddr,source_ip; + libnet_ptag_t ip, ipo; + libnet_ptag_t udp; + sbool bNeedUnlock = 0; + /* hdrOffs = fragmentation flags + offset (in bytes) + * divided by 8 */ + unsigned msgOffs, hdrOffs; + unsigned maxPktLen, pktLen; + DEFiRet; + + if(pData->pSockArray == NULL) { + CHKiRet(doTryResume(pData)); + } + + if(len > 65528) { + DBGPRINTF("omudpspoof: msg with length %d truncated to 64k: '%.768s'\n", + len, msg); + len = 65528; + } + + ip = ipo = udp = 0; + if(pData->sourcePort++ >= pData->sourcePortEnd){ + pData->sourcePort = pData->sourcePortStart; + } + + inet_pton(AF_INET, (char*)pszSourcename, &(source_ip.sin_addr)); + + bSendSuccess = RSFALSE; + d_pthread_mutex_lock(&mutLibnet); + bNeedUnlock = 1; + for (r = pData->f_addr; r && bSendSuccess == RSFALSE ; r = r->ai_next) { + tempaddr = (struct sockaddr_in *)r->ai_addr; + /* Getting max payload size (must be multiple of 8) */ + maxPktLen = (pData->mtu - LIBNET_IPV4_H) & ~0x07; + msgOffs = 0; + /* We're doing (payload size - UDP header size) and not + * checking if it's a multiple of 8 because we know the + * header is 8 bytes long */ + if(len > (maxPktLen - LIBNET_UDP_H) ) { + hdrOffs = IP_MF; + pktLen = maxPktLen - LIBNET_UDP_H; + } else { + hdrOffs = 0; + pktLen = len; + } + DBGPRINTF("omudpspoof: stage 1: MF:%d, hdrOffs %d, pktLen %d\n", + (hdrOffs & IP_MF) >> 13, (hdrOffs & 0x1FFF) << 3, pktLen); + libnet_clear_packet(pData->libnet_handle); + /* note: libnet does need ports in host order NOT in network byte order! -- rgerhards, 2009-11-12 */ + udp = libnet_build_udp( + ntohs(pData->sourcePort),/* source port */ + ntohs(tempaddr->sin_port),/* destination port */ + pktLen+LIBNET_UDP_H, /* packet length */ + 0, /* checksum */ + (u_char*)msg, /* payload */ + pktLen, /* payload size */ + pData->libnet_handle, /* libnet handle */ + udp); /* libnet id */ + if (udp == -1) { + DBGPRINTF("omudpspoof: can't build UDP header: %s\n", libnet_geterror(pData->libnet_handle)); + } + + ip = libnet_build_ipv4( + LIBNET_IPV4_H+LIBNET_UDP_H+pktLen, /* length */ + 0, /* TOS */ + 242, /* IP ID */ + hdrOffs, /* IP Frag */ + 64, /* TTL */ + IPPROTO_UDP, /* protocol */ + 0, /* checksum */ + source_ip.sin_addr.s_addr, + tempaddr->sin_addr.s_addr, + NULL, /* payload */ + 0, /* payload size */ + pData->libnet_handle, /* libnet handle */ + ip); /* libnet id */ + if (ip == -1) { + DBGPRINTF("omudpspoof: can't build IP header: %s\n", libnet_geterror(pData->libnet_handle)); + } + + /* Write it to the wire. */ + lsent = libnet_write(pData->libnet_handle); + if(lsent != (int) (LIBNET_IPV4_H+LIBNET_UDP_H+pktLen)) { + /* note: access to fd is a libnet internal. If a newer version of libnet does + * not expose that member, we should simply remove it. However, while it is there + * it is useful for consolidating with strace output. + */ + DBGPRINTF("omudpspoof: write error (total len %d): pktLen %d, sent %d, fd %d: %s\n", + len, LIBNET_IPV4_H+LIBNET_UDP_H+pktLen, lsent, pData->libnet_handle->fd, + libnet_geterror(pData->libnet_handle)); + if(lsent != -1) { + bSendSuccess = RSTRUE; + } + } else { + bSendSuccess = RSTRUE; + } + msgOffs += pktLen; + + /* We need to get rid of the UDP header to build the other fragments */ + libnet_clear_packet(pData->libnet_handle); + ip = LIBNET_PTAG_INITIALIZER; + while(len > msgOffs ) { /* loop until all payload is sent */ + /* check if there will be more fragments */ + if((len - msgOffs) > maxPktLen) { + /* In IP's eyes, the UDP header in the first packet + * needs to be in the offset, so we add its size to + * the payload offset here */ + hdrOffs = IP_MF + (msgOffs + LIBNET_UDP_H)/8; + pktLen = maxPktLen; + } else { + /* See above */ + hdrOffs = (msgOffs + LIBNET_UDP_H)/8; + pktLen = len - msgOffs; + } + DBGPRINTF("omudpspoof: stage 2: MF:%d, hdrOffs %d, pktLen %d\n", + (hdrOffs & IP_MF) >> 13, (hdrOffs & 0x1FFF) << 3, pktLen); + ip = libnet_build_ipv4( + LIBNET_IPV4_H + pktLen, /* length */ + 0, /* TOS */ + 242, /* IP ID */ + hdrOffs, /* IP Frag */ + 64, /* TTL */ + IPPROTO_UDP, /* protocol */ + 0, /* checksum */ + source_ip.sin_addr.s_addr, + tempaddr->sin_addr.s_addr, + (uint8_t*)(msg+msgOffs), /* payload */ + pktLen, /* payload size */ + pData->libnet_handle, /* libnet handle */ + ip); /* libnet id */ + if (ip == -1) { + DBGPRINTF("omudpspoof: can't build IP fragment header: %s\n", libnet_geterror(pData->libnet_handle)); + } + /* Write it to the wire. */ + lsent = libnet_write(pData->libnet_handle); + if(lsent != (int) (LIBNET_IPV4_H+pktLen)) { + DBGPRINTF("omudpspoof: fragment write error len %d, sent %d: %s\n", + LIBNET_IPV4_H+LIBNET_UDP_H+len, lsent, libnet_geterror(pData->libnet_handle)); + bSendSuccess = RSFALSE; + continue; + } + msgOffs += pktLen; + } + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(pData->libnet_handle != NULL) { + libnet_destroy(pData->libnet_handle); + pData->libnet_handle = NULL; + } + } + if(bNeedUnlock) { + d_pthread_mutex_unlock(&mutLibnet); + } + RETiRet; +} + + +/* try to resume connection if it is not ready + * rgerhards, 2007-08-02 + */ +static rsRetVal doTryResume(instanceData *pData) +{ + int iErr; + struct addrinfo *res; + struct addrinfo hints; + DEFiRet; + + if(pData->pSockArray != NULL) + FINALIZE; + + if(pData->host == NULL) + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + + if(pData->libnet_handle == NULL) { + /* Initialize the libnet library. Root priviledges are required. + * this initializes a IPv4 socket to use for forging UDP packets. + */ + pData->libnet_handle = libnet_init( + LIBNET_RAW4, /* injection type */ + NULL, /* network interface */ + pData->errbuf); /* errbuf */ + + if(pData->libnet_handle == NULL) { + if(pData->bReportLibnetInitErr) { + errmsg.LogError(0, RS_RET_ERR_LIBNET_INIT, "omudpsoof: error " + "initializing libnet - are you running as root?"); + pData->bReportLibnetInitErr = 0; + } + ABORT_FINALIZE(RS_RET_ERR_LIBNET_INIT); + } + } + DBGPRINTF("omudpspoof: libnit_init() ok\n"); + pData->bReportLibnetInitErr = 1; + + /* The remote address is not yet known and needs to be obtained */ + DBGPRINTF("omudpspoof trying resume for '%s'\n", pData->host); + memset(&hints, 0, sizeof(hints)); + /* port must be numeric, because config file syntax requires this */ + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_DGRAM; + if((iErr = (getaddrinfo((char*)pData->host, (char*)getFwdPt(pData), &hints, &res))) != 0) { + DBGPRINTF("could not get addrinfo for hostname '%s':'%s': %d%s\n", + pData->host, getFwdPt(pData), iErr, gai_strerror(iErr)); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + DBGPRINTF("%s found, resuming.\n", pData->host); + pData->f_addr = res; + pData->pSockArray = net.create_udp_socket((uchar*)pData->host, NULL, 0); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pData->f_addr != NULL) { + freeaddrinfo(pData->f_addr); + pData->f_addr = NULL; + } + if(iRet != RS_RET_DISABLE_ACTION) + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doTryResume(pData); +ENDtryResume + +BEGINdoAction + char *psz; /* temporary buffering */ + unsigned l; + int iMaxLine; +CODESTARTdoAction + CHKiRet(doTryResume(pData)); + + DBGPRINTF(" %s:%s/omudpspoof, src '%s', msg strt '%.256s'\n", pData->host, + getFwdPt(pData), ppString[1], ppString[0]); + + iMaxLine = glbl.GetMaxLine(); + psz = (char*) ppString[0]; + l = strlen((char*) psz); + if((int) l > iMaxLine) + l = iMaxLine; + + CHKiRet(UDPSend(pData, ppString[1], psz, l)); + +finalize_it: +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->tplName = NULL; + pData->sourcePortStart = DFLT_SOURCE_PORT_START; + pData->sourcePortEnd = DFLT_SOURCE_PORT_END; + pData->host = NULL; + pData->port = NULL; + pData->sourceTpl = (uchar*) strdup("RSYSLOG_omudpspoofDfltSourceTpl"); + pData->mtu = 1500; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + uchar *tplToUse; + int i; +CODESTARTnewActInst + DBGPRINTF("newActInst (omudpspoof)\n"); + + pvals = nvlstGetParams(lst, &actpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "omudpspoof: mandatory " + "parameters missing"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("action param blk in omudpspoof:\n"); + cnfparamsPrint(&actpblk, pvals); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "target")) { + pData->host = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "port")) { + pData->port = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "sourcetemplate")) { + free(pData->sourceTpl); + pData->sourceTpl = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "sourceport.start")) { + pData->sourcePortStart = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "sourceport.end")) { + pData->sourcePortEnd = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "mtu")) { + pData->mtu = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + DBGPRINTF("omudpspoof: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + CODE_STD_STRING_REQUESTnewActInst(2) + pData->sourcePort = pData->sourcePortStart; + + tplToUse = ustrdup((pData->tplName == NULL) ? getDfltTpl() : pData->tplName); + CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_NO_RQD_TPL_OPTS)); + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->sourceTpl), OMSR_NO_RQD_TPL_OPTS)); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct + uchar *sourceTpl; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(2) + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":omudpspoof:", sizeof(":omudpspoof:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":omudpspoof:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + sourceTpl = (cs.pszSourceNameTemplate == NULL) ? UCHAR_CONSTANT("RSYSLOG_omudpspoofDfltSourceTpl") + : cs.pszSourceNameTemplate; + + if(cs.pszTargetHost == NULL) { + errmsg.LogError(0, NO_ERRCODE, "No $ActionOMUDPSpoofTargetHost given, can not continue with this action."); + ABORT_FINALIZE(RS_RET_HOST_NOT_SPECIFIED); + } + + /* fill instance properties */ + CHKmalloc(pData->host = ustrdup(cs.pszTargetHost)); + if(cs.pszTargetPort == NULL) + pData->port = NULL; + else + CHKmalloc(pData->port = ustrdup(cs.pszTargetPort)); + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(sourceTpl), OMSR_NO_RQD_TPL_OPTS)); + pData->sourcePort = pData->sourcePortStart = cs.iSourcePortStart; + pData->sourcePortEnd = cs.iSourcePortEnd; + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (cs.tplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : cs.tplName)); + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* a common function to free our configuration variables - used both on exit + * and on $ResetConfig processing. -- rgerhards, 2008-05-16 + */ +static void +freeConfigVars(void) +{ + free(cs.tplName); + cs.tplName = NULL; + free(cs.pszTargetHost); + cs.pszTargetHost = NULL; + free(cs.pszTargetPort); + cs.pszTargetPort = NULL; +} + + +BEGINmodExit +CODESTARTmodExit + /* destroy the libnet state needed for forged UDP sources */ + pthread_mutex_destroy(&mutLibnet); + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + freeConfigVars(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + * rgerhards, 2008-03-28 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + freeConfigVars(); + /* we now must reset all non-string values */ + cs.iSourcePortStart = DFLT_SOURCE_PORT_START; + cs.iSourcePortEnd = DFLT_SOURCE_PORT_END; + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net,LM_NET_FILENAME)); + + pthread_mutex_init(&mutLibnet, NULL); + + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofdefaulttemplate", 0, eCmdHdlrGetWord, setLegacyDfltTpl, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofsourcenametemplate", 0, eCmdHdlrGetWord, NULL, &cs.pszSourceNameTemplate, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspooftargethost", 0, eCmdHdlrGetWord, NULL, &cs.pszTargetHost, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspooftargetport", 0, eCmdHdlrGetWord, NULL, &cs.pszTargetPort, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofsourceportstart", 0, eCmdHdlrInt, NULL, &cs.iSourcePortStart, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofsourceportend", 0, eCmdHdlrInt, NULL, &cs.iSourcePortEnd, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/omuxsock/Makefile.am b/plugins/omuxsock/Makefile.am new file mode 100644 index 00000000..997232d9 --- /dev/null +++ b/plugins/omuxsock/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omuxsock.la + +omuxsock_la_SOURCES = omuxsock.c +omuxsock_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) +omuxsock_la_LDFLAGS = -module -avoid-version +omuxsock_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/omuxsock/omuxsock.c b/plugins/omuxsock/omuxsock.c new file mode 100644 index 00000000..583b9f94 --- /dev/null +++ b/plugins/omuxsock/omuxsock.c @@ -0,0 +1,445 @@ +/* omuxsock.c + * This is the implementation of datgram unix domain socket forwarding. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2010-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <assert.h> +#include <errno.h> +#include <unistd.h> +#include "conf.h" +#include "srUtils.h" +#include "template.h" +#include "msg.h" +#include "cfsysline.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "unicode-helper.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omuxsock") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +#define INVLD_SOCK -1 + +typedef struct _instanceData { + permittedPeers_t *pPermPeers; + uchar *sockName; + int sock; + int bIsConnected; /* are we connected to remote host? 0 - no, 1 - yes, UDP means addr resolved */ + struct sockaddr_un addr; +} instanceData; + +/* config data */ +typedef struct configSettings_s { + uchar *tplName; /* name of the default template to use */ + uchar *sockName; /* name of the default template to use */ +} configSettings_t; +static configSettings_t cs; + +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "template", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + uchar *tplName; /* default template */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.tplName = NULL; + cs.sockName = NULL; +ENDinitConfVars + + +static rsRetVal doTryResume(instanceData *pData); + + +/* this function gets the default template. It coordinates action between + * old-style and new-style configuration parts. + */ +static inline uchar* +getDfltTpl(void) +{ + if(loadModConf != NULL && loadModConf->tplName != NULL) + return loadModConf->tplName; + else if(cs.tplName == NULL) + return (uchar*)"RSYSLOG_TraditionalForwardFormat"; + else + return cs.tplName; +} + +/* set the default template to be used + * This is a module-global parameter, and as such needs special handling. It needs to + * be coordinated with values set via the v2 config system (rsyslog v6+). What we do + * is we do not permit this directive after the v2 config system has been used to set + * the parameter. + */ +rsRetVal +setLegacyDfltTpl(void __attribute__((unused)) *pVal, uchar* newVal) +{ + DEFiRet; + + if(loadModConf != NULL && loadModConf->tplName != NULL) { + free(newVal); + errmsg.LogError(0, RS_RET_ERR, "omuxsock default template already set via module " + "global parameter - can no longer be changed"); + ABORT_FINALIZE(RS_RET_ERR); + } + free(cs.tplName); + cs.tplName = newVal; +finalize_it: + RETiRet; +} + + +static inline rsRetVal +closeSocket(instanceData *pData) +{ + DEFiRet; + if(pData->sock != INVLD_SOCK) { + close(pData->sock); + pData->sock = INVLD_SOCK; + } +pData->bIsConnected = 0; // TODO: remove this variable altogether + RETiRet; +} + + + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->tplName = NULL; +ENDbeginCnfLoad + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for omuxsock:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "template")) { + loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + if(cs.tplName != NULL) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "omuxsock: warning: default template " + "was already set via legacy directive - may lead to inconsistent " + "results."); + } + } else { + dbgprintf("omuxsock: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.tplName); + cs.tplName = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf + free(pModConf->tplName); +ENDfreeCnf + +BEGINcreateInstance +CODESTARTcreateInstance + pData->sock = INVLD_SOCK; +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + /* final cleanup */ + closeSocket(pData); + free(pData->sockName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + DBGPRINTF("%s", pData->sockName); +ENDdbgPrintInstInfo + + +/* Send a message via UDP + * rgehards, 2007-12-20 + */ +static rsRetVal sendMsg(instanceData *pData, char *msg, size_t len) +{ + DEFiRet; + unsigned lenSent = 0; + + if(pData->sock == INVLD_SOCK) { + CHKiRet(doTryResume(pData)); + } + + if(pData->sock != INVLD_SOCK) { + /* we need to track if we have success sending to the remote + * peer. Success is indicated by at least one sendto() call + * succeeding. We track this be bSendSuccess. We can not simply + * rely on lsent, as a call might initially work, but a later + * call fails. Then, lsent has the error status, even though + * the sendto() succeeded. -- rgerhards, 2007-06-22 + */ + lenSent = sendto(pData->sock, msg, len, 0, &pData->addr, sizeof(pData->addr)); + if(lenSent == len) { + int eno = errno; + char errStr[1024]; + DBGPRINTF("omuxsock suspending: sendto(), socket %d, error: %d = %s.\n", + pData->sock, eno, rs_strerror_r(eno, errStr, sizeof(errStr))); + } + } + +finalize_it: + RETiRet; +} + + +/* open socket to remote system + */ +static inline rsRetVal +openSocket(instanceData *pData) +{ + DEFiRet; + assert(pData->sock == INVLD_SOCK); + + if((pData->sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { + char errStr[1024]; + int eno = errno; + DBGPRINTF("error %d creating AF_UNIX/SOCK_DGRAM: %s.\n", + eno, rs_strerror_r(eno, errStr, sizeof(errStr))); + pData->sock = INVLD_SOCK; + ABORT_FINALIZE(RS_RET_NO_SOCKET); + + } + + /* set up server address structure */ + memset(&pData->addr, 0, sizeof(pData->addr)); + pData->addr.sun_family = AF_UNIX; + strcpy(pData->addr.sun_path, (char*)pData->sockName); + +finalize_it: + if(iRet != RS_RET_OK) { + closeSocket(pData); + } + RETiRet; +} + + + +/* try to resume connection if it is not ready + */ +static rsRetVal doTryResume(instanceData *pData) +{ + DEFiRet; + + DBGPRINTF("omuxsock trying to resume\n"); + closeSocket(pData); + iRet = openSocket(pData); + + if(iRet != RS_RET_OK) { + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doTryResume(pData); +ENDtryResume + +BEGINdoAction + char *psz = NULL; /* temporary buffering */ + register unsigned l; + int iMaxLine; +CODESTARTdoAction + CHKiRet(doTryResume(pData)); + + iMaxLine = glbl.GetMaxLine(); + + DBGPRINTF(" omuxsock:%s\n", pData->sockName); + + psz = (char*) ppString[0]; + l = strlen((char*) psz); + if((int) l > iMaxLine) + l = iMaxLine; + + CHKiRet(sendMsg(pData, psz, l)); + +finalize_it: +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + + /* first check if this config line is actually for us */ + if(strncmp((char*) p, ":omuxsock:", sizeof(":omuxsock:") - 1)) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + p += sizeof(":omuxsock:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + CHKiRet(createInstance(&pData)); + + /* check if a non-standard template is to be applied */ + if(*(p-1) == ';') + --p; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, 0, getDfltTpl())); + + if(cs.sockName == NULL) { + errmsg.LogError(0, RS_RET_NO_SOCK_CONFIGURED, "No output socket configured for omuxsock\n"); + ABORT_FINALIZE(RS_RET_NO_SOCK_CONFIGURED); + } + + pData->sockName = cs.sockName; + cs.sockName = NULL; /* pData is now owner and will fee it */ + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* a common function to free our configuration variables - used both on exit + * and on $ResetConfig processing. -- rgerhards, 2008-05-16 + */ +static inline void +freeConfigVars(void) +{ + free(cs.tplName); + cs.tplName = NULL; + free(cs.sockName); + cs.sockName = NULL; +} + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + + freeConfigVars(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + * rgerhards, 2008-03-28 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + freeConfigVars(); + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(regCfSysLineHdlr((uchar *)"omuxsockdefaulttemplate", 0, eCmdHdlrGetWord, setLegacyDfltTpl, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"omuxsocksocket", 0, eCmdHdlrGetWord, NULL, &cs.sockName, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/omzmq3/Makefile.am b/plugins/omzmq3/Makefile.am new file mode 100644 index 00000000..92cd7586 --- /dev/null +++ b/plugins/omzmq3/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = omzmq3.la + +omzmq3_la_SOURCES = omzmq3.c +omzmq3_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(CZMQ_CFLAGS) +omzmq3_la_LDFLAGS = -module -avoid-version +omzmq3_la_LIBADD = $(CZMQ_LIBS) + +EXTRA_DIST = diff --git a/plugins/omzmq3/README b/plugins/omzmq3/README new file mode 100644 index 00000000..c2a33555 --- /dev/null +++ b/plugins/omzmq3/README @@ -0,0 +1,19 @@ +ZeroMQ 3.x Output Plugin + +Building this plugin: +Requires libzmq and libczmq. First, download the tarballs of both libzmq +and its supporting libczmq from http://download.zeromq.org. As of this +writing (04/23/2013), the most recent versions of libzmq and czmq are +3.2.2 and 1.3.2 respectively. Configure, build, and then install both libs. + +Omzmq3 allows you to push data out of rsyslog from a zeromq socket. The example +below binds a PUB socket to port 7171, and any message fitting the criteria will +be output to the zmq socket. + +Example Rsyslog.conf snippet (NOTE: v6 format): +------------------------------------------------------------------------------- +if $msg then { + action(type="omzmq3", sockType="PUB", action="BIND", + description="tcp://*:7172) +} +------------------------------------------------------------------------------- diff --git a/plugins/omzmq3/omzmq3.c b/plugins/omzmq3/omzmq3.c new file mode 100644 index 00000000..c8552f11 --- /dev/null +++ b/plugins/omzmq3/omzmq3.c @@ -0,0 +1,476 @@ +/* omzmq3.c + * Copyright 2012 Talksum, Inc + * Using the czmq interface to zeromq, we output + * to a zmq socket. + + +* +* This program is free software: you can redistribute it and/or +* modify it under the terms of the GNU Lesser 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this program. If not, see +* <http://www.gnu.org/licenses/>. +* +* Author: David Kelly +* <davidk@talksum.com> +*/ + + +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +#include <czmq.h> + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omzmq3") + +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* convienent symbols to denote a socket we want to bind + vs one we want to just connect to +*/ +#define ACTION_CONNECT 1 +#define ACTION_BIND 2 + + +/* ---------------------------------------------------------------------------- + * structs to describe sockets + */ +struct socket_type { + char* name; + int type; +}; + +/* more overkill, but seems nice to be consistent. */ +struct socket_action { + char* name; + int action; +}; + +typedef struct _instanceData { + void* socket; + uchar* description; + int type; + int action; + int sndHWM; + int rcvHWM; + uchar* identity; + int sndBuf; + int rcvBuf; + int linger; + int backlog; + int sndTimeout; + int rcvTimeout; + int maxMsgSize; + int rate; + int recoveryIVL; + int multicastHops; + int reconnectIVL; + int reconnectIVLMax; + int ipv4Only; + int affinity; + uchar* tplName; +} instanceData; + + +/* ---------------------------------------------------------------------------- + * Static definitions/initializations + */ + +/* only 1 zctx for all the sockets, with an adjustable number of + worker threads which may be useful if we use affinity in particular + sockets +*/ +static zctx_t* s_context = NULL; +static int s_workerThreads = -1; + +static struct socket_type types[] = { + {"PUB", ZMQ_PUB }, + {"PUSH", ZMQ_PUSH }, + {"DEALER", ZMQ_DEALER }, + {"XPUB", ZMQ_XPUB } +}; + +static struct socket_action actions[] = { + {"BIND", ACTION_BIND}, + {"CONNECT", ACTION_CONNECT}, +}; + +static struct cnfparamdescr actpdescr[] = { + { "description", eCmdHdlrGetWord, 0 }, + { "sockType", eCmdHdlrGetWord, 0 }, + { "action", eCmdHdlrGetWord, 0 }, + { "sndHWM", eCmdHdlrInt, 0 }, + { "rcvHWM", eCmdHdlrInt, 0 }, + { "identity", eCmdHdlrGetWord, 0 }, + { "sndBuf", eCmdHdlrInt, 0 }, + { "rcvBuf", eCmdHdlrInt, 0 }, + { "linger", eCmdHdlrInt, 0 }, + { "backlog", eCmdHdlrInt, 0 }, + { "sndTimeout", eCmdHdlrInt, 0 }, + { "rcvTimeout", eCmdHdlrInt, 0 }, + { "maxMsgSize", eCmdHdlrInt, 0 }, + { "rate", eCmdHdlrInt, 0 }, + { "recoveryIVL", eCmdHdlrInt, 0 }, + { "multicastHops", eCmdHdlrInt, 0 }, + { "reconnectIVL", eCmdHdlrInt, 0 }, + { "reconnectIVLMax", eCmdHdlrInt, 0 }, + { "ipv4Only", eCmdHdlrInt, 0 }, + { "affinity", eCmdHdlrInt, 0 }, + { "globalWorkerThreads", eCmdHdlrInt, 0 }, + { "template", eCmdHdlrGetWord, 1 } +}; + +static struct cnfparamblk actpblk = { + CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr +}; + +/* ---------------------------------------------------------------------------- + * Helper Functions + */ + +/* get the name of a socket type, return the ZMQ_XXX type + or -1 if not a supported type (see above) +*/ +int getSocketType(char* name) { + int type = -1; + uint i; + for(i=0; i<sizeof(types)/sizeof(struct socket_type); ++i) { + if( !strcmp(types[i].name, name) ) { + type = types[i].type; + break; + } + } + return type; +} + + +static int getSocketAction(char* name) { + int action = -1; + uint i; + for(i=0; i < sizeof(actions)/sizeof(struct socket_action); ++i) { + if(!strcmp(actions[i].name, name)) { + action = actions[i].action; + break; + } + } + return action; +} + +/* closeZMQ will destroy the context and + * associated socket + */ +static void closeZMQ(instanceData* pData) { + errmsg.LogError(0, NO_ERRCODE, "closeZMQ called"); + if(s_context && pData->socket) { + if(pData->socket != NULL) { + zsocket_destroy(s_context, pData->socket); + } + } +} + + +static rsRetVal initZMQ(instanceData* pData) { + DEFiRet; + + /* create the context if necessary. */ + if (NULL == s_context) { + zsys_handler_set(NULL); + s_context = zctx_new(); + if (s_workerThreads > 0) zctx_set_iothreads(s_context, s_workerThreads); + } + + pData->socket = zsocket_new(s_context, pData->type); + if (NULL == pData->socket) { + errmsg.LogError(0, RS_RET_NO_ERRCODE, + "omzmq3: zsocket_new failed for %s: %s", + pData->description, zmq_strerror(errno)); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } + /* use czmq defaults for these, unless set to non-default values */ + if(pData->identity) zsocket_set_identity(pData->socket, (char*)pData->identity); + if(pData->sndBuf > -1) zsocket_set_sndbuf(pData->socket, pData->sndBuf); + if(pData->rcvBuf > -1) zsocket_set_sndbuf(pData->socket, pData->rcvBuf); + if(pData->linger > -1) zsocket_set_linger(pData->socket, pData->linger); + if(pData->backlog > -1) zsocket_set_backlog(pData->socket, pData->backlog); + if(pData->sndTimeout > -1) zsocket_set_sndtimeo(pData->socket, pData->sndTimeout); + if(pData->rcvTimeout > -1) zsocket_set_rcvtimeo(pData->socket, pData->rcvTimeout); + if(pData->maxMsgSize > -1) zsocket_set_maxmsgsize(pData->socket, pData->maxMsgSize); + if(pData->rate > -1) zsocket_set_rate(pData->socket, pData->rate); + if(pData->recoveryIVL > -1) zsocket_set_recovery_ivl(pData->socket, pData->recoveryIVL); + if(pData->multicastHops > -1) zsocket_set_multicast_hops(pData->socket, pData->multicastHops); + if(pData->reconnectIVL > -1) zsocket_set_reconnect_ivl(pData->socket, pData->reconnectIVL); + if(pData->reconnectIVLMax > -1) zsocket_set_reconnect_ivl_max(pData->socket, pData->reconnectIVLMax); + if(pData->ipv4Only > -1) zsocket_set_ipv4only(pData->socket, pData->ipv4Only); + if(pData->affinity != 1) zsocket_set_affinity(pData->socket, pData->affinity); + if(pData->rcvHWM > -1) zsocket_set_rcvhwm(pData->socket, pData->rcvHWM); + if(pData->sndHWM > -1) zsocket_set_sndhwm(pData->socket, pData->sndHWM); + + /* bind or connect to it */ + if (pData->action == ACTION_BIND) { + /* bind asserts, so no need to test return val here + which isn't the greatest api -- oh well */ + if(-1 == zsocket_bind(pData->socket, (char*)pData->description)) { + errmsg.LogError(0, RS_RET_NO_ERRCODE, "omzmq3: bind failed for %s: %s", + pData->description, zmq_strerror(errno)); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } + DBGPRINTF("omzmq3: bind to %s successful\n",pData->description); + } else { + if(-1 == zsocket_connect(pData->socket, (char*)pData->description)) { + errmsg.LogError(0, RS_RET_NO_ERRCODE, "omzmq3: connect failed for %s: %s", + pData->description, zmq_strerror(errno)); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } + DBGPRINTF("omzmq3: connect to %s successful", pData->description); + } + finalize_it: + RETiRet; +} + +rsRetVal writeZMQ(uchar* msg, instanceData* pData) { + DEFiRet; + + /* initialize if necessary */ + if(NULL == pData->socket) + CHKiRet(initZMQ(pData)); + + /* send it */ + int result = zstr_send(pData->socket, (char*)msg); + + /* whine if things went wrong */ + if (result == -1) { + errmsg.LogError(0, NO_ERRCODE, "omzmq3: send of %s failed: %s", msg, zmq_strerror(errno)); + ABORT_FINALIZE(RS_RET_ERR); + } + finalize_it: + RETiRet; +} + +static inline void +setInstParamDefaults(instanceData* pData) { + pData->description = NULL; + pData->socket = NULL; + pData->tplName = NULL; + pData->type = ZMQ_PUB; + pData->action = ACTION_BIND; + pData->sndHWM = -1; + pData->rcvHWM = -1; + pData->identity = NULL; + pData->sndBuf = -1; + pData->rcvBuf = -1; + pData->linger = -1; + pData->backlog = -1; + pData->sndTimeout = -1; + pData->rcvTimeout = -1; + pData->maxMsgSize = -1; + pData->rate = -1; + pData->recoveryIVL = -1; + pData->multicastHops = -1; + pData->reconnectIVL = -1; + pData->reconnectIVLMax = -1; + pData->ipv4Only = -1; + pData->affinity = 1; +} + + +/* ---------------------------------------------------------------------------- + * Output Module Functions + */ + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo +ENDdbgPrintInstInfo + +BEGINfreeInstance +CODESTARTfreeInstance + closeZMQ(pData); + free(pData->description); + free(pData->tplName); + free(pData->identity); +ENDfreeInstance + +BEGINtryResume +CODESTARTtryResume + if(NULL == pData->socket) + iRet = initZMQ(pData); +ENDtryResume + +BEGINdoAction +CODESTARTdoAction +iRet = writeZMQ(ppString[0], pData); +ENDdoAction + + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if ((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + +CHKiRet(createInstance(&pData)); +setInstParamDefaults(pData); + +CODE_STD_STRING_REQUESTnewActInst(1) + for (i = 0; i < actpblk.nParams; ++i) { + if (!pvals[i].bUsed) + continue; + if (!strcmp(actpblk.descr[i].name, "description")) { + pData->description = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "sockType")){ + pData->type = getSocketType(es_str2cstr(pvals[i].val.d.estr, NULL)); + } else if (!strcmp(actpblk.descr[i].name, "action")){ + pData->action = getSocketAction(es_str2cstr(pvals[i].val.d.estr, NULL)); + } else if (!strcmp(actpblk.descr[i].name, "sndHWM")) { + pData->sndHWM = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "rcvHWM")) { + pData->rcvHWM = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "identity")){ + pData->identity = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if (!strcmp(actpblk.descr[i].name, "sndBuf")) { + pData->sndBuf = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "rcvBuf")) { + pData->rcvBuf = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "linger")) { + pData->linger = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "backlog")) { + pData->backlog = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "sndTimeout")) { + pData->sndTimeout = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "rcvTimeout")) { + pData->rcvTimeout = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "maxMsgSize")) { + pData->maxMsgSize = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "rate")) { + pData->rate = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "recoveryIVL")) { + pData->recoveryIVL = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "multicastHops")) { + pData->multicastHops = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "reconnectIVL")) { + pData->reconnectIVL = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "reconnectIVLMax")) { + pData->reconnectIVLMax = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "ipv4Only")) { + pData->ipv4Only = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "affinity")) { + pData->affinity = (int) pvals[i].val.d.n; + } else if (!strcmp(actpblk.descr[i].name, "globalWorkerThreads")) { + s_workerThreads = (int) pvals[i].val.d.n; + } else { + errmsg.LogError(0, NO_ERRCODE, "omzmq3: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if (pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*)strdup("RSYSLOG_ForwardFormat"), OMSR_NO_RQD_TPL_OPTS)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*)pData->tplName, OMSR_NO_RQD_TPL_OPTS)); + } + if (NULL == pData->description) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "omzmq3: you didn't enter a description"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if (pData->type == -1) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "omzmq3: unknown socket type."); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if (pData->action == -1) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "omzmq3: unknown socket action"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + +/* tell the engine we only want one template string */ +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omzmq3:", sizeof(":omzmq3:") - 1)) + errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED, + "omzmq3 supports only v6 config format, use: " + "action(type=\"omzmq3\" serverport=...)"); + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + +BEGINinitConfVars /* (re)set config variables to defaults */ +CODESTARTinitConfVars +s_workerThreads = -1; +ENDinitConfVars + +BEGINmodExit +CODESTARTmodExit + if (NULL != s_context) { + zctx_destroy(&s_context); + s_context=NULL; + } +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* only supports rsyslog 6 configs */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); + DBGPRINTF("omzmq3: module compiled with rsyslog version %s.\n", VERSION); + +INITLegCnfVars +CHKiRet(omsdRegCFSLineHdlr((uchar *)"omzmq3workerthreads", 0, eCmdHdlrInt, NULL, &s_workerThreads, STD_LOADABLE_MODULE_ID)); +ENDmodInit + + + diff --git a/plugins/pmaixforwardedfrom/Makefile.am b/plugins/pmaixforwardedfrom/Makefile.am new file mode 100644 index 00000000..af359d31 --- /dev/null +++ b/plugins/pmaixforwardedfrom/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = pmaixforwardedfrom.la
+
+pmaixforwardedfrom_la_SOURCES = pmaixforwardedfrom.c
+pmaixforwardedfrom_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) -I ../../tools
+pmaixforwardedfrom_la_LDFLAGS = -module -avoid-version
+pmaixforwardedfrom_la_LIBADD =
+
+EXTRA_DIST =
diff --git a/plugins/pmaixforwardedfrom/pmaixforwardedfrom.c b/plugins/pmaixforwardedfrom/pmaixforwardedfrom.c new file mode 100644 index 00000000..76198e9c --- /dev/null +++ b/plugins/pmaixforwardedfrom/pmaixforwardedfrom.c @@ -0,0 +1,169 @@ +/* pmaixforwardedfrom.c + * + * this cleans up messages forwarded from AIX + * + * instead of actually parsing the message, this modifies the message and then falls through to allow a later parser to handle the now modified message + * + * created 2010-12-13 by David Lang based on pmlastmsg + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("pmaixforwardedfrom") +PARSER_NAME("rsyslog.aixforwardedfrom") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* static data */ +static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINparse + uchar *p2parse; + uchar *opening; + int lenMsg; +#define OpeningText "Message forwarded from " +CODESTARTparse + dbgprintf("Message will now be parsed by fix AIX Forwarded From parser.\n"); + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + + /* check if this message is of the type we handle in this (very limited) parser */ + /* first, we permit SP */ + while(lenMsg && *p2parse == ' ') { + --lenMsg; + ++p2parse; + } +dbgprintf("pmaixforwardedfrom: msg to look at: [%d]'%s'\n", lenMsg, p2parse); + if((unsigned) lenMsg < 42) { + /* too short, can not be "our" message */ + /* minimum message, 16 character timestamp, 'Message forwarded from ", 1 character name, ': '*/ +dbgprintf("msg too short!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + + /* skip over timestamp */ + lenMsg -=16; + p2parse +=16; + /* if there is the string "Message forwarded from " were the hostname should be */ + if(strncasecmp((char*) p2parse, OpeningText, sizeof(OpeningText)-1) != 0) { + /* wrong opening text */ +dbgprintf("not a AIX message forwarded from mangled log!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + /* bump the message portion up by 23 characters to overwrite the "Message forwarded from " with the hostname */ + lenMsg -=23; + memmove(p2parse, p2parse + 23, lenMsg); + *(p2parse + lenMsg) = '\n'; + *(p2parse + lenMsg + 1) = '\0'; + pMsg->iLenRawMsg -=23; + pMsg->iLenMSG -=23; + /* now look for the : after the hostname to walk past the hostname, also watch for a space in case this isn't really an AIX log, but has a similar preamble */ + while(lenMsg && *p2parse != ' ' && *p2parse != ':') { + --lenMsg; + ++p2parse; + } + if (lenMsg && *p2parse != ':') { +dbgprintf("not a AIX message forwarded from mangled log but similar enough that the preamble has been removed\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + /* bump the message portion up by one character to overwrite the extra : */ + lenMsg -=1; + memmove(p2parse, p2parse + 1, lenMsg); + *(p2parse + lenMsg) = '\n'; + *(p2parse + lenMsg + 1) = '\0'; + pMsg->iLenRawMsg -=1; + pMsg->iLenMSG -=1; + /* now, claim to abort so that something else can parse the now modified message */ + DBGPRINTF("pmaixforwardedfrom: new mesage: [%d]'%s'\n", lenMsg, pMsg->pszRawMsg + pMsg->offAfterPRI); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + +finalize_it: +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + DBGPRINTF("aixforwardedfrom parser init called, compiled with version %s\n", VERSION); + bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(); /* cache value, is set only during rsyslogd option processing */ + + +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/pmcisconames/Makefile.am b/plugins/pmcisconames/Makefile.am new file mode 100644 index 00000000..16ed347d --- /dev/null +++ b/plugins/pmcisconames/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = pmcisconames.la + +pmcisconames_la_SOURCES = pmcisconames.c +pmcisconames_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) -I ../../tools +pmcisconames_la_LDFLAGS = -module -avoid-version +pmcisconames_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/pmcisconames/pmcisconames.c b/plugins/pmcisconames/pmcisconames.c new file mode 100644 index 00000000..d8235752 --- /dev/null +++ b/plugins/pmcisconames/pmcisconames.c @@ -0,0 +1,179 @@ +/* pmcisconames.c + * + * this detects logs sent by Cisco devices that mangle their syslog output when you tell them to log by name by adding ' :' between the name and the %XXX-X-XXXXXXX: tag + * + * instead of actually parsing the message, this modifies the message and then falls through to allow a later parser to handle the now modified message + * + * created 2010-12-13 by David Lang based on pmlastmsg + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("pmcisconames") +PARSER_NAME("rsyslog.cisconames") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* static data */ +static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINparse + uchar *p2parse; + int lenMsg; +#define OpeningText ": %" +CODESTARTparse + dbgprintf("Message will now be parsed by fix Cisco Names parser.\n"); + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + + /* check if this message is of the type we handle in this (very limited) parser */ + /* first, we permit SP */ + while(lenMsg && *p2parse == ' ') { + --lenMsg; + ++p2parse; + } +dbgprintf("pmcisconames: msg to look at: [%d]'%s'\n", lenMsg, p2parse); + if((unsigned) lenMsg < 34) { + /* too short, can not be "our" message */ + /* minimum message, 16 character timestamp, 1 character name, ' : %ASA-1-000000: '*/ +dbgprintf("msg too short!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + /* check if the timestamp is a 16 character or 21 character timestamp + 'Mmm DD HH:MM:SS ' spaces at 3,6,15 : at 9,12 + 'Mmm DD YYYY HH:MM:SS ' spaces at 3,6,11,20 : at 14,17 + check for the : first as that will differentiate the two conditions the fastest + this allows the compiler to short circuit the rst of the tests if it is the wrong timestamp + but still check the rest to see if it looks correct + */ + if ( *(p2parse + 9) == ':' && *(p2parse + 12) == ':' && *(p2parse + 3) == ' ' && *(p2parse + 6) == ' ' && *(p2parse + 15) == ' ') { + /* skip over timestamp */ + dbgprintf("short timestamp found\n"); + lenMsg -=16; + p2parse +=16; + } else { + if ( *(p2parse + 14) == ':' && *(p2parse + 17) == ':' && *(p2parse + 3) == ' ' && *(p2parse + 6) == ' ' && *(p2parse + 11) == ' ' && *(p2parse + 20) == ' ') { + /* skip over timestamp */ + dbgprintf("long timestamp found\n"); + lenMsg -=21; + p2parse +=21; + } else { + dbgprintf("timestamp is not one of the valid formats\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + } + /* now look for the next space to walk past the hostname */ + while(lenMsg && *p2parse != ' ') { + --lenMsg; + ++p2parse; + } + /* skip the space after the hostname */ + lenMsg -=1; + p2parse +=1; + /* if the syslog tag is : and the next thing starts with a % assume that this is a mangled cisco log and fix it */ + if(strncasecmp((char*) p2parse, OpeningText, sizeof(OpeningText)-1) != 0) { + /* wrong opening text */ +dbgprintf("not a cisco name mangled log!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + /* bump the message portion up by two characters to overwrite the extra : */ + lenMsg -=2; + memmove(p2parse, p2parse + 2, lenMsg); + *(p2parse + lenMsg) = '\n'; + *(p2parse + lenMsg + 1) = '\0'; + pMsg->iLenRawMsg -=2; + pMsg->iLenMSG -=2; + /* now, claim to abort so that something else can parse the now modified message */ + DBGPRINTF("pmcisconames: new mesage: [%d]'%s'\n", lenMsg, p2parse); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + +finalize_it: +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + DBGPRINTF("cisconames parser init called, compiled with version %s\n", VERSION); + bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(); /* cache value, is set only during rsyslogd option processing */ + + +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/pmlastmsg/Makefile.am b/plugins/pmlastmsg/Makefile.am new file mode 100644 index 00000000..f360243c --- /dev/null +++ b/plugins/pmlastmsg/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = pmlastmsg.la + +pmlastmsg_la_SOURCES = pmlastmsg.c +pmlastmsg_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) -I ../../tools +pmlastmsg_la_LDFLAGS = -module -avoid-version +pmlastmsg_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/pmlastmsg/pmlastmsg.c b/plugins/pmlastmsg/pmlastmsg.c new file mode 100644 index 00000000..a290c446 --- /dev/null +++ b/plugins/pmlastmsg/pmlastmsg.c @@ -0,0 +1,177 @@ +/* pmlastmsg.c + * This is a parser module specifically for those horrible + * "<PRI>last message repeated n times" messages notoriously generated + * by some syslog implementations. Note that this parser should be placed + * on top of the parser stack -- it takes out only these messages and + * leaves all others for processing by the other parsers. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2010-07-13 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("pmlastmsg") +PARSER_NAME("rsyslog.lastline") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* static data */ +static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +/* parse a legay-formatted syslog message. + */ +BEGINparse + uchar *p2parse; + int lenMsg; +#define OpeningText "last message repeated " +#define ClosingText " times" +CODESTARTparse + dbgprintf("Message will now be parsed by \"last message repated n times\" parser.\n"); + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + + /* check if this message is of the type we handle in this (very limited) parser */ + /* first, we permit SP */ + while(lenMsg && *p2parse == ' ') { + --lenMsg; + ++p2parse; + } +dbgprintf("pmlastmsg: msg to look at: [%d]'%s'\n", lenMsg, p2parse); + if((unsigned) lenMsg < sizeof(OpeningText)-1 + sizeof(ClosingText)-1 + 1) { + /* too short, can not be "our" message */ +dbgprintf("msg too short!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + + if(strncasecmp((char*) p2parse, OpeningText, sizeof(OpeningText)-1) != 0) { + /* wrong opening text */ +dbgprintf("wrong opening text!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + lenMsg -= sizeof(OpeningText) - 1; + p2parse += sizeof(OpeningText) - 1; + + /* now we need an integer --> digits */ + while(lenMsg && isdigit(*p2parse)) { + --lenMsg; + ++p2parse; + } + + if(lenMsg != sizeof(ClosingText)-1) { + /* size must fit, else it is not "our" message... */ + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + + if(strncasecmp((char*) p2parse, ClosingText, lenMsg) != 0) { + /* wrong closing text */ +dbgprintf("strcasecmp: %d\n", strncasecmp((char*) p2parse, ClosingText, lenMsg)); +dbgprintf("pmlastmsg: closing msg to look at: [%d]'%s', (%s)\n", lenMsg, p2parse, ClosingText); +dbgprintf("wrong closing text!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + + /* OK, now we know we need to process this message, so we do that + * (and it is fairly simple in our case...) + */ + DBGPRINTF("pmlastmsg detected a \"last message repeated n times\" message\n"); + + setProtocolVersion(pMsg, 0); + memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime)); + MsgSetMSGoffs(pMsg, pMsg->offAfterPRI); /* we don't have a header! */ + MsgSetTAG(pMsg, (uchar*)"", 0); + +finalize_it: +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + dbgprintf("lastmsg parser init called, compiled with version %s\n", VERSION); + bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(); /* cache value, is set only during rsyslogd option processing */ + + +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/pmrfc3164sd/Makefile.am b/plugins/pmrfc3164sd/Makefile.am new file mode 100644 index 00000000..d1662d4d --- /dev/null +++ b/plugins/pmrfc3164sd/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = pmrfc3164sd.la + +pmrfc3164sd_la_SOURCES = pmrfc3164sd.c +pmrfc3164sd_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) -I ../../tools +pmrfc3164sd_la_LDFLAGS = -module -avoid-version +pmrfc3164sd_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/pmrfc3164sd/pmrfc3164sd.c b/plugins/pmrfc3164sd/pmrfc3164sd.c new file mode 100644 index 00000000..de5805bc --- /dev/null +++ b/plugins/pmrfc3164sd/pmrfc3164sd.c @@ -0,0 +1,345 @@ +/* pmrfc3164sd.c + * This is a parser module for RFC3164(legacy syslog)-formatted messages. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2009-11-04 by RGerhards + * + * Copyright 2007, 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("pmrfc3164sd") +PARSER_NAME("contrib.rfc3164sd") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* static data */ +static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + +/* Helper to parseRFCSyslogMsg. This function parses the structured + * data field of a message. It does NOT parse inside structured data, + * just gets the field as whole. Parsing the single entities is left + * to other functions. The parsepointer is advanced + * to after the terminating SP. The caller must ensure that the + * provided buffer is large enough to hold the to be extracted value. + * Returns 0 if everything is fine or 1 if either the field is not + * SP-terminated or any other error occurs. -- rger, 2005-11-24 + * The function now receives the size of the string and makes sure + * that it does not process more than that. The *pLenStr counter is + * updated on exit. -- rgerhards, 2009-09-23 + */ +static int parseRFCStructuredData(uchar **pp2parse, uchar *pResult, int *pLenStr) +{ + uchar *p2parse; + int bCont = 1; + int iRet = 0; + int lenStr; + + assert(pp2parse != NULL); + assert(*pp2parse != NULL); + assert(pResult != NULL); + + p2parse = *pp2parse; + lenStr = *pLenStr; + + /* this is the actual parsing loop + * Remeber: structured data starts with [ and includes any characters + * until the first ] followed by a SP. There may be spaces inside + * structured data. There may also be \] inside the structured data, which + * do NOT terminate an element. + */ + + /* trim */ + while(lenStr > 0 && *p2parse == ' ') { + ++p2parse; /* eat SP, but only if not at end of string */ + --lenStr; + } + + if(lenStr == 0 || *p2parse != '[') + return 1; /* this is NOT structured data! */ + + if(*p2parse == '-') { /* empty structured data? */ + *pResult++ = '-'; + ++p2parse; + --lenStr; + } else { + while(bCont) { + if(lenStr < 2) { + /* we now need to check if we have only structured data */ + if(lenStr > 0 && *p2parse == ']') { + *pResult++ = *p2parse; + p2parse++; + lenStr--; + bCont = 0; + } else { + iRet = 1; /* this is not valid! */ + bCont = 0; + } + } else if(*p2parse == '\\' && *(p2parse+1) == ']') { + /* this is escaped, need to copy both */ + *pResult++ = *p2parse++; + *pResult++ = *p2parse++; + lenStr -= 2; + } else if(*p2parse == ']' && *(p2parse+1) == ' ') { + /* found end, just need to copy the ] and eat the SP */ + *pResult++ = *p2parse; + p2parse += 2; + lenStr -= 2; + bCont = 0; + } else { + *pResult++ = *p2parse++; + --lenStr; + } + } + } + + if(lenStr > 0 && *p2parse == ' ') { + ++p2parse; /* eat SP, but only if not at end of string */ + --lenStr; + } else { + iRet = 1; /* there MUST be an SP! */ + } + *pResult = '\0'; + + /* set the new parse pointer */ + *pp2parse = p2parse; + *pLenStr = lenStr; + return 0; +} + +/* parse a legay-formatted syslog message. + */ +BEGINparse + uchar *p2parse; + int lenMsg; + int bTAGCharDetected; + int i; /* general index for parsing */ + uchar bufParseTAG[CONF_TAG_MAXSIZE]; + uchar bufParseHOSTNAME[CONF_HOSTNAME_MAXSIZE]; + uchar *pBuf = NULL; +CODESTARTparse + dbgprintf("Message will now be parsed by the legacy syslog parser with structured-data support.\n"); + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + setProtocolVersion(pMsg, 0); + + /* Check to see if msg contains a timestamp. We start by assuming + * that the message timestamp is the time of reception (which we + * generated ourselfs and then try to actually find one inside the + * message. There we go from high-to low precison and are done + * when we find a matching one. -- rgerhards, 2008-09-16 + */ + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3339 */; + } else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3164 */; + } else if(*p2parse == ' ' && lenMsg > 1) { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */ + ++p2parse; /* move over space */ + --lenMsg; + if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + /* indeed, we got it! */ + /* we are done - parse pointer is moved by ParseTIMESTAMP3164 */; + } else {/* parse pointer needs to be restored, as we moved it off-by-one + * for this try. + */ + --p2parse; + ++lenMsg; + } + } + + if(pMsg->msgFlags & IGNDATE) { + /* we need to ignore the msg data, so simply copy over reception date */ + memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime)); + } + + /* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we + * do this only when the user has not forbidden this. I now introduce some + * code that allows a user to configure rsyslogd to treat the rest of the + * message as MSG part completely. In this case, the hostname will be the + * machine that we received the message from and the tag will be empty. This + * is meant to be an interim solution, but for now it is in the code. + */ + if(bParseHOSTNAMEandTAG && !(pMsg->msgFlags & INTERNAL_MSG)) { + /* parse HOSTNAME - but only if this is network-received! + * rger, 2005-11-14: we still have a problem with BSD messages. These messages + * do NOT include a host name. In most cases, this leads to the TAG to be treated + * as hostname and the first word of the message as the TAG. Clearly, this is not + * of advantage ;) I think I have now found a way to handle this situation: there + * are certain characters which are frequently used in TAG (e.g. ':'), which are + * *invalid* in host names. So while parsing the hostname, I check for these characters. + * If I find them, I set a simple flag but continue. After parsing, I check the flag. + * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change + * the fields. I think this logic shall work with any type of syslog message. + * rgerhards, 2009-06-23: and I now have extended this logic to every character + * that is not a valid hostname. + */ + bTAGCharDetected = 0; + if(lenMsg > 0 && pMsg->msgFlags & PARSE_HOSTNAME) { + i = 0; + while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' || p2parse[i] == '.' + || p2parse[i] == '_' || p2parse[i] == '-') && i < (CONF_HOSTNAME_MAXSIZE - 1)) { + bufParseHOSTNAME[i] = p2parse[i]; + ++i; + } + + if(i == lenMsg) { + /* we have a message that is empty immediately after the hostname, + * but the hostname thus is valid! -- rgerhards, 2010-02-22 + */ + p2parse += i; + lenMsg -= i; + bufParseHOSTNAME[i] = '\0'; + MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i); + } else if(i > 0 && p2parse[i] == ' ' && isalnum(p2parse[i-1])) { + /* we got a hostname! */ + p2parse += i + 1; /* "eat" it (including SP delimiter) */ + lenMsg -= i + 1; + bufParseHOSTNAME[i] = '\0'; + MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i); + } + } + + /* now parse TAG - that should be present in message from all sources. + * This code is somewhat not compliant with RFC 3164. As of 3164, + * the TAG field is ended by any non-alphanumeric character. In + * practice, however, the TAG often contains dashes and other things, + * which would end the TAG. So it is not desirable. As such, we only + * accept colon and SP to be terminators. Even there is a slight difference: + * a colon is PART of the TAG, while a SP is NOT part of the tag + * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character + * size limit (from RFC3164) on the tag. This had bad effects on existing + * envrionments, as sysklogd didn't obey it either (probably another bug + * in RFC3164...). We now receive the full size, but will modify the + * outputs so that only 32 characters max are used by default. + */ + i = 0; + while(lenMsg > 0 && *p2parse != ':' && *p2parse != ' ' && i < CONF_TAG_MAXSIZE) { + bufParseTAG[i++] = *p2parse++; + --lenMsg; + } + if(lenMsg > 0 && *p2parse == ':') { + ++p2parse; + --lenMsg; + bufParseTAG[i++] = ':'; + } + + /* no TAG can only be detected if the message immediatly ends, in which case an empty TAG + * is considered OK. So we do not need to check for empty TAG. -- rgerhards, 2009-06-23 + */ + bufParseTAG[i] = '\0'; /* terminate string */ + MsgSetTAG(pMsg, bufParseTAG, i); + } else {/* we enter this code area when the user has instructed rsyslog NOT + * to parse HOSTNAME and TAG - rgerhards, 2006-03-13 + */ + if(!(pMsg->msgFlags & INTERNAL_MSG)) { + DBGPRINTF("HOSTNAME and TAG not parsed by user configuraton.\n"); + } + } + + CHKmalloc(pBuf = MALLOC(sizeof(uchar) * (lenMsg + 1))); + + /* STRUCTURED-DATA */ + if (parseRFCStructuredData(&p2parse, pBuf, &lenMsg) == 0) + MsgSetStructuredData(pMsg, (char*)pBuf); + else + MsgSetStructuredData(pMsg, "-"); + + /* The rest is the actual MSG */ + MsgSetMSGoffs(pMsg, p2parse - pMsg->pszRawMsg); + +finalize_it: + if(pBuf != NULL) + free(pBuf); +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + dbgprintf("rfc3164sd parser init called\n"); + bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(); /* cache value, is set only during rsyslogd option processing */ + + +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/pmsnare/Makefile.am b/plugins/pmsnare/Makefile.am new file mode 100644 index 00000000..5b2696ac --- /dev/null +++ b/plugins/pmsnare/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = pmsnare.la + +pmsnare_la_SOURCES = pmsnare.c +pmsnare_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) -I ../../tools +pmsnare_la_LDFLAGS = -module -avoid-version +pmsnare_la_LIBADD = + +EXTRA_DIST = diff --git a/plugins/pmsnare/pmsnare.c b/plugins/pmsnare/pmsnare.c new file mode 100644 index 00000000..aca0271f --- /dev/null +++ b/plugins/pmsnare/pmsnare.c @@ -0,0 +1,240 @@ +/* pmsnare.c + * + * this detects logs sent by Snare and cleans them up so that they can be processed by the normal parser + * + * there are two variations of this, if the client is set to 'syslog' mode it sends + * + * <pri>timestamp<sp>hostname<sp>tag<tab>otherstuff + * + * if the client is not set to syslog it sends + * + * hostname<tab>tag<tab>otherstuff + * + * ToDo, take advantage of items in the message itself to set more friendly information + * where the normal parser will find it by re-writing more of the message + * + * Intereting information includes: + * + * in the case of windows snare messages: + * the system hostname is field 12 + * the severity is field 3 (criticality ranging form 0 to 4) + * the source of the log is field 4 and may be able to be mapped to facility + * + * + * created 2010-12-13 by David Lang based on pmlastmsg + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("pmsnare") +PARSER_NAME("rsyslog.snare") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* static data */ +static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINparse + uchar *p2parse; + int lenMsg; + int snaremessage; + int tablength; + +CODESTARTparse + #define TabRepresentation "#011" + tablength=sizeof(TabRepresentation); + dbgprintf("Message will now be parsed by fix Snare parser.\n"); + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + + /* check if this message is of the type we handle in this (very limited) parser + + find out if we have a space separated or tab separated for the first item + if tab separated see if the second word is one of our expected tags + if so replace the tabs with spaces so that hostname and syslog tag are going to be parsed properly + optionally replace the hostname at the beginning of the message with one from later in the message + else, wrong message, abort + else, assume that we have a valid timestamp, move over to the syslog tag + if that is tab separated from the rest of the message and one of our expected tags + if so, replace the tab with a space so that it will be parsed properly + optionally replace the hostname at the beginning of the message withone from later in the message + + */ + snaremessage=0; + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + dbgprintf("pmsnare: msg to look at: [%d]'%s'\n", lenMsg, p2parse); + if((unsigned) lenMsg < 30) { + /* too short, can not be "our" message */ + dbgprintf("msg too short!\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + + while(lenMsg && *p2parse != ' ' && *p2parse != '\t' && *p2parse != '#') { + --lenMsg; + ++p2parse; + } + dbgprintf("pmsnare: separator [%d]'%s' msg after the first separator: [%d]'%s'\n", tablength,TabRepresentation,lenMsg, p2parse); + if ((lenMsg > tablength) && (*p2parse == '\t' || strncasecmp((char*) p2parse, TabRepresentation , tablength-1) == 0)) { + //if ((lenMsg > tablength) && (*p2parse == '\t' || *p2parse == '#')) { + dbgprintf("pmsnare: tab separated message\n"); + if(strncasecmp((char*) (p2parse + tablength - 1), "MSWinEventLog", 13) == 0) { + snaremessage=13; /* 0 means not a snare message, a number is how long the tag is */ + } + if(strncasecmp((char*) (p2parse + tablength - 1), "LinuxKAudit", 11) == 0) { + snaremessage=11; /* 0 means not a snare message, a number is how long the tag is */ + } + if(snaremessage) { + /* replace the tab with a space and if needed move the message portion up by the length of TabRepresentation -2 characters to overwrite the extra : */ + *p2parse = ' '; + lenMsg -=(tablength-2); + p2parse++; + lenMsg--; + memmove(p2parse, p2parse + (tablength-2), lenMsg); + *(p2parse + lenMsg) = '\n'; + *(p2parse + lenMsg + 1) = '\0'; + pMsg->iLenRawMsg -=(tablength-2); + pMsg->iLenMSG -=(tablength-2); + p2parse += snaremessage; + lenMsg -= snaremessage; + *p2parse = ' '; + p2parse++; + lenMsg--; + lenMsg -=(tablength-2); + memmove(p2parse, p2parse + (tablength-2), lenMsg); + *(p2parse + lenMsg) = '\n'; + *(p2parse + lenMsg + 1) = '\0'; + pMsg->iLenRawMsg -=(tablength-2); + pMsg->iLenMSG -=(tablength-2); + dbgprintf("found a Snare message with snare not set to send syslog messages\n"); + } + } else { + /* go back to the beginning of the message */ + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + /* skip over timestamp and space*/ + lenMsg -=17; + p2parse +=17; + /* skip over what should be the hostname */ + while(lenMsg && *p2parse != ' ') { + --lenMsg; + ++p2parse; + } + if (lenMsg){ + --lenMsg; + ++p2parse; + } + dbgprintf("pmsnare: separator [%d]'%s' msg after the timestamp and hostname: [%d]'%s'\n", tablength,TabRepresentation,lenMsg, p2parse); + if(lenMsg > 13 && strncasecmp((char*) p2parse, "MSWinEventLog", 13) == 0) { + snaremessage=13; /* 0 means not a snare message, a number is how long the tag is */ + } + if(lenMsg > 11 && strncasecmp((char*) p2parse, "LinuxKAudit", 11) == 0) { + snaremessage=11; /* 0 means not a snare message, a number is how long the tag is */ + } + if(snaremessage) { + p2parse += snaremessage; + lenMsg -= snaremessage; + *p2parse = ' '; + p2parse++; + lenMsg--; + lenMsg -=(tablength-2); + memmove(p2parse, p2parse + (tablength-2), lenMsg); + *(p2parse + lenMsg) = '\n'; + *(p2parse + lenMsg + 1) = '\0'; + pMsg->iLenRawMsg -=(tablength-2); + pMsg->iLenMSG -=(tablength-2); + dbgprintf("found a Snare message with snare set to send syslog messages\n"); + } + + } + DBGPRINTF("pmsnare: new message: [%d]'%s'\n", lenMsg, pMsg->pszRawMsg + pMsg->offAfterPRI); + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + +finalize_it: +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + DBGPRINTF("snare parser init called, compiled with version %s\n", VERSION); + bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(); /* cache value, is set only during rsyslogd option processing */ + + +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/sm_cust_bindcdr/Makefile.am b/plugins/sm_cust_bindcdr/Makefile.am new file mode 100644 index 00000000..1f71d499 --- /dev/null +++ b/plugins/sm_cust_bindcdr/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = sm_cust_bindcdr.la + +sm_cust_bindcdr_la_SOURCES = sm_cust_bindcdr.c +sm_cust_bindcdr_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +sm_cust_bindcdr_la_LDFLAGS = -module -avoid-version +sm_cust_bindcdr_la_LIBADD = diff --git a/plugins/sm_cust_bindcdr/sm_cust_bindcdr.c b/plugins/sm_cust_bindcdr/sm_cust_bindcdr.c new file mode 100644 index 00000000..acf1bfad --- /dev/null +++ b/plugins/sm_cust_bindcdr/sm_cust_bindcdr.c @@ -0,0 +1,391 @@ +/* sm_cust_bindcdr.c + * This is a custom developed plugin to process bind information into + * a specific SQL statement. While the actual processing may be too specific + * to be of general use, this module serves as a template on how this type + * of processing can be done. + * + * Format generated: + * "%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" + * Note that this is the same as smtradfile.c, except that we do have a RFC3339 timestamp. However, + * we have copied over the code from there, it is too simple to go through all the hassle + * of having a single code base. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2011-03-17 by RGerhards + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include "conf.h" +#include "syslogd-types.h" +#include "cfsysline.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "unicode-helper.h" +#include "errmsg.h" + +MODULE_TYPE_STRGEN +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("sm_cust_bindcdr") +STRGEN_NAME("Custom_BindCDR,sql") + +/* internal structures + */ +DEF_SMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* list of "allowed" IPs */ +typedef struct allowedip_s { + uchar *pszIP; + struct allowedip_s *next; +} allowedip_t; + +static allowedip_t *root; + + +/* config data */ + +/* check if the provided IP is (already) in the allowed list + */ +static int +isAllowed(uchar *pszIP) +{ + allowedip_t *pallow; + int ret = 0; + + for(pallow = root ; pallow != NULL ; pallow = pallow->next) { + if(!ustrcmp(pallow->pszIP, pszIP)) { + ret = 1; + goto finalize_it; + } + } +finalize_it: return ret; +} + +/* This function is called to add an additional allowed IP. It adds + * the IP to the linked list of them. An error is emitted if the IP + * already exists. + */ +static rsRetVal addAllowedIP(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + allowedip_t *pNew; + DEFiRet; + + if(isAllowed(pNewVal)) { + errmsg.LogError(0, NO_ERRCODE, "error: allowed IP '%s' already configured " + "duplicate ignored", pNewVal); + ABORT_FINALIZE(RS_RET_ERR); + } + + CHKmalloc(pNew = malloc(sizeof(allowedip_t))); + pNew->pszIP = pNewVal; + pNew->next = root; + root = pNew; + DBGPRINTF("sm_cust_bindcdr: allowed IP '%s' added.\n", pNewVal); + +finalize_it: + if(iRet != RS_RET_OK) { + free(pNewVal); + } + + RETiRet; +} + +/* This strgen tries to minimize the amount of reallocs be first obtaining pointers to all strings + * needed (including their length) and then calculating the actual space required. So when we + * finally copy, we know exactly what we need. So we do at most one alloc. + * An actual message sample for what we intend to parse is (one line): + <30>Mar 24 13:01:51 named[6085]: 24-Mar-2011 13:01:51.865 queries: info: client 10.0.0.96#39762: view trusted: query: 8.6.0.9.9.4.1.4.6.1.8.3.mobilecrawler.com IN TXT + (10.0.0.96) + */ +//previos dev: #define SQL_STMT "INSERT INTO CDR(`Date`,`Time`, timeMS, client, view, query, ip) VALUES ('" +#define SQL_STMT "INSERT INTO CDR(`date`,ip,user,dest) VALUES ('" +#define ADD_SQL_DELIM \ + memcpy(*ppBuf + iBuf, "', '", sizeof("', '") - 1); \ + iBuf += sizeof("', '") - 1; +#define SQL_STMT_END "');\n" +BEGINstrgen + int iBuf; + uchar *psz; + uchar szDate[64]; + unsigned lenDate; + uchar szTime[64]; + unsigned lenTime; + uchar szMSec[64]; + unsigned lenMSec; + uchar szClient[64]; + unsigned lenClient; + uchar szView[64]; + unsigned lenView; + uchar szQuery[64]; + unsigned lenQuery; + uchar szIP[64]; + unsigned lenIP; + size_t lenTotal; +CODESTARTstrgen + /* first create an empty statement. This is to be replaced if + * we have better data to fill in. + */ + /* now make sure buffer is large enough */ + if(*pLenBuf < 2) + CHKiRet(ExtendBuf(ppBuf, pLenBuf, 2)); + memcpy(*ppBuf, ";", sizeof(";")); + + /* first obtain all strings and their length (if not fixed) */ + /* Note that there are two date fields present, one in the header + * and one more in the actual message. We use the one from the message + * and parse that our. We check validity based on some fixe fields. In- + * depth verification is probably not worth the effort (CPU time), because + * we do various other checks on the message format below). + */ + psz = getMSG(pMsg); + if(psz[0] == ' ' && psz[3] == '-' && psz[7] == '-') { + memcpy(szDate, psz+8, 4); + szDate[4] = '-'; + if(!strncmp((char*)psz+4, "Jan", 3)) { + szDate[5] = '0'; + szDate[6] = '1'; + } else if(!strncmp((char*)psz+4, "Feb", 3)) { + szDate[5] = '0'; + szDate[6] = '2'; + } else if(!strncmp((char*)psz+4, "Mar", 3)) { + szDate[5] = '0'; + szDate[6] = '3'; + } else if(!strncmp((char*)psz+4, "Apr", 3)) { + szDate[5] = '0'; + szDate[6] = '4'; + } else if(!strncmp((char*)psz+4, "May", 3)) { + szDate[5] = '0'; + szDate[6] = '5'; + } else if(!strncmp((char*)psz+4, "Jun", 3)) { + szDate[5] = '0'; + szDate[6] = '6'; + } else if(!strncmp((char*)psz+4, "Jul", 3)) { + szDate[5] = '0'; + szDate[6] = '7'; + } else if(!strncmp((char*)psz+4, "Aug", 3)) { + szDate[5] = '0'; + szDate[6] = '8'; + } else if(!strncmp((char*)psz+4, "Sep", 3)) { + szDate[5] = '0'; + szDate[6] = '9'; + } else if(!strncmp((char*)psz+4, "Oct", 3)) { + szDate[5] = '1'; + szDate[6] = '0'; + } else if(!strncmp((char*)psz+4, "Nov", 3)) { + szDate[5] = '1'; + szDate[6] = '1'; + } else if(!strncmp((char*)psz+4, "Dec", 3)) { + szDate[5] = '1'; + szDate[6] = '2'; + } + szDate[7] = '-'; + szDate[8] = psz[1]; + szDate[9] = psz[2]; + szDate[10] = '\0'; + lenDate = 10; + } else { + dbgprintf("Custom_BindCDR: date part in msg missing\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* now time (pull both regular time and ms) */ + if(psz[12] == ' ' && psz[15] == ':' && psz[18] == ':' && psz[21] == '.' && psz[25] == ' ') { + memcpy(szTime, (char*)psz+13, 8); + szTime[9] = '\0'; + lenTime = 8; + memcpy(szMSec, (char*)psz+22, 3); + szMSec[4] = '\0'; + lenMSec = 3; + } else { + dbgprintf("Custom_BindCDR: date part in msg missing\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* "client" */ + psz = (uchar*) strstr((char*) getMSG(pMsg), "client "); + if(psz == NULL) { + dbgprintf("Custom_BindCDR: client part in msg missing\n"); + ABORT_FINALIZE(RS_RET_ERR); + } else { + psz += sizeof("client ") - 1; /* skip "label" */ + for( lenClient = 0 + ; *psz && *psz != '#' && lenClient < sizeof(szClient) - 1 + ; ++lenClient) { + szClient[lenClient] = *psz++; + } + szClient[lenClient] = '\0'; + } + + /* "view" */ + psz = (uchar*) strstr((char*) getMSG(pMsg), "view "); + if(psz == NULL) { + dbgprintf("Custom_BindCDR: view part in msg missing\n"); + ABORT_FINALIZE(RS_RET_ERR); + } else { + psz += sizeof("view ") - 1; /* skip "label" */ + for( lenView = 0 + ; *psz && *psz != ':' && lenView < sizeof(szView) - 1 + ; ++lenView) { + szView[lenView] = *psz++; + } + szView[lenView] = '\0'; + } + + /* "query" - we must extract just the number, and in reverse! */ + psz = (uchar*) strstr((char*) getMSG(pMsg), "query: "); + if(psz == NULL) { + dbgprintf("Custom_BindCDR: query part in msg missing\n"); + ABORT_FINALIZE(RS_RET_ERR); + } else { + psz += sizeof("query: ") - 1; /* skip "label" */ + /* first find end-of-strihttp://www.rsyslog.com/doc/omruleset.htmlng to process */ + while(*psz && (isdigit(*psz) || *psz == '.')) { + psz++; + } + /* now shuffle data */ + for( lenQuery = 0 + ; *psz && *psz != ' ' && lenQuery < sizeof(szQuery) - 1 + ; --psz) { + if(isdigit(*psz)) + szQuery[lenQuery++] = *psz; + } + szQuery[lenQuery] = '\0'; + } + + /* "ip" */ + psz = (uchar*) strstr((char*) getMSG(pMsg), "IN TXT + ("); + if(psz == NULL) { + dbgprintf("Custom_BindCDR: ip part in msg missing\n"); + ABORT_FINALIZE(RS_RET_ERR); + } else { + psz += sizeof("IN TXT + (") - 1; /* skip "label" */ + for( lenIP = 0 + ; *psz && *psz != ')' && lenIP < sizeof(szIP) - 1 + ; ++lenIP) { + szIP[lenIP] = *psz++; + } + szIP[lenIP] = '\0'; + } + + + /* --- strings extracted ---- */ + + /* now check if the IP is "allowed", in which case we should not + * insert into the database. + */ + if(isAllowed(szIP)) { + DBGPRINTF("sm_cust_bindcdr: message from allowed IP, ignoring\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* calculate len, constants for spaces and similar fixed strings */ + lenTotal = lenDate + lenTime + lenMSec + lenClient + lenView + lenQuery + + lenIP + 7 * 5 + sizeof(SQL_STMT) + sizeof(SQL_STMT_END) + 2; + + /* now make sure buffer is large enough */ + if(lenTotal >= *pLenBuf) + CHKiRet(ExtendBuf(ppBuf, pLenBuf, lenTotal)); + + /* and concatenate the resulting string */ + memcpy(*ppBuf, SQL_STMT, sizeof(SQL_STMT) - 1); + iBuf = sizeof(SQL_STMT) - 1; + + memcpy(*ppBuf + iBuf, szDate, lenDate); + iBuf += lenDate; + /* prviously: ADD_SQL_DELIM */ + *(*ppBuf + iBuf) = ' '; + ++iBuf; + + memcpy(*ppBuf + iBuf, szTime, lenTime); + iBuf += lenTime; + ADD_SQL_DELIM + + /* we shall now discard this part + memcpy(*ppBuf + iBuf, szMSec, lenMSec); + iBuf += lenMSec; + ADD_SQL_DELIM + */ + + /* Note that this seem to be the IP to use */ + memcpy(*ppBuf + iBuf, szClient, lenClient); + iBuf += lenClient; + ADD_SQL_DELIM + + memcpy(*ppBuf + iBuf, szView, lenView); + iBuf += lenView; + ADD_SQL_DELIM + + memcpy(*ppBuf + iBuf, szQuery, lenQuery); + iBuf += lenQuery; + /* this is now the last field, so we dont need: ADD_SQL_DELIM */ + + /* no longer to be included + memcpy(*ppBuf + iBuf, szIP, lenIP); + iBuf += lenIP; + */ + + /* end of SQL statement/trailer (NUL is contained in string!) */ + memcpy(*ppBuf + iBuf, SQL_STMT_END, sizeof(SQL_STMT_END)); + iBuf += sizeof(SQL_STMT_END); + +finalize_it: +ENDstrgen + + +BEGINmodExit + allowedip_t *pallow, *pdel; +CODESTARTmodExit + for(pallow = root ; pallow != NULL ; ) { + pdel = pallow; + pallow = pallow->next; + free(pdel->pszIP); + free(pdel); + } + + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_SMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + root = NULL; + CHKiRet(omsdRegCFSLineHdlr((uchar *)"sgcustombindcdrallowedip", 0, eCmdHdlrGetWord, + addAllowedIP, NULL, STD_LOADABLE_MODULE_ID)); + dbgprintf("rsyslog sm_cust_bindcdr called, compiled with version %s\n", VERSION); +ENDmodInit diff --git a/rsyslog.service.in b/rsyslog.service.in new file mode 100644 index 00000000..8e2d64c2 --- /dev/null +++ b/rsyslog.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=System Logging Service +Requires=syslog.socket + +[Service] +Type=notify +ExecStart=@sbindir@/rsyslogd -n +StandardOutput=null + +[Install] +WantedBy=multi-user.target +Alias=syslog.service diff --git a/runtime/Makefile.am b/runtime/Makefile.am new file mode 100644 index 00000000..dea06fe0 --- /dev/null +++ b/runtime/Makefile.am @@ -0,0 +1,209 @@ +sbin_PROGRAMS = +man_MANS = +noinst_LTLIBRARIES = librsyslog.la +pkglib_LTLIBRARIES = +#pkglib_LTLIBRARIES = librsyslog.la + +librsyslog_la_SOURCES = \ + rsyslog.c \ + rsyslog.h \ + typedefs.h \ + dnscache.c \ + dnscache.h \ + unicode-helper.h \ + atomic.h \ + batch.h \ + syslogd-types.h \ + module-template.h \ + im-helper.h \ + obj-types.h \ + sigprov.h \ + cryprov.h \ + nsd.h \ + glbl.h \ + glbl.c \ + unlimited_select.h \ + conf.c \ + conf.h \ + rsconf.c \ + rsconf.h \ + parser.h \ + parser.c \ + strgen.h \ + strgen.c \ + msg.c \ + msg.h \ + linkedlist.c \ + linkedlist.h \ + objomsr.c \ + objomsr.h \ + stringbuf.c \ + stringbuf.h \ + datetime.c \ + datetime.h \ + srutils.c \ + srUtils.h \ + errmsg.c \ + errmsg.h \ + debug.c \ + debug.h \ + obj.c \ + obj.h \ + modules.c \ + modules.h \ + statsobj.c \ + statsobj.h \ + stream.c \ + stream.h \ + var.c \ + var.h \ + wtp.c \ + wtp.h \ + wti.c \ + wti.h \ + queue.c \ + queue.h \ + ruleset.c \ + ruleset.h \ + prop.c \ + prop.h \ + ratelimit.c \ + ratelimit.h \ + cfsysline.c \ + cfsysline.h \ + sd-daemon.c \ + sd-daemon.h \ + \ + ../action.h \ + ../action.c \ + ../threads.c \ + ../threads.h \ + \ + ../parse.c \ + ../parse.h \ + \ + hashtable.c \ + hashtable.h \ + hashtable_itr.c \ + hashtable_itr.h \ + hashtable_private.h \ + \ + ../outchannel.c \ + ../outchannel.h \ + ../template.c \ + ../template.h +# the files with ../ we need to work on - so that they either become part of the +# runtime or will no longer be needed. -- rgerhards, 2008-06-13 +# + +if WITH_MODDIRS +librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/:$(moddirs)\" $(PTHREADS_CFLAGS) -I\$(top_srcdir)/tools +else +librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/\" -I$(top_srcdir) $(PTHREADS_CFLAGS) -I\$(top_srcdir)/tools -I\$(top_srcdir)/grammar +endif +#librsyslog_la_LDFLAGS = -module -avoid-version +librsyslog_la_LIBADD = $(DL_LIBS) $(RT_LIBS) + +# +# regular expression support +# +if ENABLE_REGEXP +pkglib_LTLIBRARIES += lmregexp.la +lmregexp_la_SOURCES = regexp.c regexp.h +lmregexp_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmregexp_la_LDFLAGS = -module -avoid-version +lmregexp_la_LIBADD = +endif + +# +# zlib support +# +if ENABLE_ZLIB +pkglib_LTLIBRARIES += lmzlibw.la +lmzlibw_la_SOURCES = zlibw.c zlibw.h +lmzlibw_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmzlibw_la_LDFLAGS = -module -avoid-version +lmzlibw_la_LIBADD = +endif + +if ENABLE_INET +pkglib_LTLIBRARIES += lmnet.la lmnetstrms.la +# +# network support +# +lmnet_la_SOURCES = net.c net.h +lmnet_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmnet_la_LDFLAGS = -module -avoid-version ../compat/compat_la-getifaddrs.lo +lmnet_la_LIBADD = + +# network stream master class and stream factory +lmnetstrms_la_SOURCES = netstrms.c netstrms.h \ + netstrm.c netstrm.h \ + nssel.c nssel.h \ + nspoll.c nspoll.h +lmnetstrms_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmnetstrms_la_LDFLAGS = -module -avoid-version +lmnetstrms_la_LIBADD = + +# generic stream server framework +pkglib_LTLIBRARIES += lmstrmsrv.la +lmstrmsrv_la_SOURCES = strmsrv.c strmsrv.h strms_sess.c strms_sess.h +lmstrmsrv_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmstrmsrv_la_LDFLAGS = -module -avoid-version +lmstrmsrv_la_LIBADD = + +# netstream drivers + +# plain tcp driver - main driver +pkglib_LTLIBRARIES += lmnsd_ptcp.la +lmnsd_ptcp_la_SOURCES = nsd_ptcp.c nsd_ptcp.h \ + nsdsel_ptcp.c nsdsel_ptcp.h \ + nsdpoll_ptcp.c nsdpoll_ptcp.h +lmnsd_ptcp_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +lmnsd_ptcp_la_LDFLAGS = -module -avoid-version +lmnsd_ptcp_la_LIBADD = +endif # if ENABLE_INET + +# +# GnuTLS netstream driver +# +if ENABLE_GNUTLS +pkglib_LTLIBRARIES += lmnsd_gtls.la +lmnsd_gtls_la_SOURCES = nsd_gtls.c nsd_gtls.h nsdsel_gtls.c nsdsel_gtls.h +lmnsd_gtls_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(GNUTLS_CFLAGS) +lmnsd_gtls_la_LDFLAGS = -module -avoid-version +lmnsd_gtls_la_LIBADD = $(GNUTLS_LIBS) +endif + +# +# support library for libgcrypt +# +if ENABLE_LIBGCRYPT + noinst_LTLIBRARIES += libgcry.la + libgcry_la_SOURCES = libgcry.c libgcry_common.c libgcry.h + libgcry_la_CPPFLAGS = $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS) + pkglib_LTLIBRARIES += lmcry_gcry.la + lmcry_gcry_la_SOURCES = lmcry_gcry.c lmcry_gcry.h + lmcry_gcry_la_CPPFLAGS = $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS) + lmcry_gcry_la_LDFLAGS = -module -avoid-version + lmcry_gcry_la_LIBADD = libgcry.la $(LIBGCRYPT_LIBS) +endif + + +# +# support library for guardtime +# +if ENABLE_GUARDTIME + noinst_LTLIBRARIES += librsgt.la + librsgt_la_SOURCES = librsgt.c librsgt_read.c librsgt.h + pkglib_LTLIBRARIES += lmsig_gt.la + lmsig_gt_la_SOURCES = lmsig_gt.c lmsig_gt.h + lmsig_gt_la_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS) + lmsig_gt_la_LDFLAGS = -module -avoid-version + lmsig_gt_la_LIBADD = librsgt.la $(GUARDTIME_LIBS) +endif + + +update-systemd: + curl http://cgit.freedesktop.org/systemd/systemd/plain/src/libsystemd-daemon/sd-daemon.c > sd-daemon.c + curl http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h > sd-daemon.h diff --git a/runtime/atomic.h b/runtime/atomic.h new file mode 100644 index 00000000..2a895581 --- /dev/null +++ b/runtime/atomic.h @@ -0,0 +1,229 @@ +/* This header supplies atomic operations. So far, we rely on GCC's + * atomic builtins. During configure, we check if atomic operatons are + * available. If they are not, I am making the necessary provisioning to live without them if + * they are not available. Please note that you should only use the macros + * here if you think you can actually live WITHOUT an explicit atomic operation, + * because in the non-presence of them, we simply do it without atomicitiy. + * Which, for word-aligned data types, usually (but only usually!) should work. + * + * We are using the functions described in + * http:/gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html + * + * THESE MACROS MUST ONLY BE USED WITH WORD-SIZED DATA TYPES! + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_ATOMIC_H +#define INCLUDED_ATOMIC_H +#include <time.h> +#include "typedefs.h" + +/* for this release, we disable atomic calls because there seem to be some + * portability problems and we can not fix that without destabilizing the build. + * They simply came in too late. -- rgerhards, 2008-04-02 + */ +#ifdef HAVE_ATOMIC_BUILTINS +# define ATOMIC_SUB(data, val, phlpmut) __sync_fetch_and_sub(data, val) +# define ATOMIC_ADD(data, val) __sync_fetch_and_add(&(data), val) +# define ATOMIC_INC(data, phlpmut) ((void) __sync_fetch_and_add(data, 1)) +# define ATOMIC_INC_AND_FETCH_int(data, phlpmut) __sync_fetch_and_add(data, 1) +# define ATOMIC_INC_AND_FETCH_unsigned(data, phlpmut) __sync_fetch_and_add(data, 1) +# define ATOMIC_DEC(data, phlpmut) ((void) __sync_sub_and_fetch(data, 1)) +# define ATOMIC_DEC_AND_FETCH(data, phlpmut) __sync_sub_and_fetch(data, 1) +# define ATOMIC_FETCH_32BIT(data, phlpmut) ((unsigned) __sync_fetch_and_and(data, 0xffffffff)) +# define ATOMIC_STORE_1_TO_32BIT(data) __sync_lock_test_and_set(&(data), 1) +# define ATOMIC_STORE_0_TO_INT(data, phlpmut) __sync_fetch_and_and(data, 0) +# define ATOMIC_STORE_1_TO_INT(data, phlpmut) __sync_fetch_and_or(data, 1) +# define ATOMIC_STORE_INT_TO_INT(data, val) __sync_fetch_and_or(&(data), (val)) +# define ATOMIC_CAS(data, oldVal, newVal, phlpmut) __sync_bool_compare_and_swap(data, (oldVal), (newVal)) +# define ATOMIC_CAS_time_t(data, oldVal, newVal, phlpmut) __sync_bool_compare_and_swap(data, (oldVal), (newVal)) +# define ATOMIC_CAS_VAL(data, oldVal, newVal, phlpmut) __sync_val_compare_and_swap(data, (oldVal), (newVal)); + + /* functions below are not needed if we have atomics */ +# define DEF_ATOMIC_HELPER_MUT(x) +# define INIT_ATOMIC_HELPER_MUT(x) +# define DESTROY_ATOMIC_HELPER_MUT(x) + + /* the following operations should preferrably be done atomic, but it is + * not fatal if not -- that means we can live with some missed updates. So be + * sure to use these macros only if that really does not matter! + */ +# define PREFER_ATOMIC_INC(data) ((void) __sync_fetch_and_add(&(data), 1)) +#else + /* note that we gained parctical proof that theoretical problems DO occur + * if we do not properly address them. See this blog post for details: + * http://blog.gerhards.net/2009/01/rsyslog-data-race-analysis.html + * The bottom line is that if there are no atomics available, we should NOT + * simply go ahead and do without them - use mutexes or other things. The + * code needs to be checked against all those cases. -- rgerhards, 2009-01-30 + */ + #include <pthread.h> +# define ATOMIC_INC(data, phlpmut) { \ + pthread_mutex_lock(phlpmut); \ + ++(*(data)); \ + pthread_mutex_unlock(phlpmut); \ + } + +# define ATOMIC_STORE_0_TO_INT(data, hlpmut) { \ + pthread_mutex_lock(hlpmut); \ + *(data) = 0; \ + pthread_mutex_unlock(hlpmut); \ + } + +# define ATOMIC_STORE_1_TO_INT(data, hlpmut) { \ + pthread_mutex_lock(hlpmut); \ + *(data) = 1; \ + pthread_mutex_unlock(hlpmut); \ + } + + static inline int + ATOMIC_CAS(int *data, int oldVal, int newVal, pthread_mutex_t *phlpmut) { + int bSuccess; + pthread_mutex_lock(phlpmut); + if(*data == oldVal) { + *data = newVal; + bSuccess = 1; + } else { + bSuccess = 0; + } + pthread_mutex_unlock(phlpmut); + return(bSuccess); + } + + static inline int + ATOMIC_CAS_time_t(time_t *data, time_t oldVal, time_t newVal, pthread_mutex_t *phlpmut) { + int bSuccess; + pthread_mutex_lock(phlpmut); + if(*data == oldVal) { + *data = newVal; + bSuccess = 1; + } else { + bSuccess = 0; + } + pthread_mutex_unlock(phlpmut); + return(bSuccess); + } + + + static inline int + ATOMIC_CAS_VAL(int *data, int oldVal, int newVal, pthread_mutex_t *phlpmut) { + int val; + pthread_mutex_lock(phlpmut); + if(*data == oldVal) { + *data = newVal; + } + val = *data; + pthread_mutex_unlock(phlpmut); + return(val); + } + +# define ATOMIC_DEC(data, phlpmut) { \ + pthread_mutex_lock(phlpmut); \ + --(*(data)); \ + pthread_mutex_unlock(phlpmut); \ + } + + static inline int + ATOMIC_INC_AND_FETCH_int(int *data, pthread_mutex_t *phlpmut) { + int val; + pthread_mutex_lock(phlpmut); + val = ++(*data); + pthread_mutex_unlock(phlpmut); + return(val); + } + + static inline unsigned + ATOMIC_INC_AND_FETCH_unsigned(unsigned *data, pthread_mutex_t *phlpmut) { + unsigned val; + pthread_mutex_lock(phlpmut); + val = ++(*data); + pthread_mutex_unlock(phlpmut); + return(val); + } + + static inline int + ATOMIC_DEC_AND_FETCH(int *data, pthread_mutex_t *phlpmut) { + int val; + pthread_mutex_lock(phlpmut); + val = --(*data); + pthread_mutex_unlock(phlpmut); + return(val); + } + + static inline int + ATOMIC_FETCH_32BIT(int *data, pthread_mutex_t *phlpmut) { + int val; + pthread_mutex_lock(phlpmut); + val = (*data); + pthread_mutex_unlock(phlpmut); + return(val); + } + + static inline void + ATOMIC_SUB(int *data, int val, pthread_mutex_t *phlpmut) { + pthread_mutex_lock(phlpmut); + (*data) -= val; + pthread_mutex_unlock(phlpmut); + } +# define DEF_ATOMIC_HELPER_MUT(x) pthread_mutex_t x +# define INIT_ATOMIC_HELPER_MUT(x) pthread_mutex_init(&(x), NULL) +# define DESTROY_ATOMIC_HELPER_MUT(x) pthread_mutex_destroy(&(x)) + +# define PREFER_ATOMIC_INC(data) ((void) ++data) + +#endif + +/* we need to handle 64bit atomics seperately as some platforms have + * 32 bit atomics, but not 64 biot ones... -- rgerhards, 2010-12-01 + */ +#ifdef HAVE_ATOMIC_BUILTINS_64BIT +# define ATOMIC_INC_uint64(data, phlpmut) ((void) __sync_fetch_and_add(data, 1)) +# define ATOMIC_DEC_unit64(data, phlpmut) ((void) __sync_sub_and_fetch(data, 1)) +# define ATOMIC_INC_AND_FETCH_uint64(data, phlpmut) __sync_fetch_and_add(data, 1) + +# define DEF_ATOMIC_HELPER_MUT64(x) +# define INIT_ATOMIC_HELPER_MUT64(x) +# define DESTROY_ATOMIC_HELPER_MUT64(x) +#else +# define ATOMIC_INC_uint64(data, phlpmut) { \ + pthread_mutex_lock(phlpmut); \ + ++(*(data)); \ + pthread_mutex_unlock(phlpmut); \ + } +# define ATOMIC_DEC_uint64(data, phlpmut) { \ + pthread_mutex_lock(phlpmut); \ + --(*(data)); \ + pthread_mutex_unlock(phlpmut); \ + } + + static inline unsigned + ATOMIC_INC_AND_FETCH_uint64(uint64 *data, pthread_mutex_t *phlpmut) { + uint64 val; + pthread_mutex_lock(phlpmut); + val = ++(*data); + pthread_mutex_unlock(phlpmut); + return(val); + } + +# define DEF_ATOMIC_HELPER_MUT64(x) pthread_mutex_t x +# define INIT_ATOMIC_HELPER_MUT64(x) pthread_mutex_init(&(x), NULL) +# define DESTROY_ATOMIC_HELPER_MUT64(x) pthread_mutex_destroy(&(x)) +#endif /* #ifdef HAVE_ATOMIC_BUILTINS_64BIT */ + +#endif /* #ifndef INCLUDED_ATOMIC_H */ diff --git a/runtime/batch.h b/runtime/batch.h new file mode 100644 index 00000000..2ec07670 --- /dev/null +++ b/runtime/batch.h @@ -0,0 +1,197 @@ +/* Definition of the batch_t data structure. + * I am not sure yet if this will become a full-blown object. For now, this header just + * includes the object definition and is not accompanied by code. + * + * Copyright 2009 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef BATCH_H_INCLUDED +#define BATCH_H_INCLUDED + +#include <string.h> +#include "msg.h" + +/* enum for batch states. Actually, we violate a layer here, in that we assume that a batch is used + * for action processing. So far, this seems acceptable, the status is simply ignored inside the + * main message queue. But over time, it could potentially be useful to split the two. + * rgerhad, 2009-05-12 + */ +#define BATCH_STATE_RDY 0 /* object ready for processing */ +#define BATCH_STATE_BAD 1 /* unrecoverable failure while processing, do NOT resubmit to same action */ +#define BATCH_STATE_SUB 2 /* message submitted for processing, outcome yet unknown */ +#define BATCH_STATE_COMM 3 /* message successfully commited */ +#define BATCH_STATE_DISC 4 /* discarded - processed OK, but do not submit to any other action */ +typedef unsigned char batch_state_t; + + +/* an object inside a batch, including any information (state!) needed for it to "life". + */ +struct batch_obj_s { + msg_t *pMsg; + /* work variables for action processing; these are reused for each action (or block of + * actions) + */ + sbool bPrevWasSuspended; + /* following are caches to save allocs if not absolutely necessary */ + uchar *staticActStrings[CONF_OMOD_NUMSTRINGS_MAXSIZE]; /**< for strings */ + /* a cache to save malloc(), if not absolutely necessary */ + void *staticActParams[CONF_OMOD_NUMSTRINGS_MAXSIZE]; /**< for anything else */ + size_t staticLenStrings[CONF_OMOD_NUMSTRINGS_MAXSIZE]; + /* and the same for the message length (if used) */ + /* end action work variables */ +}; + +/* the batch + * This object is used to dequeue multiple user pointers which are than handed over + * to processing. The size of elements is fixed after queue creation, but may be + * modified by config variables (better said: queue properties). + * Note that a "user pointer" in rsyslog context so far always is a message + * object. We stick to the more generic term because queues may potentially hold + * other types of objects, too. + * rgerhards, 2009-05-12 + * Note that nElem is not necessarily equal to nElemDeq. This is the case when we + * discard some elements (because of configuration) during dequeue processing. As + * all Elements are only deleted when the batch is processed, we can not immediately + * delete them. So we need to keep their number that we can delete them when the batch + * is completed (else, the whole process does not work correctly). + */ +struct batch_s { + int maxElem; /* maximum number of elements that this batch supports */ + int nElem; /* actual number of element in this entry */ + int nElemDeq; /* actual number of elements dequeued (and thus to be deleted) - see comment above! */ + int iDoneUpTo; /* all messages below this index have state other than RDY */ + qDeqID deqID; /* ID of dequeue operation that generated this batch */ + int *pbShutdownImmediate;/* end processing of this batch immediately if set to 1 */ + sbool *active; /* which messages are active for processing, NULL=all */ + sbool bSingleRuleset; /* do all msgs of this batch use a single ruleset? */ + batch_obj_t *pElem; /* batch elements */ + batch_state_t *eltState;/* state (array!) for individual objects. + NOTE: we have moved this out of batch_obj_t because we + get a *much* better cache hit ratio this way. So do not + move it back into this structure! Note that this is really + a HUGE saving, even if it doesn't look so (both profiler + data as well as practical tests indicate that!). + */ +}; + + +/* some inline functions (we may move this off to an object .. or not) */ +static inline void +batchSetSingleRuleset(batch_t *pBatch, sbool val) { + pBatch->bSingleRuleset = val; +} + +/* get the batches ruleset (if we have a single ruleset) */ +static inline ruleset_t* +batchGetRuleset(batch_t *pBatch) { + return (pBatch->nElem > 0) ? pBatch->pElem[0].pMsg->pRuleset : NULL; +} + +/* get the ruleset of a specifc element of the batch (index not verified!) */ +static inline ruleset_t* +batchElemGetRuleset(batch_t *pBatch, int i) { + return pBatch->pElem[i].pMsg->pRuleset; +} + +/* get number of msgs for this batch */ +static inline int +batchNumMsgs(batch_t *pBatch) { + return pBatch->nElem; +} + + +/* set the status of the i-th batch element. Note that once the status is + * DISC, it will never be reset. So this function can NOT be used to initialize + * the state table. -- rgerhards, 2010-06-10 + */ +static inline void +batchSetElemState(batch_t *pBatch, int i, batch_state_t newState) { + if(pBatch->eltState[i] != BATCH_STATE_DISC) + pBatch->eltState[i] = newState; +} + + +/* check if an element is a valid entry. We do NOT verify if the + * element index is valid. -- rgerhards, 2010-06-10 + */ +static inline int +batchIsValidElem(batch_t *pBatch, int i) { + return( (pBatch->eltState[i] != BATCH_STATE_DISC) + && (pBatch->active == NULL || pBatch->active[i])); +} + + +/* free members of a batch "object". Note that we can not do the usual + * destruction as the object typically is allocated on the stack and so the + * object itself cannot be freed! -- rgerhards, 2010-06-15 + */ +static inline void +batchFree(batch_t *pBatch) { + int i; + int j; + for(i = 0 ; i < pBatch->maxElem ; ++i) { + for(j = 0 ; j < CONF_OMOD_NUMSTRINGS_MAXSIZE ; ++j) { + /* staticActParams MUST be freed immediately (if required), + * so we do not need to do that! + */ + free(pBatch->pElem[i].staticActStrings[j]); + } + } + free(pBatch->pElem); + free(pBatch->eltState); +} + + +/* initialiaze a batch "object". The record must already exist, + * we "just" initialize it. The max number of elements must be + * provided. -- rgerhards, 2010-06-15 + */ +static inline rsRetVal +batchInit(batch_t *pBatch, int maxElem) { + DEFiRet; + pBatch->iDoneUpTo = 0; + pBatch->maxElem = maxElem; + CHKmalloc(pBatch->pElem = calloc((size_t)maxElem, sizeof(batch_obj_t))); + CHKmalloc(pBatch->eltState = calloc((size_t)maxElem, sizeof(batch_state_t))); + // TODO: replace calloc by inidividual writes? +finalize_it: + RETiRet; +} + + +/* primarily a helper for debug purposes, get human-readble name of state */ +static inline char * +batchState2String(batch_state_t state) { + switch(state) { + case BATCH_STATE_RDY: + return "BATCH_STATE_RDY"; + case BATCH_STATE_BAD: + return "BATCH_STATE_BAD"; + case BATCH_STATE_SUB: + return "BATCH_STATE_SUB"; + case BATCH_STATE_COMM: + return "BATCH_STATE_COMM"; + case BATCH_STATE_DISC: + return "BATCH_STATE_DISC"; + } + return "ERROR, batch state not known!"; +} +#endif /* #ifndef BATCH_H_INCLUDED */ diff --git a/runtime/cfsysline.c b/runtime/cfsysline.c new file mode 100644 index 00000000..a437b7f8 --- /dev/null +++ b/runtime/cfsysline.c @@ -0,0 +1,1057 @@ +/* cfsysline.c + * Implementation of the configuration system line object. + * + * File begun on 2007-07-30 by RGerhards + * + * Copyright (C) 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> + +#include "cfsysline.h" +#include "obj.h" +#include "conf.h" +#include "errmsg.h" +#include "srUtils.h" +#include "unicode-helper.h" + + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(errmsg) + +linkedList_t llCmdList; /* this is NOT a pointer - no typo here ;) */ + +/* --------------- START functions for handling canned syntaxes --------------- */ + + +/* parse a character from the config line + * added 2007-07-17 by rgerhards + * TODO: enhance this function to handle different classes of characters + * HINT: check if char is ' and, if so, use 'c' where c may also be things + * like \t etc. + */ +static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* if we are not at a '\0', we have our new char - no validity checks here... */ + if(**pp == '\0') { + errmsg.LogError(0, RS_RET_NOT_FOUND, "No character available"); + iRet = RS_RET_NOT_FOUND; + } else { + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((uchar*)pVal) = **pp; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, **pp)); + } + ++(*pp); /* eat processed char */ + } + +finalize_it: + RETiRet; +} + + +/* Parse a number from the configuration line. This is more or less + * a shell to call the custom handler. + * rgerhards, 2007-07-31 + */ +static rsRetVal doCustomHdlr(uchar **pp, rsRetVal (*pSetHdlr)(uchar**, void*), void *pVal) +{ + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(pSetHdlr(pp, pVal)); + +finalize_it: + RETiRet; +} + + +/* Parse a number from the configuration line. This functions just parses + * the number and does NOT call any handlers or set any values. It is just + * for INTERNAL USE by other parse functions! + * rgerhards, 2008-01-08 + */ +static rsRetVal parseIntVal(uchar **pp, int64 *pVal) +{ + DEFiRet; + uchar *p; + int64 i; + int bWasNegative; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pVal != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + p = *pp; + + if(*p == '-') { + bWasNegative = 1; + ++p; /* eat it */ + } else { + bWasNegative = 0; + } + + if(!isdigit((int) *p)) { + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_INT, "invalid number"); + ABORT_FINALIZE(RS_RET_INVALID_INT); + } + + /* pull value */ + for(i = 0 ; *p && (isdigit((int) *p) || *p == '.' || *p == ',') ; ++p) { + if(isdigit((int) *p)) { + i = i * 10 + *p - '0'; + } + } + + if(bWasNegative) + i *= -1; + + *pVal = i; + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse a size from the configuration line. This is basically an integer + * syntax, but modifiers may be added after the integer (e.g. 1k to mean + * 1024). The size must immediately follow the number. Note that the + * param value must be int64! + * rgerhards, 2008-01-09 + */ +static rsRetVal doGetSize(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + DEFiRet; + int64 i; + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(parseIntVal(pp, &i)); + + /* we now check if the next character is one of our known modifiers. + * If so, we accept it as such. If not, we leave it alone. tera and + * above does not make any sense as that is above a 32-bit int value. + */ + switch(**pp) { + /* traditional binary-based definitions */ + case 'k': i *= 1024; ++(*pp); break; + case 'm': i *= 1024 * 1024; ++(*pp); break; + case 'g': i *= 1024 * 1024 * 1024; ++(*pp); break; + case 't': i *= (int64) 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* tera */ + case 'p': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* peta */ + case 'e': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* exa */ + /* and now the "new" 1000-based definitions */ + case 'K': i *= 1000; ++(*pp); break; + case 'M': i *= 1000000; ++(*pp); break; + case 'G': i *= 1000000000; ++(*pp); break; + /* we need to use the multiplication below because otherwise + * the compiler gets an error during constant parsing */ + case 'T': i *= (int64) 1000 * 1000000000; ++(*pp); break; /* tera */ + case 'P': i *= (int64) 1000000 * 1000000000; ++(*pp); break; /* peta */ + case 'E': i *= (int64) 1000000000 * 1000000000; ++(*pp); break; /* exa */ + } + + /* done */ + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int64*)pVal) = i; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, i)); + } + +finalize_it: + RETiRet; +} + + +/* Parse a number from the configuration line. + * rgerhards, 2007-07-31 + */ +static rsRetVal doGetInt(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + uchar *p; + DEFiRet; + int64 i; + uchar errMsg[256]; /* for dynamic error messages */ + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(doGetSize(pp, NULL,&i)); + p = *pp; + if(i > 2147483648ll) { /*2^31*/ + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "value %lld too large for integer argument.", i); + errmsg.LogError(0, RS_RET_INVALID_VALUE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_INVALID_VALUE); + } + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = (int) i; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, (int) i)); + } + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and interpret a $FileCreateMode and $umask line. This function + * pulls the creation mode and, if successful, stores it + * into the global variable so that the rest of rsyslogd + * opens files with that mode. Any previous value will be + * overwritten. + * HINT: if we store the creation mode in selector_t, we + * can even specify multiple modes simply be virtue of + * being placed in the right section of rsyslog.conf + * rgerhards, 2007-07-4 (happy independence day to my US friends!) + * Parameter **pp has a pointer to the current config line. + * On exit, it will be updated to the processed position. + */ +static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + uchar *p; + DEFiRet; + uchar errMsg[128]; /* for dynamic error messages */ + int iVal; + + assert(pp != NULL); + assert(*pp != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + p = *pp; + + /* for now, we parse and accept only octal numbers + * Sequence of tests is important, we are using boolean shortcuts + * to avoid addressing invalid memory! + */ + if(!( (*p == '0') + && (*(p+1) && *(p+1) >= '0' && *(p+1) <= '7') + && (*(p+2) && *(p+2) >= '0' && *(p+2) <= '7') + && (*(p+3) && *(p+3) >= '0' && *(p+3) <= '7') ) ) { + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "value must be octal (e.g 0644)."); + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_VALUE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_INVALID_VALUE); + } + + /* we reach this code only if the octal number is ok - so we can now + * compute the value. + */ + iVal = (*(p+1)-'0') * 64 + (*(p+2)-'0') * 8 + (*(p+3)-'0'); + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = iVal; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, iVal)); + } + + p += 4; /* eat the octal number */ + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and interpret an on/off inside a config file line. This is most + * often used for boolean options, but of course it may also be used + * for other things. The passed-in pointer is updated to point to + * the first unparsed character on exit. Function emits error messages + * if the value is neither on or off. It returns 0 if the option is off, + * 1 if it is on and another value if there was an error. + * rgerhards, 2007-07-15 + */ +static int doParseOnOffOption(uchar **pp) +{ + uchar *pOptStart; + uchar szOpt[32]; + + assert(pp != NULL); + assert(*pp != NULL); + + pOptStart = *pp; + skipWhiteSpace(pp); /* skip over any whitespace */ + + if(getSubString(pp, (char*) szOpt, sizeof(szOpt) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, NO_ERRCODE, "Invalid $-configline - could not extract on/off option"); + return -1; + } + + if(!strcmp((char*)szOpt, "on")) { + return 1; + } else if(!strcmp((char*)szOpt, "off")) { + return 0; + } else { + errmsg.LogError(0, NO_ERRCODE, "Option value must be on or off, but is '%s'", (char*)pOptStart); + return -1; + } +} + + +/* extract a groupname and return its gid. + * rgerhards, 2007-07-17 + */ +static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + struct group *pgBuf = NULL; + struct group gBuf; + DEFiRet; + uchar szName[256]; + int bufSize = 1024; + char * stringBuf = NULL; + int err; + + assert(pp != NULL); + assert(*pp != NULL); + + if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract group name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + do { + /* Increase bufsize and try again.*/ + bufSize *= 2; + CHKmalloc(stringBuf = realloc(stringBuf, bufSize)); + err = getgrnam_r((char*)szName, &gBuf, stringBuf, bufSize, &pgBuf); + } while((pgBuf == NULL) && (err == ERANGE)); + + if(pgBuf == NULL) { + if (err != 0) { + rs_strerror_r(err, stringBuf, bufSize); + errmsg.LogError(0, RS_RET_NOT_FOUND, "Query for group '%s' resulted in an error: %s\n", + (char*)szName, stringBuf); + } else { + errmsg.LogError(0, RS_RET_NOT_FOUND, "ID for group '%s' could not be found", (char*)szName); + } + iRet = RS_RET_NOT_FOUND; + } else { + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((gid_t*)pVal) = pgBuf->gr_gid; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, pgBuf->gr_gid)); + } + dbgprintf("gid %d obtained for group '%s'\n", (int) pgBuf->gr_gid, szName); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + free(stringBuf); + RETiRet; +} + + +/* extract a username and return its uid. + * rgerhards, 2007-07-17 + */ +static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + struct passwd *ppwBuf; + struct passwd pwBuf; + DEFiRet; + uchar szName[256]; + char stringBuf[2048]; /* I hope this is large enough... */ + + assert(pp != NULL); + assert(*pp != NULL); + + if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract user name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + getpwnam_r((char*)szName, &pwBuf, stringBuf, sizeof(stringBuf), &ppwBuf); + + if(ppwBuf == NULL) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "ID for user '%s' could not be found or error", (char*)szName); + iRet = RS_RET_NOT_FOUND; + } else { + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((uid_t*)pVal) = ppwBuf->pw_uid; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, ppwBuf->pw_uid)); + } + dbgprintf("uid %d obtained for user '%s'\n", (int) ppwBuf->pw_uid, szName); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + RETiRet; +} + + +/* Parse and process an binary cofig option. pVal must be + * a pointer to an integer which is to receive the option + * value. + * rgerhards, 2007-07-15 + */ +static rsRetVal doBinaryOptionLine(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal) +{ + int iOption; + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + + if((iOption = doParseOnOffOption(pp)) == -1) + return RS_RET_ERR; /* nothing left to do */ + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = iOption; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, iOption)); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + RETiRet; +} + + +/* parse a whitespace-delimited word from the provided string. This is a + * helper function for a number of syntaxes. The parsed value is returned + * in ppStrB (which must be provided by caller). + * rgerhards, 2008-02-14 + */ +static rsRetVal +getWord(uchar **pp, cstr_t **ppStrB) +{ + DEFiRet; + uchar *p; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + ASSERT(ppStrB != NULL); + + CHKiRet(cstrConstruct(ppStrB)); + + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* parse out the word */ + p = *pp; + + while(*p && !isspace((int) *p)) { + CHKiRet(cstrAppendChar(*ppStrB, *p++)); + } + CHKiRet(cstrFinalize(*ppStrB)); + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and a word config line option. A word is a consequtive + * sequence of non-whitespace characters. pVal must be + * a pointer to a string which is to receive the option + * value. The returned string must be freed by the caller. + * rgerhards, 2007-09-07 + * To facilitate multiple instances of the same command line + * directive, doGetWord() now checks if pVal is already a + * non-NULL pointer. If so, we assume it was created by a previous + * incarnation and is automatically freed. This happens only when + * no custom handler is defined. If it is, the customer handler + * must do the cleanup. I have checked and this was al also memory + * leak with some code. Obviously, not a large one. -- rgerhards, 2007-12-20 + * Just to clarify: if pVal is parsed to a custom handler, this handler + * is responsible for freeing pVal. -- rgerhards, 2008-03-20 + */ +static rsRetVal doGetWord(uchar **pp, rsRetVal (*pSetHdlr)(void*, uchar*), void *pVal) +{ + DEFiRet; + cstr_t *pStrB; + uchar *pNewVal; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + CHKiRet(getWord(pp, &pStrB)); + CHKiRet(cstrConvSzStrAndDestruct(pStrB, &pNewVal, 0)); + pStrB = NULL; + + DBGPRINTF("doGetWord: get newval '%s' (len %d), hdlr %p\n", + pNewVal, (int) ustrlen(pNewVal), pSetHdlr); + /* we got the word, now set it */ + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + if(*((uchar**)pVal) != NULL) + free(*((uchar**)pVal)); /* free previous entry */ + *((uchar**)pVal) = pNewVal; /* set new one */ + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, pNewVal)); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrB != NULL) + cstrDestruct(&pStrB); + } + + RETiRet; +} + + +/* parse a syslog name from the string. This is the generic code that is + * called by the facility/severity functions. Note that we do not check the + * validity of numerical values, something that should probably change over + * time (TODO). -- rgerhards, 2008-02-14 + */ +static rsRetVal +doSyslogName(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), + void *pVal, syslogName_t *pNameTable) +{ + DEFiRet; + cstr_t *pStrB; + int iNewVal; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + CHKiRet(getWord(pp, &pStrB)); /* get word */ + iNewVal = decodeSyslogName(cstrGetSzStr(pStrB), pNameTable); + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = iNewVal; /* set new one */ + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, iNewVal)); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + if(pStrB != NULL) + rsCStrDestruct(&pStrB); + + RETiRet; +} + + +/* Implements the facility syntax. + * rgerhards, 2008-02-14 + */ +static rsRetVal +doFacility(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal) +{ + DEFiRet; + iRet = doSyslogName(pp, pSetHdlr, pVal, syslogFacNames); + RETiRet; +} + + +static rsRetVal +doGoneAway(__attribute__((unused)) uchar **pp, + __attribute__((unused)) rsRetVal (*pSetHdlr)(void*, int), + __attribute__((unused)) void *pVal) +{ + errmsg.LogError(0, RS_RET_CMD_GONE_AWAY, "config directive is no longer supported -- ignored"); + return RS_RET_CMD_GONE_AWAY; +} + +/* Implements the severity syntax. + * rgerhards, 2008-02-14 + */ +static rsRetVal +doSeverity(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal) +{ + DEFiRet; + iRet = doSyslogName(pp, pSetHdlr, pVal, syslogPriNames); + RETiRet; +} + + +/* --------------- END functions for handling canned syntaxes --------------- */ + +/* destructor for cslCmdHdlr + * pThis is actually a cslCmdHdlr_t, but we do not cast it as all we currently + * need to do is free it. + */ +static rsRetVal cslchDestruct(void *pThis) +{ + ASSERT(pThis != NULL); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor for cslCmdHdlr + */ +static rsRetVal cslchConstruct(cslCmdHdlr_t **ppThis) +{ + cslCmdHdlr_t *pThis; + DEFiRet; + + assert(ppThis != NULL); + if((pThis = calloc(1, sizeof(cslCmdHdlr_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + *ppThis = pThis; + RETiRet; +} + +/* destructor for linked list keys. As we do not use any dynamic memory, + * we simply return. However, this entry point must be defined for the + * linkedList class to make sure we have not forgotten a destructor. + * rgerhards, 2007-11-21 + */ +static rsRetVal cslchKeyDestruct(void __attribute__((unused)) *pData) +{ + return RS_RET_OK; +} + + +/* Key compare operation for linked list class. This compares two + * owner cookies (void *). + * rgerhards, 2007-11-21 + */ +static int cslchKeyCompare(void *pKey1, void *pKey2) +{ + if(pKey1 == pKey2) + return 0; + else + if(pKey1 < pKey2) + return -1; + else + return 1; +} + + +/* set data members for this object + */ +rsRetVal cslchSetEntry(cslCmdHdlr_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, int *permitted) +{ + assert(pThis != NULL); + assert(eType != eCmdHdlrInvalid); + + pThis->eType = eType; + pThis->cslCmdHdlr = pHdlr; + pThis->pData = pData; + pThis->permitted = permitted; + + return RS_RET_OK; +} + + +/* call the specified handler + */ +static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine) +{ + DEFiRet; + rsRetVal (*pHdlr)() = NULL; + assert(pThis != NULL); + assert(ppConfLine != NULL); + + switch(pThis->eType) { + case eCmdHdlrCustomHandler: + pHdlr = doCustomHdlr; + break; + case eCmdHdlrUID: + pHdlr = doGetUID; + break; + case eCmdHdlrGID: + pHdlr = doGetGID; + break; + case eCmdHdlrBinary: + pHdlr = doBinaryOptionLine; + break; + case eCmdHdlrFileCreateMode: + pHdlr = doFileCreateMode; + break; + case eCmdHdlrInt: + pHdlr = doGetInt; + break; + case eCmdHdlrSize: + pHdlr = doGetSize; + break; + case eCmdHdlrGetChar: + pHdlr = doGetChar; + break; + case eCmdHdlrFacility: + pHdlr = doFacility; + break; + case eCmdHdlrSeverity: + pHdlr = doSeverity; + break; + case eCmdHdlrGetWord: + pHdlr = doGetWord; + break; + case eCmdHdlrGoneAway: + pHdlr = doGoneAway; + break; + default: + iRet = RS_RET_NOT_IMPLEMENTED; + goto finalize_it; + } + + /* we got a pointer to the handler, so let's call it */ + assert(pHdlr != NULL); + CHKiRet(pHdlr(ppConfLine, pThis->cslCmdHdlr, pThis->pData)); + +finalize_it: + RETiRet; +} + + +/* ---------------------------------------------------------------------- * + * now come the handlers for cslCmd_t + * ---------------------------------------------------------------------- */ + +/* destructor for a cslCmd list key (a string as of now) + */ +static rsRetVal cslcKeyDestruct(void *pData) +{ + free(pData); /* we do not need to cast as all we do is free it anyway... */ + return RS_RET_OK; +} + +/* destructor for cslCmd + */ +static rsRetVal cslcDestruct(void *pData) +{ + cslCmd_t *pThis = (cslCmd_t*) pData; + + assert(pThis != NULL); + + llDestroy(&pThis->llCmdHdlrs); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor for cslCmd + */ +static rsRetVal cslcConstruct(cslCmd_t **ppThis, int bChainingPermitted) +{ + cslCmd_t *pThis; + DEFiRet; + + assert(ppThis != NULL); + if((pThis = calloc(1, sizeof(cslCmd_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->bChainingPermitted = bChainingPermitted; + + CHKiRet(llInit(&pThis->llCmdHdlrs, cslchDestruct, cslchKeyDestruct, cslchKeyCompare)); + +finalize_it: + *ppThis = pThis; + RETiRet; +} + + +/* add a handler entry to a known command + */ +static rsRetVal cslcAddHdlr(cslCmd_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted) +{ + DEFiRet; + cslCmdHdlr_t *pCmdHdlr = NULL; + + assert(pThis != NULL); + + CHKiRet(cslchConstruct(&pCmdHdlr)); + CHKiRet(cslchSetEntry(pCmdHdlr, eType, pHdlr, pData, permitted)); + CHKiRet(llAppend(&pThis->llCmdHdlrs, pOwnerCookie, pCmdHdlr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pHdlr != NULL) + cslchDestruct(pCmdHdlr); + } + + RETiRet; +} + + +/* function that registers cfsysline handlers. + * The supplied pCmdName is copied and a new buffer is allocated. This + * buffer is automatically destroyed when the element is freed, the + * caller does not need to take care of that. The caller must, however, + * free pCmdName if he allocated it dynamically! -- rgerhards, 2007-08-09 + * Parameter permitted has been added to support the v2 config system. With it, + * we can tell the legacy system (us here!) to check if a config directive is + * still permitted. For example, the v2 system will disable module global + * paramters if the are supplied via the native v2 callbacks. In order not + * to break exisiting modules, we have renamed the rgCfSysLinHdlr routine to + * version 2 and added a new one with the original name. It just calls the + * v2 function and supplies a "don't care (NULL)" pointer as this argument. + * rgerhards, 2012-06-26 + */ +rsRetVal regCfSysLineHdlr2(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted) +{ + DEFiRet; + cslCmd_t *pThis; + uchar *pMyCmdName; + + iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pThis); + if(iRet == RS_RET_NOT_FOUND) { + /* new command */ + CHKiRet(cslcConstruct(&pThis, bChainingPermitted)); + CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) { + cslcDestruct(pThis); + FINALIZE; + } + /* important: add to list, AFTER everything else is OK. Else + * we mess up things in the error case. + */ + if((pMyCmdName = (uchar*) strdup((char*)pCmdName)) == NULL) { + cslcDestruct(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + CHKiRet_Hdlr(llAppend(&llCmdList, pMyCmdName, (void*) pThis)) { + cslcDestruct(pThis); + FINALIZE; + } + } else { + /* command already exists, are we allowed to chain? */ + if(pThis->bChainingPermitted == 0 || bChainingPermitted == 0) { + ABORT_FINALIZE(RS_RET_CHAIN_NOT_PERMITTED); + } + CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) { + cslcDestruct(pThis); + FINALIZE; + } + } + +finalize_it: + RETiRet; +} + +rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie) +{ + DEFiRet; + iRet = regCfSysLineHdlr2(pCmdName, bChainingPermitted, eType, pHdlr, pData, pOwnerCookie, NULL); + RETiRet; +} + + +rsRetVal unregCfSysLineHdlrs(void) +{ + return llDestroy(&llCmdList); +} + + +/* helper function for unregCfSysLineHdlrs4Owner(). This is used to see if there is + * a handler of this owner inside the element and, if so, remove it. Please note that + * it keeps track of a pointer to the last linked list entry, as this is needed to + * remove an entry from the list. + * rgerhards, 2007-11-21 + */ +DEFFUNC_llExecFunc(unregHdlrsHeadExec) +{ + DEFiRet; + cslCmd_t *pListHdr = (cslCmd_t*) pData; + int iNumElts; + + /* first find element */ + iRet = llFindAndDelete(&(pListHdr->llCmdHdlrs), pParam); + + /* now go back and check how many elements are left */ + CHKiRet(llGetNumElts(&(pListHdr->llCmdHdlrs), &iNumElts)); + + if(iNumElts == 0) { + /* nothing left in header, so request to delete it */ + iRet = RS_RET_OK_DELETE_LISTENTRY; + } + +finalize_it: + RETiRet; +} +/* unregister and destroy cfSysLineHandlers for a specific owner. This method is + * most importantly used before unloading a loadable module providing some handlers. + * The full list of handlers is searched. If the to-be removed handler was the only + * handler for a directive name, the directive header, too, is deleted. + * rgerhards, 2007-11-21 + */ +rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie) +{ + DEFiRet; + /* we need to walk through all directive names, as the linked list + * class does not provide a way to just search the lower-level handlers. + */ + iRet = llExecFunc(&llCmdList, unregHdlrsHeadExec, pOwnerCookie); + + RETiRet; +} + + +/* process a cfsysline command (based on handler structure) + * param "p" is a pointer to the command line after the command. Should be + * updated. + */ +rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p) +{ + DEFiRet; + rsRetVal iRetLL; /* for linked list handling */ + cslCmd_t *pCmd; + cslCmdHdlr_t *pCmdHdlr; + linkedListCookie_t llCookieCmdHdlr; + uchar *pHdlrP; /* the handler's private p (else we could only call one handler) */ + int bWasOnceOK; /* was the result of an handler at least once RS_RET_OK? */ + uchar *pOKp = NULL; /* returned conf line pointer when it was OK */ + int bHadScopingErr = 0; /* set if a scoping error occured */ + + iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pCmd); + + if(iRet == RS_RET_NOT_FOUND) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "invalid or yet-unknown config file command '%s' - " + "have you forgotten to load a module?", pCmdName); + } + + if(iRet != RS_RET_OK) + goto finalize_it; + + llCookieCmdHdlr = NULL; + bWasOnceOK = 0; + while((iRetLL = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr)) == RS_RET_OK) { + /* for the time being, we ignore errors during handlers. The + * reason is that handlers are independent. An error in one + * handler does not necessarily mean that another one will + * fail, too. Later, we might add a config variable to control + * this behaviour (but I am not sure if that is really + * necessary). -- rgerhards, 2007-07-31 + */ + pHdlrP = *p; + if(pCmdHdlr->permitted != NULL && !*(pCmdHdlr->permitted)) { + errmsg.LogError(0, RS_RET_PARAM_NOT_PERMITTED, "command '%s' is currently not " + "permitted - did you already set it via a RainerScript command (v6+ config)?", + pCmdName); + ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED); + } else if((iRet = cslchCallHdlr(pCmdHdlr, &pHdlrP)) == RS_RET_OK) { + bWasOnceOK = 1; + pOKp = pHdlrP; + } + } + + if(bWasOnceOK == 1) { + *p = pOKp; + iRet = RS_RET_OK; + } + + if(iRetLL != RS_RET_END_OF_LINKEDLIST) + iRet = iRetLL; + + if(bHadScopingErr) { + iRet = RS_RET_CONF_INVLD_SCOPE; + } + +finalize_it: + RETiRet; +} + + +/* debug print the command handler structure + */ +void dbgPrintCfSysLineHandlers(void) +{ + cslCmd_t *pCmd; + cslCmdHdlr_t *pCmdHdlr; + linkedListCookie_t llCookieCmd; + linkedListCookie_t llCookieCmdHdlr; + uchar *pKey; + + dbgprintf("Sytem Line Configuration Commands:\n"); + llCookieCmd = NULL; + while(llGetNextElt(&llCmdList, &llCookieCmd, (void*)&pCmd) == RS_RET_OK) { + llGetKey(llCookieCmd, (void*) &pKey); /* TODO: using the cookie is NOT clean! */ + dbgprintf("\tCommand '%s':\n", pKey); + llCookieCmdHdlr = NULL; + while(llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr) == RS_RET_OK) { + dbgprintf("\t\ttype : %d\n", pCmdHdlr->eType); + dbgprintf("\t\tpData: 0x%lx\n", (unsigned long) pCmdHdlr->pData); + dbgprintf("\t\tHdlr : 0x%lx\n", (unsigned long) pCmdHdlr->cslCmdHdlr); + dbgprintf("\t\tOwner: 0x%lx\n", (unsigned long) llCookieCmdHdlr->pKey); + dbgprintf("\n"); + } + } + dbgprintf("\n"); +} + + +/* our init function. TODO: remove once converted to a class + */ +rsRetVal cfsyslineInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, strcasecmp)); + +finalize_it: + RETiRet; +} + +/* vim:set ai: + */ diff --git a/runtime/cfsysline.h b/runtime/cfsysline.h new file mode 100644 index 00000000..69389f84 --- /dev/null +++ b/runtime/cfsysline.h @@ -0,0 +1,60 @@ +/* Definition of the cfsysline (config file system line) object. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CFSYSLINE_H_INCLUDED +#define CFSYSLINE_H_INCLUDED + +#include "linkedlist.h" + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct cslCmdHdlr_s { /* config file sysline parse entry */ + ecslConfObjType __attribute__((deprecated)) eConfObjType; /* which config object is this for? */ + ecslCmdHdrlType eType; /* which type of handler is this? */ + rsRetVal (*cslCmdHdlr)(); /* function pointer to use with handler (params depending on eType) */ + void *pData; /* user-supplied data pointer */ + int *permitted; /* is this parameter currently permitted? (NULL=don't check) */ +}; +typedef struct cslCmdHdlr_s cslCmdHdlr_t; + + +/* this is the list of known configuration commands with pointers to + * their handlers. + * The short name is cslc (Configfile SysLine Command) + */ +struct cslCmd_s { /* config file sysline parse entry */ + int bChainingPermitted; /* may multiple handlers be chained for this command? */ + linkedList_t llCmdHdlrs; /* linked list of command handlers */ +}; +typedef struct cslCmd_s cslCmd_t; + +/* prototypes */ +rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie); +rsRetVal regCfSysLineHdlr2(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted); +rsRetVal unregCfSysLineHdlrs(void); +rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie); +rsRetVal processCfSysLineCommand(uchar *pCmd, uchar **p); +rsRetVal cfsyslineInit(void); +void dbgPrintCfSysLineHandlers(void); + +#endif /* #ifndef CFSYSLINE_H_INCLUDED */ diff --git a/runtime/conf.c b/runtime/conf.c new file mode 100644 index 00000000..c3c7e447 --- /dev/null +++ b/runtime/conf.c @@ -0,0 +1,741 @@ +/* The config file handler (not yet a real object) + * + * This file is based on an excerpt from syslogd.c, which dates back + * much later. I began the file on 2008-02-19 as part of the modularization + * effort. Over time, a clean abstration will become even more important + * because the config file handler will by dynamically be loaded and be + * kept in memory only as long as the config file is actually being + * processed. Thereafter, it shall be unloaded. -- rgerhards + * Please note that the original syslogd.c source was under BSD license + * at the time of the rsyslog fork from sysklogd. + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define CFGLNSIZ 64*1024 /* the maximum size of a configuraton file line, after re-combination */ +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <string.h> +#include <dlfcn.h> +#include <sys/stat.h> +#include <errno.h> +#include <ctype.h> +#include <assert.h> +#include <dirent.h> +#include <glob.h> +#include <sys/types.h> +#ifdef HAVE_LIBGEN_H +# ifndef OS_SOLARIS +# include <libgen.h> +# endif +#endif + +#include "rsyslog.h" +#include "dirty.h" +#include "parse.h" +#include "action.h" +#include "template.h" +#include "cfsysline.h" +#include "modules.h" +#include "outchannel.h" +#include "stringbuf.h" +#include "conf.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "errmsg.h" +#include "net.h" +#include "ruleset.h" +#include "rsconf.h" +#include "unicode-helper.h" +#include "rainerscript.h" + +#ifdef OS_SOLARIS +# define NAME_MAX MAXNAMELEN +#endif + +/* forward definitions */ + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) +DEFobjCurrIf(ruleset) + +int bConfStrictScoping = 0; /* force strict scoping during config processing? */ + + +/* The following module-global variables are used for building + * tag and host selector lines during startup and config reload. + * This is stored as a global variable pool because of its ease. It is + * also fairly compatible with multi-threading as the stratup code must + * be run in a single thread anyways. So there can be no race conditions. + * rgerhards 2005-10-18 + */ +EHostnameCmpMode eDfltHostnameCmpMode = HN_NO_COMP; +cstr_t *pDfltHostnameCmp = NULL; +cstr_t *pDfltProgNameCmp = NULL; + + +/* process a $ModLoad config line. */ +rsRetVal +doModLoad(uchar **pp, __attribute__((unused)) void* pVal) +{ + DEFiRet; + uchar szName[512]; + uchar *pModName; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract module name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* this below is a quick and dirty hack to provide compatibility with the + * $ModLoad MySQL forward compatibility statement. This needs to be supported + * for legacy format. + */ + if(!strcmp((char*) szName, "MySQL")) + pModName = (uchar*) "ommysql.so"; + else + pModName = szName; + + CHKiRet(module.Load(pModName, 1, NULL)); + +finalize_it: + RETiRet; +} + + +/* remove leading spaces from name; this "fixes" some anomalies in + * getSubString(), but I was not brave enough to fix the former as + * it has many other callers... -- rgerhards, 2013-05-27 + */ +static inline void +ltrim(char *src) +{ + char *dst = src; + while(isspace(*src)) + ++src; /*SKIP*/; + if(dst != src) { + while(*src != '\0') + *dst++ = *src++; + *dst = '\0'; + } +} + +/* parse and interpret a $-config line that starts with + * a name (this is common code). It is parsed to the name + * and then the proper sub-function is called to handle + * the actual directive. + * rgerhards 2004-11-17 + * rgerhards 2005-06-21: previously only for templates, now + * generalized. + */ +rsRetVal +doNameLine(uchar **pp, void* pVal) +{ + DEFiRet; + uchar *p; + enum eDirective eDir; + char szName[128]; + + ASSERT(pp != NULL); + p = *pp; + ASSERT(p != NULL); + + eDir = (enum eDirective) pVal; /* this time, it actually is NOT a pointer! */ + + if(getSubString(&p, szName, sizeof(szName) / sizeof(char), ',') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "Invalid config line: could not extract name - line ignored"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + ltrim(szName); + if(*p == ',') + ++p; /* comma was eaten */ + + /* we got the name - now we pass name & the rest of the string + * to the subfunction. It makes no sense to do further + * parsing here, as this is in close interaction with the + * respective subsystem. rgerhards 2004-11-17 + */ + + switch(eDir) { + case DIR_TEMPLATE: + tplAddLine(loadConf, szName, &p); + break; + case DIR_OUTCHANNEL: + ochAddLine(szName, &p); + break; + case DIR_ALLOWEDSENDER: + net.addAllowedSenderLine(szName, &p); + break; + default:/* we do this to avoid compiler warning - not all + * enum values call this function, so an incomplete list + * is quite ok (but then we should not run into this code, + * so at least we log a debug warning). + */ + dbgprintf("INTERNAL ERROR: doNameLine() called with invalid eDir %d.\n", + eDir); + break; + } + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and interpret a system-directive in the config line + * A system directive is one that starts with a "$" sign. It offers + * extended configuration parameters. + * 2004-11-17 rgerhards + */ +rsRetVal +cfsysline(uchar *p) +{ + DEFiRet; + uchar szCmd[64]; + + ASSERT(p != NULL); + errno = 0; + if(getSubString(&p, (char*) szCmd, sizeof(szCmd) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "Invalid $-configline - could not extract command - line ignored\n"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + /* we now try and see if we can find the command in the registered + * list of cfsysline handlers. -- rgerhards, 2007-07-31 + */ + CHKiRet(processCfSysLineCommand(szCmd, &p)); + + /* now check if we have some extra characters left on the line - that + * should not be the case. Whitespace is OK, but everything else should + * trigger a warning (that may be an indication of undesired behaviour). + * An exception, of course, are comments (starting with '#'). + * rgerhards, 2007-07-04 + */ + skipWhiteSpace(&p); + + if(*p && *p != '#') { /* we have a non-whitespace, so let's complain */ + errmsg.LogError(0, NO_ERRCODE, + "error: extra characters in config line ignored: '%s'", p); + } + +finalize_it: + RETiRet; +} + + +/* Helper to cfline() and its helpers. Parses a template name + * from an "action" line. Must be called with the Line pointer + * pointing to the first character after the semicolon. + * rgerhards 2004-11-19 + * changed function to work with OMSR. -- rgerhards, 2007-07-27 + * the default template is to be used when no template is specified. + */ +rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName) +{ + uchar *p; + uchar *tplName = NULL; + cstr_t *pStrB; + DEFiRet; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + ASSERT(pOMSR != NULL); + + p =*pp; + /* a template must follow - search it and complain, if not found */ + skipWhiteSpace(&p); + if(*p == ';') + ++p; /* eat it */ + else if(*p != '\0' && *p != '#') { + errmsg.LogError(0, RS_RET_ERR, "invalid character in selector line - ';template' expected"); + ABORT_FINALIZE(RS_RET_ERR); + } + + skipWhiteSpace(&p); /* go to begin of template name */ + + if(*p == '\0' || *p == '#') { + /* no template specified, use the default */ + /* TODO: check NULL ptr */ + tplName = (uchar*) strdup((char*)dfltTplName); + } else { + /* template specified, pick it up */ + CHKiRet(cstrConstruct(&pStrB)); + + /* now copy the string */ + while(*p && *p != '#' && !isspace((int) *p)) { + CHKiRet(cstrAppendChar(pStrB, *p)); + ++p; + } + CHKiRet(cstrFinalize(pStrB)); + CHKiRet(cstrConvSzStrAndDestruct(pStrB, &tplName, 0)); + } + + CHKiRet(OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts)); + +finalize_it: + if(iRet != RS_RET_OK) + free(tplName); + + *pp = p; + + RETiRet; +} + +/* Helper to cfline(). Parses a file name up until the first + * comma and then looks for the template specifier. Tries + * to find that template. + * rgerhards 2004-11-18 + * parameter pFileName must point to a buffer large enough + * to hold the largest possible filename. + * rgerhards, 2007-07-25 + * updated to include OMSR pointer -- rgerhards, 2007-07-27 + * updated to include template name -- rgerhards, 2008-03-28 + * rgerhards, 2010-01-19: file names end at the first space + */ +rsRetVal +cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl) +{ + register uchar *pName; + int i; + DEFiRet; + + ASSERT(pOMSR != NULL); + + pName = pFileName; + i = 1; /* we start at 1 so that we reseve space for the '\0'! */ + while(*p && *p != ';' && *p != ' ' && i < MAXFNAME) { + *pName++ = *p++; + ++i; + } + *pName = '\0'; + + iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, pszTpl); + + RETiRet; +} + + +/* Decode a traditional PRI filter */ +/* GPLv3 - stems back to sysklogd */ +rsRetVal DecodePRIFilter(uchar *pline, uchar pmask[]) +{ + uchar *p; + register uchar *q; + register int i, i2; + uchar *bp; + int pri; + int singlpri = 0; + int ignorepri = 0; + uchar buf[2048]; /* buffer for facility and priority names */ + uchar xbuf[200]; + DEFiRet; + + ASSERT(pline != NULL); + + dbgprintf("Decoding traditional PRI filter '%s'\n", pline); + + for (i = 0; i <= LOG_NFACILITIES; i++) { + pmask[i] = TABLE_NOPRI; + } + + /* scan through the list of selectors */ + for (p = pline; *p && *p != '\t' && *p != ' ';) { + /* find the end of this facility name list */ + for (q = p; *q && *q != '\t' && *q++ != '.'; ) + continue; + + /* collect priority name */ + for (bp = buf; *q && !strchr("\t ,;", *q) && bp < buf+sizeof(buf)-1 ; ) + *bp++ = *q++; + *bp = '\0'; + + /* skip cruft */ + if(*q) { + while (strchr(",;", *q)) + q++; + } + + /* decode priority name */ + if ( *buf == '!' ) { + ignorepri = 1; + /* copy below is ok, we can NOT go off the allocated area */ + for (bp=buf; *(bp+1); bp++) + *bp=*(bp+1); + *bp='\0'; + } else { + ignorepri = 0; + } + if ( *buf == '=' ) { + singlpri = 1; + pri = decodeSyslogName(&buf[1], syslogPriNames); + } + else { singlpri = 0; + pri = decodeSyslogName(buf, syslogPriNames); + } + + if (pri < 0) { + snprintf((char*) xbuf, sizeof(xbuf), "unknown priority name \"%s\"", buf); + errmsg.LogError(0, RS_RET_ERR, "%s", xbuf); + return RS_RET_ERR; + } + + /* scan facilities */ + while (*p && !strchr("\t .;", *p)) { + for (bp = buf; *p && !strchr("\t ,;.", *p) && bp < buf+sizeof(buf)-1 ; ) + *bp++ = *p++; + *bp = '\0'; + if (*buf == '*') { + for (i = 0; i <= LOG_NFACILITIES; i++) { + if ( pri == INTERNAL_NOPRI ) { + if ( ignorepri ) + pmask[i] = TABLE_ALLPRI; + else + pmask[i] = TABLE_NOPRI; + } + else if ( singlpri ) { + if ( ignorepri ) + pmask[i] &= ~(1<<pri); + else + pmask[i] |= (1<<pri); + } else { + if ( pri == TABLE_ALLPRI ) { + if ( ignorepri ) + pmask[i] = TABLE_NOPRI; + else + pmask[i] = TABLE_ALLPRI; + } else { + if ( ignorepri ) + for (i2= 0; i2 <= pri; ++i2) + pmask[i] &= ~(1<<i2); + else + for (i2= 0; i2 <= pri; ++i2) + pmask[i] |= (1<<i2); + } + } + } + } else { + i = decodeSyslogName(buf, syslogFacNames); + if (i < 0) { + + snprintf((char*) xbuf, sizeof(xbuf), "unknown facility name \"%s\"", buf); + errmsg.LogError(0, RS_RET_ERR, "%s", xbuf); + return RS_RET_ERR; + } + + if ( pri == INTERNAL_NOPRI ) { + if ( ignorepri ) + pmask[i >> 3] = TABLE_ALLPRI; + else + pmask[i >> 3] = TABLE_NOPRI; + } else if ( singlpri ) { + if ( ignorepri ) + pmask[i >> 3] &= ~(1<<pri); + else + pmask[i >> 3] |= (1<<pri); + } else { + if ( pri == TABLE_ALLPRI ) { + if ( ignorepri ) + pmask[i >> 3] = TABLE_NOPRI; + else + pmask[i >> 3] = TABLE_ALLPRI; + } else { + if ( ignorepri ) + for (i2= 0; i2 <= pri; ++i2) + pmask[i >> 3] &= ~(1<<i2); + else + for (i2= 0; i2 <= pri; ++i2) + pmask[i >> 3] |= (1<<i2); + } + } + } + while (*p == ',' || *p == ' ') + p++; + } + + p = q; + } + + RETiRet; +} + + +/* Helper to cfline(). This function takes the filter part of a property + * based filter and decodes it. It processes the line up to the beginning + * of the action part. A pointer to that beginnig is passed back to the caller. + * rgerhards 2005-09-15 + */ +rsRetVal DecodePropFilter(uchar *pline, struct cnfstmt *stmt) +{ + rsParsObj *pPars; + cstr_t *pCSCompOp; + cstr_t *pCSPropName; + rsRetVal iRet; + int iOffset; /* for compare operations */ + + ASSERT(pline != NULL); + + dbgprintf("Decoding property-based filter '%s'\n", pline); + + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, pline+1)) != RS_RET_OK) { + errmsg.LogError(0, iRet, "Error %d constructing parser object - ignoring selector", iRet); + return(iRet); + } + + /* read property */ + iRet = parsDelimCStr(pPars, &pCSPropName, ',', 1, 1, 1); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d parsing filter property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + iRet = propNameToID(pCSPropName, &stmt->d.s_propfilt.propID); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d parsing filter property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + if(stmt->d.s_propfilt.propID == PROP_CEE) { + /* in CEE case, we need to preserve the actual property name */ + if((stmt->d.s_propfilt.propName = + es_newStrFromBuf((char*)cstrGetSzStrNoNULL(pCSPropName)+2, cstrLen(pCSPropName)-2)) == NULL) { + cstrDestruct(&pCSPropName); + return(RS_RET_ERR); + } + } + cstrDestruct(&pCSPropName); + + /* read operation */ + iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1, 1); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d compare operation property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* we now first check if the condition is to be negated. To do so, we first + * must make sure we have at least one char in the param and then check the + * first one. + * rgerhards, 2005-09-26 + */ + if(rsCStrLen(pCSCompOp) > 0) { + if(*rsCStrGetBufBeg(pCSCompOp) == '!') { + stmt->d.s_propfilt.isNegated = 1; + iOffset = 1; /* ignore '!' */ + } else { + stmt->d.s_propfilt.isNegated = 0; + iOffset = 0; + } + } else { + stmt->d.s_propfilt.isNegated = 0; + iOffset = 0; + } + + if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "contains", 8)) { + stmt->d.s_propfilt.operation = FIOP_CONTAINS; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isequal", 7)) { + stmt->d.s_propfilt.operation = FIOP_ISEQUAL; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isempty", 7)) { + stmt->d.s_propfilt.operation = FIOP_ISEMPTY; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "startswith", 10)) { + stmt->d.s_propfilt.operation = FIOP_STARTSWITH; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "regex", 5)) { + stmt->d.s_propfilt.operation = FIOP_REGEX; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "ereregex", 8)) { + stmt->d.s_propfilt.operation = FIOP_EREREGEX; + } else { + errmsg.LogError(0, NO_ERRCODE, "error: invalid compare operation '%s' - ignoring selector", + (char*) rsCStrGetSzStrNoNULL(pCSCompOp)); + } + rsCStrDestruct(&pCSCompOp); /* no longer needed */ + + if(stmt->d.s_propfilt.operation != FIOP_ISEMPTY) { + /* read compare value */ + iRet = parsQuotedCStr(pPars, &stmt->d.s_propfilt.pCSCompValue); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d compare value property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + } + + return rsParsDestruct(pPars); +} + + +/* process the action part of a selector line + * rgerhards, 2007-08-01 + */ +rsRetVal cflineDoAction(rsconf_t *conf, uchar **p, action_t **ppAction) +{ + modInfo_t *pMod; + cfgmodules_etry_t *node; + omodStringRequest_t *pOMSR; + int bHadWarning = 0; + action_t *pAction = NULL; + void *pModData; + DEFiRet; + + ASSERT(p != NULL); + ASSERT(ppAction != NULL); + + /* loop through all modules and see if one picks up the line */ + node = module.GetNxtCnfType(conf, NULL, eMOD_OUT); + /* Note: clang static analyzer reports that node maybe == NULL. However, this is + * not possible, because we have the built-in output modules which are always + * present. Anyhow, we guard this by an assert. -- rgerhards, 2010-12-16 + */ + assert(node != NULL); + while(node != NULL) { + pOMSR = NULL; + pMod = node->pMod; + iRet = pMod->mod.om.parseSelectorAct(p, &pModData, &pOMSR); + dbgprintf("tried selector action for %s: %d\n", module.GetName(pMod), iRet); + if(iRet == RS_RET_OK_WARN) { + bHadWarning = 1; + iRet = RS_RET_OK; + } + if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) { + if((iRet = addAction(&pAction, pMod, pModData, pOMSR, NULL, NULL, + (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) { + /* here check if the module is compatible with select features + * (currently, we have no such features!) */ + pAction->eState = ACT_STATE_RDY; /* action is enabled */ + conf->actions.nbrActions++; /* one more active action! */ + } + break; + } else if(iRet != RS_RET_CONFLINE_UNPROCESSED) { + /* In this case, the module would have handled the config + * line, but some error occured while doing so. This error should + * already by reported by the module. We do not try any other + * modules on this line, because we found the right one. + * rgerhards, 2007-07-24 + */ + dbgprintf("error %d parsing config line\n", (int) iRet); + break; + } + node = module.GetNxtCnfType(conf, node, eMOD_OUT); + } + + *ppAction = pAction; + if(iRet == RS_RET_OK && bHadWarning) + iRet = RS_RET_OK_WARN; + RETiRet; +} + + +/* return the current number of active actions + * rgerhards, 2008-07-28 + */ +static rsRetVal +GetNbrActActions(rsconf_t *conf, int *piNbrActions) +{ + DEFiRet; + assert(piNbrActions != NULL); + *piNbrActions = conf->actions.nbrActions; + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(conf) +CODESTARTobjQueryInterface(conf) + if(pIf->ifVersion != confCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->doNameLine = doNameLine; + pIf->cfsysline = cfsysline; + pIf->doModLoad = doModLoad; + pIf->GetNbrActActions = GetNbrActActions; + +finalize_it: +ENDobjQueryInterface(conf) + + +/* Reset config variables to default values. + * rgerhards, 2010-07-23 + */ +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + bConfStrictScoping = 0; + return RS_RET_OK; +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +BEGINObjClassExit(conf, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(conf) + /* free no-longer needed module-global variables */ + if(pDfltHostnameCmp != NULL) { + rsCStrDestruct(&pDfltHostnameCmp); + } + + if(pDfltProgNameCmp != NULL) { + rsCStrDestruct(&pDfltProgNameCmp); + } + + /* release objects we no longer need */ + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(ruleset, CORE_COMPONENT); +ENDObjClassExit(conf) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINAbstractObjClassInit(conf, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); /* TODO: make this dependcy go away! */ + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + + /* These commands will NOT be supported -- the new v6.3 config system provides + * far better methods. We will remove the related code soon. -- rgerhards, 2012-01-09 + */ + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); +ENDObjClassInit(conf) + +/* vi:set ai: + */ diff --git a/runtime/conf.h b/runtime/conf.h new file mode 100644 index 00000000..a1bb51ad --- /dev/null +++ b/runtime/conf.h @@ -0,0 +1,72 @@ +/* Definitions for config file handling (not yet an object). + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_CONF_H +#define INCLUDED_CONF_H +#include "action.h" + +/* definitions used for doNameLine to differentiate between different command types + * (with otherwise identical code). This is a left-over from the previous config + * system. It stays, because it is still useful. So do not wonder why it looks + * somewhat strange (at least its name). -- rgerhards, 2007-08-01 + */ +enum eDirective { DIR_TEMPLATE = 0, DIR_OUTCHANNEL = 1, DIR_ALLOWEDSENDER = 2}; +extern ecslConfObjType currConfObj; +extern int bConfStrictScoping; /* force strict scoping during config processing? */ + +/* interfaces */ +BEGINinterface(conf) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*doNameLine)(uchar **pp, void* pVal); + rsRetVal (*cfsysline)(uchar *p); + rsRetVal (*doModLoad)(uchar **pp, __attribute__((unused)) void* pVal); + rsRetVal (*GetNbrActActions)(rsconf_t *conf, int *); + /* version 4 -- 2010-07-23 rgerhards */ + /* "just" added global variables + * FYI: we reconsider repacking as a non-object, as only the core currently + * accesses this module. The current object structure complicates things without + * any real benefit. + */ + /* version 5 -- 2011-04-19 rgerhards */ + /* complete revamp, we now use the rsconf object */ + /* version 6 -- 2011-07-06 rgerhards */ + /* again a complete revamp, using flex/bison based parser now */ +ENDinterface(conf) +#define confCURR_IF_VERSION 6 /* increment whenever you change the interface structure! */ +/* in Version 3, entry point "ReInitConf()" was removed, as we do not longer need + * to support restart-type HUP -- rgerhards, 2009-07-15 + */ + + +/* prototypes */ +PROTOTYPEObj(conf); + + +/* TODO: the following 2 need to go in conf obj interface... */ +rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName); +rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl); + +rsRetVal DecodePRIFilter(uchar *pline, uchar pmask[]); +rsRetVal DecodePropFilter(uchar *pline, struct cnfstmt *stmt); +rsRetVal cflineDoAction(rsconf_t *conf, uchar **p, action_t **ppAction); +extern EHostnameCmpMode eDfltHostnameCmpMode; +extern cstr_t *pDfltHostnameCmp; +extern cstr_t *pDfltProgNameCmp; + +#endif /* #ifndef INCLUDED_CONF_H */ diff --git a/runtime/cryprov.h b/runtime/cryprov.h new file mode 100644 index 00000000..5690904d --- /dev/null +++ b/runtime/cryprov.h @@ -0,0 +1,50 @@ +/* The interface definition for (file) crypto providers. + * + * This is just an abstract driver interface, which needs to be + * implemented by concrete classes. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_CRYPROV_H +#define INCLUDED_CRYPROV_H + +#include <gcrypt.h> + +/* we unfortunately need to have two different param names depending on the + * context in which parameters are set. Other than (re/over)engineering the core + * interface, we just define some values to keep track of that. + */ +#define CRYPROV_PARAMTYPE_REGULAR 0 +#define CRYPROV_PARAMTYPE_DISK 1 + +/* interface */ +BEGINinterface(cryprov) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(void *ppThis); + rsRetVal (*SetCnfParam)(void *ppThis, struct nvlst *lst, int paramType); + rsRetVal (*Destruct)(void *ppThis); + rsRetVal (*OnFileOpen)(void *pThis, uchar *fn, void *pFileInstData, char openMode); + rsRetVal (*Encrypt)(void *pFileInstData, uchar *buf, size_t *lenBuf); + rsRetVal (*Decrypt)(void *pFileInstData, uchar *buf, size_t *lenBuf); + rsRetVal (*OnFileClose)(void *pFileInstData, off64_t offsLogfile); + rsRetVal (*DeleteStateFiles)(uchar *logfn); + rsRetVal (*GetBytesLeftInBlock)(void *pFileInstData, ssize_t *left); + void (*SetDeleteOnClose)(void *pFileInstData, int val); +ENDinterface(cryprov) +#define cryprovCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ +#endif /* #ifndef INCLUDED_CRYPROV_H */ diff --git a/runtime/datetime.c b/runtime/datetime.c new file mode 100644 index 00000000..841ff625 --- /dev/null +++ b/runtime/datetime.c @@ -0,0 +1,1023 @@ +/* The datetime object. It contains date and time related functions. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c. The main intension was to move code out of syslogd.c + * in a useful manner. It is still undecided if all functions will continue + * to stay here or some will be moved into parser modules (once we have them). + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <ctype.h> +#include <assert.h> +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include "rsyslog.h" +#include "obj.h" +#include "modules.h" +#include "datetime.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + +/* the following table of ten powers saves us some computation */ +static const int tenPowers[6] = { 1, 10, 100, 1000, 10000, 100000 }; + +/* ------------------------------ methods ------------------------------ */ + + +/** + * Convert struct timeval to syslog_time + */ +void +timeval2syslogTime(struct timeval *tp, struct syslogTime *t) +{ + struct tm *tm; + struct tm tmBuf; + long lBias; + time_t secs; + + secs = tp->tv_sec; + tm = localtime_r(&secs, &tmBuf); + + t->year = tm->tm_year + 1900; + t->month = tm->tm_mon + 1; + t->day = tm->tm_mday; + t->hour = tm->tm_hour; + t->minute = tm->tm_min; + t->second = tm->tm_sec; + t->secfrac = tp->tv_usec; + t->secfracPrecision = 6; + +# if __sun + /* Solaris uses a different method of exporting the time zone. + * It is UTC - localtime, which is the opposite sign of mins east of GMT. + */ + lBias = -(tm->tm_isdst ? altzone : timezone); +# elif defined(__hpux) + lBias = tz.tz_dsttime ? - tz.tz_minuteswest : 0; +# else + lBias = tm->tm_gmtoff; +# endif + if(lBias < 0) { + t->OffsetMode = '-'; + lBias *= -1; + } else + t->OffsetMode = '+'; + t->OffsetHour = lBias / 3600; + t->OffsetMinute = (lBias % 3600) / 60; + t->timeType = TIME_TYPE_RFC5424; /* we have a high precision timestamp */ +} + +/** + * Get the current date/time in the best resolution the operating + * system has to offer (well, actually at most down to the milli- + * second level. + * + * The date and time is returned in separate fields as this is + * most portable and removes the need for additional structures + * (but I have to admit it is somewhat "bulky";)). + * + * Obviously, *t must not be NULL... + * + * rgerhards, 2008-10-07: added ttSeconds to provide a way to + * obtain the second-resolution UNIX timestamp. This is needed + * in some situations to minimize time() calls (namely when doing + * output processing). This can be left NULL if not needed. + */ +static void getCurrTime(struct syslogTime *t, time_t *ttSeconds) +{ + struct timeval tp; +# if defined(__hpux) + struct timezone tz; +# endif + + assert(t != NULL); +# if defined(__hpux) + /* TODO: check this: under HP UX, the tz information is actually valid + * data. So we need to obtain and process it there. + */ + gettimeofday(&tp, &tz); +# else + gettimeofday(&tp, NULL); +# endif + if(ttSeconds != NULL) + *ttSeconds = tp.tv_sec; + + timeval2syslogTime(&tp, t); +} + + +/* A fast alternative to getCurrTime() and time() that only obtains + * a timestamp like time() does. I was told that gettimeofday(), at + * least under Linux, is much faster than time() and I could confirm + * this testing. So I created that function as a replacement. + * rgerhards, 2009-11-12 + */ +static time_t +getTime(time_t *ttSeconds) +{ + struct timeval tp; + + if(gettimeofday(&tp, NULL) == -1) + return -1; + + if(ttSeconds != NULL) + *ttSeconds = tp.tv_sec; + return tp.tv_sec; +} + + +/******************************************************************* + * BEGIN CODE-LIBLOGGING * + ******************************************************************* + * Code in this section is borrowed from liblogging. This is an + * interim solution. Once liblogging is fully integrated, this is + * to be removed (see http://www.monitorware.com/liblogging for + * more details. 2004-11-16 rgerhards + * + * Please note that the orginal liblogging code is modified so that + * it fits into the context of the current version of syslogd.c. + * + * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!! + */ + + +/** + * Parse a 32 bit integer number from a string. + * + * \param ppsz Pointer to the Pointer to the string being parsed. It + * must be positioned at the first digit. Will be updated + * so that on return it points to the first character AFTER + * the integer parsed. + * \param pLenStr pointer to string length, decremented on exit by + * characters processed + * Note that if an empty string (len < 1) is passed in, + * the method always returns zero. + * \retval The number parsed. + */ +static inline int +srSLMGParseInt32(uchar** ppsz, int *pLenStr) +{ + register int i; + + i = 0; + while(*pLenStr > 0 && **ppsz >= '0' && **ppsz <= '9') { + i = i * 10 + **ppsz - '0'; + ++(*ppsz); + --(*pLenStr); + } + + return i; +} + + +/** + * Parse a TIMESTAMP-3339. + * updates the parse pointer position. The pTime parameter + * is guranteed to be updated only if a new valid timestamp + * could be obtained (restriction added 2008-09-16 by rgerhards). + * This method now also checks the maximum string length it is passed. + * If a *valid* timestamp is found, the string length is decremented + * by the number of characters processed. If it is not a valid timestamp, + * the length is kept unmodified. -- rgerhards, 2009-09-23 + */ +static rsRetVal +ParseTIMESTAMP3339(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr) +{ + uchar *pszTS = *ppszTS; + /* variables to temporarily hold time information while we parse */ + int year; + int month; + int day; + int hour; /* 24 hour clock */ + int minute; + int second; + int secfrac; /* fractional seconds (must be 32 bit!) */ + int secfracPrecision; + char OffsetMode; /* UTC offset + or - */ + char OffsetHour; /* UTC offset in hours */ + int OffsetMinute; /* UTC offset in minutes */ + int lenStr; + /* end variables to temporarily hold time information while we parse */ + DEFiRet; + + assert(pTime != NULL); + assert(ppszTS != NULL); + assert(pszTS != NULL); + + lenStr = *pLenStr; + year = srSLMGParseInt32(&pszTS, &lenStr); + + /* We take the liberty to accept slightly malformed timestamps e.g. in + * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course, + * with the current state of affairs, we would never run into this code + * here because at postion 11, there is no "T" in such cases ;) + */ + if(lenStr == 0 || *pszTS++ != '-') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + month = srSLMGParseInt32(&pszTS, &lenStr); + if(month < 1 || month > 12) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != '-') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + day = srSLMGParseInt32(&pszTS, &lenStr); + if(day < 1 || day > 31) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != 'T') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + + hour = srSLMGParseInt32(&pszTS, &lenStr); + if(hour < 0 || hour > 23) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != ':') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + minute = srSLMGParseInt32(&pszTS, &lenStr); + if(minute < 0 || minute > 59) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != ':') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + second = srSLMGParseInt32(&pszTS, &lenStr); + if(second < 0 || second > 60) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + /* Now let's see if we have secfrac */ + if(lenStr > 0 && *pszTS == '.') { + --lenStr; + uchar *pszStart = ++pszTS; + secfrac = srSLMGParseInt32(&pszTS, &lenStr); + secfracPrecision = (int) (pszTS - pszStart); + } else { + secfracPrecision = 0; + secfrac = 0; + } + + /* check the timezone */ + if(lenStr == 0) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(*pszTS == 'Z') { + --lenStr; + pszTS++; /* eat Z */ + OffsetMode = 'Z'; + OffsetHour = 0; + OffsetMinute = 0; + } else if((*pszTS == '+') || (*pszTS == '-')) { + OffsetMode = *pszTS; + --lenStr; + pszTS++; + + OffsetHour = srSLMGParseInt32(&pszTS, &lenStr); + if(OffsetHour < 0 || OffsetHour > 23) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS != ':') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + pszTS++; + OffsetMinute = srSLMGParseInt32(&pszTS, &lenStr); + if(OffsetMinute < 0 || OffsetMinute > 59) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else { + /* there MUST be TZ information */ + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } + + /* OK, we actually have a 3339 timestamp, so let's indicated this */ + if(lenStr > 0) { + if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */ + ABORT_FINALIZE(RS_RET_INVLD_TIME); + ++pszTS; /* just skip past it */ + --lenStr; + } + + /* we had success, so update parse pointer and caller-provided timestamp */ + *ppszTS = pszTS; + pTime->timeType = 2; + pTime->year = year; + pTime->month = month; + pTime->day = day; + pTime->hour = hour; + pTime->minute = minute; + pTime->second = second; + pTime->secfrac = secfrac; + pTime->secfracPrecision = secfracPrecision; + pTime->OffsetMode = OffsetMode; + pTime->OffsetHour = OffsetHour; + pTime->OffsetMinute = OffsetMinute; + *pLenStr = lenStr; + +finalize_it: + RETiRet; +} + + +/** + * Parse a TIMESTAMP-3164. The pTime parameter + * is guranteed to be updated only if a new valid timestamp + * could be obtained (restriction added 2008-09-16 by rgerhards). This + * also means the caller *must* provide a valid (probably current) + * timstamp in pTime when calling this function. a 3164 timestamp contains + * only partial information and only that partial information is updated. + * So the "output timestamp" is a valid timestamp only if the "input + * timestamp" was valid, too. The is actually an optimization, as it + * permits us to use a pre-aquired timestamp and thus avoids to do + * a (costly) time() call. Thanks to David Lang for insisting on + * time() call reduction ;). + * This method now also checks the maximum string length it is passed. + * If a *valid* timestamp is found, the string length is decremented + * by the number of characters processed. If it is not a valid timestamp, + * the length is kept unmodified. -- rgerhards, 2009-09-23 + */ +static rsRetVal +ParseTIMESTAMP3164(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr) +{ + /* variables to temporarily hold time information while we parse */ + int month; + int day; + int year = 0; /* 0 means no year provided */ + int hour; /* 24 hour clock */ + int minute; + int second; + /* end variables to temporarily hold time information while we parse */ + int lenStr; + uchar *pszTS; + DEFiRet; + + assert(ppszTS != NULL); + pszTS = *ppszTS; + assert(pszTS != NULL); + assert(pTime != NULL); + assert(pLenStr != NULL); + lenStr = *pLenStr; + + /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), + * we may see the following character sequences occur: + * + * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec + * + * We will use this for parsing, as it probably is the + * fastest way to parse it. + * + * 2009-08-17: we now do case-insensitive comparisons, as some devices obviously do not + * obey to the RFC-specified case. As we need to guess in any case, we can ignore case + * in the first place -- rgerhards + * + * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C... + * Fixed a bug that lead to invalid detection of the data. The issue was that + * we had an if(++pszTS == 'x') inside of some of the consturcts below. However, + * there were also some elseifs (doing the same ++), which than obviously did not + * check the orginal character but the next one. Now removed the ++ and put it + * into the statements below. Was a really nasty bug... I didn't detect it before + * june, when it first manifested. This also lead to invalid parsing of the rest + * of the message, as the time stamp was not detected to be correct. - rgerhards + */ + if(lenStr < 3) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + switch(*pszTS++) + { + case 'j': + case 'J': + if(*pszTS == 'a' || *pszTS == 'A') { + ++pszTS; + if(*pszTS == 'n' || *pszTS == 'N') { + ++pszTS; + month = 1; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else if(*pszTS == 'u' || *pszTS == 'U') { + ++pszTS; + if(*pszTS == 'n' || *pszTS == 'N') { + ++pszTS; + month = 6; + } else if(*pszTS == 'l' || *pszTS == 'L') { + ++pszTS; + month = 7; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 'f': + case 'F': + if(*pszTS == 'e' || *pszTS == 'E') { + ++pszTS; + if(*pszTS == 'b' || *pszTS == 'B') { + ++pszTS; + month = 2; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 'm': + case 'M': + if(*pszTS == 'a' || *pszTS == 'A') { + ++pszTS; + if(*pszTS == 'r' || *pszTS == 'R') { + ++pszTS; + month = 3; + } else if(*pszTS == 'y' || *pszTS == 'Y') { + ++pszTS; + month = 5; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 'a': + case 'A': + if(*pszTS == 'p' || *pszTS == 'P') { + ++pszTS; + if(*pszTS == 'r' || *pszTS == 'R') { + ++pszTS; + month = 4; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else if(*pszTS == 'u' || *pszTS == 'U') { + ++pszTS; + if(*pszTS == 'g' || *pszTS == 'G') { + ++pszTS; + month = 8; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 's': + case 'S': + if(*pszTS == 'e' || *pszTS == 'E') { + ++pszTS; + if(*pszTS == 'p' || *pszTS == 'P') { + ++pszTS; + month = 9; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 'o': + case 'O': + if(*pszTS == 'c' || *pszTS == 'C') { + ++pszTS; + if(*pszTS == 't' || *pszTS == 'T') { + ++pszTS; + month = 10; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 'n': + case 'N': + if(*pszTS == 'o' || *pszTS == 'O') { + ++pszTS; + if(*pszTS == 'v' || *pszTS == 'V') { + ++pszTS; + month = 11; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + case 'd': + case 'D': + if(*pszTS == 'e' || *pszTS == 'E') { + ++pszTS; + if(*pszTS == 'c' || *pszTS == 'C') { + ++pszTS; + month = 12; + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } else + ABORT_FINALIZE(RS_RET_INVLD_TIME); + break; + default: + ABORT_FINALIZE(RS_RET_INVLD_TIME); + } + + lenStr -= 3; + + /* done month */ + + if(lenStr == 0 || *pszTS++ != ' ') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + + /* we accept a slightly malformed timestamp when receiving. This is + * we accept one-digit days + */ + if(*pszTS == ' ') { + --lenStr; + ++pszTS; + } + + day = srSLMGParseInt32(&pszTS, &lenStr); + if(day < 1 || day > 31) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != ' ') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + + /* time part */ + hour = srSLMGParseInt32(&pszTS, &lenStr); + if(hour > 1970 && hour < 2100) { + /* if so, we assume this actually is a year. This is a format found + * e.g. in Cisco devices. + * (if you read this 2100+ trying to fix a bug, congratulate me + * to how long the code survived - me no longer ;)) -- rgerhards, 2008-11-18 + */ + year = hour; + + /* re-query the hour, this time it must be valid */ + if(lenStr == 0 || *pszTS++ != ' ') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + hour = srSLMGParseInt32(&pszTS, &lenStr); + } + + if(hour < 0 || hour > 23) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != ':') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + minute = srSLMGParseInt32(&pszTS, &lenStr); + if(minute < 0 || minute > 59) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + if(lenStr == 0 || *pszTS++ != ':') + ABORT_FINALIZE(RS_RET_INVLD_TIME); + --lenStr; + second = srSLMGParseInt32(&pszTS, &lenStr); + if(second < 0 || second > 60) + ABORT_FINALIZE(RS_RET_INVLD_TIME); + + /* we provide support for an extra ":" after the date. While this is an + * invalid format, it occurs frequently enough (e.g. with Cisco devices) + * to permit it as a valid case. -- rgerhards, 2008-09-12 + */ + if(lenStr > 0 && *pszTS == ':') { + ++pszTS; /* just skip past it */ + --lenStr; + } + if(lenStr > 0) { + if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */ + ABORT_FINALIZE(RS_RET_INVLD_TIME); + ++pszTS; /* just skip past it */ + --lenStr; + } + + /* we had success, so update parse pointer and caller-provided timestamp + * fields we do not have are not updated in the caller's timestamp. This + * is the reason why the caller must pass in a correct timestamp. + */ + *ppszTS = pszTS; /* provide updated parse position back to caller */ + pTime->timeType = 1; + pTime->month = month; + if(year > 0) + pTime->year = year; /* persist year if detected */ + pTime->day = day; + pTime->hour = hour; + pTime->minute = minute; + pTime->second = second; + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + *pLenStr = lenStr; + +finalize_it: + RETiRet; +} + +/******************************************************************* + * END CODE-LIBLOGGING * + *******************************************************************/ + +/** + * Format a syslogTimestamp into format required by MySQL. + * We are using the 14 digits format. For example 20041111122600 + * is interpreted as '2004-11-11 12:26:00'. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + */ +int formatTimestampToMySQL(struct syslogTime *ts, char* pBuf) +{ + /* currently we do not consider localtime/utc. This may later be + * added. If so, I recommend using a property replacer option + * and/or a global configuration option. However, we should wait + * on user requests for this feature before doing anything. + * rgerhards, 2007-06-26 + */ + assert(ts != NULL); + assert(pBuf != NULL); + + pBuf[0] = (ts->year / 1000) % 10 + '0'; + pBuf[1] = (ts->year / 100) % 10 + '0'; + pBuf[2] = (ts->year / 10) % 10 + '0'; + pBuf[3] = ts->year % 10 + '0'; + pBuf[4] = (ts->month / 10) % 10 + '0'; + pBuf[5] = ts->month % 10 + '0'; + pBuf[6] = (ts->day / 10) % 10 + '0'; + pBuf[7] = ts->day % 10 + '0'; + pBuf[8] = (ts->hour / 10) % 10 + '0'; + pBuf[9] = ts->hour % 10 + '0'; + pBuf[10] = (ts->minute / 10) % 10 + '0'; + pBuf[11] = ts->minute % 10 + '0'; + pBuf[12] = (ts->second / 10) % 10 + '0'; + pBuf[13] = ts->second % 10 + '0'; + pBuf[14] = '\0'; + return 15; + +} + +int formatTimestampToPgSQL(struct syslogTime *ts, char *pBuf) +{ + /* see note in formatTimestampToMySQL, applies here as well */ + assert(ts != NULL); + assert(pBuf != NULL); + + pBuf[0] = (ts->year / 1000) % 10 + '0'; + pBuf[1] = (ts->year / 100) % 10 + '0'; + pBuf[2] = (ts->year / 10) % 10 + '0'; + pBuf[3] = ts->year % 10 + '0'; + pBuf[4] = '-'; + pBuf[5] = (ts->month / 10) % 10 + '0'; + pBuf[6] = ts->month % 10 + '0'; + pBuf[7] = '-'; + pBuf[8] = (ts->day / 10) % 10 + '0'; + pBuf[9] = ts->day % 10 + '0'; + pBuf[10] = ' '; + pBuf[11] = (ts->hour / 10) % 10 + '0'; + pBuf[12] = ts->hour % 10 + '0'; + pBuf[13] = ':'; + pBuf[14] = (ts->minute / 10) % 10 + '0'; + pBuf[15] = ts->minute % 10 + '0'; + pBuf[16] = ':'; + pBuf[17] = (ts->second / 10) % 10 + '0'; + pBuf[18] = ts->second % 10 + '0'; + pBuf[19] = '\0'; + return 19; +} + + +/** + * Format a syslogTimestamp to just the fractional seconds. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + * The buffer must be at least 7 bytes large. + * rgerhards, 2008-06-06 + */ +int formatTimestampSecFrac(struct syslogTime *ts, char* pBuf) +{ + int iBuf; + int power; + int secfrac; + short digit; + + assert(ts != NULL); + assert(pBuf != NULL); + + iBuf = 0; + if(ts->secfracPrecision > 0) + { + power = tenPowers[(ts->secfracPrecision - 1) % 6]; + secfrac = ts->secfrac; + while(power > 0) { + digit = secfrac / power; + secfrac -= digit * power; + power /= 10; + pBuf[iBuf++] = digit + '0'; + } + } else { + pBuf[iBuf++] = '0'; + } + pBuf[iBuf] = '\0'; + + return iBuf; +} + + +/** + * Format a syslogTimestamp to a RFC3339 timestamp string (as + * specified in syslog-protocol). + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + */ +int formatTimestamp3339(struct syslogTime *ts, char* pBuf) +{ + int iBuf; + int power; + int secfrac; + short digit; + + BEGINfunc + assert(ts != NULL); + assert(pBuf != NULL); + + /* start with fixed parts */ + /* year yyyy */ + pBuf[0] = (ts->year / 1000) % 10 + '0'; + pBuf[1] = (ts->year / 100) % 10 + '0'; + pBuf[2] = (ts->year / 10) % 10 + '0'; + pBuf[3] = ts->year % 10 + '0'; + pBuf[4] = '-'; + /* month */ + pBuf[5] = (ts->month / 10) % 10 + '0'; + pBuf[6] = ts->month % 10 + '0'; + pBuf[7] = '-'; + /* day */ + pBuf[8] = (ts->day / 10) % 10 + '0'; + pBuf[9] = ts->day % 10 + '0'; + pBuf[10] = 'T'; + /* hour */ + pBuf[11] = (ts->hour / 10) % 10 + '0'; + pBuf[12] = ts->hour % 10 + '0'; + pBuf[13] = ':'; + /* minute */ + pBuf[14] = (ts->minute / 10) % 10 + '0'; + pBuf[15] = ts->minute % 10 + '0'; + pBuf[16] = ':'; + /* second */ + pBuf[17] = (ts->second / 10) % 10 + '0'; + pBuf[18] = ts->second % 10 + '0'; + + iBuf = 19; /* points to next free entry, now it becomes dynamic! */ + + if(ts->secfracPrecision > 0) { + pBuf[iBuf++] = '.'; + power = tenPowers[(ts->secfracPrecision - 1) % 6]; + secfrac = ts->secfrac; + while(power > 0) { + digit = secfrac / power; + secfrac -= digit * power; + power /= 10; + pBuf[iBuf++] = digit + '0'; + } + } + + if(ts->OffsetMode == 'Z') { + pBuf[iBuf++] = 'Z'; + } else { + pBuf[iBuf++] = ts->OffsetMode; + pBuf[iBuf++] = (ts->OffsetHour / 10) % 10 + '0'; + pBuf[iBuf++] = ts->OffsetHour % 10 + '0'; + pBuf[iBuf++] = ':'; + pBuf[iBuf++] = (ts->OffsetMinute / 10) % 10 + '0'; + pBuf[iBuf++] = ts->OffsetMinute % 10 + '0'; + } + + pBuf[iBuf] = '\0'; + + ENDfunc + return iBuf; +} + +/** + * Format a syslogTimestamp to a RFC3164 timestamp sring. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string termnator). If 0 is returend, an error occured. + * rgerhards, 2010-03-05: Added support to for buggy 3164 dates, + * where a zero-digit is written instead of a space for the first + * day character if day < 10. syslog-ng seems to do that, and some + * parsing scripts (in migration cases) rely on that. + */ +int formatTimestamp3164(struct syslogTime *ts, char* pBuf, int bBuggyDay) +{ + static char* monthNames[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + int iDay; + assert(ts != NULL); + assert(pBuf != NULL); + + pBuf[0] = monthNames[(ts->month - 1)% 12][0]; + pBuf[1] = monthNames[(ts->month - 1) % 12][1]; + pBuf[2] = monthNames[(ts->month - 1) % 12][2]; + pBuf[3] = ' '; + iDay = (ts->day / 10) % 10; /* we need to write a space if the first digit is 0 */ + pBuf[4] = (bBuggyDay || iDay > 0) ? iDay + '0' : ' '; + pBuf[5] = ts->day % 10 + '0'; + pBuf[6] = ' '; + pBuf[7] = (ts->hour / 10) % 10 + '0'; + pBuf[8] = ts->hour % 10 + '0'; + pBuf[9] = ':'; + pBuf[10] = (ts->minute / 10) % 10 + '0'; + pBuf[11] = ts->minute % 10 + '0'; + pBuf[12] = ':'; + pBuf[13] = (ts->second / 10) % 10 + '0'; + pBuf[14] = ts->second % 10 + '0'; + pBuf[15] = '\0'; + return 16; /* traditional: number of bytes written */ +} + + +/** + * convert syslog timestamp to time_t + */ +time_t syslogTime2time_t(struct syslogTime *ts) +{ + long MonthInDays, NumberOfYears, NumberOfDays, i; + int utcOffset; + time_t TimeInUnixFormat; + + /* Counting how many Days have passed since the 01.01 of the + * selected Year (Month level), according to the selected Month*/ + + switch(ts->month) + { + case 1: + MonthInDays = 0; //until 01 of January + break; + case 2: + MonthInDays = 31; //until 01 of February - leap year handling down below! + break; + case 3: + MonthInDays = 59; //until 01 of March + break; + case 4: + MonthInDays = 90; //until 01 of April + break; + case 5: + MonthInDays = 120; //until 01 of Mai + break; + case 6: + MonthInDays = 151; //until 01 of June + break; + case 7: + MonthInDays = 181; //until 01 of July + break; + case 8: + MonthInDays = 212; //until 01 of August + break; + case 9: + MonthInDays = 243; //until 01 of September + break; + case 10: + MonthInDays = 273; //until 01 of Oktober + break; + case 11: + MonthInDays = 304; //until 01 of November + break; + case 12: + MonthInDays = 334; //until 01 of December + break; + default: /* this cannot happen (and would be a program error) + * but we need the code to keep the compiler silent. + */ + MonthInDays = 0; /* any value fits ;) */ + break; + } + + + /* 1) Counting how many Years have passed since 1970 + 2) Counting how many Days have passed since the 01.01 of the selected Year + (Day level) according to the Selected Month and Day. Last day doesn't count, + it should be until last day + 3) Calculating this period (NumberOfDays) in seconds*/ + + NumberOfYears = ts->year - 1970; + NumberOfDays = MonthInDays + ts->day - 1; + TimeInUnixFormat = NumberOfYears * 31536000 + NumberOfDays * 86400; + + /* Now we need to adjust the number of years for leap + * year processing. If we are in Jan or Feb, this year + * will never be considered - because we haven't arrived + * at then end of Feb right now. [Feb, 29th in a leap year + * is handled correctly, because the day (29) is correctly + * added to the date serial] + */ + if(ts->month < 3) + NumberOfYears--; + + /*...AND ADDING ONE DAY FOR EACH YEAR WITH 366 DAYS + * note that we do not handle 2000 any special, as it was a + * leap year. The current code works OK until 2100, when it will + * break. As we do not process future dates, we accept that fate... + * the whole thing could be refactored by a table-based approach. + */ + for(i = 1;i <= NumberOfYears; i++) + { + /* If i = 2 we have 1972, which was a Year with 366 Days + and if (i + 2) Mod (4) = 0 we have a Year after 1972 + which is also a Year with 366 Days (repeated every 4 Years) */ + if ((i == 2) || (((i + 2) % 4) == 0)) + { /*Year with 366 Days!!!*/ + TimeInUnixFormat += 86400; + } + } + + /*Add Hours, minutes and seconds */ + TimeInUnixFormat += ts->hour*60*60; + TimeInUnixFormat += ts->minute*60; + TimeInUnixFormat += ts->second; + /* do UTC offset */ + utcOffset = ts->OffsetHour*3600 + ts->OffsetMinute*60; + if(ts->OffsetMode == '+') + utcOffset *= -1; /* if timestamp is ahead, we need to "go back" to UTC */ + TimeInUnixFormat += utcOffset; + return TimeInUnixFormat; +} + + +/** + * format a timestamp as a UNIX timestamp; subsecond resolution is + * discarded. + * Note that this code can use some refactoring. I decided to use it + * because mktime() requires an upfront TZ update as it works on local + * time. In any case, it is worth reconsidering to move to mktime() or + * some other method. + * Important: pBuf must point to a buffer of at least 11 bytes. + * rgerhards, 2012-03-29 + */ +int formatTimestampUnix(struct syslogTime *ts, char *pBuf) +{ + snprintf(pBuf, 11, "%u", (unsigned) syslogTime2time_t(ts)); + return 11; +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(datetime) +CODESTARTobjQueryInterface(datetime) + if(pIf->ifVersion != datetimeCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->getCurrTime = getCurrTime; + pIf->GetTime = getTime; + pIf->timeval2syslogTime = timeval2syslogTime; + pIf->ParseTIMESTAMP3339 = ParseTIMESTAMP3339; + pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164; + pIf->formatTimestampToMySQL = formatTimestampToMySQL; + pIf->formatTimestampToPgSQL = formatTimestampToPgSQL; + pIf->formatTimestampSecFrac = formatTimestampSecFrac; + pIf->formatTimestamp3339 = formatTimestamp3339; + pIf->formatTimestamp3164 = formatTimestamp3164; + pIf->formatTimestampUnix = formatTimestampUnix; + pIf->syslogTime2time_t = syslogTime2time_t; +finalize_it: +ENDobjQueryInterface(datetime) + + +/* Initialize the datetime class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(datetime, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDObjClassInit(datetime) + +/* vi:set ai: + */ diff --git a/runtime/datetime.h b/runtime/datetime.h new file mode 100644 index 00000000..9f3611e1 --- /dev/null +++ b/runtime/datetime.h @@ -0,0 +1,66 @@ +/* The datetime object. Contains time-related functions. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_DATETIME_H +#define INCLUDED_DATETIME_H + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the datetime object */ +typedef struct datetime_s { + char dummy; +} datetime_t; + + +/* interfaces */ +BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */ + void (*getCurrTime)(struct syslogTime *t, time_t *ttSeconds); + rsRetVal (*ParseTIMESTAMP3339)(struct syslogTime *pTime, uchar** ppszTS, int*); + rsRetVal (*ParseTIMESTAMP3164)(struct syslogTime *pTime, uchar** pszTS, int*); + int (*formatTimestampToMySQL)(struct syslogTime *ts, char* pDst); + int (*formatTimestampToPgSQL)(struct syslogTime *ts, char *pDst); + int (*formatTimestamp3339)(struct syslogTime *ts, char* pBuf); + int (*formatTimestamp3164)(struct syslogTime *ts, char* pBuf, int); + int (*formatTimestampSecFrac)(struct syslogTime *ts, char* pBuf); + /* v3, 2009-11-12 */ + time_t (*GetTime)(time_t *ttSeconds); + /* v6, 2011-06-20 */ + void (*timeval2syslogTime)(struct timeval *tp, struct syslogTime *t); + /* v7, 2012-03-29 */ + int (*formatTimestampUnix)(struct syslogTime *ts, char*pBuf); + time_t (*syslogTime2time_t)(struct syslogTime *ts); +ENDinterface(datetime) +#define datetimeCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */ +/* interface changes: + * 1 - initial version + * 2 - not compatible to 1 - bugfix required ParseTIMESTAMP3164 to accept char ** as + * last parameter. Did not try to remain compatible as this is not something any + * third-party module should call. -- rgerhards, 2008.-09-12 + * 3 - taken by v5 branch! + * 4 - formatTimestamp3164 takes a third int parameter + * 5 - merge of versions 3 + 4 (2010-03-09) + * 6 - see above + */ + +/* prototypes */ +PROTOTYPEObj(datetime); + +#endif /* #ifndef INCLUDED_DATETIME_H */ diff --git a/runtime/debug.c b/runtime/debug.c new file mode 100644 index 00000000..68474989 --- /dev/null +++ b/runtime/debug.c @@ -0,0 +1,1509 @@ +/* debug.c + * + * This file proides debug and run time error analysis support. Some of the + * settings are very performance intense and my be turned off during a release + * build. + * + * File begun on 2008-01-22 by RGerhards + * + * Some functions are controlled by environment variables: + * + * RSYSLOG_DEBUGLOG if set, a debug log file is written to that location + * RSYSLOG_DEBUG specific debug options + * + * For details, visit doc/debug.html + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" /* autotools! */ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <pthread.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_SYSCALL_H +# include <sys/syscall.h> +#endif +#if _POSIX_TIMERS <= 0 +#include <sys/time.h> +#endif + +#include "rsyslog.h" +#include "debug.h" +#include "atomic.h" +#include "cfsysline.h" +#include "obj.h" + + +/* static data (some time to be replaced) */ +DEFobjCurrIf(obj) +int Debug; /* debug flag - read-only after startup */ +int debugging_on = 0; /* read-only, except on sig USR1 */ +static int bLogFuncFlow = 0; /* shall the function entry and exit be logged to the debug log? */ +static int bLogAllocFree = 0; /* shall calls to (m/c)alloc and free be logged to the debug log? */ +static int bPrintFuncDBOnExit = 0; /* shall the function entry and exit be logged to the debug log? */ +static int bPrintMutexAction = 0; /* shall mutex calls be printed to the debug log? */ +static int bPrintTime = 1; /* print a timestamp together with debug message */ +static int bPrintAllDebugOnExit = 0; +static int bAbortTrace = 1; /* print a trace after SIGABRT or SIGSEGV */ +static int bOutputTidToStderr = 0;/* output TID to stderr on thread creation */ +static char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */ +static int altdbg = -1; /* and the handle for alternate debug output */ +int stddbg = 1; /* the handle for regular debug output, set to stdout if not forking, -1 otherwise */ + +/* list of files/objects that should be printed */ +typedef struct dbgPrintName_s { + uchar *pName; + struct dbgPrintName_s *pNext; +} dbgPrintName_t; + + +/* forward definitions */ +static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID); +static dbgThrdInfo_t *dbgGetThrdInfo(void); +static int dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot); + + +/* This lists are single-linked and members are added at the top */ +static dbgPrintName_t *printNameFileRoot = NULL; + + +/* list of all known FuncDBs. We use a special list, because it must only be single-linked. As + * functions never disappear, we only need to add elements when we see a new one and never need + * to remove anything. For this, we simply add at the top, which saves us a Last pointer. The goal + * is to use as few memory as possible. + */ +typedef struct dbgFuncDBListEntry_s { + dbgFuncDB_t *pFuncDB; + struct dbgFuncDBListEntry_s *pNext; +} dbgFuncDBListEntry_t; +dbgFuncDBListEntry_t *pFuncDBListRoot; + +static pthread_mutex_t mutFuncDBList; + +typedef struct dbgMutLog_s { + struct dbgMutLog_s *pNext; + struct dbgMutLog_s *pPrev; + pthread_mutex_t *mut; + pthread_t thrd; + dbgFuncDB_t *pFuncDB; + int lockLn; /* the actual line where the mutex was locked */ + short mutexOp; +} dbgMutLog_t; +static dbgMutLog_t *dbgMutLogListRoot = NULL; +static dbgMutLog_t *dbgMutLogListLast = NULL; +static pthread_mutex_t mutMutLog; + + +static dbgThrdInfo_t *dbgCallStackListRoot = NULL; +static dbgThrdInfo_t *dbgCallStackListLast = NULL; +static pthread_mutex_t mutCallStack; + +static pthread_mutex_t mutdbgprint; + +static pthread_key_t keyCallStack; + + +/* we do not have templates, so we use some macros to create linked list handlers + * for the several types + * DLL means "doubly linked list" + * rgerhards, 2008-01-23 + */ +#define DLL_Del(type, pThis) \ + if(pThis->pPrev != NULL) \ + pThis->pPrev->pNext = pThis->pNext; \ + if(pThis->pNext != NULL) \ + pThis->pNext->pPrev = pThis->pPrev; \ + if(pThis == dbg##type##ListRoot) \ + dbg##type##ListRoot = pThis->pNext; \ + if(pThis == dbg##type##ListLast) \ + dbg##type##ListLast = pThis->pPrev; \ + free(pThis); + +#define DLL_Add(type, pThis) \ + if(dbg##type##ListRoot == NULL) { \ + dbg##type##ListRoot = pThis; \ + dbg##type##ListLast = pThis; \ + } else { \ + pThis->pPrev = dbg##type##ListLast; \ + dbg##type##ListLast->pNext = pThis; \ + dbg##type##ListLast = pThis; \ + } + +/* we need to do our own mutex cancel cleanup handler as it shall not + * be subject to the debugging instrumentation (that would probably run us + * into an infinite loop + */ +static void dbgMutexCancelCleanupHdlr(void *pmut) +{ + pthread_mutex_unlock((pthread_mutex_t*) pmut); +} + + +/* handler to update the last execution location seen + * rgerhards, 2008-01-28 + */ +static inline void +dbgRecordExecLocation(int iStackPtr, int line) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + pThrd->lastLine[iStackPtr] = line; +} + + +/* ------------------------- mutex tracking code ------------------------- */ + +/* ------------------------- FuncDB utility functions ------------------------- */ + +#define SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ((int) (sizeof(pFuncDB->mutInfo) / sizeof(dbgFuncDBmutInfoEntry_t))) + +/* print a FuncDB + */ +static void dbgFuncDBPrint(dbgFuncDB_t *pFuncDB) +{ + assert(pFuncDB != NULL); + assert(pFuncDB->magic == dbgFUNCDB_MAGIC); + /* make output suitable for sorting on invocation count */ + dbgprintf("%10.10ld times called: %s:%d:%s\n", pFuncDB->nTimesCalled, pFuncDB->file, pFuncDB->line, pFuncDB->func); +} + + +/* print all funcdb entries + */ +static void dbgFuncDBPrintAll(void) +{ + dbgFuncDBListEntry_t *pFuncDBList; + int nFuncs = 0; + + for(pFuncDBList = pFuncDBListRoot ; pFuncDBList != NULL ; pFuncDBList = pFuncDBList->pNext) { + dbgFuncDBPrint(pFuncDBList->pFuncDB); + nFuncs++; + } + + dbgprintf("%d unique functions called\n", nFuncs); +} + + +/* find a mutex inside the FuncDB mutex table. Returns NULL if not found. Only mutexes from the same thread + * are found. + */ +static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBGetMutexInfo(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) +{ + int i; + int iFound = -1; + pthread_t ourThrd = pthread_self(); + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].pmut == pmut && pFuncDB->mutInfo[i].lockLn != -1 && pFuncDB->mutInfo[i].thrd == ourThrd) { + iFound = i; + break; + } + } + + return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; +} + + +/* print any mutex that can be found in the FuncDB. Custom header is provided. + * "thrd" is the thread that is searched. If it is 0, mutexes for all threads + * shall be printed. + */ +static inline void +dbgFuncDBPrintActiveMutexes(dbgFuncDB_t *pFuncDB, char *pszHdrText, pthread_t thrd) +{ + int i; + char pszThrdName[64]; + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].lockLn != -1 && (thrd == 0 || thrd == pFuncDB->mutInfo[i].thrd)) { + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pFuncDB->mutInfo[i].thrd, 1); + dbgprintf("%s:%d:%s:invocation %ld: %s %p[%d/%s]\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, + pFuncDB->mutInfo[i].lInvocation, pszHdrText, (void*)pFuncDB->mutInfo[i].pmut, i, + pszThrdName); + } + } +} + +/* find a free mutex info spot in FuncDB. NULL is returned if table is full. + */ +static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBFindFreeMutexInfo(dbgFuncDB_t *pFuncDB) +{ + int i; + int iFound = -1; + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].lockLn == -1) { + iFound = i; + break; + } + } + + if(iFound == -1) { + dbgprintf("%s:%d:%s: INFO: out of space in FuncDB for mutex info (max %d entries) - ignoring\n", + pFuncDB->file, pFuncDB->line, pFuncDB->func, SIZE_FUNCDB_MUTEX_TABLE(pFuncDB)); + } + + return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; +} + +/* add a mutex lock to the FuncDB. If the size is exhausted, info is discarded. + */ +static inline void dbgFuncDBAddMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut, int lockLn) +{ + dbgFuncDBmutInfoEntry_t *pMutInfo; + + if((pMutInfo = dbgFuncDBFindFreeMutexInfo(pFuncDB)) != NULL) { + pMutInfo->pmut = pmut; + pMutInfo->lockLn = lockLn; + pMutInfo->lInvocation = pFuncDB->nTimesCalled; + pMutInfo->thrd = pthread_self(); + } +} + +/* remove a locked mutex from the FuncDB (unlock case!). + */ +static inline void dbgFuncDBRemoveMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) +{ + dbgFuncDBmutInfoEntry_t *pMutInfo; + + if((pMutInfo = dbgFuncDBGetMutexInfo(pFuncDB, pmut)) != NULL) { + pMutInfo->lockLn = -1; + } +} + + +/* ------------------------- END FuncDB utility functions ------------------------- */ + +/* output the current thread ID to "relevant" places + * (what "relevant" means is determinded by various ways) + */ +void +dbgOutputTID(char* name) +{ +# if defined(HAVE_SYSCALL) && defined(HAVE_SYS_gettid) + if(bOutputTidToStderr) + fprintf(stderr, "thread tid %u, name '%s'\n", + (unsigned)syscall(SYS_gettid), name); + DBGPRINTF("thread created, tid %u, name '%s'\n", + (unsigned)syscall(SYS_gettid), name); +# endif +} + +/* ########################################################################### + * IMPORTANT NOTE + * Mutex instrumentation reduces the code's concurrency and thus affects its + * order of execution. It is vital to test the code also with mutex + * instrumentation turned off! Some bugs may not show up while it on... + * ########################################################################### + */ + +/* constructor & add new entry to list + */ +dbgMutLog_t *dbgMutLogAddEntry(pthread_mutex_t *pmut, short mutexOp, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pLog = calloc(1, sizeof(dbgMutLog_t)); + assert(pLog != NULL); + + /* fill data members */ + pLog->mut = pmut; + pLog->thrd = pthread_self(); + pLog->mutexOp = mutexOp; + pLog->lockLn = lockLn; + pLog->pFuncDB = pFuncDB; + + DLL_Add(MutLog, pLog); + + return pLog; +} + + +/* destruct log entry + */ +void dbgMutLogDelEntry(dbgMutLog_t *pLog) +{ + assert(pLog != NULL); + DLL_Del(MutLog, pLog); +} + + +/* print a single mutex log entry */ +static void dbgMutLogPrintOne(dbgMutLog_t *pLog) +{ + char *strmutop; + char buf[64]; + char pszThrdName[64]; + + assert(pLog != NULL); + switch(pLog->mutexOp) { + case MUTOP_LOCKWAIT: + strmutop = "waited on"; + break; + case MUTOP_LOCK: + strmutop = "owned"; + break; + default: + snprintf(buf, sizeof(buf)/sizeof(char), "unknown state %d - should not happen!", pLog->mutexOp); + strmutop = buf; + break; + } + + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pLog->thrd, 1); + dbgprintf("mutex 0x%lx is being %s by code at %s:%d, thread %s\n", (unsigned long) pLog->mut, + strmutop, pLog->pFuncDB->file, + (pLog->mutexOp == MUTOP_LOCK) ? pLog->lockLn : pLog->pFuncDB->line, + pszThrdName); +} + +/* print the complete mutex log */ +static void dbgMutLogPrintAll(void) +{ + dbgMutLog_t *pLog; + + dbgprintf("Mutex log for all known mutex operations:\n"); + for(pLog = dbgMutLogListRoot ; pLog != NULL ; pLog = pLog->pNext) + dbgMutLogPrintOne(pLog); + +} + + +/* find the last log entry for that specific mutex object. Is used to delete + * a thread's own requests. Searches occur from the back. + * The pFuncDB is optional and may be NULL to indicate no specific funciont is + * reqested (aka "it is ignored" ;)). This is important for the unlock case. + */ +dbgMutLog_t *dbgMutLogFindSpecific(pthread_mutex_t *pmut, short mutop, dbgFuncDB_t *pFuncDB) +{ + dbgMutLog_t *pLog; + pthread_t mythrd = pthread_self(); + + pLog = dbgMutLogListLast; + while(pLog != NULL) { + if( pLog->mut == pmut && pLog->thrd == mythrd && pLog->mutexOp == mutop + && (pFuncDB == NULL || pLog->pFuncDB == pFuncDB)) + break; + pLog = pLog->pPrev; + } + + return pLog; +} + + +/* find mutex object from the back of the list */ +dbgMutLog_t *dbgMutLogFindFromBack(pthread_mutex_t *pmut, dbgMutLog_t *pLast) +{ + dbgMutLog_t *pLog; + + if(pLast == NULL) + pLog = dbgMutLogListLast; + else + pLog = pLast->pPrev; /* if we get the last processed one, we need to go one before it, else its an endless loop */ + + while(pLog != NULL) { + if(pLog->mut == pmut) { + break; + } + pLog = pLog->pPrev; + } + + return pLog; +} + + +/* find lock aquire for mutex from back of list */ +dbgMutLog_t *dbgMutLogFindHolder(pthread_mutex_t *pmut) +{ + dbgMutLog_t *pLog; + + pLog = dbgMutLogFindFromBack(pmut, NULL); + while(pLog != NULL) { + if(pLog->mutexOp == MUTOP_LOCK) + break; + pLog = dbgMutLogFindFromBack(pmut, pLog); + } + + return pLog; +} + +/* report wait on a mutex and add it to the mutex log */ +static inline void dbgMutexPreLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln) +{ + dbgMutLog_t *pHolder; + char pszBuf[128]; + char pszHolderThrdName[64]; + char *pszHolder; + + pthread_mutex_lock(&mutMutLog); + pHolder = dbgMutLogFindHolder(pmut); + dbgMutLogAddEntry(pmut, MUTOP_LOCKWAIT, pFuncDB, ln); + + if(pHolder == NULL) + pszHolder = "[NONE]"; + else { + dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1); + snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName); + pszHolder = pszBuf; + } + + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p waiting on lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder); + pthread_mutex_unlock(&mutMutLog); +} + + +/* report aquired mutex */ +static inline void dbgMutexLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + + /* find and delete "waiting" entry */ + pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCKWAIT, pFuncDB); + assert(pLog != NULL); + dbgMutLogDelEntry(pLog); + + /* add "lock" entry */ + dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn); + dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn); + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut); +} + + +/* report trylock on a mutex and add it to the mutex log */ +static inline void dbgMutexPreTryLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln) +{ + dbgMutLog_t *pHolder; + char pszBuf[128]; + char pszHolderThrdName[64]; + char *pszHolder; + + pthread_mutex_lock(&mutMutLog); + pHolder = dbgMutLogFindHolder(pmut); + dbgMutLogAddEntry(pmut, MUTOP_TRYLOCK, pFuncDB, ln); + + if(pHolder == NULL) + pszHolder = "[NONE]"; + else { + dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1); + snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName); + pszHolder = pszBuf; + } + + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p trying to get lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder); + pthread_mutex_unlock(&mutMutLog); +} + + +/* report attempted mutex lock */ +static inline void dbgMutexTryLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + + /* find and delete "trylock" entry */ + pLog = dbgMutLogFindSpecific(pmut, MUTOP_TRYLOCK, pFuncDB); + assert(pLog != NULL); + dbgMutLogDelEntry(pLog); + + /* add "lock" entry */ + dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn); + dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn); + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut); +} + + +/* if we unlock, we just remove the lock aquired entry from the log list */ +static inline void dbgMutexUnlockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int unlockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCK, NULL); +#if 0 /* toggle for testing */ + assert(pLog != NULL); +#else +/* the change below seems not to work - the problem seems to be a real race... I keep this code in just in case + * I need to re-use it. It should be removed once we are finished analyzing this problem. -- rgerhards, 2008-09-17 + */ +if(pLog == NULL) { + /* this may happen due to some races. We do not try to avoid + * this, as it would complicate the "real" code. This is not justified + * just to keep the debug info system up. -- rgerhards, 2008-09-17 + */ + pthread_mutex_unlock(&mutMutLog); + dbgprintf("%s:%d:%s: mutex %p UNlocked [but we did not yet know this mutex!]\n", + pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut); + return; /* if we don't know it yet, we can not clean up... */ +} +#endif +#include <sys/syscall.h> + + /* we found the last lock entry. We now need to see from which FuncDB we need to + * remove it. This is recorded inside the mutex log entry. + */ + dbgFuncDBRemoveMutexLock(pLog->pFuncDB, pmut); + + /* donw with the log entry, get rid of it... */ + dbgMutLogDelEntry(pLog); + + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p UNlocked\n", pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut); +} + + +/* wrapper for pthread_mutex_lock() */ +int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); + ret = pthread_mutex_lock(pmut); + if(ret == 0) { + dbgMutexLockLog(pmut, pFuncDB, ln); + } else { + dbgprintf("%s:%d:%s: ERROR: pthread_mutex_lock() for mutex %p failed with error %d\n", + pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret); + } + + return ret; +} + + +/* wrapper for pthread_mutex_trylock() */ +int dbgMutexTryLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); // TODO : update this + ret = pthread_mutex_trylock(pmut); + if(ret == 0 || ret == EBUSY) { + // TODO : update this + dbgMutexLockLog(pmut, pFuncDB, ln); + } else { + dbgprintf("%s:%d:%s: ERROR: pthread_mutex_trylock() for mutex %p failed with error %d\n", + pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret); + } + + return ret; +} + + +/* wrapper for pthread_mutex_unlock() */ +int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + ret = pthread_mutex_unlock(pmut); + return ret; +} + + +/* wrapper for pthread_cond_wait() */ +int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + if(bPrintMutexAction) { + dbgprintf("%s:%d:%s: mutex %p waiting on condition %p\n", pFuncDB->file, pFuncDB->line, + pFuncDB->func, (void*)pmut, (void*)cond); + } + dbgMutexPreLockLog(pmut, pFuncDB, ln); + ret = pthread_cond_wait(cond, pmut); + return ret; +} + + +/* wrapper for pthread_cond_timedwait() */ +int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); + if(bPrintMutexAction) { + dbgprintf("%s:%d:%s: mutex %p waiting on condition %p (with timeout)\n", pFuncDB->file, + pFuncDB->line, pFuncDB->func, (void*)pmut, (void*)cond); + } + ret = pthread_cond_timedwait(cond, pmut, abstime); + dbgMutexLockLog(pmut, pFuncDB, ln); + return ret; +} + + +/* ------------------------- end mutex tracking code ------------------------- */ + + +/* ------------------------- malloc/free tracking code ------------------------- */ + +/* wrapper for free() */ +void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + dbgRecordExecLocation(iStackPtr, ln); + if(bLogAllocFree) { + dbgprintf("%s:%d:%s: free %p\n", pFuncDB->file, ln, pFuncDB->func, (void*) pMem); + } + free(pMem); +} + + +/* ------------------------- end malloc/free tracking code ------------------------- */ + +/* ------------------------- thread tracking code ------------------------- */ + +/* get ptr to call stack - if none exists, create a new stack + */ +static dbgThrdInfo_t *dbgGetThrdInfo(void) +{ + dbgThrdInfo_t *pThrd; + + pthread_mutex_lock(&mutCallStack); + if((pThrd = pthread_getspecific(keyCallStack)) == NULL) { + /* construct object */ + pThrd = calloc(1, sizeof(dbgThrdInfo_t)); + pThrd->thrd = pthread_self(); + (void) pthread_setspecific(keyCallStack, pThrd); + DLL_Add(CallStack, pThrd); + } + pthread_mutex_unlock(&mutCallStack); + return pThrd; +} + + + +/* find a specific thread ID. It must be present, else something is wrong + */ +static inline dbgThrdInfo_t *dbgFindThrd(pthread_t thrd) +{ + dbgThrdInfo_t *pThrd; + + for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { + if(pThrd->thrd == thrd) + break; + } + return pThrd; +} + + +/* build a string with the thread name. If none is set, the thread ID is + * used instead. Caller must provide buffer space. If bIncludeNumID is set + * to 1, the numerical ID is always included. + * rgerhards 2008-01-23 + */ +static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID) +{ + dbgThrdInfo_t *pThrd; + + assert(pszBuf != NULL); + + pThrd = dbgFindThrd(thrd); + + if(pThrd == 0 || pThrd->pszThrdName == NULL) { + /* no thread name, use numeric value */ + snprintf(pszBuf, lenBuf, "%lx", (long) thrd); + } else { + if(bIncludeNumID) { + snprintf(pszBuf, lenBuf, "%s (%lx)", pThrd->pszThrdName, (long) thrd); + } else { + snprintf(pszBuf, lenBuf, "%s", pThrd->pszThrdName); + } + } + +} + + +/* set a name for the current thread. The caller provided string is duplicated. + */ +void dbgSetThrdName(uchar *pszName) +{ +return; + + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + if(pThrd->pszThrdName != NULL) + free(pThrd->pszThrdName); + pThrd->pszThrdName = strdup((char*)pszName); +} + + +/* destructor for a call stack object */ +static void dbgCallStackDestruct(void *arg) +{ + dbgThrdInfo_t *pThrd = (dbgThrdInfo_t*) arg; + + dbgprintf("destructor for debug call stack %p called\n", pThrd); + if(pThrd->pszThrdName != NULL) { + free(pThrd->pszThrdName); + } + + pthread_mutex_lock(&mutCallStack); + DLL_Del(CallStack, pThrd); + pthread_mutex_unlock(&mutCallStack); +} + + +/* print a thread's call stack + */ +static void dbgCallStackPrint(dbgThrdInfo_t *pThrd) +{ + int i; + char pszThrdName[64]; + + pthread_mutex_lock(&mutCallStack); + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pThrd->thrd, 1); + dbgprintf("\n"); + dbgprintf("Recorded Call Order for Thread '%s':\n", pszThrdName); + for(i = 0 ; i < pThrd->stackPtr ; i++) { + dbgprintf("%d: %s:%d:%s:\n", i, pThrd->callStack[i]->file, pThrd->lastLine[i], pThrd->callStack[i]->func); + } + dbgprintf("maximum number of nested calls for this thread: %d.\n", pThrd->stackPtrMax); + dbgprintf("NOTE: not all calls may have been recorded, code does not currently guarantee that!\n"); + pthread_mutex_unlock(&mutCallStack); +} + +/* print all threads call stacks + */ +void dbgCallStackPrintAll(void) +{ + dbgThrdInfo_t *pThrd; + /* stack info */ + for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { + dbgCallStackPrint(pThrd); + } +} + + +/* handler for SIGSEGV - MUST terminiate the app, but does so in a somewhat + * more meaningful way. + * rgerhards, 2008-01-22 + */ +void +sigsegvHdlr(int signum) +{ + char *signame; + struct sigaction sigAct; + + /* first, restore the default abort handler */ + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + sigaction(SIGABRT, &sigAct, NULL); + + /* then do our actual processing */ + if(signum == SIGSEGV) { + signame = " (SIGSEGV)"; + } else if(signum == SIGABRT) { + signame = " (SIGABRT)"; + } else { + signame = ""; + } + + dbgprintf("\n\n\n\nSignal %d%s occured, execution must be terminated.\n\n\n\n", signum, signame); + + if(bAbortTrace) { + dbgPrintAllDebugInfo(); + dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n"); + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + } + + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + + /* and finally abort... */ + /* TODO: think about restarting rsyslog in this case: may be a good idea, + * but may also be a very bad one (restart loops!) + */ + abort(); +} + +/* actually write the debug message. This is a separate fuction because the cleanup_push/_pop + * interface otherwise is unsafe to use (generates compiler warnings at least). + * 2009-05-20 rgerhards + */ +static inline void +do_dbgprint(uchar *pszObjName, char *pszMsg, size_t lenMsg) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + char pszWriteBuf[32*1024]; + size_t lenCopy; + size_t offsWriteBuf = 0; + size_t lenWriteBuf; + struct timespec t; +# if _POSIX_TIMERS <= 0 + struct timeval tv; +# endif + +#if 1 + /* The bWasNL handler does not really work. It works if no thread + * switching occurs during non-NL messages. Else, things are messed + * up. Anyhow, it works well enough to provide useful help during + * getting this up and running. It is questionable if the extra effort + * is worth fixing it, giving the limited appliability. -- rgerhards, 2005-10-25 + * I have decided that it is not worth fixing it - especially as it works + * pretty well. -- rgerhards, 2007-06-15 + */ + if(ptLastThrdID != pthread_self()) { + if(!bWasNL) { + pszWriteBuf[0] = '\n'; + offsWriteBuf = 1; + bWasNL = 1; + } + ptLastThrdID = pthread_self(); + } + + /* do not cache the thread name, as the caller might have changed it + * TODO: optimized, invalidate cache when new name is set + */ + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); + + if(bWasNL) { + if(bPrintTime) { +# if _POSIX_TIMERS > 0 + /* this is the "regular" code */ + clock_gettime(CLOCK_REALTIME, &t); +# else + gettimeofday(&tv, NULL); + t.tv_sec = tv.tv_sec; + t.tv_nsec = tv.tv_usec * 1000; +# endif + lenWriteBuf = snprintf(pszWriteBuf+offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, + "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + offsWriteBuf += lenWriteBuf; + } + + lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszThrdName); + offsWriteBuf += lenWriteBuf; + /* print object name header if we have an object */ + if(pszObjName != NULL) { + lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszObjName); + offsWriteBuf += lenWriteBuf; + } + } +#endif + if(lenMsg > sizeof(pszWriteBuf) - offsWriteBuf) + lenCopy = sizeof(pszWriteBuf) - offsWriteBuf; + else + lenCopy = lenMsg; + memcpy(pszWriteBuf + offsWriteBuf, pszMsg, lenCopy); + offsWriteBuf += lenCopy; + /* the write is included in an "if" just to silence compiler + * warnings. Here, we really don't care if the write fails, we + * have no good response to that in any case... -- rgerhards, 2012-11-28 + */ + if(stddbg != -1) if(write(stddbg, pszWriteBuf, offsWriteBuf)){}; + if(altdbg != -1) if(write(altdbg, pszWriteBuf, offsWriteBuf)){}; + + bWasNL = (pszMsg[lenMsg - 1] == '\n') ? 1 : 0; +} + +#pragma GCC diagnostic ignored "-Wempty-body" +/* write the debug message. This is a helper to dbgprintf and dbgoprint which + * contains common code. added 2008-09-26 rgerhards + */ +static void +dbgprint(obj_t *pObj, char *pszMsg, size_t lenMsg) +{ + uchar *pszObjName = NULL; + + /* we must get the object name before we lock the mutex, because the object + * potentially calls back into us. If we locked the mutex, we would deadlock + * ourselfs. On the other hand, the GetName call needs not to be protected, as + * this thread has a valid reference. If such an object is deleted by another + * thread, we are in much more trouble than just for dbgprint(). -- rgerhards, 2008-09-26 + */ + if(pObj != NULL) { + pszObjName = obj.GetName(pObj); + } + + pthread_mutex_lock(&mutdbgprint); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprint); + + do_dbgprint(pszObjName, pszMsg, lenMsg); + + pthread_cleanup_pop(1); +} +#pragma GCC diagnostic warning "-Wempty-body" + +/* print some debug output when an object is given + * This is mostly a copy of dbgprintf, but I do not know how to combine it + * into a single function as we have variable arguments and I don't know how to call + * from one vararg function into another. I don't dig in this, it is OK for the + * time being. -- rgerhards, 2008-01-29 + */ +void +dbgoprint(obj_t *pObj, char *fmt, ...) +{ + va_list ap; + char pszWriteBuf[32*1024]; + size_t lenWriteBuf; + + if(!(Debug && debugging_on)) + return; + + /* a quick and very dirty hack to enable us to display just from those objects + * that we are interested in. So far, this must be changed at compile time (and + * chances are great it is commented out while you read it. Later, this shall + * be selectable via the environment. -- rgerhards, 2008-02-20 + */ +#if 0 + if(objGetObjID(pObj) != OBJexpr) + return; +#endif + + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + va_end(ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* prevent buffer overrruns and garbagge display */ + pszWriteBuf[sizeof(pszWriteBuf) - 5] = '.'; + pszWriteBuf[sizeof(pszWriteBuf) - 4] = '.'; + pszWriteBuf[sizeof(pszWriteBuf) - 3] = '.'; + pszWriteBuf[sizeof(pszWriteBuf) - 2] = '\n'; + pszWriteBuf[sizeof(pszWriteBuf) - 1] = '\0'; + lenWriteBuf = sizeof(pszWriteBuf); + } + dbgprint(pObj, pszWriteBuf, lenWriteBuf); +} + + +/* print some debug output when no object is given + * WARNING: duplicate code, see dbgoprin above! + */ +void +dbgprintf(char *fmt, ...) +{ + va_list ap; + char pszWriteBuf[32*1024]; + size_t lenWriteBuf; + + if(!(Debug && debugging_on)) + return; + + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + va_end(ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* prevent buffer overrruns and garbagge display */ + pszWriteBuf[sizeof(pszWriteBuf) - 5] = '.'; + pszWriteBuf[sizeof(pszWriteBuf) - 4] = '.'; + pszWriteBuf[sizeof(pszWriteBuf) - 3] = '.'; + pszWriteBuf[sizeof(pszWriteBuf) - 2] = '\n'; + pszWriteBuf[sizeof(pszWriteBuf) - 1] = '\0'; + lenWriteBuf = sizeof(pszWriteBuf); + } + dbgprint(NULL, pszWriteBuf, lenWriteBuf); +} + +void tester(void) +{ +BEGINfunc +ENDfunc +} + +/* handler called when a function is entered. This function creates a new + * funcDB on the heap if the passed-in pointer is NULL. + */ +int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line) +{ + int iStackPtr = 0; /* TODO: find some better default, this one hurts the least, but it is not clean */ + dbgThrdInfo_t *pThrd; + dbgFuncDBListEntry_t *pFuncDBListEntry; + unsigned int i; + dbgFuncDB_t *pFuncDB; + + assert(ppFuncDB != NULL); + assert(file != NULL); + assert(func != NULL); + pFuncDB = *ppFuncDB; + assert((pFuncDB == NULL) || (pFuncDB->magic == dbgFUNCDB_MAGIC)); + + pThrd = dbgGetThrdInfo(); /* we must do this AFTER the mutexes are initialized! */ + + if(pFuncDB == NULL) { + /* we do not yet have a funcDB and need to create a new one. We also add it + * to the linked list of funcDBs. Please note that when a module is unloaded and + * then reloaded again, we currently do not try to find its previous funcDB but + * instead create a duplicate. While finding the past one is straightforward, it + * opens up the question what to do with e.g. mutex data left in it. We do not + * yet see any need to handle these questions, so duplicaton seems to be the right + * thing to do. -- rgerhards, 2008-03-10 + */ + /* dbgprintf("%s:%d:%s: called first time, initializing FuncDB\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); */ + /* get a new funcDB and add it to the list (all of this is protected by the mutex) */ + pthread_mutex_lock(&mutFuncDBList); + if((pFuncDBListEntry = calloc(1, sizeof(dbgFuncDBListEntry_t))) == NULL) { + dbgprintf("Error %d allocating memory for FuncDB List entry, not adding\n", errno); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } else { + if((pFuncDB = calloc(1, sizeof(dbgFuncDB_t))) == NULL) { + dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); + free(pFuncDBListEntry); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } else { + pFuncDBListEntry->pFuncDB = pFuncDB; + pFuncDBListEntry->pNext = pFuncDBListRoot; + pFuncDBListRoot = pFuncDBListEntry; + } + } + /* now intialize the funcDB + * note that we duplicate the strings, because the address provided may go away + * if a loadable module is unloaded! + */ + pFuncDB->magic = dbgFUNCDB_MAGIC; + pFuncDB->file = strdup(file); + pFuncDB->func = strdup(func); + pFuncDB->line = line; + pFuncDB->nTimesCalled = 0; + for(i = 0 ; i < sizeof(pFuncDB->mutInfo)/sizeof(dbgFuncDBmutInfoEntry_t) ; ++i) { + pFuncDB->mutInfo[i].lockLn = -1; /* set to not Locked */ + } + + /* a round of safety checks... */ + if(pFuncDB->file == NULL || pFuncDB->func == NULL) { + dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); + /* do a little bit of cleanup */ + if(pFuncDB->file != NULL) + free(pFuncDB->file); + if(pFuncDB->func != NULL) + free(pFuncDB->func); + free(pFuncDB); + free(pFuncDBListEntry); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } + + /* done mutex-protected operations */ + pthread_mutex_unlock(&mutFuncDBList); + + *ppFuncDB = pFuncDB; /* all went well, so we can update the caller */ + } + + /* when we reach this point, we have a fully-initialized FuncDB! */ + PREFER_ATOMIC_INC(pFuncDB->nTimesCalled); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) + if(strcmp(pFuncDB->file, "stringbuf.c")) { /* TODO: make configurable */ + dbgprintf("%s:%d: %s: enter\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); + } + if(pThrd->stackPtr >= (int) (sizeof(pThrd->callStack) / sizeof(dbgFuncDB_t*))) { + dbgprintf("%s:%d: %s: debug module: call stack for this thread full, suspending call tracking\n", + pFuncDB->file, pFuncDB->line, pFuncDB->func); + iStackPtr = pThrd->stackPtr; + } else { + iStackPtr = pThrd->stackPtr++; + if(pThrd->stackPtr > pThrd->stackPtrMax) + pThrd->stackPtrMax = pThrd->stackPtr; + pThrd->callStack[iStackPtr] = pFuncDB; + pThrd->lastLine[iStackPtr] = line; + } + +exit_it: + return iStackPtr; +} + + +/* handler called when a function is exited + */ +void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + + assert(iStackPtrRestore >= 0); + assert(pFuncDB != NULL); + assert(pFuncDB->magic == dbgFUNCDB_MAGIC); + + dbgFuncDBPrintActiveMutexes(pFuncDB, "WARNING: mutex still owned by us as we exit function, mutex: ", pthread_self()); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) { + if(strcmp(pFuncDB->file, "stringbuf.c")) { /* TODO: make configurable */ + if(iRet == RS_RET_NO_IRET) + dbgprintf("%s:%d: %s: exit: (no iRet)\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); + else + dbgprintf("%s:%d: %s: exit: %d\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, iRet); + } + } + pThrd->stackPtr = iStackPtrRestore; + if(pThrd->stackPtr < 0) { + dbgprintf("Stack pointer for thread %lx below 0 - resetting (some RETiRet still wrong!)\n", (long) pthread_self()); + pThrd->stackPtr = 0; + } +} + + +/* externally-callable handler to record the last exec location. We use a different function + * so that the internal one can be inline. + */ +void +dbgSetExecLocation(int iStackPtr, int line) +{ + dbgRecordExecLocation(iStackPtr, line); +} + + +void dbgPrintAllDebugInfo(void) +{ + dbgCallStackPrintAll(); + dbgMutLogPrintAll(); + if(bPrintFuncDBOnExit) + dbgFuncDBPrintAll(); +} + + +/* Handler for SIGUSR2. Dumps all available debug output + */ +static void sigusr2Hdlr(int __attribute__((unused)) signum) +{ + dbgprintf("SIGUSR2 received, dumping debug information\n"); + dbgPrintAllDebugInfo(); +} + +/* support system to set debug options at runtime */ + + +/* parse a param/value pair from the current location of the + * option string. Returns 1 if an option was found, 0 + * otherwise. 0 means there are NO MORE options to be + * processed. -- rgerhards, 2008-02-28 + */ +static int +dbgGetRTOptNamVal(uchar **ppszOpt, uchar **ppOptName, uchar **ppOptVal) +{ + int bRet = 0; + uchar *p; + size_t i; + static uchar optname[128]; /* not thread- or reentrant-safe, but that */ + static uchar optval[1024]; /* doesn't matter (called only once at startup) */ + + assert(ppszOpt != NULL); + assert(*ppszOpt != NULL); + + /* make sure we have some initial values */ + optname[0] = '\0'; + optval[0] = '\0'; + + p = *ppszOpt; + /* skip whitespace */ + while(*p && isspace(*p)) + ++p; + + /* name - up until '=' or whitespace */ + i = 0; + while(i < (sizeof(optname)/sizeof(uchar) - 1) && *p && *p != '=' && !isspace(*p)) { + optname[i++] = *p++; + } + + if(i > 0) { + bRet = 1; + optname[i] = '\0'; + if(*p == '=') { + /* we have a value, get it */ + ++p; + i = 0; + while(i < (sizeof(optval)/sizeof(uchar) - 1) && *p && !isspace(*p)) { + optval[i++] = *p++; + } + optval[i] = '\0'; + } + } + + /* done */ + *ppszOpt = p; + *ppOptName = optname; + *ppOptVal = optval; + return bRet; +} + + +/* create new PrintName list entry and add it to list (they will never + * be removed. -- rgerhards, 2008-02-28 + */ +static void +dbgPrintNameAdd(uchar *pName, dbgPrintName_t **ppRoot) +{ + dbgPrintName_t *pEntry; + + if((pEntry = calloc(1, sizeof(dbgPrintName_t))) == NULL) { + fprintf(stderr, "ERROR: out of memory during debug setup\n"); + exit(1); + } + + if((pEntry->pName = (uchar*) strdup((char*) pName)) == NULL) { + fprintf(stderr, "ERROR: out of memory during debug setup\n"); + exit(1); + } + + if(*ppRoot != NULL) { + pEntry->pNext = *ppRoot; /* we enqueue at the front */ + } + *ppRoot = pEntry; +} + + +/* check if name is in a printName list - returns 1 if so, 0 otherwise. + * There is one special handling: if the root pointer is NULL, the function + * always returns 1. This is because when no name is set, output shall be + * unrestricted. + * rgerhards, 2008-02-28 + */ +static int +dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot) +{ + int bFound = 0; + dbgPrintName_t *pEntry = pRoot; + + if(pRoot == NULL) + bFound = 1; + + while(pEntry != NULL && !bFound) { + if(!strcasecmp((char*)pEntry->pName, (char*)pName)) { + bFound = 1; + } else { + pEntry = pEntry->pNext; + } + } + + return bFound; +} + + +/* this is a special version of malloc that fills the alloced memory with + * HIGHVALUE, as this helps to identify bugs. -- rgerhards, 2009-10-22 + */ +void * +dbgmalloc(size_t size) +{ + void *pRet; + pRet = malloc(size); + if(pRet != NULL) + memset(pRet, 0xff, size); + return pRet; +} + + +/* report fd used for debug log. This is needed in case of + * auto-backgrounding, where the debug log shall not be closed. + */ +int +dbgGetDbglogFd(void) +{ + return altdbg; +} + +/* read in the runtime options + * rgerhards, 2008-02-28 + */ +static void +dbgGetRuntimeOptions(void) +{ + uchar *pszOpts; + uchar *optval; + uchar *optname; + + /* set some defaults */ + if((pszOpts = (uchar*) getenv("RSYSLOG_DEBUG")) != NULL) { + /* we have options set, so let's process them */ + while(dbgGetRTOptNamVal(&pszOpts, &optname, &optval)) { + if(!strcasecmp((char*)optname, "help")) { + fprintf(stderr, + "rsyslogd " VERSION " runtime debug support - help requested, rsyslog terminates\n\n" + "environment variables:\n" + "addional logfile: export RSYSLOG_DEBUGFILE=\"/path/to/file\"\n" + "to set: export RSYSLOG_DEBUG=\"cmd cmd cmd\"\n\n" + "Commands are (all case-insensitive):\n" + "help (this list, terminates rsyslogd\n" + "LogFuncFlow\n" + "LogAllocFree (very partly implemented)\n" + "PrintFuncDB\n" + "PrintMutexAction\n" + "PrintAllDebugInfoOnExit (not yet implemented)\n" + "NoLogTimestamp\n" + "Nostdoout\n" + "OutputTidToStderr\n" + "filetrace=file (may be provided multiple times)\n" + "DebugOnDemand - enables debugging on USR1, but does not turn on output\n" + "\nSee debug.html in your doc set or http://www.rsyslog.com for details\n"); + exit(1); + } else if(!strcasecmp((char*)optname, "debug")) { + /* this is earlier in the process than the -d option, as such it + * allows us to spit out debug messages from the very beginning. + */ + Debug = DEBUG_FULL; + debugging_on = 1; + } else if(!strcasecmp((char*)optname, "debugondemand")) { + /* Enables debugging, but turns off debug output */ + Debug = DEBUG_ONDEMAND; + debugging_on = 1; + dbgprintf("Note: debug on demand turned on via configuraton file, " + "use USR1 signal to activate.\n"); + debugging_on = 0; + } else if(!strcasecmp((char*)optname, "logfuncflow")) { + bLogFuncFlow = 1; + } else if(!strcasecmp((char*)optname, "logallocfree")) { + bLogAllocFree = 1; + } else if(!strcasecmp((char*)optname, "printfuncdb")) { + bPrintFuncDBOnExit = 1; + } else if(!strcasecmp((char*)optname, "printmutexaction")) { + bPrintMutexAction = 1; + } else if(!strcasecmp((char*)optname, "printalldebuginfoonexit")) { + bPrintAllDebugOnExit = 1; + } else if(!strcasecmp((char*)optname, "nologtimestamp")) { + bPrintTime = 0; + } else if(!strcasecmp((char*)optname, "nostdout")) { + stddbg = -1; + } else if(!strcasecmp((char*)optname, "noaborttrace")) { + bAbortTrace = 0; + } else if(!strcasecmp((char*)optname, "outputtidtostderr")) { + bOutputTidToStderr = 1; + } else if(!strcasecmp((char*)optname, "filetrace")) { + if(*optval == '\0') { + fprintf(stderr, "rsyslogd " VERSION " error: logfile debug option requires filename, " + "e.g. \"logfile=debug.c\"\n"); + exit(1); + } else { + /* create new entry and add it to list */ + dbgPrintNameAdd(optval, &printNameFileRoot); + } + } else { + fprintf(stderr, "rsyslogd " VERSION " error: invalid debug option '%s', value '%s' - ignored\n", + optval, optname); + } + } + } +} + + +void +dbgSetDebugLevel(int level) +{ + Debug = level; + debugging_on = (level == DEBUG_FULL) ? 1 : 0; +} + +void +dbgSetDebugFile(uchar *fn) +{ + if(altdbg != -1) { + dbgprintf("switching to debug file %s\n", fn); + close(altdbg); + } + if((altdbg = open((char*)fn, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, S_IRUSR|S_IWUSR)) == -1) { + fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno)); + } +} + +/* end support system to set debug options at runtime */ + +rsRetVal dbgClassInit(void) +{ + pthread_mutexattr_t mutAttr; + rsRetVal iRet; /* do not use DEFiRet, as this makes calls into the debug system! */ + + struct sigaction sigAct; + sigset_t sigSet; + + (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */ + + /* the mutexes must be recursive, because it may be called from within + * signal handlers, which can lead to a hang if the signal interrupted dbgprintf + * (yes, we have really seen that situation in practice!). -- rgerhards, 2013-05-17 + */ + pthread_mutexattr_init(&mutAttr); + pthread_mutexattr_settype(&mutAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutFuncDBList, &mutAttr); + pthread_mutex_init(&mutMutLog, &mutAttr); + pthread_mutex_init(&mutCallStack, &mutAttr); + pthread_mutex_init(&mutdbgprint, &mutAttr); + + /* while we try not to use any of the real rsyslog code (to avoid infinite loops), we + * need to have the ability to query object names. Thus, we need to obtain a pointer to + * the object interface. -- rgerhards, 2008-02-29 + */ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sigusr2Hdlr; + sigaction(SIGUSR2, &sigAct, NULL); + + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGUSR2); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + + dbgGetRuntimeOptions(); /* init debug system from environment */ + pszAltDbgFileName = getenv("RSYSLOG_DEBUGLOG"); + + if(pszAltDbgFileName != NULL) { + /* we have a secondary file, so let's open it) */ + if((altdbg = open(pszAltDbgFileName, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, S_IRUSR|S_IWUSR)) == -1) { + fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno)); + } + } + + dbgSetThrdName((uchar*)"main thread"); + +finalize_it: + return(iRet); +} + + +rsRetVal dbgClassExit(void) +{ + dbgFuncDBListEntry_t *pFuncDBListEtry, *pToDel; + pthread_key_delete(keyCallStack); + + if(bPrintAllDebugOnExit) + dbgPrintAllDebugInfo(); + + if(altdbg != -1) + close(altdbg); + + /* now free all of our memory to make the memory debugger happy... */ + pFuncDBListEtry = pFuncDBListRoot; + while(pFuncDBListEtry != NULL) { + pToDel = pFuncDBListEtry; + pFuncDBListEtry = pFuncDBListEtry->pNext; + free(pToDel->pFuncDB->file); + free(pToDel->pFuncDB->func); + free(pToDel->pFuncDB); + free(pToDel); + } + + return RS_RET_OK; +} +/* vi:set ai: + */ diff --git a/runtime/debug.h b/runtime/debug.h new file mode 100644 index 00000000..f3226098 --- /dev/null +++ b/runtime/debug.h @@ -0,0 +1,172 @@ +/* debug.h + * + * Definitions for the debug and run-time analysis support module. + * Contains a lot of macros. + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef DEBUG_H_INCLUDED +#define DEBUG_H_INCLUDED + +#include <pthread.h> +#include "obj-types.h" + +/* some settings for various debug modes */ +#define DEBUG_OFF 0 +#define DEBUG_ONDEMAND 1 +#define DEBUG_FULL 2 + +/* external static data elements (some time to be replaced) */ +extern int Debug; /* debug flag - read-only after startup */ +extern int debugging_on; /* read-only, except on sig USR1 */ +extern int stddbg; /* the handle for regular debug output, set to stdout if not forking, -1 otherwise */ + +/* data types */ + +/* the function database. It is used as a static var inside each function. That provides + * us the fast access to it that we need to make the instrumentation work. It's address + * also serves as a unique function identifier and can be used inside other structures + * to refer to the function (e.g. for pretty-printing names). + * rgerhards, 2008-01-24 + */ +typedef struct dbgFuncDBmutInfoEntry_s { + pthread_mutex_t *pmut; + int lockLn; /* line where it was locked (inside our func): -1 means mutex is not locked */ + pthread_t thrd; /* thrd where the mutex was locked */ + unsigned long lInvocation; /* invocation (unique during program run!) of this function that locked the mutex */ +} dbgFuncDBmutInfoEntry_t; +typedef struct dbgFuncDB_s { + unsigned magic; + unsigned long nTimesCalled; + char *func; + char *file; + int line; + dbgFuncDBmutInfoEntry_t mutInfo[5]; + /* remember to update the initializer if you add anything or change the order! */ +} dbgFuncDB_t; +#define dbgFUNCDB_MAGIC 0xA1B2C3D4 +#define dbgFuncDB_t_INITIALIZER \ + { \ + .magic = dbgFUNCDB_MAGIC,\ + .nTimesCalled = 0,\ + .func = __func__, \ + .file = __FILE__, \ + .line = __LINE__ \ + } + +/* the structure below was originally just the thread's call stack, but it has + * a bit evolved over time. So we have now ended up with the fact that it + * all debug info we know about the thread. + */ +typedef struct dbgCallStack_s { + pthread_t thrd; + dbgFuncDB_t *callStack[500]; + int lastLine[500]; /* last line where code execution was seen */ + int stackPtr; + int stackPtrMax; + char *pszThrdName; + struct dbgCallStack_s *pNext; + struct dbgCallStack_s *pPrev; +} dbgThrdInfo_t; + + +/* prototypes */ +rsRetVal dbgClassInit(void); +rsRetVal dbgClassExit(void); +void dbgSetDebugFile(uchar *fn); +void dbgSetDebugLevel(int level); +void sigsegvHdlr(int signum); +void dbgoprint(obj_t *pObj, char *fmt, ...) __attribute__((format(printf, 2, 3))); +void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2))); +int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgMutexTryLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr); +int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line); +void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet); +void dbgSetExecLocation(int iStackPtr, int line); +void dbgSetThrdName(uchar *pszName); +void dbgPrintAllDebugInfo(void); +void *dbgmalloc(size_t size); +void dbgOutputTID(char* name); +int dbgGetDbglogFd(void); + +/* macros */ +#ifdef DEBUGLESS +# define DBGPRINTF(...) {} +# define DBGOPRINT(...) {} +#else +# define DBGPRINTF(...) if(Debug) { dbgprintf(__VA_ARGS__); } +# define DBGOPRINT(...) if(Debug) { dbgoprint(__VA_ARGS__); } +#endif +#ifdef RTINST +# define BEGINfunc static dbgFuncDB_t *pdbgFuncDB; int dbgCALLStaCK_POP_POINT = dbgEntrFunc(&pdbgFuncDB, __FILE__, __func__, __LINE__); +# define ENDfunc dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, RS_RET_NO_IRET); +# define ENDfuncIRet dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, iRet); +# define ASSERT(x) assert(x) +#else +# define BEGINfunc +# define ENDfunc +# define ENDfuncIRet +# define ASSERT(x) +#endif +#ifdef RTINST +# define RUNLOG dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__); dbgprintf("%s:%d: %s: log point\n", __FILE__, __LINE__, __func__) +# define RUNLOG_VAR(fmt, x) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ + dbgprintf("%s:%d: %s: var '%s'[%s]: " fmt "\n", __FILE__, __LINE__, __func__, #x, fmt, x) +# define RUNLOG_STR(str) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ + dbgprintf("%s:%d: %s: %s\n", __FILE__, __LINE__, __func__, str) +#else +# define RUNLOG +# define RUNLOG_VAR(fmt, x) +# define RUNLOG_STR(str) +#endif + +#ifdef MEMCHECK +# define MALLOC(x) dbgmalloc(x) +#else +# define MALLOC(x) malloc(x) +#endif + +/* mutex operations */ +#define MUTOP_LOCKWAIT 1 +#define MUTOP_LOCK 2 +#define MUTOP_UNLOCK 3 +#define MUTOP_TRYLOCK 4 + + +/* debug aides */ +#ifdef RTINST +#define d_pthread_mutex_lock(x) dbgMutexLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_mutex_trylock(x) dbgMutexTryLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_mutex_unlock(x) dbgMutexUnlock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_cond_wait(cond, mut) dbgCondWait(cond, mut, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_cond_timedwait(cond, mut, to) dbgCondTimedWait(cond, mut, to, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_free(x) dbgFree(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#else +#define d_pthread_mutex_lock(x) pthread_mutex_lock(x) +#define d_pthread_mutex_trylock(x) pthread_mutex_trylock(x) +#define d_pthread_mutex_unlock(x) pthread_mutex_unlock(x) +#define d_pthread_cond_wait(cond, mut) pthread_cond_wait(cond, mut) +#define d_pthread_cond_timedwait(cond, mut, to) pthread_cond_timedwait(cond, mut, to) +#define d_free(x) free(x) +#endif +#endif /* #ifndef DEBUG_H_INCLUDED */ diff --git a/runtime/dnscache.c b/runtime/dnscache.c new file mode 100644 index 00000000..2096aa36 --- /dev/null +++ b/runtime/dnscache.c @@ -0,0 +1,465 @@ +/* dnscache.c + * Implementation of a real DNS cache + * + * File begun on 2011-06-06 by RGerhards + * The initial implementation is far from being optimal. The idea is to + * first get somethting that'S functionally OK, and then evolve the algorithm. + * In any case, even the initial implementaton is far faster than what we had + * before. -- rgerhards, 2011-06-06 + * + * Copyright 2011-2013 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <netdb.h> +#include <unistd.h> +#include <ctype.h> + +#include "syslogd-types.h" +#include "glbl.h" +#include "errmsg.h" +#include "obj.h" +#include "unicode-helper.h" +#include "net.h" +#include "hashtable.h" +#include "prop.h" +#include "dnscache.h" + +/* module data structures */ +struct dnscache_entry_s { + struct sockaddr_storage addr; + prop_t *fqdn; + prop_t *fqdnLowerCase; + prop_t *localName; /* only local name, without domain part (if configured so) */ + prop_t *ip; + struct dnscache_entry_s *next; + unsigned nUsed; +}; +typedef struct dnscache_entry_s dnscache_entry_t; +struct dnscache_s { + pthread_rwlock_t rwlock; + struct hashtable *ht; + unsigned nEntries; +}; +typedef struct dnscache_s dnscache_t; + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(prop) +static dnscache_t dnsCache; +static prop_t *staticErrValue; + + +/* Our hash function. + * TODO: check how well it performs on socket addresses! + */ +unsigned int +hash_from_key_fn(void *k) +{ + int len; + uchar *rkey = (uchar*) k; /* we treat this as opaque bytes */ + unsigned hashval = 1; + + len = SALEN((struct sockaddr*)k); + while(len--) + hashval = hashval * 33 + *rkey++; + + return hashval; +} + +static int +key_equals_fn(void *key1, void *key2) +{ + return (SALEN((struct sockaddr*)key1) == SALEN((struct sockaddr*) key2) + && !memcmp(key1, key2, SALEN((struct sockaddr*) key1))); +} + +/* destruct a cache entry. + * Precondition: entry must already be unlinked from list + */ +static void +entryDestruct(dnscache_entry_t *etry) +{ + if(etry->fqdn != NULL) + prop.Destruct(&etry->fqdn); + if(etry->fqdnLowerCase != NULL) + prop.Destruct(&etry->fqdnLowerCase); + if(etry->localName != NULL) + prop.Destruct(&etry->localName); + if(etry->ip != NULL) + prop.Destruct(&etry->ip); + free(etry); +} + +/* init function (must be called once) */ +rsRetVal +dnscacheInit(void) +{ + DEFiRet; + if((dnsCache.ht = create_hashtable(100, hash_from_key_fn, key_equals_fn, + (void(*)(void*))entryDestruct)) == NULL) { + DBGPRINTF("dnscache: error creating hash table!\n"); + ABORT_FINALIZE(RS_RET_ERR); // TODO: make this degrade, but run! + } + dnsCache.nEntries = 0; + pthread_rwlock_init(&dnsCache.rwlock, NULL); + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + prop.Construct(&staticErrValue); + prop.SetString(staticErrValue, (uchar*)"???", 3); + prop.ConstructFinalize(staticErrValue); +finalize_it: + RETiRet; +} + +/* deinit function (must be called once) */ +rsRetVal +dnscacheDeinit(void) +{ + DEFiRet; + prop.Destruct(&staticErrValue); + hashtable_destroy(dnsCache.ht, 1); /* 1 => free all values automatically */ + pthread_rwlock_destroy(&dnsCache.rwlock); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + RETiRet; +} + + +static inline dnscache_entry_t* +findEntry(struct sockaddr_storage *addr) +{ + return((dnscache_entry_t*) hashtable_search(dnsCache.ht, addr)); +} + + +/* This is a cancel-safe getnameinfo() version, because we learned + * (via drd/valgrind) that getnameinfo() seems to have some issues + * when being cancelled, at least if the module was dlloaded. + * rgerhards, 2008-09-30 + */ +static inline int +mygetnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ + int iCancelStateSave; + int i; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + pthread_setcancelstate(iCancelStateSave, NULL); + return i; +} + + +/* get only the local part of the hostname and set it in cache entry */ +static inline void +setLocalHostName(dnscache_entry_t *etry) +{ + uchar *fqdnLower; + uchar *p; + int count; + int i; + uchar hostbuf[NI_MAXHOST]; + + if(glbl.GetPreserveFQDN()) { + prop.AddRef(etry->fqdnLowerCase); + etry->localName = etry->fqdnLowerCase; + goto done; + } + + /* strip domain, if configured for this entry */ + fqdnLower = propGetSzStr(etry->fqdnLowerCase); + p = (uchar*)strchr((char*)fqdnLower, '.'); /* find start of domain name "machine.example.com" */ + if(p == NULL) { /* do we have a domain part? */ + prop.AddRef(etry->fqdnLowerCase); /* no! */ + etry->localName = etry->fqdnLowerCase; + goto done; + } + + i = p - fqdnLower; /* length of hostname */ + memcpy(hostbuf, fqdnLower, i); + /* now check if we belong to any of the domain names that were specified + * in the -s command line option. If so, remove and we are done. + */ + if(glbl.GetStripDomains() != NULL) { + count=0; + while(glbl.GetStripDomains()[count]) { + if(strcmp((char*)(p + 1), glbl.GetStripDomains()[count]) == 0) { + prop.CreateStringProp(&etry->localName, hostbuf, i); + goto done; + } + count++; + } + } + /* if we reach this point, we have not found any domain we should strip. Now + * we try and see if the host itself is listed in the -l command line option + * and so should be stripped also. If so, we do it and return. Please note that + * -l list FQDNs, not just the hostname part. If it did just list the hostname, the + * door would be wide-open for all kinds of mixing up of hosts. Because of this, + * you'll see comparison against the full string (pszHostFQDN) below. + */ + if(glbl.GetLocalHosts() != NULL) { + count=0; + while(glbl.GetLocalHosts()[count]) { + if(!strcmp((char*)fqdnLower, (char*)glbl.GetLocalHosts()[count])) { + prop.CreateStringProp(&etry->localName, hostbuf, i); + goto done; + } + count++; + } + } + + /* at this point, we have not found anything, so we again use the + * already-created complete full name property. + */ + prop.AddRef(etry->fqdnLowerCase); + etry->localName = etry->fqdnLowerCase; +done: return; +} + + +/* resolve an address. + * + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. We do by default not + * discard message where we detected malicouos DNS PTR records. However, + * there is a user-configurabel option that will tell us if + * we should abort. For this, the return value tells the caller if the + * message should be processed (1) or discarded (0). + */ +static rsRetVal +resolveAddr(struct sockaddr_storage *addr, dnscache_entry_t *etry) +{ + DEFiRet; + int error; + sigset_t omask, nmask; + struct addrinfo hints, *res; + char szIP[80]; /* large enough for IPv6 */ + char fqdnBuf[NI_MAXHOST]; + rs_size_t fqdnLen; + rs_size_t i; + + error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *)addr), + (char*) szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST); + if(error) { + dbgprintf("Malformed from address %s\n", gai_strerror(error)); + ABORT_FINALIZE(RS_RET_INVALID_SOURCE); + } + + if(!glbl.GetDisableDNS()) { + sigemptyset(&nmask); + sigaddset(&nmask, SIGHUP); + pthread_sigmask(SIG_BLOCK, &nmask, &omask); + + error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *) addr), + fqdnBuf, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + + if(error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if(getaddrinfo (fqdnBuf, NULL, &hints, &res) == 0) { + uchar szErrMsg[1024]; + freeaddrinfo (res); + /* OK, we know we have evil. The question now is what to do about + * it. One the one hand, the message might probably be intended + * to harm us. On the other hand, losing the message may also harm us. + * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords + * option. If it tells us we should discard, we do so, else we proceed, + * but log an error message together with it. + * time being, we simply drop the name we obtained and use the IP - that one + * is OK in any way. We do also log the error message. rgerhards, 2007-07-16 + */ + if(glbl.GetDropMalPTRMsgs() == 1) { + snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), + "Malicious PTR record, message dropped " + "IP = \"%s\" HOST = \"%s\"", + szIP, fqdnBuf); + errmsg.LogError(0, RS_RET_MALICIOUS_ENTITY, "%s", szErrMsg); + pthread_sigmask(SIG_SETMASK, &omask, NULL); + ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY); + } + + /* Please note: we deal with a malicous entry. Thus, we have crafted + * the snprintf() below so that all text is in front of the entry - maybe + * it contains characters that make the message unreadable + * (OK, I admit this is more or less impossible, but I am paranoid...) + * rgerhards, 2007-07-16 + */ + snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), + "Malicious PTR record (message accepted, but used IP " + "instead of PTR name: IP = \"%s\" HOST = \"%s\"", + szIP, fqdnBuf); + errmsg.LogError(0, NO_ERRCODE, "%s", szErrMsg); + + error = 1; /* that will trigger using IP address below. */ + } else {/* we have a valid entry, so let's create the respective properties */ + fqdnLen = strlen(fqdnBuf); + prop.CreateStringProp(&etry->fqdn, (uchar*)fqdnBuf, fqdnLen); + for(i = 0 ; i < fqdnLen ; ++i) + fqdnBuf[i] = tolower(fqdnBuf[i]); + prop.CreateStringProp(&etry->fqdnLowerCase, (uchar*)fqdnBuf, fqdnLen); + } + } + pthread_sigmask(SIG_SETMASK, &omask, NULL); + } + + +finalize_it: + if(iRet != RS_RET_OK) { + strcpy(szIP, "?error.obtaining.ip?"); + error = 1; /* trigger hostname copies below! */ + } + + /* we need to create the inputName property (only once during our lifetime) */ + prop.CreateStringProp(&etry->ip, (uchar*)szIP, strlen(szIP)); + + if(error || glbl.GetDisableDNS()) { + dbgprintf("Host name for your address (%s) unknown\n", szIP); + prop.AddRef(etry->ip); + etry->fqdn = etry->ip; + prop.AddRef(etry->ip); + etry->fqdnLowerCase = etry->ip; + } + + setLocalHostName(etry); + + RETiRet; +} + + +static inline rsRetVal +addEntry(struct sockaddr_storage *addr, dnscache_entry_t **pEtry) +{ + int r; + struct sockaddr_storage *keybuf; + dnscache_entry_t *etry = NULL; + DEFiRet; + + CHKmalloc(etry = MALLOC(sizeof(dnscache_entry_t))); + CHKiRet(resolveAddr(addr, etry)); + memcpy(&etry->addr, addr, SALEN((struct sockaddr*) addr)); + etry->nUsed = 0; + *pEtry = etry; + + CHKmalloc(keybuf = malloc(sizeof(struct sockaddr_storage))); + memcpy(keybuf, addr, sizeof(struct sockaddr_storage)); + + pthread_rwlock_unlock(&dnsCache.rwlock); /* release read lock */ + pthread_rwlock_wrlock(&dnsCache.rwlock); /* and re-aquire for writing */ + r = hashtable_insert(dnsCache.ht, keybuf, *pEtry); + if(r == 0) { + DBGPRINTF("dnscache: inserting element failed\n"); + } + pthread_rwlock_unlock(&dnsCache.rwlock); + pthread_rwlock_rdlock(&dnsCache.rwlock); /* we need this again */ + +finalize_it: + if(iRet != RS_RET_OK && etry != NULL) { + /* Note: sub-fields cannot be populated in this case */ + free(etry); + } + RETiRet; +} + + +/* validate if an entry is still valid and, if not, re-query it. + * In the initial implementation, this is a dummy! + * TODO: implement! + */ +static inline rsRetVal +validateEntry(dnscache_entry_t __attribute__((unused)) *etry, struct sockaddr_storage __attribute__((unused)) *addr) +{ + return RS_RET_OK; +} + + +/* This is the main function: it looks up an entry and returns it's name + * and IP address. If the entry is not yet inside the cache, it is added. + * If the entry can not be resolved, an error is reported back. If fqdn + * or fqdnLowerCase are NULL, they are not set. + */ +rsRetVal +dnscacheLookup(struct sockaddr_storage *addr, prop_t **fqdn, prop_t **fqdnLowerCase, + prop_t **localName, prop_t **ip) +{ + dnscache_entry_t *etry; + DEFiRet; + + pthread_rwlock_rdlock(&dnsCache.rwlock); /* TODO: optimize this! */ + etry = findEntry(addr); + dbgprintf("dnscache: entry %p found\n", etry); + if(etry == NULL) { + CHKiRet(addEntry(addr, &etry)); + } else { + CHKiRet(validateEntry(etry, addr)); + } + prop.AddRef(etry->ip); + *ip = etry->ip; + if(fqdn != NULL) { + prop.AddRef(etry->fqdn); + *fqdn = etry->fqdn; + } + if(fqdnLowerCase != NULL) { + prop.AddRef(etry->fqdnLowerCase); + *fqdnLowerCase = etry->fqdnLowerCase; + } + if(localName != NULL) { + prop.AddRef(etry->localName); + *localName = etry->localName; + } + +finalize_it: + pthread_rwlock_unlock(&dnsCache.rwlock); + if(iRet != RS_RET_OK && iRet != RS_RET_ADDRESS_UNKNOWN) { + DBGPRINTF("dnscacheLookup failed with iRet %d\n", iRet); + prop.AddRef(staticErrValue); + *ip = staticErrValue; + if(fqdn != NULL) { + prop.AddRef(staticErrValue); + *fqdn = staticErrValue; + } + if(fqdnLowerCase != NULL) { + prop.AddRef(staticErrValue); + *fqdnLowerCase = staticErrValue; + } + if(localName != NULL) { + prop.AddRef(staticErrValue); + *localName = staticErrValue; + } + } + RETiRet; +} diff --git a/runtime/dnscache.h b/runtime/dnscache.h new file mode 100644 index 00000000..9c21a645 --- /dev/null +++ b/runtime/dnscache.h @@ -0,0 +1,29 @@ +/* Definitions for dnscache module. + * + * Copyright 2011-2013 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDED_DNSCACHE_H +#define INCLUDED_DNSCACHE_H + +rsRetVal dnscacheInit(void); +rsRetVal dnscacheDeinit(void); +rsRetVal dnscacheLookup(struct sockaddr_storage *addr, prop_t **fqdn, prop_t **fqdnLowerCase, prop_t **localName, prop_t **ip); + +#endif /* #ifndef INCLUDED_DNSCACHE_H */ diff --git a/runtime/errmsg.c b/runtime/errmsg.c new file mode 100644 index 00000000..dcb5b185 --- /dev/null +++ b/runtime/errmsg.c @@ -0,0 +1,144 @@ +/* The errmsg object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c. I converted this module to lgpl and have checked that + * all contributors agreed to that step. + * Now moving to ASL 2.0, and contributor checks tell that there is no need + * to take further case, as the code now boils to be either my own or, a few lines, + * of the original BSD-licenses sysklogd code. rgerhards, 2012-01-16 + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "errmsg.h" +#include "srUtils.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + + +/* We now receive three parameters: one is the internal error code + * which will also become the error message number, the second is + * errno - if it is non-zero, the corresponding error message is included + * in the text and finally the message text itself. Note that it is not + * 100% clean to use the internal errcode, as it may be reached from + * multiple actual error causes. However, it is much better than having + * no error code at all (and in most cases, a single internal error code + * maps to a specific error event). + * rgerhards, 2008-06-27 + */ +static void __attribute__((format(printf, 3, 4))) +LogError(int iErrno, int iErrCode, char *fmt, ... ) +{ + va_list ap; + char buf[1024]; + char msg[1024]; + char errStr[1024]; + size_t lenBuf; + + BEGINfunc + assert(fmt != NULL); + /* Format parameters */ + va_start(ap, fmt); + lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap); + if(lenBuf >= sizeof(buf)) { + /* if our buffer was too small, we simply truncate. */ + lenBuf--; + } + va_end(ap); + + /* Log the error now */ + buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + + dbgprintf("Called LogError, msg: %s\n", buf); + + if(iErrno != 0) { + rs_strerror_r(iErrno, errStr, sizeof(errStr)); + if(iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) { + snprintf(msg, sizeof(msg), "%s: %s", buf, errStr); + } else { + snprintf(msg, sizeof(msg), "%s: %s [try http://www.rsyslog.com/e/%d ]", buf, errStr, iErrCode * -1); + } + } else { + if(iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) { + snprintf(msg, sizeof(msg), "%s", buf); + } else { + snprintf(msg, sizeof(msg), "%s [try http://www.rsyslog.com/e/%d ]", buf, iErrCode * -1); + } + } + msg[sizeof(msg)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + errno = 0; + + glblErrLogger(iErrCode, (uchar*)msg); + + ENDfunc +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(errmsg) +CODESTARTobjQueryInterface(errmsg) + if(pIf->ifVersion != errmsgCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->LogError = LogError; +finalize_it: +ENDobjQueryInterface(errmsg) + + +/* Initialize the errmsg class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(errmsg, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ +ENDObjClassInit(errmsg) + +/* Exit the class. + * rgerhards, 2008-04-17 + */ +BEGINObjClassExit(errmsg, OBJ_IS_CORE_MODULE) /* class, version */ + /* release objects we no longer need */ +ENDObjClassExit(errmsg) + +/* vi:set ai: + */ diff --git a/runtime/errmsg.h b/runtime/errmsg.h new file mode 100644 index 00000000..dfa70c00 --- /dev/null +++ b/runtime/errmsg.h @@ -0,0 +1,45 @@ +/* The errmsg object. It is used to emit error message inside rsyslog. + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_ERRMSG_H +#define INCLUDED_ERRMSG_H + +#include "errmsg.h" + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the errmsg object */ +typedef struct errmsg_s { + char dummy; +} errmsg_t; + + +/* interfaces */ +BEGINinterface(errmsg) /* name must also be changed in ENDinterface macro! */ + void __attribute__((format(printf, 3, 4))) (*LogError)(int iErrno, int iErrCode, char *pszErrFmt, ... ); +ENDinterface(errmsg) +#define errmsgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(errmsg); + +#endif /* #ifndef INCLUDED_ERRMSG_H */ diff --git a/runtime/glbl.c b/runtime/glbl.c new file mode 100644 index 00000000..ccb978ba --- /dev/null +++ b/runtime/glbl.c @@ -0,0 +1,696 @@ +/* glbl.c - this module holds global defintions and data items. + * These are shared among the runtime library. Their use should be + * limited to cases where it is actually needed. The main intension for + * implementing them was support for the transistion from v2 to v4 + * (with fully modular design), but it turned out that there may also + * be some other good use cases besides backwards-compatibility. + * + * Module begun 2008-04-16 by Rainer Gerhards + * + * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "unicode-helper.h" +#include "cfsysline.h" +#include "glbl.h" +#include "prop.h" +#include "atomic.h" +#include "errmsg.h" +#include "rainerscript.h" +#include "net.h" + +/* some defaults */ +#ifndef DFLT_NETSTRM_DRVR +# define DFLT_NETSTRM_DRVR ((uchar*)"ptcp") +#endif + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(prop) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) + +/* static data + * For this object, these variables are obviously what makes the "meat" of the + * class... + */ +static uchar *pszWorkDir = NULL; +static int bOptimizeUniProc = 1; /* enable uniprocessor optimizations */ +static int bParseHOSTNAMEandTAG = 1; /* parser modification (based on startup params!) */ +static int bPreserveFQDN = 0; /* should FQDNs always be preserved? */ +static int iMaxLine = 8096; /* maximum length of a syslog message */ +static int iDefPFFamily = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both) */ +static int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */ +static int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */ +static int bDisableDNS = 0; /* don't look up IP addresses of remote messages */ +static prop_t *propLocalIPIF = NULL;/* IP address to report for the local host (default is 127.0.0.1) */ +static prop_t *propLocalHostName = NULL;/* our hostname as FQDN - read-only after startup */ +static uchar *LocalHostName = NULL;/* our hostname - read-only after startup, except HUP */ +static uchar *LocalHostNameOverride = NULL;/* user-overridden hostname - read-only after startup */ +static uchar *LocalFQDNName = NULL;/* our hostname as FQDN - read-only after startup, except HUP */ +static uchar *LocalDomain = NULL;/* our local domain name - read-only after startup, except HUP */ +static char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */ +static char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */ +static uchar *pszDfltNetstrmDrvr = NULL; /* module name of default netstream driver */ +static uchar *pszDfltNetstrmDrvrCAF = NULL; /* default CA file for the netstrm driver */ +static uchar *pszDfltNetstrmDrvrKeyFile = NULL; /* default key file for the netstrm driver (server) */ +static uchar *pszDfltNetstrmDrvrCertFile = NULL; /* default cert file for the netstrm driver (server) */ +static int bTerminateInputs = 0; /* global switch that inputs shall terminate ASAP (1=> terminate) */ +pid_t glbl_ourpid; +#ifndef HAVE_ATOMIC_BUILTINS +static DEF_ATOMIC_HELPER_MUT(mutTerminateInputs); +#endif +#ifdef USE_UNLIMITED_SELECT +static int iFdSetSize = howmany(FD_SETSIZE, __NFDBITS) * sizeof (fd_mask); /* size of select() bitmask in bytes */ +#endif +static uchar *SourceIPofLocalClient = NULL; /* [ar] Source IP for local client to be used on multihomed host */ + + +/* tables for interfacing with the v6 config system */ +static struct cnfparamdescr cnfparamdescr[] = { + { "workdirectory", eCmdHdlrString, 0 }, + { "dropmsgswithmaliciousdnsptrrecords", eCmdHdlrBinary, 0 }, + { "localhostname", eCmdHdlrGetWord, 0 }, + { "preservefqdn", eCmdHdlrBinary, 0 }, + { "defaultnetstreamdrivercafile", eCmdHdlrString, 0 }, + { "defaultnetstreamdriverkeyfile", eCmdHdlrString, 0 }, + { "defaultnetstreamdriver", eCmdHdlrString, 0 }, + { "maxmessagesize", eCmdHdlrSize, 0 }, +}; +static struct cnfparamblk paramblk = + { CNFPARAMBLK_VERSION, + sizeof(cnfparamdescr)/sizeof(struct cnfparamdescr), + cnfparamdescr + }; + +static struct cnfparamvals *cnfparamvals = NULL; +/* we need to support multiple calls into our param block, so we need + * to persist the current settings. Note that this must be re-set + * each time a new config load begins (TODO: create interface?) + */ + +/* define a macro for the simple properties' set and get functions + * (which are always the same). This is only suitable for pretty + * simple cases which require neither checks nor memory allocation. + */ +#define SIMP_PROP(nameFunc, nameVar, dataType) \ + SIMP_PROP_GET(nameFunc, nameVar, dataType) \ + SIMP_PROP_SET(nameFunc, nameVar, dataType) +#define SIMP_PROP_SET(nameFunc, nameVar, dataType) \ +static rsRetVal Set##nameFunc(dataType newVal) \ +{ \ + nameVar = newVal; \ + return RS_RET_OK; \ +} +#define SIMP_PROP_GET(nameFunc, nameVar, dataType) \ +static dataType Get##nameFunc(void) \ +{ \ + return(nameVar); \ +} + +SIMP_PROP(ParseHOSTNAMEandTAG, bParseHOSTNAMEandTAG, int) +SIMP_PROP(OptimizeUniProc, bOptimizeUniProc, int) +SIMP_PROP(PreserveFQDN, bPreserveFQDN, int) +SIMP_PROP(MaxLine, iMaxLine, int) +SIMP_PROP(DefPFFamily, iDefPFFamily, int) /* note that in the future we may check the family argument */ +SIMP_PROP(DropMalPTRMsgs, bDropMalPTRMsgs, int) +SIMP_PROP(Option_DisallowWarning, option_DisallowWarning, int) +SIMP_PROP(DisableDNS, bDisableDNS, int) +SIMP_PROP(StripDomains, StripDomains, char**) +SIMP_PROP(LocalHosts, LocalHosts, char**) +#ifdef USE_UNLIMITED_SELECT +SIMP_PROP(FdSetSize, iFdSetSize, int) +#endif + +SIMP_PROP_SET(DfltNetstrmDrvr, pszDfltNetstrmDrvr, uchar*) /* TODO: use custom function which frees existing value */ +SIMP_PROP_SET(DfltNetstrmDrvrCAF, pszDfltNetstrmDrvrCAF, uchar*) /* TODO: use custom function which frees existing value */ +SIMP_PROP_SET(DfltNetstrmDrvrKeyFile, pszDfltNetstrmDrvrKeyFile, uchar*) /* TODO: use custom function which frees existing value */ +SIMP_PROP_SET(DfltNetstrmDrvrCertFile, pszDfltNetstrmDrvrCertFile, uchar*) /* TODO: use custom function which frees existing value */ + +#undef SIMP_PROP +#undef SIMP_PROP_SET +#undef SIMP_PROP_GET + + +/* return global input termination status + * rgerhards, 2009-07-20 + */ +static int GetGlobalInputTermState(void) +{ + return ATOMIC_FETCH_32BIT(&bTerminateInputs, &mutTerminateInputs); +} + + +/* set global termination state to "terminate". Note that this is a + * "once in a lifetime" action which can not be undone. -- gerhards, 2009-07-20 + */ +static void SetGlobalInputTermination(void) +{ + ATOMIC_STORE_1_TO_INT(&bTerminateInputs, &mutTerminateInputs); +} + + +/* set the local host IP address to a specific string. Helper to + * small set of functions. No checks done, caller must ensure it is + * ok to call. Most importantly, the IP address must not already have + * been set. -- rgerhards, 2012-03-21 + */ +static inline rsRetVal +storeLocalHostIPIF(uchar *myIP) +{ + DEFiRet; + CHKiRet(prop.Construct(&propLocalIPIF)); + CHKiRet(prop.SetString(propLocalIPIF, myIP, ustrlen(myIP))); + CHKiRet(prop.ConstructFinalize(propLocalIPIF)); + DBGPRINTF("rsyslog/glbl: using '%s' as localhost IP\n", myIP); +finalize_it: + RETiRet; +} + + +/* This function is used to set the IP address that is to be + * reported for the local host. Note that in order to ease things + * for the v6 config interface, we do not allow to set this more + * than once. + * rgerhards, 2012-03-21 + */ +static rsRetVal +setLocalHostIPIF(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + uchar myIP[128]; + rsRetVal localRet; + DEFiRet; + + CHKiRet(objUse(net, CORE_COMPONENT)); + + if(propLocalIPIF != NULL) { + errmsg.LogError(0, RS_RET_ERR, "$LocalHostIPIF is already set " + "and cannot be reset; place it at TOP OF rsyslog.conf!"); + ABORT_FINALIZE(RS_RET_ERR); + } + + localRet = net.GetIFIPAddr(pNewVal, AF_UNSPEC, myIP, (int) sizeof(myIP)); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, RS_RET_ERR, "$LocalHostIPIF: IP address for interface " + "'%s' cannnot be obtained - ignoring directive", pNewVal); + } else { + storeLocalHostIPIF(myIP); + } + + +finalize_it: + free(pNewVal); /* no longer needed -> is in prop! */ + RETiRet; +} + + +/* This function is used to set the global work directory name. + * It verifies that the provided directory actually exists and + * emits an error message if not. + * rgerhards, 2011-02-16 + */ +static rsRetVal setWorkDir(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + size_t lenDir; + int i; + struct stat sb; + DEFiRet; + + /* remove trailing slashes */ + lenDir = ustrlen(pNewVal); + i = lenDir - 1; + while(i > 0 && pNewVal[i] == '/') { + --i; + } + + if(i < 0) { + errmsg.LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: empty value " + "- directive ignored"); + ABORT_FINALIZE(RS_RET_ERR_WRKDIR); + } + + if(i != (int) lenDir - 1) { + pNewVal[i+1] = '\0'; + errmsg.LogError(0, RS_RET_WRN_WRKDIR, "$WorkDirectory: trailing slashes " + "removed, new value is '%s'", pNewVal); + } + + if(stat((char*) pNewVal, &sb) != 0) { + errmsg.LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: %s can not be " + "accessed, probably does not exist - directive ignored", pNewVal); + ABORT_FINALIZE(RS_RET_ERR_WRKDIR); + } + + if(!S_ISDIR(sb.st_mode)) { + errmsg.LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: %s not a directory - directive ignored", + pNewVal); + ABORT_FINALIZE(RS_RET_ERR_WRKDIR); + } + + free(pszWorkDir); + pszWorkDir = pNewVal; + +finalize_it: + RETiRet; +} + + +static rsRetVal +setDebugFile(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + dbgSetDebugFile(pNewVal); + free(pNewVal); + RETiRet; +} + + +static rsRetVal +setDebugLevel(void __attribute__((unused)) *pVal, int level) +{ + DEFiRet; + dbgSetDebugLevel(level); + dbgprintf("debug level %d set via config file\n", level); + dbgprintf("This is rsyslog version " VERSION "\n"); + RETiRet; +} + + +/* return our local IP. + * If no local IP is set, "127.0.0.1" is selected *and* set. This + * is an intensional side effect that we do in order to keep things + * consistent and avoid config errors (this will make us not accept + * setting the local IP address once a module has obtained it - so + * it forces the $LocalHostIPIF directive high up in rsyslog.conf) + * rgerhards, 2012-03-21 + */ +static prop_t* +GetLocalHostIP(void) +{ + if(propLocalIPIF == NULL) + storeLocalHostIPIF((uchar*)"127.0.0.1"); + return(propLocalIPIF); +} + + +/* set our local hostname. Free previous hostname, if it was already set. + * Note that we do now do this in a thread + * "once in a lifetime" action which can not be undone. -- gerhards, 2009-07-20 + */ +static rsRetVal +SetLocalHostName(uchar *newname) +{ + free(LocalHostName); + LocalHostName = newname; + return RS_RET_OK; +} + + +/* return our local hostname. if it is not set, "[localhost]" is returned + */ +static uchar* +GetLocalHostName(void) +{ + uchar *pszRet; + + if(LocalHostNameOverride != NULL) { + pszRet = LocalHostNameOverride; + goto done; + } + + if(LocalHostName == NULL) + pszRet = (uchar*) "[localhost]"; + else { + if(GetPreserveFQDN() == 1) + pszRet = LocalFQDNName; + else + pszRet = LocalHostName; + } +done: + return(pszRet); +} + + +/* set our local domain name. Free previous domain, if it was already set. + */ +static rsRetVal +SetLocalDomain(uchar *newname) +{ + free(LocalDomain); + LocalDomain = newname; + return RS_RET_OK; +} + + +/* return our local hostname. if it is not set, "[localhost]" is returned + */ +static uchar* +GetLocalDomain(void) +{ + return LocalDomain; +} + + +/* generate the local hostname property. This must be done after the hostname info + * has been set as well as PreserveFQDN. + * rgerhards, 2009-06-30 + */ +static rsRetVal +GenerateLocalHostNameProperty(void) +{ + DEFiRet; + uchar *pszName; + + if(propLocalHostName != NULL) + prop.Destruct(&propLocalHostName); + + CHKiRet(prop.Construct(&propLocalHostName)); + if(LocalHostNameOverride == NULL) { + if(LocalHostName == NULL) + pszName = (uchar*) "[localhost]"; + else { + if(GetPreserveFQDN() == 1) + pszName = LocalFQDNName; + else + pszName = LocalHostName; + } + } else { /* local hostname is overriden via config */ + pszName = LocalHostNameOverride; + } + DBGPRINTF("GenerateLocalHostName uses '%s'\n", pszName); + CHKiRet(prop.SetString(propLocalHostName, pszName, ustrlen(pszName))); + CHKiRet(prop.ConstructFinalize(propLocalHostName)); + +finalize_it: + RETiRet; +} + + +/* return our local hostname as a string property + */ +static prop_t* +GetLocalHostNameProp(void) +{ + return(propLocalHostName); +} + + +static rsRetVal +SetLocalFQDNName(uchar *newname) +{ + free(LocalFQDNName); + LocalFQDNName = newname; + return RS_RET_OK; +} + +/* return the current localhost name as FQDN (requires FQDN to be set) + * TODO: we should set the FQDN ourselfs in here! + */ +static uchar* +GetLocalFQDNName(void) +{ + return(LocalFQDNName == NULL ? (uchar*) "[localhost]" : LocalFQDNName); +} + + +/* return the current working directory */ +static uchar* +GetWorkDir(void) +{ + return(pszWorkDir == NULL ? (uchar*) "" : pszWorkDir); +} + + +/* return the current default netstream driver */ +static uchar* +GetDfltNetstrmDrvr(void) +{ + return(pszDfltNetstrmDrvr == NULL ? DFLT_NETSTRM_DRVR : pszDfltNetstrmDrvr); +} + + +/* return the current default netstream driver CA File */ +static uchar* +GetDfltNetstrmDrvrCAF(void) +{ + return(pszDfltNetstrmDrvrCAF); +} + + +/* return the current default netstream driver key File */ +static uchar* +GetDfltNetstrmDrvrKeyFile(void) +{ + return(pszDfltNetstrmDrvrKeyFile); +} + + +/* return the current default netstream driver certificate File */ +static uchar* +GetDfltNetstrmDrvrCertFile(void) +{ + return(pszDfltNetstrmDrvrCertFile); +} + + +/* [ar] Source IP for local client to be used on multihomed host */ +static rsRetVal +SetSourceIPofLocalClient(uchar *newname) +{ + if(SourceIPofLocalClient != NULL) { + free(SourceIPofLocalClient); } + SourceIPofLocalClient = newname; + return RS_RET_OK; +} + +static uchar* +GetSourceIPofLocalClient(void) +{ + return(SourceIPofLocalClient); +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(glbl) +CODESTARTobjQueryInterface(glbl) + if(pIf->ifVersion != glblCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->GetWorkDir = GetWorkDir; + pIf->GenerateLocalHostNameProperty = GenerateLocalHostNameProperty; + pIf->GetLocalHostNameProp = GetLocalHostNameProp; + pIf->GetLocalHostIP = GetLocalHostIP; + pIf->SetGlobalInputTermination = SetGlobalInputTermination; + pIf->GetGlobalInputTermState = GetGlobalInputTermState; + pIf->GetSourceIPofLocalClient = GetSourceIPofLocalClient; /* [ar] */ + pIf->SetSourceIPofLocalClient = SetSourceIPofLocalClient; /* [ar] */ +#define SIMP_PROP(name) \ + pIf->Get##name = Get##name; \ + pIf->Set##name = Set##name; + SIMP_PROP(MaxLine); + SIMP_PROP(OptimizeUniProc); + SIMP_PROP(ParseHOSTNAMEandTAG); + SIMP_PROP(PreserveFQDN); + SIMP_PROP(DefPFFamily); + SIMP_PROP(DropMalPTRMsgs); + SIMP_PROP(Option_DisallowWarning); + SIMP_PROP(DisableDNS); + SIMP_PROP(LocalFQDNName) + SIMP_PROP(LocalHostName) + SIMP_PROP(LocalDomain) + SIMP_PROP(StripDomains) + SIMP_PROP(LocalHosts) + SIMP_PROP(DfltNetstrmDrvr) + SIMP_PROP(DfltNetstrmDrvrCAF) + SIMP_PROP(DfltNetstrmDrvrKeyFile) + SIMP_PROP(DfltNetstrmDrvrCertFile) +#ifdef USE_UNLIMITED_SELECT + SIMP_PROP(FdSetSize) +#endif +#undef SIMP_PROP +finalize_it: +ENDobjQueryInterface(glbl) + + +/* Reset config variables to default values. + * rgerhards, 2008-04-17 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + free(pszDfltNetstrmDrvr); + pszDfltNetstrmDrvr = NULL; + free(pszDfltNetstrmDrvrCAF); + pszDfltNetstrmDrvrCAF = NULL; + free(pszDfltNetstrmDrvrKeyFile); + pszDfltNetstrmDrvrKeyFile = NULL; + free(pszDfltNetstrmDrvrCertFile); + pszDfltNetstrmDrvrCertFile = NULL; + free(LocalHostNameOverride); + LocalHostNameOverride = NULL; + free(pszWorkDir); + pszWorkDir = NULL; + bDropMalPTRMsgs = 0; + bOptimizeUniProc = 1; + bPreserveFQDN = 0; + iMaxLine = 8192; +#ifdef USE_UNLIMITED_SELECT + iFdSetSize = howmany(FD_SETSIZE, __NFDBITS) * sizeof (fd_mask); +#endif + return RS_RET_OK; +} + + +/* Prepare for new config + */ +void +glblPrepCnf(void) +{ + free(cnfparamvals); + cnfparamvals = NULL; +} + +/* handle a global config object. Note that multiple global config statements + * are permitted (because of plugin support), so once we got a param block, + * we need to hold to it. + * rgerhards, 2011-07-19 + */ +void +glblProcessCnf(struct cnfobj *o) +{ + cnfparamvals = nvlstGetParams(o->nvlst, ¶mblk, cnfparamvals); + dbgprintf("glbl param blk after glblProcessCnf:\n"); + cnfparamsPrint(¶mblk, cnfparamvals); +} + +void +glblDoneLoadCnf(void) +{ + int i; + unsigned char *cstr; + + if(cnfparamvals == NULL) + goto finalize_it; + + for(i = 0 ; i < paramblk.nParams ; ++i) { + if(!cnfparamvals[i].bUsed) + continue; + if(!strcmp(paramblk.descr[i].name, "workdirectory")) { + cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL); + setWorkDir(NULL, cstr); + } else if(!strcmp(paramblk.descr[i].name, "localhostname")) { + free(LocalHostNameOverride); + LocalHostNameOverride = (uchar*) + es_str2cstr(cnfparamvals[i].val.d.estr, NULL); + } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdriverkeyfile")) { + free(pszDfltNetstrmDrvrKeyFile); + pszDfltNetstrmDrvrKeyFile = (uchar*) + es_str2cstr(cnfparamvals[i].val.d.estr, NULL); + } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdrivercafile")) { + free(pszDfltNetstrmDrvrCAF); + pszDfltNetstrmDrvrCAF = (uchar*) + es_str2cstr(cnfparamvals[i].val.d.estr, NULL); + } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdriver")) { + free(pszDfltNetstrmDrvr); + pszDfltNetstrmDrvr = (uchar*) + es_str2cstr(cnfparamvals[i].val.d.estr, NULL); + } else if(!strcmp(paramblk.descr[i].name, "preservefqdn")) { + bPreserveFQDN = (int) cnfparamvals[i].val.d.n; + } else if(!strcmp(paramblk.descr[i].name, + "dropmsgswithmaliciousdnsptrrecords")) { + bDropMalPTRMsgs = (int) cnfparamvals[i].val.d.n; + } else if(!strcmp(paramblk.descr[i].name, "maxmessagesize")) { + iMaxLine = (int) cnfparamvals[i].val.d.n; + } else { + dbgprintf("glblDoneLoadCnf: program error, non-handled " + "param '%s'\n", paramblk.descr[i].name); + } + } +finalize_it: ; +} + + +/* Initialize the glbl class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(glbl, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* config handlers are never unregistered and need not be - we are always loaded ;) */ + CHKiRet(regCfSysLineHdlr((uchar *)"debugfile", 0, eCmdHdlrGetWord, setDebugFile, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debuglevel", 0, eCmdHdlrInt, setDebugLevel, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, setWorkDir, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriver", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercafile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrCAF, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriverkeyfile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrKeyFile, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercertfile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrCertFile, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"localhostname", 0, eCmdHdlrGetWord, NULL, &LocalHostNameOverride, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"localhostipif", 0, eCmdHdlrGetWord, setLocalHostIPIF, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"optimizeforuniprocessor", 0, eCmdHdlrBinary, NULL, &bOptimizeUniProc, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"preservefqdn", 0, eCmdHdlrBinary, NULL, &bPreserveFQDN, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"maxmessagesize", 0, eCmdHdlrSize, + NULL, &iMaxLine, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); + + INIT_ATOMIC_HELPER_MUT(mutTerminateInputs); +ENDObjClassInit(glbl) + + +/* Exit the glbl class. + * rgerhards, 2008-04-17 + */ +BEGINObjClassExit(glbl, OBJ_IS_CORE_MODULE) /* class, version */ + free(pszDfltNetstrmDrvr); + free(pszDfltNetstrmDrvrCAF); + free(pszDfltNetstrmDrvrKeyFile); + free(pszDfltNetstrmDrvrCertFile); + free(pszWorkDir); + free(LocalDomain); + free(LocalHostName); + free(LocalHostNameOverride); + free(LocalFQDNName); + objRelease(prop, CORE_COMPONENT); + DESTROY_ATOMIC_HELPER_MUT(mutTerminateInputs); +ENDObjClassExit(glbl) + +void glblProcessCnf(struct cnfobj *o); + +/* vi:set ai: + */ diff --git a/runtime/glbl.h b/runtime/glbl.h new file mode 100644 index 00000000..2c7f3b31 --- /dev/null +++ b/runtime/glbl.h @@ -0,0 +1,101 @@ +/* Definition of globally-accessible data items. + * + * This module provides access methods to items of global scope. Most often, + * these globals serve as defaults to initialize local settings. Currently, + * many of them are either constants or global variable references. However, + * this module provides the necessary hooks to change that at any time. + * + * Please note that there currently is no glbl.c file as we do not yet + * have any implementations. + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLBL_H_INCLUDED +#define GLBL_H_INCLUDED + +#include <sys/types.h> +#include "rainerscript.h" +#include "prop.h" + +#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */ + +extern pid_t glbl_ourpid; + +/* interfaces */ +BEGINinterface(glbl) /* name must also be changed in ENDinterface macro! */ + uchar* (*GetWorkDir)(void); +#define SIMP_PROP(name, dataType) \ + dataType (*Get##name)(void); \ + rsRetVal (*Set##name)(dataType); + SIMP_PROP(MaxLine, int) + SIMP_PROP(OptimizeUniProc, int) + SIMP_PROP(PreserveFQDN, int) + SIMP_PROP(DefPFFamily, int) + SIMP_PROP(DropMalPTRMsgs, int) + SIMP_PROP(Option_DisallowWarning, int) + SIMP_PROP(DisableDNS, int) + SIMP_PROP(LocalFQDNName, uchar*) + SIMP_PROP(LocalHostName, uchar*) + SIMP_PROP(LocalDomain, uchar*) + SIMP_PROP(StripDomains, char**) + SIMP_PROP(LocalHosts, char**) + SIMP_PROP(DfltNetstrmDrvr, uchar*) + SIMP_PROP(DfltNetstrmDrvrCAF, uchar*) + SIMP_PROP(DfltNetstrmDrvrKeyFile, uchar*) + SIMP_PROP(DfltNetstrmDrvrCertFile, uchar*) + /* added v3, 2009-06-30 */ + rsRetVal (*GenerateLocalHostNameProperty)(void); + prop_t* (*GetLocalHostNameProp)(void); + /* added v4, 2009-07-20 */ + int (*GetGlobalInputTermState)(void); + void (*SetGlobalInputTermination)(void); + /* added v5, 2009-11-03 */ + SIMP_PROP(ParseHOSTNAMEandTAG, int) + /* note: v4, v5 are already used by more recent versions, so we need to skip them! */ + /* added v6, 2009-11-16 as part of varmojfekoj's "unlimited select()" patch + * Note that it must be always present, otherwise the interface would have different + * versions depending on compile settings, what is not acceptable. + * Use this property with care, it is only truly available if UNLIMITED_SELECT is enabled + * (I did not yet further investigate the details, because that code hopefully can be removed + * at some later stage). + */ + SIMP_PROP(FdSetSize, int) + /* v7: was neeeded to mean v5+v6 - do NOT add anything else for that version! */ + /* next change is v9! */ + /* v8 - 2012-03-21 */ + prop_t* (*GetLocalHostIP)(void); + uchar* (*GetSourceIPofLocalClient)(void); /* [ar] */ + rsRetVal (*SetSourceIPofLocalClient)(uchar*); /* [ar] */ +#undef SIMP_PROP +ENDinterface(glbl) +#define glblCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */ +/* version 2 had PreserveFQDN added - rgerhards, 2008-12-08 */ + +/* the remaining prototypes */ +PROTOTYPEObj(glbl); + +static inline pid_t glblGetOurPid(void) { return glbl_ourpid; } +static inline void glblSetOurPid(pid_t pid) { glbl_ourpid = pid; } + +void glblPrepCnf(void); +void glblProcessCnf(struct cnfobj *o); +void glblDoneLoadCnf(void); + +#endif /* #ifndef GLBL_H_INCLUDED */ diff --git a/runtime/hashtable.c b/runtime/hashtable.c new file mode 100644 index 00000000..f718bd43 --- /dev/null +++ b/runtime/hashtable.c @@ -0,0 +1,323 @@ +/* Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ +/* taken from http://www.cl.cam.ac.uk/~cwc22/hashtable/ */ + +#include "hashtable.h" +#include "hashtable_private.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +/* +Credit for primes table: Aaron Krowne + http://br.endernet.org/~akrowne/ + http://planetmath.org/encyclopedia/GoodHashTablePrimes.html +*/ +static const unsigned int primes[] = { +53, 97, 193, 389, +769, 1543, 3079, 6151, +12289, 24593, 49157, 98317, +196613, 393241, 786433, 1572869, +3145739, 6291469, 12582917, 25165843, +50331653, 100663319, 201326611, 402653189, +805306457, 1610612741 +}; +const unsigned int prime_table_length = sizeof(primes)/sizeof(primes[0]); + +#define MAX_LOAD_FACTOR 65 /* to get real factor, divide by 100! */ + +/* compute max load. We use a constant factor of 0.65, but do + * everything times 100, so that we do not need floats. + */ +static inline unsigned +getLoadLimit(unsigned size) +{ + return (unsigned int) ((unsigned long long) size * MAX_LOAD_FACTOR) / 100; +} + +/*****************************************************************************/ +struct hashtable * +create_hashtable(unsigned int minsize, + unsigned int (*hashf) (void*), + int (*eqf) (void*,void*), void (*dest)(void*)) +{ + struct hashtable *h; + unsigned int pindex, size = primes[0]; + /* Check requested hashtable isn't too large */ + if (minsize > (1u << 30)) return NULL; + /* Enforce size as prime */ + for (pindex=0; pindex < prime_table_length; pindex++) { + if (primes[pindex] > minsize) { size = primes[pindex]; break; } + } + h = (struct hashtable *)malloc(sizeof(struct hashtable)); + if (NULL == h) return NULL; /*oom*/ + h->table = (struct entry **)malloc(sizeof(struct entry*) * size); + if (NULL == h->table) { free(h); return NULL; } /*oom*/ + memset(h->table, 0, size * sizeof(struct entry *)); + h->tablelength = size; + h->primeindex = pindex; + h->entrycount = 0; + h->hashfn = hashf; + h->eqfn = eqf; + h->dest = dest; + h->loadlimit = getLoadLimit(size); + return h; +} + +/*****************************************************************************/ +unsigned int +hash(struct hashtable *h, void *k) +{ + /* Aim to protect against poor hash functions by adding logic here + * - logic taken from java 1.4 hashtable source */ + unsigned int i = h->hashfn(k); + i += ~(i << 9); + i ^= ((i >> 14) | (i << 18)); /* >>> */ + i += (i << 4); + i ^= ((i >> 10) | (i << 22)); /* >>> */ + return i; +} + +/*****************************************************************************/ +static int +hashtable_expand(struct hashtable *h) +{ + /* Double the size of the table to accomodate more entries */ + struct entry **newtable; + struct entry *e; + struct entry **pE; + unsigned int newsize, i, idx; + /* Check we're not hitting max capacity */ + if (h->primeindex == (prime_table_length - 1)) return 0; + newsize = primes[++(h->primeindex)]; + + newtable = (struct entry **)malloc(sizeof(struct entry*) * newsize); + if (NULL != newtable) + { + memset(newtable, 0, newsize * sizeof(struct entry *)); + /* This algorithm is not 'stable'. ie. it reverses the list + * when it transfers entries between the tables */ + for (i = 0; i < h->tablelength; i++) { + while (NULL != (e = h->table[i])) { + h->table[i] = e->next; + idx = indexFor(newsize,e->h); + e->next = newtable[idx]; + newtable[idx] = e; + } + } + free(h->table); + h->table = newtable; + } + /* Plan B: realloc instead */ + else + { + newtable = (struct entry **) + realloc(h->table, newsize * sizeof(struct entry *)); + if (NULL == newtable) { (h->primeindex)--; return 0; } + h->table = newtable; + memset(newtable[h->tablelength], 0, newsize - h->tablelength); + for (i = 0; i < h->tablelength; i++) { + for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) { + idx = indexFor(newsize,e->h); + if (idx == i) + { + pE = &(e->next); + } + else + { + *pE = e->next; + e->next = newtable[idx]; + newtable[idx] = e; + } + } + } + } + h->tablelength = newsize; + h->loadlimit = getLoadLimit(newsize); + return -1; +} + +/*****************************************************************************/ +unsigned int +hashtable_count(struct hashtable *h) +{ + return h->entrycount; +} + +/*****************************************************************************/ +int +hashtable_insert(struct hashtable *h, void *k, void *v) +{ + /* This method allows duplicate keys - but they shouldn't be used */ + unsigned int idx; + struct entry *e; + if (++(h->entrycount) > h->loadlimit) + { + /* Ignore the return value. If expand fails, we should + * still try cramming just this value into the existing table + * -- we may not have memory for a larger table, but one more + * element may be ok. Next time we insert, we'll try expanding again.*/ + hashtable_expand(h); + } + e = (struct entry *)malloc(sizeof(struct entry)); + if (NULL == e) { --(h->entrycount); return 0; } /*oom*/ + e->h = hash(h,k); + idx = indexFor(h->tablelength,e->h); + e->k = k; + e->v = v; + e->next = h->table[idx]; + h->table[idx] = e; + return -1; +} + +/*****************************************************************************/ +void * /* returns value associated with key */ +hashtable_search(struct hashtable *h, void *k) +{ + struct entry *e; + unsigned int hashvalue, idx; + hashvalue = hash(h,k); + idx = indexFor(h->tablelength,hashvalue); + e = h->table[idx]; + while (NULL != e) + { + /* Check hash value to short circuit heavier comparison */ + if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v; + e = e->next; + } + return NULL; +} + +/*****************************************************************************/ +void * /* returns value associated with key */ +hashtable_remove(struct hashtable *h, void *k) +{ + /* TODO: consider compacting the table when the load factor drops enough, + * or provide a 'compact' method. */ + + struct entry *e; + struct entry **pE; + void *v; + unsigned int hashvalue, idx; + + hashvalue = hash(h,k); + idx = indexFor(h->tablelength,hash(h,k)); + pE = &(h->table[idx]); + e = *pE; + while (NULL != e) + { + /* Check hash value to short circuit heavier comparison */ + if ((hashvalue == e->h) && (h->eqfn(k, e->k))) + { + *pE = e->next; + h->entrycount--; + v = e->v; + freekey(e->k); + free(e); + return v; + } + pE = &(e->next); + e = e->next; + } + return NULL; +} + +/*****************************************************************************/ +/* destroy */ +void +hashtable_destroy(struct hashtable *h, int free_values) +{ + unsigned int i; + struct entry *e, *f; + struct entry **table = h->table; + if (free_values) + { + for (i = 0; i < h->tablelength; i++) + { + e = table[i]; + while (NULL != e) + { + f = e; + e = e->next; + freekey(f->k); + if(h->dest == NULL) + free(f->v); + else + h->dest(f->v); + free(f); + } + } + } + else + { + for (i = 0; i < h->tablelength; i++) + { + e = table[i]; + while (NULL != e) + { f = e; e = e->next; freekey(f->k); free(f); } + } + } + free(h->table); + free(h); +} + +/* some generic hash functions */ + +/* one provided by Aaaron Wiebe based on perl's hashing algorithm + * (so probably pretty generic). Not for excessively large strings! + */ +unsigned int +hash_from_string(void *k) +{ + int len; + char *rkey = (char*) k; + unsigned hashval = 1; + + len = (int) strlen(rkey); + while (len--) + hashval = hashval * 33 + *rkey++; + + return hashval; +} + + +int +key_equals_string(void *key1, void *key2) +{ + /* we must return true IF the keys are equal! */ + return !strcmp(key1, key2); +} + + +/* + * Copyright (c) 2002, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/hashtable.h b/runtime/hashtable.h new file mode 100644 index 00000000..f777ad0b --- /dev/null +++ b/runtime/hashtable.h @@ -0,0 +1,202 @@ +/* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#ifndef __HASHTABLE_CWC22_H__ +#define __HASHTABLE_CWC22_H__ + +struct hashtable; + +/* Example of use: + * + * struct hashtable *h; + * struct some_key *k; + * struct some_value *v; + * + * static unsigned int hash_from_key_fn( void *k ); + * static int keys_equal_fn ( void *key1, void *key2 ); + * + * h = create_hashtable(16, hash_from_key_fn, keys_equal_fn); + * k = (struct some_key *) malloc(sizeof(struct some_key)); + * v = (struct some_value *) malloc(sizeof(struct some_value)); + * + * (initialise k and v to suitable values) + * + * if (! hashtable_insert(h,k,v) ) + * { exit(-1); } + * + * if (NULL == (found = hashtable_search(h,k) )) + * { printf("not found!"); } + * + * if (NULL == (found = hashtable_remove(h,k) )) + * { printf("Not found\n"); } + * + */ + +/* Macros may be used to define type-safe(r) hashtable access functions, with + * methods specialized to take known key and value types as parameters. + * + * Example: + * + * Insert this at the start of your file: + * + * DEFINE_HASHTABLE_INSERT(insert_some, struct some_key, struct some_value); + * DEFINE_HASHTABLE_SEARCH(search_some, struct some_key, struct some_value); + * DEFINE_HASHTABLE_REMOVE(remove_some, struct some_key, struct some_value); + * + * This defines the functions 'insert_some', 'search_some' and 'remove_some'. + * These operate just like hashtable_insert etc., with the same parameters, + * but their function signatures have 'struct some_key *' rather than + * 'void *', and hence can generate compile time errors if your program is + * supplying incorrect data as a key (and similarly for value). + * + * Note that the hash and key equality functions passed to create_hashtable + * still take 'void *' parameters instead of 'some key *'. This shouldn't be + * a difficult issue as they're only defined and passed once, and the other + * functions will ensure that only valid keys are supplied to them. + * + * The cost for this checking is increased code size and runtime overhead + * - if performance is important, it may be worth switching back to the + * unsafe methods once your program has been debugged with the safe methods. + * This just requires switching to some simple alternative defines - eg: + * #define insert_some hashtable_insert + * + */ + +/***************************************************************************** + * create_hashtable + + * @name create_hashtable + * @param minsize minimum initial size of hashtable + * @param hashfunction function for hashing keys + * @param key_eq_fn function for determining key equality + * @param dest destructor for value entries (NULL -> use free()) + * @return newly created hashtable or NULL on failure + */ + +struct hashtable * +create_hashtable(unsigned int minsize, + unsigned int (*hashfunction) (void*), + int (*key_eq_fn) (void*,void*), void (*dest) (void*)); + +/***************************************************************************** + * hashtable_insert + + * @name hashtable_insert + * @param h the hashtable to insert into + * @param k the key - hashtable claims ownership and will free on removal + * @param v the value - does not claim ownership + * @return non-zero for successful insertion + * + * This function will cause the table to expand if the insertion would take + * the ratio of entries to table size over the maximum load factor. + * + * This function does not check for repeated insertions with a duplicate key. + * The value returned when using a duplicate key is undefined -- when + * the hashtable changes size, the order of retrieval of duplicate key + * entries is reversed. + * If in doubt, remove before insert. + */ + +int +hashtable_insert(struct hashtable *h, void *k, void *v); + +#define DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \ +int fnname (struct hashtable *h, keytype *k, valuetype *v) \ +{ \ + return hashtable_insert(h,k,v); \ +} + +/***************************************************************************** + * hashtable_search + + * @name hashtable_search + * @param h the hashtable to search + * @param k the key to search for - does not claim ownership + * @return the value associated with the key, or NULL if none found + */ + +void * +hashtable_search(struct hashtable *h, void *k); + +#define DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \ +valuetype * fnname (struct hashtable *h, keytype *k) \ +{ \ + return (valuetype *) (hashtable_search(h,k)); \ +} + +/***************************************************************************** + * hashtable_remove + + * @name hashtable_remove + * @param h the hashtable to remove the item from + * @param k the key to search for - does not claim ownership + * @return the value associated with the key, or NULL if none found + */ + +void * /* returns value */ +hashtable_remove(struct hashtable *h, void *k); + +#define DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \ +valuetype * fnname (struct hashtable *h, keytype *k) \ +{ \ + return (valuetype *) (hashtable_remove(h,k)); \ +} + + +/***************************************************************************** + * hashtable_count + + * @name hashtable_count + * @param h the hashtable + * @return the number of items stored in the hashtable + */ +unsigned int +hashtable_count(struct hashtable *h); + + +/***************************************************************************** + * hashtable_destroy + + * @name hashtable_destroy + * @param h the hashtable + * @param free_values whether to call 'free' on the remaining values + */ + +void +hashtable_destroy(struct hashtable *h, int free_values); + +#endif /* __HASHTABLE_CWC22_H__ */ + +/* + * Copyright (c) 2002, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +unsigned int hash_from_string(void *k) ; +int key_equals_string(void *key1, void *key2); diff --git a/runtime/hashtable/Makefile b/runtime/hashtable/Makefile new file mode 100644 index 00000000..3b7b5e9f --- /dev/null +++ b/runtime/hashtable/Makefile @@ -0,0 +1,26 @@ + +tester: hashtable.o tester.o hashtable_itr.o + gcc -g -Wall -O -lm -o tester hashtable.o hashtable_itr.o tester.o + +all: tester old_tester + +tester.o: tester.c + gcc -g -Wall -O -c tester.c -o tester.o + +old_tester: hashtable_powers.o tester.o hashtable_itr.o + gcc -g -Wall -O -o old_tester hashtable_powers.o hashtable_itr.o tester.o + +hashtable_powers.o: hashtable_powers.c + gcc -g -Wall -O -c hashtable_powers.c -o hashtable_powers.o + +hashtable.o: hashtable.c + gcc -g -Wall -O -c hashtable.c -o hashtable.o + +hashtable_itr.o: hashtable_itr.c + gcc -g -Wall -O -c hashtable_itr.c -o hashtable_itr.o + +tidy: + rm *.o + +clean: tidy + rm -f tester old_tester diff --git a/runtime/hashtable/README b/runtime/hashtable/README new file mode 100644 index 00000000..5cadde0c --- /dev/null +++ b/runtime/hashtable/README @@ -0,0 +1,11 @@ +This is the hashtable code provided by +Christopher Clark <firstname.lastname@cl.cam.ac.uk> +available at http://www.cl.cam.ac.uk/~cwc22/hashtable/ + +It may be slightly modified. The plan is to streamline +the code based on our needs and "really" integrate it into +the rsyslog runtime library. For the time being, we use it from +inside this subdirectory. We do not need all files, but I thought +I keep them together in case we later need something else. + +rgerhards, 2010-09-28 diff --git a/runtime/hashtable/hashtable_utility.c b/runtime/hashtable/hashtable_utility.c new file mode 100644 index 00000000..c3176709 --- /dev/null +++ b/runtime/hashtable/hashtable_utility.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#include "hashtable.h" +#include "hashtable_private.h" +#include "hashtable_utility.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +/*****************************************************************************/ +/* hashtable_change + * + * function to change the value associated with a key, where there already + * exists a value bound to the key in the hashtable. + * Source due to Holger Schemel. + * + * */ +int +hashtable_change(struct hashtable *h, void *k, void *v) +{ + struct entry *e; + unsigned int hashvalue, index; + hashvalue = hash(h,k); + index = indexFor(h->tablelength,hashvalue); + e = h->table[index]; + while (NULL != e) + { + /* Check hash value to short circuit heavier comparison */ + if ((hashvalue == e->h) && (h->eqfn(k, e->k))) + { + free(e->v); + e->v = v; + return -1; + } + e = e->next; + } + return 0; +} + +/* + * Copyright (c) 2002, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/hashtable/hashtable_utility.h b/runtime/hashtable/hashtable_utility.h new file mode 100644 index 00000000..56a0ffd1 --- /dev/null +++ b/runtime/hashtable/hashtable_utility.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#ifndef __HASHTABLE_CWC22_UTILITY_H__ +#define __HASHTABLE_CWC22_UTILITY_H__ + +/***************************************************************************** + * hashtable_change + * + * function to change the value associated with a key, where there already + * exists a value bound to the key in the hashtable. + * Source due to Holger Schemel. + * + * @name hashtable_change + * @param h the hashtable + * @param key + * @param value + * + */ +int +hashtable_change(struct hashtable *h, void *k, void *v); + +#endif /* __HASHTABLE_CWC22_H__ */ + +/* + * Copyright (c) 2002, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/hashtable/tester.c b/runtime/hashtable/tester.c new file mode 100644 index 00000000..4678ffa8 --- /dev/null +++ b/runtime/hashtable/tester.c @@ -0,0 +1,270 @@ +/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#include "hashtable.h" +#include "hashtable_itr.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> /* for memcmp */ + +static const int ITEM_COUNT = 4000; + +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; + +/*****************************************************************************/ +struct key +{ + uint32_t one_ip; uint32_t two_ip; uint16_t one_port; uint16_t two_port; +}; + +struct value +{ + char *id; +}; + +DEFINE_HASHTABLE_INSERT(insert_some, struct key, struct value); +DEFINE_HASHTABLE_SEARCH(search_some, struct key, struct value); +DEFINE_HASHTABLE_REMOVE(remove_some, struct key, struct value); +DEFINE_HASHTABLE_ITERATOR_SEARCH(search_itr_some, struct key); + + +/*****************************************************************************/ +static unsigned int +hashfromkey(void *ky) +{ + struct key *k = (struct key *)ky; + return (((k->one_ip << 17) | (k->one_ip >> 15)) ^ k->two_ip) + + (k->one_port * 17) + (k->two_port * 13 * 29); +} + +static int +equalkeys(void *k1, void *k2) +{ + return (0 == memcmp(k1,k2,sizeof(struct key))); +} + +/*****************************************************************************/ +int +main(int argc, char **argv) +{ + struct key *k, *kk; + struct value *v, *found; + struct hashtable *h; + struct hashtable_itr *itr; + int i; + + h = create_hashtable(16, hashfromkey, equalkeys); + if (NULL == h) exit(-1); /*oom*/ + + +/*****************************************************************************/ +/* Insertion */ + for (i = 0; i < ITEM_COUNT; i++) + { + k = (struct key *)malloc(sizeof(struct key)); + if (NULL == k) { + printf("ran out of memory allocating a key\n"); + return 1; + } + k->one_ip = 0xcfccee40 + i; + k->two_ip = 0xcf0cee67 - (5 * i); + k->one_port = 22 + (7 * i); + k->two_port = 5522 - (3 * i); + + v = (struct value *)malloc(sizeof(struct value)); + v->id = "a value"; + + if (!insert_some(h,k,v)) exit(-1); /*oom*/ + } + printf("After insertion, hashtable contains %u items.\n", + hashtable_count(h)); + +/*****************************************************************************/ +/* Hashtable search */ + k = (struct key *)malloc(sizeof(struct key)); + if (NULL == k) { + printf("ran out of memory allocating a key\n"); + return 1; + } + + for (i = 0; i < ITEM_COUNT; i++) + { + k->one_ip = 0xcfccee40 + i; + k->two_ip = 0xcf0cee67 - (5 * i); + k->one_port = 22 + (7 * i); + k->two_port = 5522 - (3 * i); + + if (NULL == (found = search_some(h,k))) { + printf("BUG: key not found\n"); + } + } + +/*****************************************************************************/ +/* Hashtable iteration */ + /* Iterator constructor only returns a valid iterator if + * the hashtable is not empty */ + itr = hashtable_iterator(h); + i = 0; + if (hashtable_count(h) > 0) + { + do { + kk = hashtable_iterator_key(itr); + v = hashtable_iterator_value(itr); + /* here (kk,v) are a valid (key, value) pair */ + /* We could call 'hashtable_remove(h,kk)' - and this operation + * 'free's kk. However, the iterator is then broken. + * This is why hashtable_iterator_remove exists - see below. + */ + i++; + + } while (hashtable_iterator_advance(itr)); + } + printf("Iterated through %u entries.\n", i); + +/*****************************************************************************/ +/* Hashtable iterator search */ + + /* Try the search some method */ + for (i = 0; i < ITEM_COUNT; i++) + { + k->one_ip = 0xcfccee40 + i; + k->two_ip = 0xcf0cee67 - (5 * i); + k->one_port = 22 + (7 * i); + k->two_port = 5522 - (3 * i); + + if (0 == search_itr_some(itr,h,k)) { + printf("BUG: key not found searching with iterator"); + } + } + +/*****************************************************************************/ +/* Hashtable removal */ + + for (i = 0; i < ITEM_COUNT; i++) + { + k->one_ip = 0xcfccee40 + i; + k->two_ip = 0xcf0cee67 - (5 * i); + k->one_port = 22 + (7 * i); + k->two_port = 5522 - (3 * i); + + if (NULL == (found = remove_some(h,k))) { + printf("BUG: key not found for removal\n"); + } + } + printf("After removal, hashtable contains %u items.\n", + hashtable_count(h)); + +/*****************************************************************************/ +/* Hashtable destroy and create */ + + hashtable_destroy(h, 1); + h = NULL; + free(k); + + h = create_hashtable(160, hashfromkey, equalkeys); + if (NULL == h) { + printf("out of memory allocating second hashtable\n"); + return 1; + } + +/*****************************************************************************/ +/* Hashtable insertion */ + + for (i = 0; i < ITEM_COUNT; i++) + { + k = (struct key *)malloc(sizeof(struct key)); + k->one_ip = 0xcfccee40 + i; + k->two_ip = 0xcf0cee67 - (5 * i); + k->one_port = 22 + (7 * i); + k->two_port = 5522 - (3 * i); + + v = (struct value *)malloc(sizeof(struct value)); + v->id = "a value"; + + if (!insert_some(h,k,v)) + { + printf("out of memory inserting into second hashtable\n"); + return 1; + } + } + printf("After insertion, hashtable contains %u items.\n", + hashtable_count(h)); + +/*****************************************************************************/ +/* Hashtable iterator search and iterator remove */ + + k = (struct key *)malloc(sizeof(struct key)); + if (NULL == k) { + printf("ran out of memory allocating a key\n"); + return 1; + } + + for (i = ITEM_COUNT - 1; i >= 0; i = i - 7) + { + k->one_ip = 0xcfccee40 + i; + k->two_ip = 0xcf0cee67 - (5 * i); + k->one_port = 22 + (7 * i); + k->two_port = 5522 - (3 * i); + + if (0 == search_itr_some(itr, h, k)) { + printf("BUG: key %u not found for search preremoval using iterator\n", i); + return 1; + } + if (0 == hashtable_iterator_remove(itr)) { + printf("BUG: key not found for removal using iterator\n"); + return 1; + } + } + free(itr); + +/*****************************************************************************/ +/* Hashtable iterator remove and advance */ + + for (itr = hashtable_iterator(h); + hashtable_iterator_remove(itr) != 0; ) { + ; + } + free(itr); + printf("After removal, hashtable contains %u items.\n", + hashtable_count(h)); + +/*****************************************************************************/ +/* Hashtable destroy */ + + hashtable_destroy(h, 1); + free(k); + return 0; +} + +/* + * Copyright (c) 2002, 2004, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/hashtable_itr.c b/runtime/hashtable_itr.c new file mode 100644 index 00000000..967287f1 --- /dev/null +++ b/runtime/hashtable_itr.c @@ -0,0 +1,190 @@ +/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#include "hashtable.h" +#include "hashtable_private.h" +#include "hashtable_itr.h" +#include <stdlib.h> /* defines NULL */ + +/*****************************************************************************/ +/* hashtable_iterator - iterator constructor */ + +struct hashtable_itr * +hashtable_iterator(struct hashtable *h) +{ + unsigned int i, tablelength; + struct hashtable_itr *itr = (struct hashtable_itr *) + malloc(sizeof(struct hashtable_itr)); + if (NULL == itr) return NULL; + itr->h = h; + itr->e = NULL; + itr->parent = NULL; + tablelength = h->tablelength; + itr->index = tablelength; + if (0 == h->entrycount) return itr; + + for (i = 0; i < tablelength; i++) + { + if (NULL != h->table[i]) + { + itr->e = h->table[i]; + itr->index = i; + break; + } + } + return itr; +} + +/*****************************************************************************/ +/* key - return the key of the (key,value) pair at the current position */ +/* value - return the value of the (key,value) pair at the current position */ + +#if 0 /* these are now inline functions! */ +void * +hashtable_iterator_key(struct hashtable_itr *i) +{ return i->e->k; } + +void * +hashtable_iterator_value(struct hashtable_itr *i) +{ return i->e->v; } +#endif + +/*****************************************************************************/ +/* advance - advance the iterator to the next element + * returns zero if advanced to end of table */ + +int +hashtable_iterator_advance(struct hashtable_itr *itr) +{ + unsigned int j,tablelength; + struct entry **table; + struct entry *next; + if (NULL == itr->e) return 0; /* stupidity check */ + + next = itr->e->next; + if (NULL != next) + { + itr->parent = itr->e; + itr->e = next; + return -1; + } + tablelength = itr->h->tablelength; + itr->parent = NULL; + if (tablelength <= (j = ++(itr->index))) + { + itr->e = NULL; + return 0; + } + table = itr->h->table; + while (NULL == (next = table[j])) + { + if (++j >= tablelength) + { + itr->index = tablelength; + itr->e = NULL; + return 0; + } + } + itr->index = j; + itr->e = next; + return -1; +} + +/*****************************************************************************/ +/* remove - remove the entry at the current iterator position + * and advance the iterator, if there is a successive + * element. + * If you want the value, read it before you remove: + * beware memory leaks if you don't. + * Returns zero if end of iteration. */ + +int +hashtable_iterator_remove(struct hashtable_itr *itr) +{ + struct entry *remember_e, *remember_parent; + int ret; + + /* Do the removal */ + if (NULL == (itr->parent)) + { + /* element is head of a chain */ + itr->h->table[itr->index] = itr->e->next; + } else { + /* element is mid-chain */ + itr->parent->next = itr->e->next; + } + /* itr->e is now outside the hashtable */ + remember_e = itr->e; + itr->h->entrycount--; + freekey(remember_e->k); + + /* Advance the iterator, correcting the parent */ + remember_parent = itr->parent; + ret = hashtable_iterator_advance(itr); + if (itr->parent == remember_e) { itr->parent = remember_parent; } + free(remember_e); + return ret; +} + +/*****************************************************************************/ +int /* returns zero if not found */ +hashtable_iterator_search(struct hashtable_itr *itr, + struct hashtable *h, void *k) +{ + struct entry *e, *parent; + unsigned int hashvalue, index; + + hashvalue = hash(h,k); + index = indexFor(h->tablelength,hashvalue); + + e = h->table[index]; + parent = NULL; + while (NULL != e) + { + /* Check hash value to short circuit heavier comparison */ + if ((hashvalue == e->h) && (h->eqfn(k, e->k))) + { + itr->index = index; + itr->e = e; + itr->parent = parent; + itr->h = h; + return -1; + } + parent = e; + e = e->next; + } + return 0; +} + + +/* + * Copyright (c) 2002, 2004, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/hashtable_itr.h b/runtime/hashtable_itr.h new file mode 100644 index 00000000..1c206b6e --- /dev/null +++ b/runtime/hashtable_itr.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#ifndef __HASHTABLE_ITR_CWC22__ +#define __HASHTABLE_ITR_CWC22__ +#include "hashtable.h" +#include "hashtable_private.h" /* needed to enable inlining */ + +/*****************************************************************************/ +/* This struct is only concrete here to allow the inlining of two of the + * accessor functions. */ +struct hashtable_itr +{ + struct hashtable *h; + struct entry *e; + struct entry *parent; + unsigned int index; +}; + + +/*****************************************************************************/ +/* hashtable_iterator + */ + +struct hashtable_itr * +hashtable_iterator(struct hashtable *h); + +/*****************************************************************************/ +/* hashtable_iterator_key + * - return the value of the (key,value) pair at the current position */ + +static inline void * +hashtable_iterator_key(struct hashtable_itr *i) +{ + return i->e->k; +} + +/*****************************************************************************/ +/* value - return the value of the (key,value) pair at the current position */ + +static inline void * +hashtable_iterator_value(struct hashtable_itr *i) +{ + return i->e->v; +} + +/*****************************************************************************/ +/* advance - advance the iterator to the next element + * returns zero if advanced to end of table */ + +int +hashtable_iterator_advance(struct hashtable_itr *itr); + +/*****************************************************************************/ +/* remove - remove current element and advance the iterator to the next element + * NB: if you need the value to free it, read it before + * removing. ie: beware memory leaks! + * returns zero if advanced to end of table */ + +int +hashtable_iterator_remove(struct hashtable_itr *itr); + +/*****************************************************************************/ +/* search - overwrite the supplied iterator, to point to the entry + * matching the supplied key. + h points to the hashtable to be searched. + * returns zero if not found. */ +int +hashtable_iterator_search(struct hashtable_itr *itr, + struct hashtable *h, void *k); + +#define DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \ +int fnname (struct hashtable_itr *i, struct hashtable *h, keytype *k) \ +{ \ + return (hashtable_iterator_search(i,h,k)); \ +} + + + +#endif /* __HASHTABLE_ITR_CWC22__*/ + +/* + * Copyright (c) 2002, 2004, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/hashtable_private.h b/runtime/hashtable_private.h new file mode 100644 index 00000000..10b82da4 --- /dev/null +++ b/runtime/hashtable_private.h @@ -0,0 +1,86 @@ +/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ + +#ifndef __HASHTABLE_PRIVATE_CWC22_H__ +#define __HASHTABLE_PRIVATE_CWC22_H__ + +#include "hashtable.h" + +/*****************************************************************************/ +struct entry +{ + void *k, *v; + unsigned int h; + struct entry *next; +}; + +struct hashtable { + unsigned int tablelength; + struct entry **table; + unsigned int entrycount; + unsigned int loadlimit; + unsigned int primeindex; + unsigned int (*hashfn) (void *k); + int (*eqfn) (void *k1, void *k2); + void (*dest) (void *v); /* destructor for values, if NULL use free() */ +}; + +/*****************************************************************************/ +unsigned int +hash(struct hashtable *h, void *k); + +/*****************************************************************************/ +/* indexFor */ +static inline unsigned int +indexFor(unsigned int tablelength, unsigned int hashvalue) { + return (hashvalue % tablelength); +}; + +/* Only works if tablelength == 2^N */ +/*static inline unsigned int +indexFor(unsigned int tablelength, unsigned int hashvalue) +{ + return (hashvalue & (tablelength - 1u)); +} +*/ + +/*****************************************************************************/ +#define freekey(X) free(X) +/*define freekey(X) ; */ + + +/*****************************************************************************/ + +#endif /* __HASHTABLE_PRIVATE_CWC22_H__*/ + +/* + * Copyright (c) 2002, Christopher Clark + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/runtime/im-helper.h b/runtime/im-helper.h new file mode 100644 index 00000000..5c58dcd8 --- /dev/null +++ b/runtime/im-helper.h @@ -0,0 +1,65 @@ +/* im-helper.h + * This file contains helper constructs that save time writing input modules. It + * assumes some common field names and plumbing. It is intended to be used together + * with module-template.h + * + * File begun on 2011-05-04 by RGerhards + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef IM_HELPER_H_INCLUDED +#define IM_HELPER_H_INCLUDED 1 + + +/* The following function provides a complete implementation to check a + * ruleset and set the actual ruleset pointer. The macro assumes that + * standard field names are used. A functon std_checkRuleset_genErrMsg() + * must be defined to generate error messages in case the ruleset cannot + * be found. + */ +static inline void std_checkRuleset_genErrMsg(modConfData_t *modConf, instanceConf_t *inst); +static inline rsRetVal +std_checkRuleset(modConfData_t *modConf, instanceConf_t *inst) +{ + ruleset_t *pRuleset; + rsRetVal localRet; + DEFiRet; + + inst->pBindRuleset = NULL; /* assume default ruleset */ + + if(inst->pszBindRuleset == NULL) + FINALIZE; + + localRet = ruleset.GetRuleset(modConf->pConf, &pRuleset, inst->pszBindRuleset); + if(localRet == RS_RET_NOT_FOUND) { + std_checkRuleset_genErrMsg(modConf, inst); + } + CHKiRet(localRet); + inst->pBindRuleset = pRuleset; + +finalize_it: + RETiRet; +} + +#endif /* #ifndef IM_HELPER_H_INCLUDED */ + +/* vim:set ai: + */ diff --git a/runtime/libgcry.c b/runtime/libgcry.c new file mode 100644 index 00000000..4772cf47 --- /dev/null +++ b/runtime/libgcry.c @@ -0,0 +1,716 @@ +/* gcry.c - rsyslog's libgcrypt based crypto provider + * + * Copyright 2013 Adiscon GmbH. + * + * We need to store some additional information in support of encryption. + * For this, we create a side-file, which is named like the actual log + * file, but with the suffix ".encinfo" appended. It contains the following + * records: + * IV:<hex> The initial vector used at block start. Also indicates start + * start of block. + * END:<int> The end offset of the block, as uint64_t in decimal notation. + * This is used during encryption to know when the current + * encryption block ends. + * For the current implementation, there must always be an IV record + * followed by an END record. Each records is LF-terminated. Record + * types can simply be extended in the future by specifying new + * types (like "IV") before the colon. + * To identify a file as rsyslog encryption info file, it must start with + * the line "FILETYPE:rsyslog-enrcyption-info" + * There are some size constraints: the recordtype must be 31 bytes at + * most and the actual value (between : and LF) must be 1023 bytes at most. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <gcrypt.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include "rsyslog.h" +#include "srUtils.h" +#include "libgcry.h" + +#define READBUF_SIZE 4096 /* size of the read buffer */ + +static rsRetVal rsgcryBlkBegin(gcryfile gf); + +static rsRetVal +eiWriteRec(gcryfile gf, char *recHdr, size_t lenRecHdr, char *buf, size_t lenBuf) +{ + struct iovec iov[3]; + ssize_t nwritten, towrite; + DEFiRet; + + iov[0].iov_base = recHdr; + iov[0].iov_len = lenRecHdr; + iov[1].iov_base = buf; + iov[1].iov_len = lenBuf; + iov[2].iov_base = "\n"; + iov[2].iov_len = 1; + towrite = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len; + nwritten = writev(gf->fd, iov, sizeof(iov)/sizeof(struct iovec)); + if(nwritten != towrite) { + DBGPRINTF("eiWrite%s: error writing file, towrite %d, " + "nwritten %d\n", recHdr, (int) towrite, (int) nwritten); + ABORT_FINALIZE(RS_RET_EI_WR_ERR); + } + DBGPRINTF("encryption info file %s: written %s, len %d\n", + recHdr, gf->eiName, (int) nwritten); +finalize_it: + RETiRet; +} + +static rsRetVal +eiOpenRead(gcryfile gf) +{ + DEFiRet; + gf->fd = open((char*)gf->eiName, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if(gf->fd == -1) { + ABORT_FINALIZE(errno == ENOENT ? RS_RET_EI_NO_EXISTS : RS_RET_EI_OPN_ERR); + } +finalize_it: + RETiRet; +} + +static rsRetVal +eiRead(gcryfile gf) +{ + ssize_t nRead; + DEFiRet; + + if(gf->readBuf == NULL) { + CHKmalloc(gf->readBuf = malloc(READBUF_SIZE)); + } + + nRead = read(gf->fd, gf->readBuf, READBUF_SIZE); + if(nRead <= 0) { /* TODO: provide specific EOF case? */ + ABORT_FINALIZE(RS_RET_ERR); + } + gf->readBufMaxIdx = (int16_t) nRead; + gf->readBufIdx = 0; + +finalize_it: + RETiRet; +} + + +/* returns EOF on any kind of error */ +static int +eiReadChar(gcryfile gf) +{ + int c; + + if(gf->readBufIdx >= gf->readBufMaxIdx) { + if(eiRead(gf) != RS_RET_OK) { + c = EOF; + goto finalize_it; + } + } + c = gf->readBuf[gf->readBufIdx++]; +finalize_it: + return c; +} + + +static rsRetVal +eiCheckFiletype(gcryfile gf) +{ + char hdrBuf[128]; + size_t toRead, didRead; + sbool bNeedClose = 0; + DEFiRet; + + if(gf->fd == -1) { + bNeedClose = 1; + CHKiRet(eiOpenRead(gf)); + } + + if(Debug) memset(hdrBuf, 0, sizeof(hdrBuf)); /* for dbgprintf below! */ + toRead = sizeof("FILETYPE:")-1 + sizeof(RSGCRY_FILETYPE_NAME)-1 + 1; + didRead = read(gf->fd, hdrBuf, toRead); + if(bNeedClose) { + close(gf->fd); + gf->fd = -1; + } + DBGPRINTF("eiCheckFiletype read %d bytes: '%s'\n", didRead, hdrBuf); + if( didRead != toRead + || strncmp(hdrBuf, "FILETYPE:" RSGCRY_FILETYPE_NAME "\n", toRead)) + iRet = RS_RET_EI_INVLD_FILE; +finalize_it: + RETiRet; +} + +/* rectype/value must be EIF_MAX_*_LEN+1 long! + * returns 0 on success or something else on error/EOF + */ +static rsRetVal +eiGetRecord(gcryfile gf, char *rectype, char *value) +{ + unsigned short i, j; + int c; + DEFiRet; + + c = eiReadChar(gf); + if(c == EOF) { ABORT_FINALIZE(RS_RET_NO_DATA); } + for(i = 0 ; i < EIF_MAX_RECTYPE_LEN ; ++i) { + if(c == ':' || c == EOF) + break; + rectype[i] = c; + c = eiReadChar(gf); + } + if(c != ':') { ABORT_FINALIZE(RS_RET_ERR); } + rectype[i] = '\0'; + j = 0; + for(++i ; i < EIF_MAX_VALUE_LEN ; ++i, ++j) { + c = eiReadChar(gf); + if(c == '\n' || c == EOF) + break; + value[j] = c; + } + if(c != '\n') { ABORT_FINALIZE(RS_RET_ERR); } + value[j] = '\0'; +finalize_it: + RETiRet; +} + +static rsRetVal +eiGetIV(gcryfile gf, uchar *iv, size_t leniv) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + size_t valueLen; + unsigned short i, j; + unsigned char nibble; + DEFiRet; + + CHKiRet(eiGetRecord(gf, rectype, value)); + if(strcmp(rectype, "IV")) { + DBGPRINTF("no IV record found when expected, record type " + "seen is '%s'\n", rectype); + ABORT_FINALIZE(RS_RET_ERR); + } + valueLen = strlen(value); + if(valueLen/2 != leniv) { + DBGPRINTF("length of IV is %d, expected %d\n", + valueLen/2, leniv); + ABORT_FINALIZE(RS_RET_ERR); + } + + for(i = j = 0 ; i < valueLen ; ++i) { + if(value[i] >= '0' && value[i] <= '9') + nibble = value[i] - '0'; + else if(value[i] >= 'a' && value[i] <= 'f') + nibble = value[i] - 'a' + 10; + else { + DBGPRINTF("invalid IV '%s'\n", value); + ABORT_FINALIZE(RS_RET_ERR); + } + if(i % 2 == 0) + iv[j] = nibble << 4; + else + iv[j++] |= nibble; + } +finalize_it: + RETiRet; +} + +static rsRetVal +eiGetEND(gcryfile gf, off64_t *offs) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + DEFiRet; + + CHKiRet(eiGetRecord(gf, rectype, value)); + if(strcmp(rectype, "END")) { + DBGPRINTF("no END record found when expected, record type " + "seen is '%s'\n", rectype); + ABORT_FINALIZE(RS_RET_ERR); + } + *offs = atoll(value); +finalize_it: + RETiRet; +} + +static rsRetVal +eiOpenAppend(gcryfile gf) +{ + rsRetVal localRet; + DEFiRet; + localRet = eiCheckFiletype(gf); + if(localRet == RS_RET_OK) { + gf->fd = open((char*)gf->eiName, + O_WRONLY|O_APPEND|O_NOCTTY|O_CLOEXEC, 0600); + if(gf->fd == -1) { + ABORT_FINALIZE(RS_RET_EI_OPN_ERR); + } + } else if(localRet == RS_RET_EI_NO_EXISTS) { + /* looks like we need to create a new file */ + gf->fd = open((char*)gf->eiName, + O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600); + if(gf->fd == -1) { + ABORT_FINALIZE(RS_RET_EI_OPN_ERR); + } + CHKiRet(eiWriteRec(gf, "FILETYPE:", 9, RSGCRY_FILETYPE_NAME, + sizeof(RSGCRY_FILETYPE_NAME)-1)); + } else { + gf->fd = -1; + ABORT_FINALIZE(localRet); + } + DBGPRINTF("encryption info file %s: opened as #%d\n", + gf->eiName, gf->fd); +finalize_it: + RETiRet; +} + +static rsRetVal +eiWriteIV(gcryfile gf, uchar *iv) +{ + static const char hexchars[16] = + {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + unsigned iSrc, iDst; + char hex[4096]; + DEFiRet; + + if(gf->blkLength > sizeof(hex)/2) { + DBGPRINTF("eiWriteIV: crypto block len way too large, aborting " + "write"); + ABORT_FINALIZE(RS_RET_ERR); + } + + for(iSrc = iDst = 0 ; iSrc < gf->blkLength ; ++iSrc) { + hex[iDst++] = hexchars[iv[iSrc]>>4]; + hex[iDst++] = hexchars[iv[iSrc]&0x0f]; + } + + iRet = eiWriteRec(gf, "IV:", 3, hex, gf->blkLength*2); +finalize_it: + RETiRet; +} + +/* we do not return an error state, as we MUST close the file, + * no matter what happens. + */ +static void +eiClose(gcryfile gf, off64_t offsLogfile) +{ + char offs[21]; + size_t len; + if(gf->fd == -1) + return; + if(gf->openMode == 'w') { + /* 2^64 is 20 digits, so the snprintf buffer is large enough */ + len = snprintf(offs, sizeof(offs), "%lld", offsLogfile); + eiWriteRec(gf, "END:", 4, offs, len); + } + gcry_cipher_close(gf->chd); + free(gf->readBuf); + close(gf->fd); + gf->fd = -1; + DBGPRINTF("encryption info file %s: closed\n", gf->eiName); +} + +/* this returns the number of bytes left inside the block or -1, if the block + * size is unbounded. The function automatically handles end-of-block and begins + * to read the next block in this case. + */ +rsRetVal +gcryfileGetBytesLeftInBlock(gcryfile gf, ssize_t *left) +{ + DEFiRet; + if(gf->bytesToBlkEnd == 0) { + DBGPRINTF("libgcry: end of current crypto block\n"); + gcry_cipher_close(gf->chd); + CHKiRet(rsgcryBlkBegin(gf)); + } + *left = gf->bytesToBlkEnd; +finalize_it: + // TODO: remove once this code is sufficiently well-proven + DBGPRINTF("gcryfileGetBytesLeftInBlock returns %lld, iRet %d\n", (long long) *left, iRet); + RETiRet; +} + +/* this is a special functon for use by the rsyslog disk queue subsystem. It + * needs to have the capability to delete state when a queue file is rolled + * over. This simply generates the file name and deletes it. It must take care + * of "all" state files, which currently happens to be a single one. + */ +rsRetVal +gcryfileDeleteState(uchar *logfn) +{ + char fn[MAXFNAME+1]; + DEFiRet; + snprintf(fn, sizeof(fn), "%s%s", logfn, ENCINFO_SUFFIX); + fn[MAXFNAME] = '\0'; /* be on save side */ + DBGPRINTF("crypto provider deletes state file '%s' on request\n", fn); + unlink(fn); + RETiRet; +} + +static rsRetVal +gcryfileConstruct(gcryctx ctx, gcryfile *pgf, uchar *logfn) +{ + char fn[MAXFNAME+1]; + gcryfile gf; + DEFiRet; + + CHKmalloc(gf = calloc(1, sizeof(struct gcryfile_s))); + gf->ctx = ctx; + gf->fd = -1; + snprintf(fn, sizeof(fn), "%s%s", logfn, ENCINFO_SUFFIX); + fn[MAXFNAME] = '\0'; /* be on save side */ + gf->eiName = (uchar*) strdup(fn); + *pgf = gf; +finalize_it: + RETiRet; +} + + +gcryctx +gcryCtxNew(void) +{ + gcryctx ctx; + ctx = calloc(1, sizeof(struct gcryctx_s)); + ctx->algo = GCRY_CIPHER_AES128; + ctx->mode = GCRY_CIPHER_MODE_CBC; + return ctx; +} + +int +gcryfileDestruct(gcryfile gf, off64_t offsLogfile) +{ + int r = 0; + if(gf == NULL) + goto done; + + DBGPRINTF("libgcry: close file %s\n", gf->eiName); + eiClose(gf, offsLogfile); + if(gf->bDeleteOnClose) { + DBGPRINTF("unlink file '%s' due to bDeleteOnClose set\n", gf->eiName); + unlink((char*)gf->eiName); + } + free(gf->eiName); + free(gf); +done: return r; +} +void +rsgcryCtxDel(gcryctx ctx) +{ + if(ctx != NULL) { + free(ctx); + } +} + +static inline void +addPadding(gcryfile pF, uchar *buf, size_t *plen) +{ + unsigned i; + size_t nPad; + nPad = (pF->blkLength - *plen % pF->blkLength) % pF->blkLength; + DBGPRINTF("libgcry: addPadding %d chars, blkLength %d, mod %d, pad %d\n", + *plen, pF->blkLength, *plen % pF->blkLength, nPad); + for(i = 0 ; i < nPad ; ++i) + buf[(*plen)+i] = 0x00; + (*plen)+= nPad; +} + +static inline void +removePadding(uchar *buf, size_t *plen) +{ + unsigned len = (unsigned) *plen; + unsigned iSrc, iDst; + uchar *frstNUL; + + frstNUL = (uchar*)strchr((char*)buf, 0x00); + if(frstNUL == NULL) + goto done; + iDst = iSrc = frstNUL - buf; + + while(iSrc < len) { + if(buf[iSrc] != 0x00) + buf[iDst++] = buf[iSrc]; + ++iSrc; + } + + *plen = iDst; +done: return; +} + +/* returns 0 on succes, positive if key length does not match and key + * of return value size is required. + */ +int +rsgcrySetKey(gcryctx ctx, unsigned char *key, uint16_t keyLen) +{ + uint16_t reqKeyLen; + int r; + + reqKeyLen = gcry_cipher_get_algo_keylen(ctx->algo); + if(keyLen != reqKeyLen) { + r = reqKeyLen; + goto done; + } + ctx->keyLen = keyLen; + ctx->key = malloc(keyLen); + memcpy(ctx->key, key, keyLen); + r = 0; +done: return r; +} + +rsRetVal +rsgcrySetMode(gcryctx ctx, uchar *modename) +{ + int mode; + DEFiRet; + + mode = rsgcryModename2Mode((char *)modename); + if(mode == GCRY_CIPHER_MODE_NONE) { + ABORT_FINALIZE(RS_RET_CRY_INVLD_MODE); + } + ctx->mode = mode; +finalize_it: + RETiRet; +} + +rsRetVal +rsgcrySetAlgo(gcryctx ctx, uchar *algoname) +{ + int algo; + DEFiRet; + + algo = rsgcryAlgoname2Algo((char *)algoname); + if(algo == GCRY_CIPHER_NONE) { + ABORT_FINALIZE(RS_RET_CRY_INVLD_ALGO); + } + ctx->algo = algo; +finalize_it: + RETiRet; +} + +/* As of some Linux and security expert I spoke to, /dev/urandom + * provides very strong random numbers, even if it runs out of + * entropy. As far as he knew, this is save for all applications + * (and he had good proof that I currently am not permitted to + * reproduce). -- rgerhards, 2013-03-04 + */ +void +seedIV(gcryfile gf, uchar **iv) +{ + int fd; + + *iv = malloc(gf->blkLength); /* do NOT zero-out! */ + /* if we cannot obtain data from /dev/urandom, we use whatever + * is present at the current memory location as random data. Of + * course, this is very weak and we should consider a different + * option, especially when not running under Linux (for Linux, + * unavailability of /dev/urandom is just a theoretic thing, it + * will always work...). -- TODO -- rgerhards, 2013-03-06 + */ + if((fd = open("/dev/urandom", O_RDONLY)) > 0) { + if(read(fd, *iv, gf->blkLength)) {}; /* keep compiler happy */ + close(fd); + } +} + +static inline rsRetVal +readIV(gcryfile gf, uchar **iv) +{ + rsRetVal localRet; + DEFiRet; + + if(gf->fd == -1) { + while(gf->fd == -1) { + localRet = eiOpenRead(gf); + if(localRet == RS_RET_EI_NO_EXISTS) { + /* wait until it is created */ + srSleep(0, 10000); + } else { + CHKiRet(localRet); + } + } + CHKiRet(eiCheckFiletype(gf)); + } + *iv = malloc(gf->blkLength); /* do NOT zero-out! */ + CHKiRet(eiGetIV(gf, *iv, (size_t) gf->blkLength)); +finalize_it: + RETiRet; +} + +/* this tries to read the END record. HOWEVER, no such record may be + * present, which is the case if we handle a currently-written to queue + * file. On the other hand, the queue file may contain multiple blocks. So + * what we do is try to see if there is a block end or not - and set the + * status accordingly. Note that once we found no end-of-block, we will never + * retry. This is because that case can never happen under current queue + * implementations. -- gerhards, 2013-05-16 + */ +static inline rsRetVal +readBlkEnd(gcryfile gf) +{ + off64_t blkEnd; + DEFiRet; + + iRet = eiGetEND(gf, &blkEnd); + if(iRet == RS_RET_OK) { + gf->bytesToBlkEnd = (ssize_t) blkEnd; + } else if(iRet == RS_RET_NO_DATA) { + gf->bytesToBlkEnd = -1; + } else { + FINALIZE; + } + +finalize_it: + RETiRet; +} + + +/* Read the block begin metadata and set our state variables accordingly. Can also + * be used to init the first block in write case. + */ +static rsRetVal +rsgcryBlkBegin(gcryfile gf) +{ + gcry_error_t gcryError; + uchar *iv = NULL; + DEFiRet; + + gcryError = gcry_cipher_open(&gf->chd, gf->ctx->algo, gf->ctx->mode, 0); + if (gcryError) { + DBGPRINTF("gcry_cipher_open failed: %s/%s\n", + gcry_strsource(gcryError), gcry_strerror(gcryError)); + ABORT_FINALIZE(RS_RET_ERR); + } + + gcryError = gcry_cipher_setkey(gf->chd, gf->ctx->key, gf->ctx->keyLen); + if (gcryError) { + DBGPRINTF("gcry_cipher_setkey failed: %s/%s\n", + gcry_strsource(gcryError), gcry_strerror(gcryError)); + ABORT_FINALIZE(RS_RET_ERR); + } + + if(gf->openMode == 'r') { + readIV(gf, &iv); + readBlkEnd(gf); + } else { + seedIV(gf, &iv); + } + + gcryError = gcry_cipher_setiv(gf->chd, iv, gf->blkLength); + if (gcryError) { + DBGPRINTF("gcry_cipher_setiv failed: %s/%s\n", + gcry_strsource(gcryError), gcry_strerror(gcryError)); + ABORT_FINALIZE(RS_RET_ERR); + } + + if(gf->openMode == 'w') { + CHKiRet(eiOpenAppend(gf)); + CHKiRet(eiWriteIV(gf, iv)); + } +finalize_it: + free(iv); + RETiRet; +} + +rsRetVal +rsgcryInitCrypt(gcryctx ctx, gcryfile *pgf, uchar *fname, char openMode) +{ + gcryfile gf = NULL; + DEFiRet; + + CHKiRet(gcryfileConstruct(ctx, &gf, fname)); + gf->openMode = openMode; + gf->blkLength = gcry_cipher_get_algo_blklen(ctx->algo); + CHKiRet(rsgcryBlkBegin(gf)); + *pgf = gf; +finalize_it: + if(iRet != RS_RET_OK && gf != NULL) + gcryfileDestruct(gf, -1); + RETiRet; +} + +rsRetVal +rsgcryEncrypt(gcryfile pF, uchar *buf, size_t *len) +{ + int gcryError; + DEFiRet; + + if(*len == 0) + FINALIZE; + + addPadding(pF, buf, len); + gcryError = gcry_cipher_encrypt(pF->chd, buf, *len, NULL, 0); + if(gcryError) { + dbgprintf("gcry_cipher_encrypt failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + ABORT_FINALIZE(RS_RET_ERR); + } +finalize_it: + RETiRet; +} + +/* TODO: handle multiple blocks + * test-read END record; if present, store offset, else unbounded (current active block) + * when decrypting, check if bound is reached. If yes, split into two blocks, get new IV for + * second one. + */ +rsRetVal +rsgcryDecrypt(gcryfile pF, uchar *buf, size_t *len) +{ + gcry_error_t gcryError; + DEFiRet; + + if(pF->bytesToBlkEnd != -1) + pF->bytesToBlkEnd -= *len; + gcryError = gcry_cipher_decrypt(pF->chd, buf, *len, NULL, 0); + if(gcryError) { + DBGPRINTF("gcry_cipher_decrypt failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + ABORT_FINALIZE(RS_RET_ERR); + } + removePadding(buf, len); + // TODO: remove dbgprintf once things are sufficently stable -- rgerhards, 2013-05-16 + dbgprintf("libgcry: decrypted, bytesToBlkEnd %lld, buffer is now '%50.50s'\n", (long long) pF->bytesToBlkEnd, buf); + +finalize_it: + RETiRet; +} + + + +/* module-init dummy for potential later use */ +int +rsgcryInit(void) +{ + return 0; +} + +/* module-deinit dummy for potential later use */ +void +rsgcryExit(void) +{ + return; +} diff --git a/runtime/libgcry.h b/runtime/libgcry.h new file mode 100644 index 00000000..2f700554 --- /dev/null +++ b/runtime/libgcry.h @@ -0,0 +1,121 @@ +/* libgcry.h - rsyslog's guardtime support library + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_LIBGCRY_H +#define INCLUDED_LIBGCRY_H +#include <stdint.h> + + +struct gcryctx_s { + uchar *key; + size_t keyLen; + int algo; + int mode; +}; +typedef struct gcryctx_s *gcryctx; +typedef struct gcryfile_s *gcryfile; + +/* this describes a file, as far as libgcry is concerned */ +struct gcryfile_s { + gcry_cipher_hd_t chd; /* cypher handle */ + size_t blkLength; /* size of low-level crypto block */ + uchar *eiName; /* name of .encinfo file */ + int fd; /* descriptor of .encinfo file (-1 if not open) */ + char openMode; /* 'r': read, 'w': write */ + gcryctx ctx; + uchar *readBuf; + int16_t readBufIdx; + int16_t readBufMaxIdx; + int8_t bDeleteOnClose; /* for queue support, similar to stream subsys */ + ssize_t bytesToBlkEnd; /* number of bytes remaining in current crypto block + -1 means -> no end (still being writen to, queue files), + 0 means -> end of block, new one must be started. */ +}; + +int gcryGetKeyFromFile(char *fn, char **key, unsigned *keylen); +int rsgcryInit(void); +void rsgcryExit(void); +int rsgcrySetKey(gcryctx ctx, unsigned char *key, uint16_t keyLen); +rsRetVal rsgcrySetMode(gcryctx ctx, uchar *algoname); +rsRetVal rsgcrySetAlgo(gcryctx ctx, uchar *modename); +gcryctx gcryCtxNew(void); +void rsgcryCtxDel(gcryctx ctx); +int gcryfileDestruct(gcryfile gf, off64_t offsLogfile); +rsRetVal rsgcryInitCrypt(gcryctx ctx, gcryfile *pgf, uchar *fname, char openMode); +rsRetVal rsgcryEncrypt(gcryfile pF, uchar *buf, size_t *len); +rsRetVal rsgcryDecrypt(gcryfile pF, uchar *buf, size_t *len); +int gcryGetKeyFromProg(char *cmd, char **key, unsigned *keylen); +rsRetVal gcryfileDeleteState(uchar *fn); +rsRetVal gcryfileGetBytesLeftInBlock(gcryfile gf, ssize_t *left); + +/* error states */ +#define RSGCRYE_EI_OPEN 1 /* error opening .encinfo file */ +#define RSGCRYE_OOM 4 /* ran out of memory */ + +#define EIF_MAX_RECTYPE_LEN 31 /* max length of record types */ +#define EIF_MAX_VALUE_LEN 1023 /* max length of value types */ +#define RSGCRY_FILETYPE_NAME "rsyslog-enrcyption-info" +#define ENCINFO_SUFFIX ".encinfo" + +/* Note: gf may validly be NULL, e.g. if file has not yet been opened! */ +static inline void +gcryfileSetDeleteOnClose(gcryfile gf, int val) +{ + if(gf != NULL) + gf->bDeleteOnClose = val; +} + +static inline int +rsgcryAlgoname2Algo(char *algoname) { + if(!strcmp((char*)algoname, "3DES")) return GCRY_CIPHER_3DES; + if(!strcmp((char*)algoname, "CAST5")) return GCRY_CIPHER_CAST5; + if(!strcmp((char*)algoname, "BLOWFISH")) return GCRY_CIPHER_BLOWFISH; + if(!strcmp((char*)algoname, "AES128")) return GCRY_CIPHER_AES128; + if(!strcmp((char*)algoname, "AES192")) return GCRY_CIPHER_AES192; + if(!strcmp((char*)algoname, "AES256")) return GCRY_CIPHER_AES256; + if(!strcmp((char*)algoname, "TWOFISH")) return GCRY_CIPHER_TWOFISH; + if(!strcmp((char*)algoname, "TWOFISH128")) return GCRY_CIPHER_TWOFISH128; + if(!strcmp((char*)algoname, "ARCFOUR")) return GCRY_CIPHER_ARCFOUR; + if(!strcmp((char*)algoname, "DES")) return GCRY_CIPHER_DES; + if(!strcmp((char*)algoname, "SERPENT128")) return GCRY_CIPHER_SERPENT128; + if(!strcmp((char*)algoname, "SERPENT192")) return GCRY_CIPHER_SERPENT192; + if(!strcmp((char*)algoname, "SERPENT256")) return GCRY_CIPHER_SERPENT256; + if(!strcmp((char*)algoname, "RFC2268_40")) return GCRY_CIPHER_RFC2268_40; + if(!strcmp((char*)algoname, "SEED")) return GCRY_CIPHER_SEED; + if(!strcmp((char*)algoname, "CAMELLIA128")) return GCRY_CIPHER_CAMELLIA128; + if(!strcmp((char*)algoname, "CAMELLIA192")) return GCRY_CIPHER_CAMELLIA192; + if(!strcmp((char*)algoname, "CAMELLIA256")) return GCRY_CIPHER_CAMELLIA256; + return GCRY_CIPHER_NONE; +} + +static inline int +rsgcryModename2Mode(char *modename) { + if(!strcmp((char*)modename, "ECB")) return GCRY_CIPHER_MODE_ECB; + if(!strcmp((char*)modename, "CFB")) return GCRY_CIPHER_MODE_CFB; + if(!strcmp((char*)modename, "CBC")) return GCRY_CIPHER_MODE_CBC; + if(!strcmp((char*)modename, "STREAM")) return GCRY_CIPHER_MODE_STREAM; + if(!strcmp((char*)modename, "OFB")) return GCRY_CIPHER_MODE_OFB; + if(!strcmp((char*)modename, "CTR")) return GCRY_CIPHER_MODE_CTR; +# ifdef GCRY_CIPHER_MODE_AESWRAP + if(!strcmp((char*)modename, "AESWRAP")) return GCRY_CIPHER_MODE_AESWRAP; +# endif + return GCRY_CIPHER_MODE_NONE; +} +#endif /* #ifndef INCLUDED_LIBGCRY_H */ diff --git a/runtime/libgcry_common.c b/runtime/libgcry_common.c new file mode 100644 index 00000000..07a524dc --- /dev/null +++ b/runtime/libgcry_common.c @@ -0,0 +1,206 @@ +/* libgcry_common.c + * This file hosts functions both being used by the rsyslog runtime as + * well as tools who do not use the runtime (so we can maintain the + * code at a single place). + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <gcrypt.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include "rsyslog.h" /* we need data typedefs */ +#include "libgcry.h" + + +/* read a key from a key file + * @param[out] key - key buffer, must be freed by caller + * @param[out] keylen - length of buffer + * @returns 0 if OK, something else otherwise (we do not use + * iRet as this is also called from non-rsyslog w/o runtime) + * The key length is limited to 64KiB to prevent DoS. + * Note well: key is a blob, not a C string (NUL may be present!) + */ +int +gcryGetKeyFromFile(char *fn, char **key, unsigned *keylen) +{ + struct stat sb; + int fd; + int r; + + if(stat(fn, &sb) == -1) { + r = 1; goto done; + } + if((sb.st_mode & S_IFMT) != S_IFREG) { + r = 2; goto done; + } + if(sb.st_size > 64*1024) { + r = 3; goto done; + } + if((*key = malloc(sb.st_size)) == NULL) { + r = -1; goto done; + } + if((fd = open(fn, O_RDONLY)) < 0) { + r = 4; goto done; + } + if(read(fd, *key, sb.st_size) != sb.st_size) { + r = 5; goto done; + } + *keylen = sb.st_size; + close(fd); + r = 0; +done: return r; +} + + +/* execute the child process (must be called in child context + * after fork). + */ + +static void +execKeyScript(char *cmd, int pipefd[]) +{ + char *newargv[] = { NULL }; + char *newenviron[] = { NULL }; + + dup2(pipefd[0], STDIN_FILENO); + dup2(pipefd[1], STDOUT_FILENO); + + /* finally exec child */ +fprintf(stderr, "pre execve: %s\n", cmd); + execve(cmd, newargv, newenviron); + /* switch to? + execlp((char*)program, (char*) program, (char*)arg, NULL); + */ + + /* we should never reach this point, but if we do, we terminate */ + return; +} + + +static int +openPipe(char *cmd, int *fd) +{ + int pipefd[2]; + pid_t cpid; + int r; + + if(pipe(pipefd) == -1) { + r = 1; goto done; + } + + cpid = fork(); + if(cpid == -1) { + r = 1; goto done; + } + + if(cpid == 0) { + /* we are the child */ + execKeyScript(cmd, pipefd); + exit(1); + } + + close(pipefd[1]); + *fd = pipefd[0]; + r = 0; +done: return r; +} + + +/* Read a character from the program's output. */ +// TODO: highly unoptimized version, should be used in buffered +// mode +static int +readProgChar(int fd, char *c) +{ + int r; + if(read(fd, c, 1) != 1) { + r = 1; goto done; + } + r = 0; +done: return r; +} + +/* Read a line from the script. Line is terminated by LF, which + * is NOT put into the buffer. + * buf must be 64KiB + */ +static int +readProgLine(int fd, char *buf) +{ + char c; + int r; + unsigned i; + + for(i = 0 ; i < 64*1024 ; ++i) { + if((r = readProgChar(fd, &c)) != 0) goto done; + if(c == '\n') + break; + buf[i] = c; + }; + if(i >= 64*1024) { + r = 1; goto done; + } + buf[i] = '\0'; + r = 0; +done: return r; +} +static int +readProgKey(int fd, char *buf, unsigned keylen) +{ + char c; + int r; + unsigned i; + + for(i = 0 ; i < keylen ; ++i) { + if((r = readProgChar(fd, &c)) != 0) goto done; + buf[i] = c; + }; + r = 0; +done: return r; +} + +int +gcryGetKeyFromProg(char *cmd, char **key, unsigned *keylen) +{ + int r; + int fd; + char rcvBuf[64*1024]; + + if((r = openPipe(cmd, &fd)) != 0) goto done; + if((r = readProgLine(fd, rcvBuf)) != 0) goto done; + if(strcmp(rcvBuf, "RSYSLOG-KEY-PROVIDER:0")) { + r = 2; goto done; + } + if((r = readProgLine(fd, rcvBuf)) != 0) goto done; + *keylen = atoi(rcvBuf); + if((*key = malloc(*keylen)) == NULL) { + r = -1; goto done; + } + if((r = readProgKey(fd, *key, *keylen)) != 0) goto done; +done: return r; +} diff --git a/runtime/librsgt.c b/runtime/librsgt.c new file mode 100644 index 00000000..85fc7742 --- /dev/null +++ b/runtime/librsgt.c @@ -0,0 +1,845 @@ +/* librsgt.c - rsyslog's guardtime support library + * + * Regarding the online algorithm for Merkle tree signing. Expected + * calling sequence is: + * + * sigblkConstruct + * for each signature block: + * sigblkInit + * for each record: + * sigblkAddRecord + * sigblkFinish + * sigblkDestruct + * + * Obviously, the next call after sigblkFinsh must either be to + * sigblkInit or sigblkDestruct (if no more signature blocks are + * to be emitted, e.g. on file close). sigblkDestruct saves state + * information (most importantly last block hash) and sigblkConstruct + * reads (or initilizes if not present) it. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#define MAXFNAME 1024 + +#include <gt_http.h> + +#include "librsgt.h" + +typedef unsigned char uchar; +#ifndef VERSION +#define VERSION "no-version" +#endif + + +static void +reportErr(gtctx ctx, char *errmsg) +{ + if(ctx->errFunc == NULL) + goto done; + ctx->errFunc(ctx->usrptr, (uchar*)errmsg); +done: return; +} + +static void +reportGTAPIErr(gtctx ctx, gtfile gf, char *apiname, int ecode) +{ + char errbuf[4096]; + snprintf(errbuf, sizeof(errbuf), "%s[%s:%d]: %s", + (gf == NULL) ? (uchar*)"" : gf->sigfilename, + apiname, ecode, GT_getErrorString(ecode)); + errbuf[sizeof(errbuf)-1] = '\0'; + reportErr(ctx, errbuf); +} + +void +rsgtsetErrFunc(gtctx ctx, void (*func)(void*, uchar *), void *usrptr) +{ + ctx->usrptr = usrptr; + ctx->errFunc = func; +} + +imprint_t * +rsgtImprintFromGTDataHash(GTDataHash *hash) +{ + imprint_t *imp; + + if((imp = calloc(1, sizeof(imprint_t))) == NULL) { + goto done; + } + imp->hashID = hashIdentifier(hash->algorithm), + imp->len = hash->digest_length; + if((imp->data = (uint8_t*)malloc(imp->len)) == NULL) { + free(imp); imp = NULL; goto done; + } + memcpy(imp->data, hash->digest, imp->len); +done: return imp; +} + +void +rsgtimprintDel(imprint_t *imp) +{ + if(imp != NULL) { + free(imp->data), + free(imp); + } +} + +int +rsgtInit(char *usragent) +{ + int r = 0; + int ret = GT_OK; + + ret = GT_init(); + if(ret != GT_OK) { + r = 1; + goto done; + } + ret = GTHTTP_init(usragent, 1); + if(ret != GT_OK) { + r = 1; + goto done; + } +done: return r; +} + +void +rsgtExit(void) +{ + GTHTTP_finalize(); + GT_finalize(); +} + + +static inline gtfile +rsgtfileConstruct(gtctx ctx) +{ + gtfile gf; + if((gf = calloc(1, sizeof(struct gtfile_s))) == NULL) + goto done; + gf->ctx = ctx; + gf->hashAlg = ctx->hashAlg; + gf->blockSizeLimit = ctx->blockSizeLimit; + gf->bKeepRecordHashes = ctx->bKeepRecordHashes; + gf->bKeepTreeHashes = ctx->bKeepTreeHashes; + gf->x_prev = NULL; + +done: return gf; +} + +static inline int +tlvbufPhysWrite(gtfile gf) +{ + ssize_t lenBuf; + ssize_t iTotalWritten; + ssize_t iWritten; + char *pWriteBuf; + int r = 0; + + lenBuf = gf->tlvIdx; + pWriteBuf = gf->tlvBuf; + iTotalWritten = 0; + do { + iWritten = write(gf->fd, pWriteBuf, lenBuf); + if(iWritten < 0) { + iWritten = 0; /* we have written NO bytes! */ + if(errno == EINTR) { + /*NO ERROR, just continue */; + } else { + reportErr(gf->ctx, "signature file write error"); + r = RSGTE_IO; + goto finalize_it; + } + } + /* advance buffer to next write position */ + iTotalWritten += iWritten; + lenBuf -= iWritten; + pWriteBuf += iWritten; + } while(lenBuf > 0); /* Warning: do..while()! */ + +finalize_it: + gf->tlvIdx = 0; + return r; +} + +static inline int +tlvbufChkWrite(gtfile gf) +{ + if(gf->tlvIdx == sizeof(gf->tlvBuf)) { + return tlvbufPhysWrite(gf); + } + return 0; +} + + +/* write to TLV file buffer. If buffer is full, an actual call occurs. Else + * output is written only on flush or close. + */ +static inline int +tlvbufAddOctet(gtfile gf, int8_t octet) +{ + int r; + r = tlvbufChkWrite(gf); + if(r != 0) goto done; + gf->tlvBuf[gf->tlvIdx++] = octet; +done: return r; +} +static inline int +tlvbufAddOctetString(gtfile gf, uint8_t *octet, int size) +{ + int i, r = 0; + for(i = 0 ; i < size ; ++i) { + r = tlvbufAddOctet(gf, octet[i]); + if(r != 0) goto done; + } +done: return r; +} +/* return the actual length in to-be-written octets of an integer */ +static inline uint8_t +tlvbufGetInt64OctetSize(uint64_t val) +{ + if(val >> 56) + return 8; + if((val >> 48) & 0xff) + return 7; + if((val >> 40) & 0xff) + return 6; + if((val >> 32) & 0xff) + return 5; + if((val >> 24) & 0xff) + return 4; + if((val >> 16) & 0xff) + return 3; + if((val >> 8) & 0xff) + return 2; + return 1; +} +static inline int +tlvbufAddInt64(gtfile gf, uint64_t val) +{ + uint8_t doWrite = 0; + int r; + if(val >> 56) { + r = tlvbufAddOctet(gf, (val >> 56) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + if(doWrite || ((val >> 48) & 0xff)) { + r = tlvbufAddOctet(gf, (val >> 48) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + if(doWrite || ((val >> 40) & 0xff)) { + r = tlvbufAddOctet(gf, (val >> 40) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + if(doWrite || ((val >> 32) & 0xff)) { + r = tlvbufAddOctet(gf, (val >> 32) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + if(doWrite || ((val >> 24) & 0xff)) { + r = tlvbufAddOctet(gf, (val >> 24) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + if(doWrite || ((val >> 16) & 0xff)) { + r = tlvbufAddOctet(gf, (val >> 16) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + if(doWrite || ((val >> 8) & 0xff)) { + r = tlvbufAddOctet(gf, (val >> 8) & 0xff), doWrite = 1; + if(r != 0) goto done; + } + r = tlvbufAddOctet(gf, val & 0xff); +done: return r; +} + + +int +tlv8Write(gtfile gf, int flags, int tlvtype, int len) +{ + int r; + r = tlvbufAddOctet(gf, (flags << 5)|tlvtype); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, len & 0xff); +done: return r; +} + +int +tlv16Write(gtfile gf, int flags, int tlvtype, uint16_t len) +{ + uint16_t typ; + int r; + typ = ((flags|1) << 15)|tlvtype; + r = tlvbufAddOctet(gf, typ >> 8); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, typ & 0xff); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, (len >> 8) & 0xff); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, len & 0xff); +done: return r; +} + +int +tlvFlush(gtfile gf) +{ + return (gf->tlvIdx == 0) ? 0 : tlvbufPhysWrite(gf); +} + +int +tlvWriteHash(gtfile gf, uint16_t tlvtype, GTDataHash *rec) +{ + unsigned tlvlen; + int r; + tlvlen = 1 + rec->digest_length; + r = tlv16Write(gf, 0x00, tlvtype, tlvlen); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg)); + if(r != 0) goto done; + r = tlvbufAddOctetString(gf, rec->digest, rec->digest_length); +done: return r; +} + +int +tlvWriteBlockSig(gtfile gf, uchar *der, uint16_t lenDer) +{ + unsigned tlvlen; + uint8_t tlvlenRecords; + int r; + + tlvlenRecords = tlvbufGetInt64OctetSize(gf->nRecords); + tlvlen = 2 + 1 /* hash algo TLV */ + + 2 + hashOutputLengthOctets(gf->hashAlg) /* iv */ + + 2 + 1 + gf->lenBlkStrtHash /* last hash */ + + 2 + tlvlenRecords /* rec-count */ + + 4 + lenDer /* rfc-3161 */; + /* write top-level TLV object (block-sig */ + r = tlv16Write(gf, 0x00, 0x0902, tlvlen); + if(r != 0) goto done; + /* and now write the children */ + //FIXME: flags??? + /* hash-algo */ + r = tlv8Write(gf, 0x00, 0x00, 1); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg)); + if(r != 0) goto done; + /* block-iv */ + r = tlv8Write(gf, 0x00, 0x01, hashOutputLengthOctets(gf->hashAlg)); + if(r != 0) goto done; + r = tlvbufAddOctetString(gf, gf->IV, hashOutputLengthOctets(gf->hashAlg)); + if(r != 0) goto done; + /* last-hash */ + r = tlv8Write(gf, 0x00, 0x02, gf->lenBlkStrtHash+1); + if(r != 0) goto done; + r = tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg)); + if(r != 0) goto done; + r = tlvbufAddOctetString(gf, gf->blkStrtHash, gf->lenBlkStrtHash); + if(r != 0) goto done; + /* rec-count */ + r = tlv8Write(gf, 0x00, 0x03, tlvlenRecords); + if(r != 0) goto done; + r = tlvbufAddInt64(gf, gf->nRecords); + if(r != 0) goto done; + /* rfc-3161 */ + r = tlv16Write(gf, 0x00, 0x906, lenDer); + if(r != 0) goto done; + r = tlvbufAddOctetString(gf, der, lenDer); +done: return r; +} + +/* support for old platforms - graceful degrade */ +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +/* read rsyslog log state file; if we cannot access it or the + * contents looks invalid, we flag it as non-present (and thus + * begin a new hash chain). + * The context is initialized accordingly. + */ +static void +readStateFile(gtfile gf) +{ + int fd; + struct rsgtstatefile sf; + + fd = open((char*)gf->statefilename, O_RDONLY|O_NOCTTY|O_CLOEXEC, 0600); + if(fd == -1) goto err; + + if(read(fd, &sf, sizeof(sf)) != sizeof(sf)) goto err; + if(strncmp(sf.hdr, "GTSTAT10", 8)) goto err; + + gf->lenBlkStrtHash = sf.lenHash; + gf->blkStrtHash = calloc(1, gf->lenBlkStrtHash); + if(read(fd, gf->blkStrtHash, gf->lenBlkStrtHash) + != gf->lenBlkStrtHash) { + free(gf->blkStrtHash); + goto err; + } +return; + +err: + gf->lenBlkStrtHash = hashOutputLengthOctets(gf->hashAlg); + gf->blkStrtHash = calloc(1, gf->lenBlkStrtHash); +} + +/* persist all information that we need to re-open and append + * to a log signature file. + */ +static void +writeStateFile(gtfile gf) +{ + int fd; + struct rsgtstatefile sf; + + fd = open((char*)gf->statefilename, + O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0600); + if(fd == -1) + goto done; + + memcpy(sf.hdr, "GTSTAT10", 8); + sf.hashID = hashIdentifier(gf->hashAlg); + sf.lenHash = gf->x_prev->len; + /* if the write fails, we cannot do anything against that. We check + * the condition just to keep the compiler happy. + */ + if(write(fd, &sf, sizeof(sf))){}; + if(write(fd, gf->x_prev->data, gf->x_prev->len)){}; + close(fd); +done: return; +} + + +int +tlvClose(gtfile gf) +{ + int r; + r = tlvFlush(gf); + close(gf->fd); + gf->fd = -1; + writeStateFile(gf); + return r; +} + + +/* note: if file exists, the last hash for chaining must + * be read from file. + */ +int +tlvOpen(gtfile gf, char *hdr, unsigned lenHdr) +{ + int r = 0; + gf->fd = open((char*)gf->sigfilename, + O_WRONLY|O_APPEND|O_NOCTTY|O_CLOEXEC, 0600); + if(gf->fd == -1) { + /* looks like we need to create a new file */ + gf->fd = open((char*)gf->sigfilename, + O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600); + if(gf->fd == -1) { + r = RSGTE_IO; + goto done; + } + memcpy(gf->tlvBuf, hdr, lenHdr); + gf->tlvIdx = lenHdr; + } else { + gf->tlvIdx = 0; /* header already present! */ + } + /* we now need to obtain the last previous hash, so that + * we can continue the hash chain. We do not check for error + * as a state file error can be recovered by graceful degredation. + */ + readStateFile(gf); +done: return r; +} + +/* + * As of some Linux and security expert I spoke to, /dev/urandom + * provides very strong random numbers, even if it runs out of + * entropy. As far as he knew, this is save for all applications + * (and he had good proof that I currently am not permitted to + * reproduce). -- rgerhards, 2013-03-04 + */ +void +seedIV(gtfile gf) +{ + int hashlen; + int fd; + + hashlen = hashOutputLengthOctets(gf->hashAlg); + gf->IV = malloc(hashlen); /* do NOT zero-out! */ + /* if we cannot obtain data from /dev/urandom, we use whatever + * is present at the current memory location as random data. Of + * course, this is very weak and we should consider a different + * option, especially when not running under Linux (for Linux, + * unavailability of /dev/urandom is just a theoretic thing, it + * will always work...). -- TODO -- rgerhards, 2013-03-06 + */ + if((fd = open("/dev/urandom", O_RDONLY)) > 0) { + if(read(fd, gf->IV, hashlen)) {}; /* keep compiler happy */ + close(fd); + } +} + +gtctx +rsgtCtxNew(void) +{ + gtctx ctx; + ctx = calloc(1, sizeof(struct gtctx_s)); + ctx->hashAlg = GT_HASHALG_SHA256; + ctx->errFunc = NULL; + ctx->usrptr = NULL; + ctx->timestamper = strdup( + "http://stamper.guardtime.net/gt-signingservice"); + return ctx; +} + +/* either returns gtfile object or NULL if something went wrong */ +gtfile +rsgtCtxOpenFile(gtctx ctx, unsigned char *logfn) +{ + gtfile gf; + char fn[MAXFNAME+1]; + + if((gf = rsgtfileConstruct(ctx)) == NULL) + goto done; + + snprintf(fn, sizeof(fn), "%s.gtsig", logfn); + fn[MAXFNAME] = '\0'; /* be on save side */ + gf->sigfilename = (uchar*) strdup(fn); + snprintf(fn, sizeof(fn), "%s.gtstate", logfn); + fn[MAXFNAME] = '\0'; /* be on save side */ + gf->statefilename = (uchar*) strdup(fn); + if(tlvOpen(gf, LOGSIGHDR, sizeof(LOGSIGHDR)-1) != 0) { + reportErr(ctx, "signature file open failed"); + gf = NULL; + } +done: return gf; +} + + +/* returns 0 on succes, 1 if algo is unknown */ +int +rsgtSetHashFunction(gtctx ctx, char *algName) +{ + int r = 0; + if(!strcmp(algName, "SHA2-256")) + ctx->hashAlg = GT_HASHALG_SHA256; + else if(!strcmp(algName, "SHA2-384")) + ctx->hashAlg = GT_HASHALG_SHA384; + else if(!strcmp(algName, "SHA2-512")) + ctx->hashAlg = GT_HASHALG_SHA512; + else if(!strcmp(algName, "SHA1")) + ctx->hashAlg = GT_HASHALG_SHA1; + else if(!strcmp(algName, "RIPEMD-160")) + ctx->hashAlg = GT_HASHALG_RIPEMD160; + else if(!strcmp(algName, "SHA2-224")) + ctx->hashAlg = GT_HASHALG_SHA224; + else + r = 1; + return r; +} + +int +rsgtfileDestruct(gtfile gf) +{ + int r = 0; + if(gf == NULL) + goto done; + + if(!gf->disabled && gf->bInBlk) { + r = sigblkFinish(gf); + if(r != 0) gf->disabled = 1; + } + if(!gf->disabled) + r = tlvClose(gf); + free(gf->sigfilename); + free(gf->statefilename); + free(gf->IV); + free(gf->blkStrtHash); + rsgtimprintDel(gf->x_prev); + free(gf); +done: return r; +} + +void +rsgtCtxDel(gtctx ctx) +{ + if(ctx != NULL) { + free(ctx->timestamper); + free(ctx); + } +} + +/* new sigblk is initialized, but maybe in existing ctx */ +void +sigblkInit(gtfile gf) +{ + if(gf == NULL) goto done; + seedIV(gf); + memset(gf->roots_valid, 0, sizeof(gf->roots_valid)/sizeof(char)); + gf->nRoots = 0; + gf->nRecords = 0; + gf->bInBlk = 1; +done: return; +} + + +/* concat: add IV to buffer */ +static inline void +bufAddIV(gtfile gf, uchar *buf, size_t *len) +{ + memcpy(buf+*len, gf->IV, hashOutputLengthOctets(gf->hashAlg)); + *len += sizeof(gf->IV); +} + + +/* concat: add imprint to buffer */ +static inline void +bufAddImprint(gtfile gf, uchar *buf, size_t *len, imprint_t *imp) +{ + if(imp == NULL) { + /* TODO: how to get the REAL HASH ID? --> add field? */ + buf[*len] = hashIdentifier(gf->hashAlg); + ++(*len); + memcpy(buf+*len, gf->blkStrtHash, gf->lenBlkStrtHash); + *len += gf->lenBlkStrtHash; + } else { + buf[*len] = imp->hashID; + ++(*len); + memcpy(buf+*len, imp->data, imp->len); + *len += imp->len; + } +} +/* concat: add hash to buffer */ +static inline void +bufAddHash(gtfile gf, uchar *buf, size_t *len, GTDataHash *hash) +{ + buf[*len] = hashIdentifier(gf->hashAlg); + ++(*len); + memcpy(buf+*len, hash->digest, hash->digest_length); + *len += hash->digest_length; +} +/* concat: add tree level to buffer */ +static inline void +bufAddLevel(uchar *buf, size_t *len, uint8_t level) +{ + memcpy(buf+*len, &level, sizeof(level)); + *len += sizeof(level); +} + + +int +hash_m(gtfile gf, GTDataHash **m) +{ + int rgt; + uchar concatBuf[16*1024]; + size_t len = 0; + int r = 0; + + bufAddImprint(gf, concatBuf, &len, gf->x_prev); + bufAddIV(gf, concatBuf, &len); + rgt = GTDataHash_create(gf->hashAlg, concatBuf, len, m); + if(rgt != GT_OK) { + reportGTAPIErr(gf->ctx, gf, "GTDataHash_create", rgt); + r = RSGTE_HASH_CREATE; + goto done; + } +done: return r; +} + +int +hash_r(gtfile gf, GTDataHash **r, const uchar *rec, const size_t len) +{ + int ret = 0, rgt; + rgt = GTDataHash_create(gf->hashAlg, rec, len, r); + if(rgt != GT_OK) { + reportGTAPIErr(gf->ctx, gf, "GTDataHash_create", rgt); + ret = RSGTE_HASH_CREATE; + goto done; + } +done: return ret; +} + + +int +hash_node(gtfile gf, GTDataHash **node, GTDataHash *m, GTDataHash *rec, + uint8_t level) +{ + int r = 0, rgt; + uchar concatBuf[16*1024]; + size_t len = 0; + + bufAddHash(gf, concatBuf, &len, m); + bufAddHash(gf, concatBuf, &len, rec); + bufAddLevel(concatBuf, &len, level); + rgt = GTDataHash_create(gf->hashAlg, concatBuf, len, node); + if(rgt != GT_OK) { + reportGTAPIErr(gf->ctx, gf, "GTDataHash_create", rgt); + r = RSGTE_HASH_CREATE; + goto done; + } +done: return r; +} + + +int +sigblkAddRecord(gtfile gf, const uchar *rec, const size_t len) +{ + GTDataHash *x; /* current hash */ + GTDataHash *m, *r, *t, *t_del; + uint8_t j; + int ret = 0; + + if(gf == NULL || gf->disabled) goto done; + if((ret = hash_m(gf, &m)) != 0) goto done; + if((ret = hash_r(gf, &r, rec, len)) != 0) goto done; + if(gf->bKeepRecordHashes) + tlvWriteHash(gf, 0x0900, r); + if((ret = hash_node(gf, &x, m, r, 1)) != 0) goto done; /* hash leaf */ + /* persists x here if Merkle tree needs to be persisted! */ + if(gf->bKeepTreeHashes) + tlvWriteHash(gf, 0x0901, x); + rsgtimprintDel(gf->x_prev); + gf->x_prev = rsgtImprintFromGTDataHash(x); + /* add x to the forest as new leaf, update roots list */ + t = x; + for(j = 0 ; j < gf->nRoots ; ++j) { + if(gf->roots_valid[j] == 0) { + gf->roots_hash[j] = t; + gf->roots_valid[j] = 1; + t = NULL; + break; + } else if(t != NULL) { + /* hash interim node */ + t_del = t; + ret = hash_node(gf, &t, gf->roots_hash[j], t_del, j+2); + gf->roots_valid[j] = 0; + GTDataHash_free(gf->roots_hash[j]); + GTDataHash_free(t_del); + if(ret != 0) goto done; + if(gf->bKeepTreeHashes) + tlvWriteHash(gf, 0x0901, t); + } + } + if(t != NULL) { + /* new level, append "at the top" */ + gf->roots_hash[gf->nRoots] = t; + gf->roots_valid[gf->nRoots] = 1; + ++gf->nRoots; + assert(gf->nRoots < MAX_ROOTS); + t = NULL; + } + ++gf->nRecords; + + /* cleanup (x is cleared as part of the roots array) */ + GTDataHash_free(m); + GTDataHash_free(r); + + if(gf->nRecords == gf->blockSizeLimit) { + ret = sigblkFinish(gf); + if(ret != 0) goto done; + sigblkInit(gf); + } +done: + if(ret != 0) { + gf->disabled = 1; + } + return ret; +} + +static int +timestampIt(gtfile gf, GTDataHash *hash) +{ + unsigned char *der = NULL; + size_t lenDer; + int r = GT_OK; + int ret = 0; + GTTimestamp *timestamp = NULL; + + /* Get the timestamp. */ + r = GTHTTP_createTimestampHash(hash, gf->ctx->timestamper, ×tamp); + + if(r != GT_OK) { + reportGTAPIErr(gf->ctx, gf, "GTHTTP_createTimestampHash", r); + ret = 1; + goto done; + } + + /* Encode timestamp. */ + r = GTTimestamp_getDEREncoded(timestamp, &der, &lenDer); + if(r != GT_OK) { + reportGTAPIErr(gf->ctx, gf, "GTTimestamp_getDEREncoded", r); + ret = 1; + goto done; + } + + tlvWriteBlockSig(gf, der, lenDer); + +done: + GT_free(der); + GTTimestamp_free(timestamp); + return ret; +} + + +int +sigblkFinish(gtfile gf) +{ + GTDataHash *root, *rootDel; + int8_t j; + int ret = 0; + + if(gf->nRecords == 0) + goto done; + + root = NULL; + for(j = 0 ; j < gf->nRoots ; ++j) { + if(root == NULL) { + root = gf->roots_valid[j] ? gf->roots_hash[j] : NULL; + gf->roots_valid[j] = 0; + } else if(gf->roots_valid[j]) { + rootDel = root; + ret = hash_node(gf, &root, gf->roots_hash[j], rootDel, j+2); + gf->roots_valid[j] = 0; + GTDataHash_free(gf->roots_hash[j]); + GTDataHash_free(rootDel); + if(ret != 0) goto done; /* checks hash_node() result! */ + } + } + if((ret = timestampIt(gf, root)) != 0) goto done; + + GTDataHash_free(root); + free(gf->blkStrtHash); + gf->lenBlkStrtHash = gf->x_prev->len; + gf->blkStrtHash = malloc(gf->lenBlkStrtHash); + memcpy(gf->blkStrtHash, gf->x_prev->data, gf->x_prev->len); +done: + gf->bInBlk = 0; + return ret; +} diff --git a/runtime/librsgt.h b/runtime/librsgt.h new file mode 100644 index 00000000..bfcc4628 --- /dev/null +++ b/runtime/librsgt.h @@ -0,0 +1,388 @@ +/* librsgt.h - rsyslog's guardtime support library + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_LIBRSGT_H +#define INCLUDED_LIBRSGT_H +#include <gt_base.h> + +/* Max number of roots inside the forest. This permits blocks of up to + * 2^MAX_ROOTS records. We assume that 64 is sufficient for all use + * cases ;) [and 64 is not really a waste of memory, so we do not even + * try to work with reallocs and such...] + */ +#define MAX_ROOTS 64 +#define LOGSIGHDR "LOGSIG10" + +/* context for gt calls. This primarily serves as a container for the + * config settings. The actual file-specific data is kept in gtfile. + */ +struct gtctx_s { + enum GTHashAlgorithm hashAlg; + uint8_t bKeepRecordHashes; + uint8_t bKeepTreeHashes; + uint64_t blockSizeLimit; + char *timestamper; + void (*errFunc)(void *, unsigned char*); + void *usrptr; /* for error function */ +}; +typedef struct gtctx_s *gtctx; +typedef struct gtfile_s *gtfile; +typedef struct gterrctx_s gterrctx_t; +typedef struct imprint_s imprint_t; +typedef struct block_sig_s block_sig_t; +typedef struct tlvrecord_s tlvrecord_t; + +/* this describes a file, as far as librsgt is concerned */ +struct gtfile_s { + /* the following data items are mirrored from gtctx to + * increase cache hit ratio (they are frequently accesed). + */ + enum GTHashAlgorithm hashAlg; + uint8_t bKeepRecordHashes; + uint8_t bKeepTreeHashes; + /* end mirrored properties */ + uint8_t disabled; /* permits to disable this file --> set to 1 */ + uint64_t blockSizeLimit; + uint8_t *IV; /* initial value for blinding masks */ + imprint_t *x_prev; /* last leaf hash (maybe of previous block) --> preserve on term */ + unsigned char *sigfilename; + unsigned char *statefilename; + int fd; + unsigned char *blkStrtHash; /* last hash from previous block */ + uint16_t lenBlkStrtHash; + uint64_t nRecords; /* current number of records in current block */ + uint64_t bInBlk; /* are we currently inside a blk --> need to finish on close */ + int8_t nRoots; + /* algo engineering: roots structure is split into two arrays + * in order to improve cache hits. + */ + int8_t roots_valid[MAX_ROOTS]; + GTDataHash *roots_hash[MAX_ROOTS]; + /* data members for the associated TLV file */ + char tlvBuf[4096]; + int tlvIdx; /* current index into tlvBuf */ + gtctx ctx; +}; + +struct tlvrecord_s { + uint16_t tlvtype; + uint16_t tlvlen; + uint8_t hdr[4]; /* the raw header (as persisted to file) */ + uint8_t lenHdr; /* length of raw header */ + uint8_t data[64*1024]; /* the actual data part (of length tlvlen) */ +}; + +/* The following structure describes the "error context" to be used + * for verification and similiar reader functions. While verifying, + * we need some information (like filenames or block numbers) that + * is not readily available from the other objects (or not even known + * to librsgt). In order to provide meaningful error messages, this + * information must be passed in from the external callers. In order + * to centralize information (and make it more manageable), we use + * ths error context here, which contains everything needed to + * generate good error messages. Members of this structure are + * maintained both by library users (the callers) as well as + * the library itself. Who does what simply depends on who has + * the relevant information. + */ +struct gterrctx_s { + FILE *fp; /**< file for error messages */ + char *filename; + uint8_t verbose; + uint64_t recNumInFile; + uint64_t recNum; + uint64_t blkNum; + uint8_t treeLevel; + GTDataHash *computedHash; + GTDataHash *lefthash, *righthash; /* hashes to display if tree hash fails */ + imprint_t *fileHash; + int gtstate; /* status from last relevant GT.*() function call */ + char *errRec; + char *frstRecInBlk; /* This holds the first message seen inside the current block */ +}; + +struct imprint_s { + uint8_t hashID; + int len; + uint8_t *data; +}; + +#define SIGID_RFC3161 0 +struct block_sig_s { + uint8_t hashID; + uint8_t sigID; /* what type of *signature*? */ + uint8_t *iv; + imprint_t lastHash; + uint64_t recCount; + struct { + struct { + uint8_t *data; + size_t len; /* must be size_t due to GT API! */ + } der; + } sig; +}; + + +/* the following defines the gtstate file record. Currently, this record + * is fixed, we may change that over time. + */ +struct rsgtstatefile { + char hdr[8]; /* must be "GTSTAT10" */ + uint8_t hashID; + uint8_t lenHash; + /* after that, the hash value is contained within the file */ +}; + +/* Flags and record types for TLV handling */ +#define RSGT_FLAG_TLV16 0x20 + +/* error states */ +#define RSGTE_IO 1 /* any kind of io error */ +#define RSGTE_FMT 2 /* data fromat error */ +#define RSGTE_INVLTYP 3 /* invalid TLV type record (unexcpected at this point) */ +#define RSGTE_OOM 4 /* ran out of memory */ +#define RSGTE_LEN 5 /* error related to length records */ +#define RSGTE_TS_EXTEND 6/* error extending timestamp */ +#define RSGTE_INVLD_RECCNT 7/* mismatch between actual records and records + given in block-sig record */ +#define RSGTE_INVLHDR 8/* invalid file header */ +#define RSGTE_EOF 9 /* specific EOF */ +#define RSGTE_MISS_REC_HASH 10 /* record hash missing when expected */ +#define RSGTE_MISS_TREE_HASH 11 /* tree hash missing when expected */ +#define RSGTE_INVLD_REC_HASH 12 /* invalid record hash (failed verification) */ +#define RSGTE_INVLD_TREE_HASH 13 /* invalid tree hash (failed verification) */ +#define RSGTE_INVLD_REC_HASHID 14 /* invalid record hash ID (failed verification) */ +#define RSGTE_INVLD_TREE_HASHID 15 /* invalid tree hash ID (failed verification) */ +#define RSGTE_MISS_BLOCKSIG 16 /* block signature record missing when expected */ +#define RSGTE_INVLD_TIMESTAMP 17 /* RFC3161 timestamp is invalid */ +#define RSGTE_TS_DERDECODE 18 /* error DER-Decoding a timestamp */ +#define RSGTE_TS_DERENCODE 19 /* error DER-Encoding a timestamp */ +#define RSGTE_HASH_CREATE 20 /* error creating a hash */ + +/* the following function maps RSGTE_* state to a string - must be updated + * whenever a new state is added. + * Note: it is thread-safe to call this function, as it returns a pointer + * into constant memory pool. + */ +static inline char * +RSGTE2String(int err) +{ + switch(err) { + case 0: + return "success"; + case RSGTE_IO: + return "i/o error"; + case RSGTE_FMT: + return "data format error"; + case RSGTE_INVLTYP: + return "invalid/unexpected tlv record type"; + case RSGTE_OOM: + return "out of memory"; + case RSGTE_LEN: + return "length record problem"; + case RSGTE_TS_EXTEND: + return "error extending timestamp"; + case RSGTE_INVLD_RECCNT: + return "mismatch between actual record count and number in block signature record"; + case RSGTE_INVLHDR: + return "invalid file header"; + case RSGTE_EOF: + return "EOF"; + case RSGTE_MISS_REC_HASH: + return "record hash missing"; + case RSGTE_MISS_TREE_HASH: + return "tree hash missing"; + case RSGTE_INVLD_REC_HASH: + return "record hash mismatch"; + case RSGTE_INVLD_TREE_HASH: + return "tree hash mismatch"; + case RSGTE_INVLD_REC_HASHID: + return "invalid record hash ID"; + case RSGTE_INVLD_TREE_HASHID: + return "invalid tree hash ID"; + case RSGTE_MISS_BLOCKSIG: + return "missing block signature record"; + case RSGTE_INVLD_TIMESTAMP: + return "RFC3161 timestamp invalid"; + case RSGTE_TS_DERDECODE: + return "error DER-decoding RFC3161 timestamp"; + case RSGTE_TS_DERENCODE: + return "error DER-encoding RFC3161 timestamp"; + case RSGTE_HASH_CREATE: + return "error creating hash"; + default: + return "unknown error"; + } +} + + +static inline uint16_t +hashOutputLengthOctets(uint8_t hashID) +{ + switch(hashID) { + case GT_HASHALG_SHA1: /* paper: SHA1 */ + return 20; + case GT_HASHALG_RIPEMD160: /* paper: RIPEMD-160 */ + return 20; + case GT_HASHALG_SHA224: /* paper: SHA2-224 */ + return 28; + case GT_HASHALG_SHA256: /* paper: SHA2-256 */ + return 32; + case GT_HASHALG_SHA384: /* paper: SHA2-384 */ + return 48; + case GT_HASHALG_SHA512: /* paper: SHA2-512 */ + return 64; + default:return 32; + } +} + +static inline uint8_t +hashIdentifier(enum GTHashAlgorithm hashID) +{ + switch(hashID) { + case GT_HASHALG_SHA1: /* paper: SHA1 */ + return 0x00; + case GT_HASHALG_RIPEMD160: /* paper: RIPEMD-160 */ + return 0x02; + case GT_HASHALG_SHA224: /* paper: SHA2-224 */ + return 0x03; + case GT_HASHALG_SHA256: /* paper: SHA2-256 */ + return 0x01; + case GT_HASHALG_SHA384: /* paper: SHA2-384 */ + return 0x04; + case GT_HASHALG_SHA512: /* paper: SHA2-512 */ + return 0x05; + default:return 0xff; + } +} +static inline char * +hashAlgName(uint8_t hashID) +{ + switch(hashID) { + case GT_HASHALG_SHA1: + return "SHA1"; + case GT_HASHALG_RIPEMD160: + return "RIPEMD-160"; + case GT_HASHALG_SHA224: + return "SHA2-224"; + case GT_HASHALG_SHA256: + return "SHA2-256"; + case GT_HASHALG_SHA384: + return "SHA2-384"; + case GT_HASHALG_SHA512: + return "SHA2-512"; + default:return "[unknown]"; + } +} +static inline enum GTHashAlgorithm +hashID2Alg(uint8_t hashID) +{ + switch(hashID) { + case 0x00: + return GT_HASHALG_SHA1; + case 0x02: + return GT_HASHALG_RIPEMD160; + case 0x03: + return GT_HASHALG_SHA224; + case 0x01: + return GT_HASHALG_SHA256; + case 0x04: + return GT_HASHALG_SHA384; + case 0x05: + return GT_HASHALG_SHA512; + default: + return 0xff; + } +} +static inline char * +sigTypeName(uint8_t sigID) +{ + switch(sigID) { + case SIGID_RFC3161: + return "RFC3161"; + default:return "[unknown]"; + } +} +static inline uint16_t +getIVLen(block_sig_t *bs) +{ + return hashOutputLengthOctets(bs->hashID); +} +static inline void +rsgtSetTimestamper(gtctx ctx, char *timestamper) +{ + free(ctx->timestamper); + ctx->timestamper = strdup(timestamper); +} +static inline void +rsgtSetBlockSizeLimit(gtctx ctx, uint64_t limit) +{ + ctx->blockSizeLimit = limit; +} +static inline void +rsgtSetKeepRecordHashes(gtctx ctx, int val) +{ + ctx->bKeepRecordHashes = val; +} +static inline void +rsgtSetKeepTreeHashes(gtctx ctx, int val) +{ + ctx->bKeepTreeHashes = val; +} + +int rsgtSetHashFunction(gtctx ctx, char *algName); +int rsgtInit(char *usragent); +void rsgtExit(void); +gtctx rsgtCtxNew(void); +void rsgtsetErrFunc(gtctx ctx, void (*func)(void*, unsigned char *), void *usrptr); +gtfile rsgtCtxOpenFile(gtctx ctx, unsigned char *logfn); +int rsgtfileDestruct(gtfile gf); +void rsgtCtxDel(gtctx ctx); +void sigblkInit(gtfile gf); +int sigblkAddRecord(gtfile gf, const unsigned char *rec, const size_t len); +int sigblkFinish(gtfile gf); +imprint_t * rsgtImprintFromGTDataHash(GTDataHash *hash); +void rsgtimprintDel(imprint_t *imp); +/* reader functions */ +int rsgt_tlvrdHeader(FILE *fp, unsigned char *hdr); +int rsgt_tlvrd(FILE *fp, tlvrecord_t *rec, void *obj); +void rsgt_tlvprint(FILE *fp, uint16_t tlvtype, void *obj, uint8_t verbose); +void rsgt_printBLOCK_SIG(FILE *fp, block_sig_t *bs, uint8_t verbose); +int rsgt_getBlockParams(FILE *fp, uint8_t bRewind, block_sig_t **bs, uint8_t *bHasRecHashes, uint8_t *bHasIntermedHashes); +int rsgt_chkFileHdr(FILE *fp, char *expect); +gtfile rsgt_vrfyConstruct_gf(void); +void rsgt_vrfyBlkInit(gtfile gf, block_sig_t *bs, uint8_t bHasRecHashes, uint8_t bHasIntermedHashes); +int rsgt_vrfy_nextRec(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp, unsigned char *rec, size_t len, gterrctx_t *ectx); +int verifyBLOCK_SIG(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp, uint8_t bExtend, gterrctx_t *ectx); +void rsgt_errctxInit(gterrctx_t *ectx); +void rsgt_errctxExit(gterrctx_t *ectx); +void rsgt_errctxSetErrRec(gterrctx_t *ectx, char *rec); +void rsgt_errctxFrstRecInBlk(gterrctx_t *ectx, char *rec); +void rsgt_objfree(uint16_t tlvtype, void *obj); + + +/* TODO: replace these? */ +int hash_m(gtfile gf, GTDataHash **m); +int hash_r(gtfile gf, GTDataHash **r, const unsigned char *rec, const size_t len); +int hash_node(gtfile gf, GTDataHash **node, GTDataHash *m, GTDataHash *r, uint8_t level); +extern char *rsgt_read_puburl; /**< url of publication server */ +extern uint8_t rsgt_read_showVerified; + +#endif /* #ifndef INCLUDED_LIBRSGT_H */ diff --git a/runtime/librsgt_read.c b/runtime/librsgt_read.c new file mode 100644 index 00000000..a6e33160 --- /dev/null +++ b/runtime/librsgt_read.c @@ -0,0 +1,1092 @@ +/* librsgt_read.c - rsyslog's guardtime support library + * This includes functions used for reading signature (and + * other related) files. Well, actually it also contains + * some writing functionality, but only as far as rsyslog + * itself is not concerned, but "just" the utility programs. + * + * This part of the library uses C stdio and expects that the + * caller will open and close the file to be read itself. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <gt_http.h> + +#include "librsgt.h" + +typedef unsigned char uchar; +#ifndef VERSION +#define VERSION "no-version" +#endif +#define MAXFNAME 1024 + +static int rsgt_read_debug = 0; +char *rsgt_read_puburl = "http://verify.guardtime.com/gt-controlpublications.bin"; +char *rsgt_extend_puburl = "http://verifier.guardtime.net/gt-extendingservice"; +uint8_t rsgt_read_showVerified = 0; + +/* macro to obtain next char from file including error tracking */ +#define NEXTC if((c = fgetc(fp)) == EOF) { \ + r = feof(fp) ? RSGTE_EOF : RSGTE_IO; \ + goto done; \ + } + +/* check return state of operation and abort, if non-OK */ +#define CHKr(code) if((r = code) != 0) goto done + + +/* if verbose==0, only the first and last two octets are shown, + * otherwise everything. + */ +static void +outputHexBlob(FILE *fp, uint8_t *blob, uint16_t len, uint8_t verbose) +{ + unsigned i; + if(verbose || len <= 8) { + for(i = 0 ; i < len ; ++i) + fprintf(fp, "%2.2x", blob[i]); + } else { + fprintf(fp, "%2.2x%2.2x%2.2x[...]%2.2x%2.2x%2.2x", + blob[0], blob[1], blob[2], + blob[len-3], blob[len-2], blob[len-1]); + } +} + +static inline void +outputHash(FILE *fp, char *hdr, uint8_t *data, uint16_t len, uint8_t verbose) +{ + fprintf(fp, "%s", hdr); + outputHexBlob(fp, data, len, verbose); + fputc('\n', fp); +} + +void +rsgt_errctxInit(gterrctx_t *ectx) +{ + ectx->fp = NULL; + ectx->filename = NULL; + ectx->recNum = 0; + ectx->gtstate = 0; + ectx->recNumInFile = 0; + ectx->blkNum = 0; + ectx->verbose = 0; + ectx->errRec = NULL; + ectx->frstRecInBlk = NULL; + ectx->fileHash = NULL; + ectx->lefthash = ectx->righthash = ectx->computedHash = NULL; +} +void +rsgt_errctxExit(gterrctx_t *ectx) +{ + free(ectx->filename); + free(ectx->frstRecInBlk); +} + +/* note: we do not copy the record, so the caller MUST not destruct + * it before processing of the record is completed. To remove the + * current record without setting a new one, call this function + * with rec==NULL. + */ +void +rsgt_errctxSetErrRec(gterrctx_t *ectx, char *rec) +{ + ectx->errRec = strdup(rec); +} +/* This stores the block's first record. Here we copy the data, + * as the caller will usually not preserve it long enough. + */ +void +rsgt_errctxFrstRecInBlk(gterrctx_t *ectx, char *rec) +{ + free(ectx->frstRecInBlk); + ectx->frstRecInBlk = strdup(rec); +} + +static void +reportError(int errcode, gterrctx_t *ectx) +{ + if(ectx->fp != NULL) { + fprintf(ectx->fp, "%s[%llu:%llu:%llu]: error[%u]: %s\n", + ectx->filename, + (long long unsigned) ectx->blkNum, (long long unsigned) ectx->recNum, + (long long unsigned) ectx->recNumInFile, + errcode, RSGTE2String(errcode)); + if(ectx->frstRecInBlk != NULL) + fprintf(ectx->fp, "\tBlock Start Record.: '%s'\n", ectx->frstRecInBlk); + if(ectx->errRec != NULL) + fprintf(ectx->fp, "\tRecord in Question.: '%s'\n", ectx->errRec); + if(ectx->computedHash != NULL) { + outputHash(ectx->fp, "\tComputed Hash......: ", ectx->computedHash->digest, + ectx->computedHash->digest_length, ectx->verbose); + } + if(ectx->fileHash != NULL) { + outputHash(ectx->fp, "\tSignature File Hash: ", ectx->fileHash->data, + ectx->fileHash->len, ectx->verbose); + } + if(errcode == RSGTE_INVLD_TREE_HASH || + errcode == RSGTE_INVLD_TREE_HASHID) { + fprintf(ectx->fp, "\tTree Level.........: %d\n", (int) ectx->treeLevel); + outputHash(ectx->fp, "\tTree Left Hash.....: ", ectx->lefthash->digest, + ectx->lefthash->digest_length, ectx->verbose); + outputHash(ectx->fp, "\tTree Right Hash....: ", ectx->righthash->digest, + ectx->righthash->digest_length, ectx->verbose); + } + if(errcode == RSGTE_INVLD_TIMESTAMP || + errcode == RSGTE_TS_DERDECODE) { + fprintf(ectx->fp, "\tPublication Server.: %s\n", rsgt_read_puburl); + fprintf(ectx->fp, "\tGT Verify Timestamp: [%u]%s\n", + ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate)); + } + if(errcode == RSGTE_TS_EXTEND || + errcode == RSGTE_TS_DERDECODE) { + fprintf(ectx->fp, "\tExtending Server...: %s\n", rsgt_extend_puburl); + fprintf(ectx->fp, "\tGT Extend Timestamp: [%u]%s\n", + ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate)); + } + if(errcode == RSGTE_TS_DERENCODE) { + fprintf(ectx->fp, "\tAPI return state...: [%u]%s\n", + ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate)); + } + } +} + +/* obviously, this is not an error-reporting function. We still use + * ectx, as it has most information we need. + */ +static void +reportVerifySuccess(gterrctx_t *ectx, GTVerificationInfo *vrfyInf) +{ + if(ectx->fp != NULL) { + fprintf(ectx->fp, "%s[%llu:%llu:%llu]: block signature successfully verified\n", + ectx->filename, + (long long unsigned) ectx->blkNum, (long long unsigned) ectx->recNum, + (long long unsigned) ectx->recNumInFile); + if(ectx->frstRecInBlk != NULL) + fprintf(ectx->fp, "\tBlock Start Record.: '%s'\n", ectx->frstRecInBlk); + if(ectx->errRec != NULL) + fprintf(ectx->fp, "\tBlock End Record...: '%s'\n", ectx->errRec); + fprintf(ectx->fp, "\tGT Verify Timestamp: [%u]%s\n", + ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate)); + GTVerificationInfo_print(ectx->fp, 0, vrfyInf); + } +} + +/** + * Write the provided record to the current file position. + * + * @param[in] fp file pointer for writing + * @param[out] rec tlvrecord to write + * + * @returns 0 if ok, something else otherwise + */ +static int +rsgt_tlvwrite(FILE *fp, tlvrecord_t *rec) +{ + int r = RSGTE_IO; + if(fwrite(rec->hdr, (size_t) rec->lenHdr, 1, fp) != 1) goto done; + if(fwrite(rec->data, (size_t) rec->tlvlen, 1, fp) != 1) goto done; + r = 0; +done: return r; +} + +/** + * Read a header from a binary file. + * @param[in] fp file pointer for processing + * @param[in] hdr buffer for the header. Must be 9 bytes + * (8 for header + NUL byte) + * @returns 0 if ok, something else otherwise + */ +int +rsgt_tlvrdHeader(FILE *fp, uchar *hdr) +{ + int r; + if(fread(hdr, 8, 1, fp) != 1) { + r = RSGTE_IO; + goto done; + } + hdr[8] = '\0'; + r = 0; +done: return r; +} + +/* read type a complete tlv record + */ +static int +rsgt_tlvRecRead(FILE *fp, tlvrecord_t *rec) +{ + int r = 1; + int c; + + NEXTC; + rec->hdr[0] = c; + rec->tlvtype = c & 0x1f; + if(c & 0x80) { /* tlv16? */ + rec->lenHdr = 4; + NEXTC; + rec->hdr[1] = c; + rec->tlvtype = (rec->tlvtype << 8) | c; + NEXTC; + rec->hdr[2] = c; + rec->tlvlen = c << 8; + NEXTC; + rec->hdr[3] = c; + rec->tlvlen |= c; + } else { + NEXTC; + rec->lenHdr = 2; + rec->hdr[1] = c; + rec->tlvlen = c; + } + if(fread(rec->data, (size_t) rec->tlvlen, 1, fp) != 1) { + r = RSGTE_IO; + goto done; + } + + if(rsgt_read_debug) + printf("read tlvtype %4.4x, len %u\n", (unsigned) rec->tlvtype, + (unsigned) rec->tlvlen); + r = 0; +done: return r; +} + +/* decode a sub-tlv record from an existing record's memory buffer + */ +static int +rsgt_tlvDecodeSUBREC(tlvrecord_t *rec, uint16_t *stridx, tlvrecord_t *newrec) +{ + int r = 1; + int c; + + if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;} + c = rec->data[(*stridx)++]; + newrec->hdr[0] = c; + newrec->tlvtype = c & 0x1f; + if(c & 0x80) { /* tlv16? */ + newrec->lenHdr = 4; + if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;} + c = rec->data[(*stridx)++]; + newrec->hdr[1] = c; + newrec->tlvtype = (newrec->tlvtype << 8) | c; + if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;} + c = rec->data[(*stridx)++]; + newrec->hdr[2] = c; + newrec->tlvlen = c << 8; + if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;} + c = rec->data[(*stridx)++]; + newrec->hdr[3] = c; + newrec->tlvlen |= c; + } else { + if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;} + c = rec->data[(*stridx)++]; + newrec->lenHdr = 2; + newrec->hdr[1] = c; + newrec->tlvlen = c; + } + if(rec->tlvlen < *stridx + newrec->tlvlen) {r=RSGTE_LEN; goto done;} + memcpy(newrec->data, (rec->data)+(*stridx), newrec->tlvlen); + *stridx += newrec->tlvlen; + + if(rsgt_read_debug) + printf("read sub-tlv: tlvtype %4.4x, len %u\n", + (unsigned) newrec->tlvtype, + (unsigned) newrec->tlvlen); + r = 0; +done: return r; +} + + +static int +rsgt_tlvDecodeIMPRINT(tlvrecord_t *rec, imprint_t **imprint) +{ + int r = 1; + imprint_t *imp; + + if((imp = calloc(1, sizeof(imprint_t))) == NULL) { + r = RSGTE_OOM; + goto done; + } + + imp->hashID = rec->data[0]; + if(rec->tlvlen != 1 + hashOutputLengthOctets(imp->hashID)) { + r = RSGTE_LEN; + goto done; + } + imp->len = rec->tlvlen - 1; + if((imp->data = (uint8_t*)malloc(imp->len)) == NULL) {r=RSGTE_OOM;goto done;} + memcpy(imp->data, rec->data+1, imp->len); + *imprint = imp; + r = 0; +done: return r; +} + +static int +rsgt_tlvDecodeHASH_ALGO(tlvrecord_t *rec, uint16_t *strtidx, uint8_t *hashAlg) +{ + int r = 1; + tlvrecord_t subrec; + + CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec)); + if(!(subrec.tlvtype == 0x00 && subrec.tlvlen == 1)) { + r = RSGTE_FMT; + goto done; + } + *hashAlg = subrec.data[0]; + r = 0; +done: return r; +} +static int +rsgt_tlvDecodeBLOCK_IV(tlvrecord_t *rec, uint16_t *strtidx, uint8_t **iv) +{ + int r = 1; + tlvrecord_t subrec; + + CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec)); + if(!(subrec.tlvtype == 0x01)) { + r = RSGTE_INVLTYP; + goto done; + } + if((*iv = (uint8_t*)malloc(subrec.tlvlen)) == NULL) {r=RSGTE_OOM;goto done;} + memcpy(*iv, subrec.data, subrec.tlvlen); + r = 0; +done: return r; +} +static int +rsgt_tlvDecodeLAST_HASH(tlvrecord_t *rec, uint16_t *strtidx, imprint_t *imp) +{ + int r = 1; + tlvrecord_t subrec; + + CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec)); + if(!(subrec.tlvtype == 0x02)) { r = RSGTE_INVLTYP; goto done; } + imp->hashID = subrec.data[0]; + if(subrec.tlvlen != 1 + hashOutputLengthOctets(imp->hashID)) { + r = RSGTE_LEN; + goto done; + } + imp->len = subrec.tlvlen - 1; + if((imp->data = (uint8_t*)malloc(imp->len)) == NULL) {r=RSGTE_OOM;goto done;} + memcpy(imp->data, subrec.data+1, subrec.tlvlen-1); + r = 0; +done: return r; +} +static int +rsgt_tlvDecodeREC_COUNT(tlvrecord_t *rec, uint16_t *strtidx, uint64_t *cnt) +{ + int r = 1; + int i; + uint64_t val; + tlvrecord_t subrec; + + CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec)); + if(!(subrec.tlvtype == 0x03 && subrec.tlvlen <= 8)) { r = RSGTE_INVLTYP; goto done; } + val = 0; + for(i = 0 ; i < subrec.tlvlen ; ++i) { + val = (val << 8) + subrec.data[i]; + } + *cnt = val; + r = 0; +done: return r; +} +static int +rsgt_tlvDecodeSIG(tlvrecord_t *rec, uint16_t *strtidx, block_sig_t *bs) +{ + int r = 1; + tlvrecord_t subrec; + + CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec)); + if(!(subrec.tlvtype == 0x0906)) { r = RSGTE_INVLTYP; goto done; } + bs->sig.der.len = subrec.tlvlen; + bs->sigID = SIGID_RFC3161; + if((bs->sig.der.data = (uint8_t*)malloc(bs->sig.der.len)) == NULL) {r=RSGTE_OOM;goto done;} + memcpy(bs->sig.der.data, subrec.data, bs->sig.der.len); + r = 0; +done: return r; +} + +static int +rsgt_tlvDecodeBLOCK_SIG(tlvrecord_t *rec, block_sig_t **blocksig) +{ + int r = 1; + uint16_t strtidx = 0; + block_sig_t *bs; + if((bs = calloc(1, sizeof(block_sig_t))) == NULL) { + r = RSGTE_OOM; + goto done; + } + CHKr(rsgt_tlvDecodeHASH_ALGO(rec, &strtidx, &(bs->hashID))); + CHKr(rsgt_tlvDecodeBLOCK_IV(rec, &strtidx, &(bs->iv))); + CHKr(rsgt_tlvDecodeLAST_HASH(rec, &strtidx, &(bs->lastHash))); + CHKr(rsgt_tlvDecodeREC_COUNT(rec, &strtidx, &(bs->recCount))); + CHKr(rsgt_tlvDecodeSIG(rec, &strtidx, bs)); + if(strtidx != rec->tlvlen) { + r = RSGTE_LEN; + goto done; + } + *blocksig = bs; + r = 0; +done: return r; +} +static int +rsgt_tlvRecDecode(tlvrecord_t *rec, void *obj) +{ + int r = 1; + switch(rec->tlvtype) { + case 0x0900: + case 0x0901: + r = rsgt_tlvDecodeIMPRINT(rec, obj); + if(r != 0) goto done; + break; + case 0x0902: + r = rsgt_tlvDecodeBLOCK_SIG(rec, obj); + if(r != 0) goto done; + break; + } +done: + return r; +} + +static int +rsgt_tlvrdRecHash(FILE *fp, FILE *outfp, imprint_t **imp) +{ + int r; + tlvrecord_t rec; + + if((r = rsgt_tlvrd(fp, &rec, imp)) != 0) goto done; + if(rec.tlvtype != 0x0900) { + r = RSGTE_MISS_REC_HASH; + rsgt_objfree(rec.tlvtype, *imp); + goto done; + } + if(outfp != NULL) + if((r = rsgt_tlvwrite(outfp, &rec)) != 0) goto done; + r = 0; +done: return r; +} + +static int +rsgt_tlvrdTreeHash(FILE *fp, FILE *outfp, imprint_t **imp) +{ + int r; + tlvrecord_t rec; + + if((r = rsgt_tlvrd(fp, &rec, imp)) != 0) goto done; + if(rec.tlvtype != 0x0901) { + r = RSGTE_MISS_TREE_HASH; + rsgt_objfree(rec.tlvtype, *imp); + goto done; + } + if(outfp != NULL) + if((r = rsgt_tlvwrite(outfp, &rec)) != 0) goto done; + r = 0; +done: return r; +} + +/* read BLOCK_SIG during verification phase */ +static int +rsgt_tlvrdVrfyBlockSig(FILE *fp, block_sig_t **bs, tlvrecord_t *rec) +{ + int r; + + if((r = rsgt_tlvrd(fp, rec, bs)) != 0) goto done; + if(rec->tlvtype != 0x0902) { + r = RSGTE_MISS_BLOCKSIG; + rsgt_objfree(rec->tlvtype, *bs); + goto done; + } + r = 0; +done: return r; +} + +/** + * Read the next "object" from file. This usually is + * a single TLV, but may be something larger, for + * example in case of a block-sig TLV record. + * Unknown type records are ignored (or run aborted + * if we are not permitted to skip). + * + * @param[in] fp file pointer for processing + * @param[out] tlvtype type of tlv record (top-level for + * structured objects. + * @param[out] tlvlen length of the tlv record value + * @param[out] obj pointer to object; This is a proper + * tlv record structure, which must be casted + * by the caller according to the reported type. + * The object must be freed by the caller (TODO: better way?) + * + * @returns 0 if ok, something else otherwise + */ +int +rsgt_tlvrd(FILE *fp, tlvrecord_t *rec, void *obj) +{ + int r; + if((r = rsgt_tlvRecRead(fp, rec)) != 0) goto done; + r = rsgt_tlvRecDecode(rec, obj); +done: return r; +} + + +/* return if a blob is all zero */ +static inline int +blobIsZero(uint8_t *blob, uint16_t len) +{ + int i; + for(i = 0 ; i < len ; ++i) + if(blob[i] != 0) + return 0; + return 1; +} + +static void +rsgt_printIMPRINT(FILE *fp, char *name, imprint_t *imp, uint8_t verbose) +{ + fprintf(fp, "%s", name); + outputHexBlob(fp, imp->data, imp->len, verbose); + fputc('\n', fp); +} + +static void +rsgt_printREC_HASH(FILE *fp, imprint_t *imp, uint8_t verbose) +{ + rsgt_printIMPRINT(fp, "[0x0900]Record hash: ", + imp, verbose); +} + +static void +rsgt_printINT_HASH(FILE *fp, imprint_t *imp, uint8_t verbose) +{ + rsgt_printIMPRINT(fp, "[0x0901]Tree hash..: ", + imp, verbose); +} + +/** + * Output a human-readable representation of a block_sig_t + * to proviced file pointer. This function is mainly inteded for + * debugging purposes or dumping tlv files. + * + * @param[in] fp file pointer to send output to + * @param[in] bsig ponter to block_sig_t to output + * @param[in] verbose if 0, abbreviate blob hexdump, else complete + */ +void +rsgt_printBLOCK_SIG(FILE *fp, block_sig_t *bs, uint8_t verbose) +{ + fprintf(fp, "[0x0902]Block Signature Record:\n"); + fprintf(fp, "\tPrevious Block Hash:\n"); + fprintf(fp, "\t Algorithm..: %s\n", hashAlgName(bs->lastHash.hashID)); + fprintf(fp, "\t Hash.......: "); + outputHexBlob(fp, bs->lastHash.data, bs->lastHash.len, verbose); + fputc('\n', fp); + if(blobIsZero(bs->lastHash.data, bs->lastHash.len)) + fprintf(fp, "\t NOTE: New Hash Chain Start!\n"); + fprintf(fp, "\tHash Algorithm: %s\n", hashAlgName(bs->hashID)); + fprintf(fp, "\tIV............: "); + outputHexBlob(fp, bs->iv, getIVLen(bs), verbose); + fputc('\n', fp); + fprintf(fp, "\tRecord Count..: %llu\n", bs->recCount); + fprintf(fp, "\tSignature Type: %s\n", sigTypeName(bs->sigID)); + fprintf(fp, "\tSignature Len.: %u\n", bs->sig.der.len); + fprintf(fp, "\tSignature.....: "); + outputHexBlob(fp, bs->sig.der.data, bs->sig.der.len, verbose); + fputc('\n', fp); +} + + +/** + * Output a human-readable representation of a tlv object. + * + * @param[in] fp file pointer to send output to + * @param[in] tlvtype type of tlv object (record) + * @param[in] verbose if 0, abbreviate blob hexdump, else complete + */ +void +rsgt_tlvprint(FILE *fp, uint16_t tlvtype, void *obj, uint8_t verbose) +{ + switch(tlvtype) { + case 0x0900: + rsgt_printREC_HASH(fp, obj, verbose); + break; + case 0x0901: + rsgt_printINT_HASH(fp, obj, verbose); + break; + case 0x0902: + rsgt_printBLOCK_SIG(fp, obj, verbose); + break; + default:fprintf(fp, "unknown tlv record %4.4x\n", tlvtype); + break; + } +} + +/** + * Free the provided object. + * + * @param[in] tlvtype type of tlv object (record) + * @param[in] obj the object to be destructed + */ +void +rsgt_objfree(uint16_t tlvtype, void *obj) +{ + switch(tlvtype) { + case 0x0900: + case 0x0901: + free(((imprint_t*)obj)->data); + break; + case 0x0902: + free(((block_sig_t*)obj)->iv); + free(((block_sig_t*)obj)->lastHash.data); + free(((block_sig_t*)obj)->sig.der.data); + break; + default:fprintf(stderr, "rsgt_objfree: unknown tlv record %4.4x\n", + tlvtype); + break; + } + free(obj); +} + +/** + * Read block parameters. This detects if the block contains the + * individual log hashes, the intermediate hashes and the overall + * block paramters (from the signature block). As we do not have any + * begin of block record, we do not know e.g. the hash algorithm or IV + * until reading the block signature record. And because the file is + * purely sequential and variable size, we need to read all records up to + * the next signature record. + * If a caller intends to verify a log file based on the parameters, + * he must re-read the file from the begining (we could keep things + * in memory, but this is impractical for large blocks). In order + * to facitate this, the function permits to rewind to the original + * read location when it is done. + * + * @param[in] fp file pointer of tlv file + * @param[in] bRewind 0 - do not rewind at end of procesing, 1 - do so + * @param[out] bs block signature record + * @param[out] bHasRecHashes 0 if record hashes are present, 1 otherwise + * @param[out] bHasIntermedHashes 0 if intermediate hashes are present, + * 1 otherwise + * + * @returns 0 if ok, something else otherwise + */ +int +rsgt_getBlockParams(FILE *fp, uint8_t bRewind, block_sig_t **bs, + uint8_t *bHasRecHashes, uint8_t *bHasIntermedHashes) +{ + int r; + uint64_t nRecs = 0; + uint8_t bDone = 0; + off_t rewindPos = 0; + void *obj; + tlvrecord_t rec; + + if(bRewind) + rewindPos = ftello(fp); + *bHasRecHashes = 0; + *bHasIntermedHashes = 0; + *bs = NULL; + + while(!bDone) { /* we will err out on EOF */ + if((r = rsgt_tlvrd(fp, &rec, &obj)) != 0) goto done; + switch(rec.tlvtype) { + case 0x0900: + ++nRecs; + *bHasRecHashes = 1; + break; + case 0x0901: + *bHasIntermedHashes = 1; + break; + case 0x0902: + *bs = (block_sig_t*) obj; + bDone = 1; + break; + default:fprintf(fp, "unknown tlv record %4.4x\n", rec.tlvtype); + break; + } + if(!bDone) + rsgt_objfree(rec.tlvtype, obj); + } + + if(*bHasRecHashes && (nRecs != (*bs)->recCount)) { + r = RSGTE_INVLD_RECCNT; + goto done; + } + + if(bRewind) { + if(fseeko(fp, rewindPos, SEEK_SET) != 0) { + r = RSGTE_IO; + goto done; + } + } +done: + return r; +} + + +/** + * Read the file header and compare it to the expected value. + * The file pointer is placed right after the header. + * @param[in] fp file pointer of tlv file + * @param[in] excpect expected header (e.g. "LOGSIG10") + * @returns 0 if ok, something else otherwise + */ +int +rsgt_chkFileHdr(FILE *fp, char *expect) +{ + int r; + char hdr[9]; + + if((r = rsgt_tlvrdHeader(fp, (uchar*)hdr)) != 0) goto done; + if(strcmp(hdr, expect)) + r = RSGTE_INVLHDR; + else + r = 0; +done: + return r; +} + +gtfile +rsgt_vrfyConstruct_gf(void) +{ + gtfile gf; + if((gf = calloc(1, sizeof(struct gtfile_s))) == NULL) + goto done; + gf->x_prev = NULL; + +done: return gf; +} + +void +rsgt_vrfyBlkInit(gtfile gf, block_sig_t *bs, uint8_t bHasRecHashes, uint8_t bHasIntermedHashes) +{ + gf->hashAlg = hashID2Alg(bs->hashID); + gf->bKeepRecordHashes = bHasRecHashes; + gf->bKeepTreeHashes = bHasIntermedHashes; + free(gf->IV); + gf->IV = malloc(getIVLen(bs)); + memcpy(gf->IV, bs->iv, getIVLen(bs)); + free(gf->blkStrtHash); + gf->lenBlkStrtHash = bs->lastHash.len; + gf->blkStrtHash = malloc(gf->lenBlkStrtHash); + memcpy(gf->blkStrtHash, bs->lastHash.data, gf->lenBlkStrtHash); +} + +static int +rsgt_vrfy_chkRecHash(gtfile gf, FILE *sigfp, FILE *nsigfp, + GTDataHash *recHash, gterrctx_t *ectx) +{ + int r = 0; + imprint_t *imp = NULL; + + if((r = rsgt_tlvrdRecHash(sigfp, nsigfp, &imp)) != 0) + reportError(r, ectx); + goto done; + if(imp->hashID != hashIdentifier(gf->hashAlg)) { + reportError(r, ectx); + r = RSGTE_INVLD_REC_HASHID; + goto done; + } + if(memcmp(imp->data, recHash->digest, + hashOutputLengthOctets(imp->hashID))) { + r = RSGTE_INVLD_REC_HASH; + ectx->computedHash = recHash; + ectx->fileHash = imp; + reportError(r, ectx); + ectx->computedHash = NULL, ectx->fileHash = NULL; + goto done; + } + r = 0; +done: + if(imp != NULL) + rsgt_objfree(0x0900, imp); + return r; +} + +static int +rsgt_vrfy_chkTreeHash(gtfile gf, FILE *sigfp, FILE *nsigfp, + GTDataHash *hash, gterrctx_t *ectx) +{ + int r = 0; + imprint_t *imp = NULL; + + if((r = rsgt_tlvrdTreeHash(sigfp, nsigfp, &imp)) != 0) { + reportError(r, ectx); + goto done; + } + if(imp->hashID != hashIdentifier(gf->hashAlg)) { + reportError(r, ectx); + r = RSGTE_INVLD_TREE_HASHID; + goto done; + } + if(memcmp(imp->data, hash->digest, + hashOutputLengthOctets(imp->hashID))) { + r = RSGTE_INVLD_TREE_HASH; + ectx->computedHash = hash; + ectx->fileHash = imp; + reportError(r, ectx); + ectx->computedHash = NULL, ectx->fileHash = NULL; + goto done; + } + r = 0; +done: + if(imp != NULL) + rsgt_objfree(0x0901, imp); + return r; +} + +int +rsgt_vrfy_nextRec(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp, + unsigned char *rec, size_t len, gterrctx_t *ectx) +{ + int r = 0; + GTDataHash *x; /* current hash */ + GTDataHash *m, *recHash = NULL, *t, *t_del; + uint8_t j; + + hash_m(gf, &m); + hash_r(gf, &recHash, rec, len); + if(gf->bKeepRecordHashes) { + r = rsgt_vrfy_chkRecHash(gf, sigfp, nsigfp, recHash, ectx); + if(r != 0) goto done; + } + hash_node(gf, &x, m, recHash, 1); /* hash leaf */ + if(gf->bKeepTreeHashes) { + ectx->treeLevel = 0; + ectx->lefthash = m; + ectx->righthash = recHash; + r = rsgt_vrfy_chkTreeHash(gf, sigfp, nsigfp, x, ectx); + if(r != 0) goto done; + } + rsgtimprintDel(gf->x_prev); + gf->x_prev = rsgtImprintFromGTDataHash(x); + /* add x to the forest as new leaf, update roots list */ + t = x; + for(j = 0 ; j < gf->nRoots ; ++j) { + if(gf->roots_valid[j] == 0) { + gf->roots_hash[j] = t; + gf->roots_valid[j] = 1; + t = NULL; + break; + } else if(t != NULL) { + /* hash interim node */ + ectx->treeLevel = j+1; + ectx->righthash = t; + t_del = t; + hash_node(gf, &t, gf->roots_hash[j], t_del, j+2); + gf->roots_valid[j] = 0; + if(gf->bKeepTreeHashes) { + ectx->lefthash = gf->roots_hash[j]; + r = rsgt_vrfy_chkTreeHash(gf, sigfp, nsigfp, t, ectx); + if(r != 0) goto done; /* mem leak ok, we terminate! */ + } + GTDataHash_free(gf->roots_hash[j]); + GTDataHash_free(t_del); + } + } + if(t != NULL) { + /* new level, append "at the top" */ + gf->roots_hash[gf->nRoots] = t; + gf->roots_valid[gf->nRoots] = 1; + ++gf->nRoots; + assert(gf->nRoots < MAX_ROOTS); + t = NULL; + } + ++gf->nRecords; + + /* cleanup */ + GTDataHash_free(m); +done: + if(recHash != NULL) + GTDataHash_free(recHash); + return r; +} + + +/* TODO: think about merging this with the writer. The + * same applies to the other computation algos. + */ +static int +verifySigblkFinish(gtfile gf, GTDataHash **pRoot) +{ + GTDataHash *root, *rootDel; + int8_t j; + int r; + + if(gf->nRecords == 0) + goto done; + + root = NULL; + for(j = 0 ; j < gf->nRoots ; ++j) { + if(root == NULL) { + root = gf->roots_valid[j] ? gf->roots_hash[j] : NULL; + gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */ + } else if(gf->roots_valid[j]) { + rootDel = root; + hash_node(gf, &root, gf->roots_hash[j], root, j+2); + gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */ + GTDataHash_free(rootDel); + } + } + + free(gf->blkStrtHash); + gf->blkStrtHash = NULL; + *pRoot = root; + r = 0; +done: + gf->bInBlk = 0; + return r; +} + + +/* helper for rsgt_extendSig: */ +#define COPY_SUBREC_TO_NEWREC \ + memcpy(newrec.data+iWr, subrec.hdr, subrec.lenHdr); \ + iWr += subrec.lenHdr; \ + memcpy(newrec.data+iWr, subrec.data, subrec.tlvlen); \ + iWr += subrec.tlvlen; +static inline int +rsgt_extendSig(GTTimestamp *timestamp, tlvrecord_t *rec, gterrctx_t *ectx) +{ + GTTimestamp *out_timestamp; + uint8_t *der; + size_t lenDer; + int r, rgt; + tlvrecord_t newrec, subrec; + uint16_t iRd, iWr; + + rgt = GTHTTP_extendTimestamp(timestamp, rsgt_extend_puburl, &out_timestamp); + if(rgt != GT_OK) { + ectx->gtstate = rgt; + r = RSGTE_TS_EXTEND; + goto done; + } + r = GTTimestamp_getDEREncoded(out_timestamp, &der, &lenDer); + if(r != GT_OK) { + r = RSGTE_TS_DERENCODE; + ectx->gtstate = rgt; + goto done; + } + /* update block_sig tlv record with new extended timestamp */ + /* we now need to copy all tlv records before the actual der + * encoded part. + */ + iRd = iWr = 0; + // TODO; check tlvtypes at comment places below! + if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done; + /* HASH_ALGO */ + COPY_SUBREC_TO_NEWREC + if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done; + /* BLOCK_IV */ + COPY_SUBREC_TO_NEWREC + if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done; + /* LAST_HASH */ + COPY_SUBREC_TO_NEWREC + if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done; + /* REC_COUNT */ + COPY_SUBREC_TO_NEWREC + if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done; + /* actual sig! */ + newrec.data[iWr++] = 0x09 | RSGT_FLAG_TLV16; + newrec.data[iWr++] = 0x06; + newrec.data[iWr++] = (lenDer >> 8) & 0xff; + newrec.data[iWr++] = lenDer & 0xff; + /* now we know how large the new main record is */ + newrec.tlvlen = (uint16_t) iWr+lenDer; + newrec.tlvtype = rec->tlvtype; + newrec.hdr[0] = rec->hdr[0]; + newrec.hdr[1] = rec->hdr[1]; + newrec.hdr[2] = (newrec.tlvlen >> 8) & 0xff; + newrec.hdr[3] = newrec.tlvlen & 0xff; + newrec.lenHdr = 4; + memcpy(newrec.data+iWr, der, lenDer); + /* and finally copy back new record to existing one */ + memcpy(rec, &newrec, sizeof(newrec)-sizeof(newrec.data)+newrec.tlvlen+4); + r = 0; +done: + return r; +} + + +/* verify the root hash. This also means we need to compute the + * Merkle tree root for the current block. + */ +int +verifyBLOCK_SIG(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp, + uint8_t bExtend, gterrctx_t *ectx) +{ + int r; + int gtstate; + block_sig_t *file_bs = NULL; + GTTimestamp *timestamp = NULL; + GTVerificationInfo *vrfyInf; + GTDataHash *root = NULL; + tlvrecord_t rec; + + if((r = verifySigblkFinish(gf, &root)) != 0) + goto done; + if((r = rsgt_tlvrdVrfyBlockSig(sigfp, &file_bs, &rec)) != 0) + goto done; + if(ectx->recNum != bs->recCount) { + r = RSGTE_INVLD_RECCNT; + goto done; + } + + gtstate = GTTimestamp_DERDecode(file_bs->sig.der.data, + file_bs->sig.der.len, ×tamp); + if(gtstate != GT_OK) { + r = RSGTE_TS_DERDECODE; + ectx->gtstate = gtstate; + goto done; + } + + gtstate = GTHTTP_verifyTimestampHash(timestamp, root, NULL, + NULL, NULL, rsgt_read_puburl, 0, &vrfyInf); + if(! (gtstate == GT_OK + && vrfyInf->verification_errors == GT_NO_FAILURES) ) { + r = RSGTE_INVLD_TIMESTAMP; + ectx->gtstate = gtstate; + goto done; + } + + if(rsgt_read_showVerified) + reportVerifySuccess(ectx, vrfyInf); + if(bExtend) + if((r = rsgt_extendSig(timestamp, &rec, ectx)) != 0) goto done; + + if(nsigfp != NULL) + if((r = rsgt_tlvwrite(nsigfp, &rec)) != 0) goto done; + r = 0; +done: + if(file_bs != NULL) + rsgt_objfree(0x0902, file_bs); + if(r != 0) + reportError(r, ectx); + if(timestamp != NULL) + GTTimestamp_free(timestamp); + return r; +} diff --git a/runtime/linkedlist.c b/runtime/linkedlist.c new file mode 100644 index 00000000..53aace47 --- /dev/null +++ b/runtime/linkedlist.c @@ -0,0 +1,412 @@ +/* linkedlist.c + * This file set implements a generic linked list object. It can be used + * wherever a linke list is required. + * + * NOTE: we do not currently provide a constructor and destructor for the + * object itself as we assume it will always be part of another strucuture. + * Having a pointer to it, I think, does not really make sense but costs + * performance. Consequently, there is is llInit() and llDestroy() and they + * do what a constructor and destructur do, except for creating the + * linkedList_t structure itself. + * + * File begun on 2007-07-31 by RGerhards + * + * Copyright (C) 2007-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "linkedlist.h" + + +/* Initialize an existing linkedList_t structure + * pKey destructor may be zero to take care of non-keyed lists. + */ +rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()) +{ + assert(pThis != NULL); + assert(pEltDestructor != NULL); + + pThis->pEltDestruct = pEltDestructor; + pThis->pKeyDestruct = pKeyDestructor; + pThis->cmpOp = pCmpOp; + pThis->pKey = NULL; + pThis->iNumElts = 0; + pThis->pRoot = NULL; + pThis->pLast = NULL; + + return RS_RET_OK; +}; + + +/* llDestroyEltData - destroys a list element + * It is a separate function as the + * functionality is needed in multiple code-pathes. + */ +static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt) +{ + DEFiRet; + + assert(pList != NULL); + assert(pElt != NULL); + + /* we ignore errors during destruction, as we need to try + * free the element in any case. + */ + if(pElt->pData != NULL) + pList->pEltDestruct(pElt->pData); + if(pElt->pKey != NULL) + pList->pKeyDestruct(pElt->pKey); + free(pElt); + pList->iNumElts--; /* one less */ + + RETiRet; +} + + +/* llDestroy - destroys a COMPLETE linkedList + */ +rsRetVal llDestroy(linkedList_t *pThis) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + assert(pThis != NULL); + + pElt = pThis->pRoot; + while(pElt != NULL) { + pEltPrev = pElt; + pElt = pElt->pNext; + /* we ignore errors during destruction, as we need to try + * finish the linked list in any case. + */ + llDestroyElt(pThis, pEltPrev); + } + /* now clean up the pointers */ + pThis->pRoot = NULL; + pThis->pLast = NULL; + + RETiRet; +} + +/* llDestroyRootElt - destroy the root element but otherwise + * keeps this list intact. -- rgerhards, 2007-08-03 + */ +rsRetVal llDestroyRootElt(linkedList_t *pThis) +{ + DEFiRet; + llElt_t *pPrev; + + if(pThis->pRoot == NULL) { + ABORT_FINALIZE(RS_RET_EMPTY_LIST); + } + + pPrev = pThis->pRoot; + if(pPrev->pNext == NULL) { + /* it was the only list element */ + pThis->pLast = NULL; + pThis->pRoot = NULL; + } else { + /* there are other list elements */ + pThis->pRoot = pPrev->pNext; + } + + CHKiRet(llDestroyElt(pThis, pPrev)); + +finalize_it: + RETiRet; +} + + +/* get next user data element of a linked list. The caller must also + * provide a "cookie" to the function. On initial call, it must be + * NULL. Other than that, the caller is not allowed to to modify the + * cookie. In the current implementation, the cookie is an actual + * pointer to the current list element, but this is nothing that the + * caller should rely on. + */ +rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr) +{ + llElt_t *pElt; + DEFiRet; + + assert(pThis != NULL); + assert(ppElt != NULL); + assert(ppUsr != NULL); + + pElt = *ppElt; + + pElt = (pElt == NULL) ? pThis->pRoot : pElt->pNext; + + if(pElt == NULL) { + iRet = RS_RET_END_OF_LINKEDLIST; + } else { + *ppUsr = pElt->pData; + } + + *ppElt = pElt; + + RETiRet; +} + + +/* return the key of an Elt + * rgerhards, 2007-09-11: note that ppDatea is actually a void**, + * but I need to make it a void* to avoid lots of compiler warnings. + * It will be converted later down in the code. + */ +rsRetVal llGetKey(llElt_t *pThis, void *ppData) +{ + assert(pThis != NULL); + assert(ppData != NULL); + + *(void**) ppData = pThis->pKey; + + return RS_RET_OK; +} + + +/* construct a new llElt_t + */ +static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData) +{ + DEFiRet; + llElt_t *pThis; + + assert(ppThis != NULL); + + if((pThis = (llElt_t*) calloc(1, sizeof(llElt_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->pKey = pKey; + pThis->pData = pData; + +finalize_it: + *ppThis = pThis; + RETiRet; +} + + +/* append a user element to the end of the linked list. This includes setting a key. If no + * key is desired, simply pass in a NULL pointer for it. + */ +rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData) +{ + llElt_t *pElt; + DEFiRet; + + CHKiRet(llEltConstruct(&pElt, pKey, pData)); + + pThis->iNumElts++; /* one more */ + if(pThis->pLast == NULL) { + pThis->pRoot = pElt; + } else { + pThis->pLast->pNext = pElt; + } + pThis->pLast = pElt; + +finalize_it: + RETiRet; +} + + +/* unlink a requested element. As we have singly-linked lists, the + * caller also needs to pass in the previous element (or NULL, if it is the + * root element). + * rgerhards, 2007-11-21 + */ +static rsRetVal llUnlinkElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) +{ + assert(pElt != NULL); + + if(pEltPrev == NULL) { /* root element? */ + pThis->pRoot = pElt->pNext; + } else { /* regular element */ + pEltPrev->pNext = pElt->pNext; + } + + if(pElt == pThis->pLast) + pThis->pLast = pEltPrev; + + return RS_RET_OK; +} + + +/* unlinks and immediately deletes an element. Previous element must + * be given (or zero if the root element is to be deleted). + * rgerhards, 2007-11-21 + */ +static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) +{ + DEFiRet; + + assert(pElt != NULL); + + CHKiRet(llUnlinkElt(pThis, pElt, pEltPrev)); + CHKiRet(llDestroyElt(pThis, pElt)); + +finalize_it: + RETiRet; +} + +/* find a user element based on the provided key - this is the + * internal variant, which also tracks the last element pointer + * before the found element. This is necessary to delete elements. + * NULL means there is no element in front of it, aka the found elt + * is the root elt. + * rgerhards, 2007-11-21 + */ +static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llElt_t **ppEltPrev) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev = NULL; + int bFound = 0; + + assert(pThis != NULL); + assert(pKey != NULL); + assert(ppElt != NULL); + assert(ppEltPrev != NULL); + + pElt = pThis->pRoot; + while(pElt != NULL && bFound == 0) { + if(pThis->cmpOp(pKey, pElt->pKey) == 0) + bFound = 1; + else { + pEltPrev = pElt; + pElt = pElt->pNext; + } + } + + if(bFound == 1) { + *ppElt = pElt; + *ppEltPrev = pEltPrev; + } else + iRet = RS_RET_NOT_FOUND; + + RETiRet; +} + + +/* find a user element based on the provided key + */ +rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); + + /* if we reach this point, we have found the element */ + *ppData = pElt->pData; + +finalize_it: + RETiRet; +} + + +/* find a delete an element based on user-provided key. The element is + * delete, the caller does not receive anything. If we need to receive + * the element before destruction, we may implement an llFindAndUnlink() + * at that time. + * rgerhards, 2007-11-21 + */ +rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); + + /* if we reach this point, we have found an element */ + CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev)); + +finalize_it: + RETiRet; +} + + +/* provide the count of linked list elements + */ +rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt) +{ + DEFiRet; + + assert(pThis != NULL); + assert(piCnt != NULL); + + *piCnt = pThis->iNumElts; + + RETiRet; +} + + +/* execute a function on all list members. The functions receives a + * user-supplied parameter, which may be either a simple value + * or a pointer to a structure with more data. If the user-supplied + * function does not return RS_RET_OK, this function here terminates. + * rgerhards, 2007-08-02 + * rgerhards, 2007-11-21: added functionality to delete a list element. + * If the called user function returns RS_RET_OK_DELETE_LISTENTRY the current element + * is deleted. + */ +rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + DEFiRet; + rsRetVal iRetLL; + void *pData; + linkedListCookie_t llCookie = NULL; + linkedListCookie_t llCookiePrev = NULL; /* previous list element (needed for deletion, NULL = at root) */ + + assert(pThis != NULL); + assert(pFunc != NULL); + + while((iRetLL = llGetNextElt(pThis, &llCookie, (void**)&pData)) == RS_RET_OK) { + iRet = pFunc(pData, pParam); + if(iRet == RS_RET_OK_DELETE_LISTENTRY) { + /* delete element */ + CHKiRet(llUnlinkAndDelteElt(pThis, llCookie, llCookiePrev)); + /* we need to revert back, as we have just deleted the current element. + * So the actual current element is the one before it, which happens to be + * stored in llCookiePrev. -- rgerhards, 2007-11-21 + */ + llCookie = llCookiePrev; + } else if (iRet != RS_RET_OK) { + FINALIZE; + } + llCookiePrev = llCookie; + } + + if(iRetLL != RS_RET_END_OF_LINKEDLIST) + iRet = iRetLL; + +finalize_it: + RETiRet; +} + +/* vim:set ai: + */ diff --git a/runtime/linkedlist.h b/runtime/linkedlist.h new file mode 100644 index 00000000..eb829af9 --- /dev/null +++ b/runtime/linkedlist.h @@ -0,0 +1,71 @@ +/* Definition of the linkedlist object. + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LINKEDLIST_H_INCLUDED +#define LINKEDLIST_H_INCLUDED + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct llElt_s { /* config file sysline parse entry */ + struct llElt_s *pNext; + void *pKey; /* key for this element */ + void *pData; /* user-supplied data pointer */ +}; +typedef struct llElt_s llElt_t; + + +/* this is the list of known configuration commands with pointers to + * their handlers. + * The short name is cslc (Configfile SysLine Command) + */ +struct linkedList_s { /* config file sysline parse entry */ + int iNumElts; /* number of elements in list */ + rsRetVal (*pEltDestruct)(void*pData); /* destructor for user pointer in llElt_t's */ + rsRetVal (*pKeyDestruct)(void*pKey); /* destructor for key pointer in llElt_t's */ + int (*cmpOp)(void*, void*); /* pointer to key compare operation function, retval like strcmp */ + void *pKey; /* the list key (searchable, if set) */ + llElt_t *pRoot; /* list root */ + llElt_t *pLast; /* list tail */ +}; +typedef struct linkedList_s linkedList_t; + +typedef llElt_t* linkedListCookie_t; /* this type avoids exposing internals and keeps us flexible */ + +/* prototypes */ +rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()); +rsRetVal llDestroy(linkedList_t *pThis); +rsRetVal llDestroyRootElt(linkedList_t *pThis); +rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr); +rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData); +rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData); +rsRetVal llGetKey(llElt_t *pThis, void *ppData); +rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt); +rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam); +rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey); +/* use the macro below to define a function that will be executed by + * llExecFunc() + */ +#define DEFFUNC_llExecFunc(funcName)\ + static rsRetVal funcName(void __attribute__((unused)) *pData, void __attribute__((unused)) *pParam) + +#endif /* #ifndef LINKEDLIST_H_INCLUDED */ diff --git a/runtime/lmcry_gcry.c b/runtime/lmcry_gcry.c new file mode 100644 index 00000000..9a0c0072 --- /dev/null +++ b/runtime/lmcry_gcry.c @@ -0,0 +1,337 @@ +/* lmcry_gcry.c + * + * An implementation of the cryprov interface for libgcrypt. + * + * Copyright 2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "cryprov.h" +#include "libgcry.h" +#include "lmcry_gcry.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +/* tables for interfacing with the v6 config system */ +static struct cnfparamdescr cnfpdescrRegular[] = { + { "cry.key", eCmdHdlrGetWord, 0 }, + { "cry.keyfile", eCmdHdlrGetWord, 0 }, + { "cry.keyprogram", eCmdHdlrGetWord, 0 }, + { "cry.mode", eCmdHdlrGetWord, 0 }, /* CBC, ECB, etc */ + { "cry.algo", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk pblkRegular = + { CNFPARAMBLK_VERSION, + sizeof(cnfpdescrRegular)/sizeof(struct cnfparamdescr), + cnfpdescrRegular + }; + +static struct cnfparamdescr cnfpdescrQueue[] = { + { "queue.cry.key", eCmdHdlrGetWord, 0 }, + { "queue.cry.keyfile", eCmdHdlrGetWord, 0 }, + { "queue.cry.keyprogram", eCmdHdlrGetWord, 0 }, + { "queue.cry.mode", eCmdHdlrGetWord, 0 }, /* CBC, ECB, etc */ + { "queue.cry.algo", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk pblkQueue = + { CNFPARAMBLK_VERSION, + sizeof(cnfpdescrQueue)/sizeof(struct cnfparamdescr), + cnfpdescrQueue + }; + + +#if 0 +static void +errfunc(__attribute__((unused)) void *usrptr, uchar *emsg) +{ + errmsg.LogError(0, RS_RET_CRYPROV_ERR, "Crypto Provider" + "Error: %s - disabling encryption", emsg); +} +#endif + +/* Standard-Constructor + */ +BEGINobjConstruct(lmcry_gcry) + pThis->ctx = gcryCtxNew(); +ENDobjConstruct(lmcry_gcry) + + +/* destructor for the lmcry_gcry object */ +BEGINobjDestruct(lmcry_gcry) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(lmcry_gcry) + rsgcryCtxDel(pThis->ctx); +ENDobjDestruct(lmcry_gcry) + + +/* apply all params from param block to us. This must be called + * after construction, but before the OnFileOpen() entry point. + * Defaults are expected to have been set during construction. + */ +static rsRetVal +SetCnfParam(void *pT, struct nvlst *lst, int paramType) +{ + lmcry_gcry_t *pThis = (lmcry_gcry_t*) pT; + int i, r; + unsigned keylen; + uchar *key = NULL; + uchar *keyfile = NULL; + uchar *keyprogram = NULL; + uchar *algo = NULL; + uchar *mode = NULL; + int nKeys; /* number of keys (actually methods) specified */ + struct cnfparamvals *pvals; + struct cnfparamblk *pblk; + DEFiRet; + + pblk = (paramType == CRYPROV_PARAMTYPE_REGULAR ) ? &pblkRegular : &pblkQueue; + nKeys = 0; + pvals = nvlstGetParams(lst, pblk, NULL); + if(Debug) { + dbgprintf("param blk in lmcry_gcry:\n"); + cnfparamsPrint(pblk, pvals); + } + + for(i = 0 ; i < pblk->nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblk->descr[i].name, "cry.key") || + !strcmp(pblk->descr[i].name, "queue.cry.key")) { + key = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + ++nKeys; + } else if(!strcmp(pblk->descr[i].name, "cry.keyfile") || + !strcmp(pblk->descr[i].name, "queue.cry.keyfile")) { + keyfile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + ++nKeys; + } else if(!strcmp(pblk->descr[i].name, "cry.keyprogram") || + !strcmp(pblk->descr[i].name, "queue.cry.keyprogram")) { + keyprogram = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + ++nKeys; + } else if(!strcmp(pblk->descr[i].name, "cry.mode") || + !strcmp(pblk->descr[i].name, "queue.cry.mode")) { + mode = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblk->descr[i].name, "cry.algo") || + !strcmp(pblk->descr[i].name, "queue.cry.algo")) { + algo = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + DBGPRINTF("lmcry_gcry: program error, non-handled " + "param '%s'\n", pblk->descr[i].name); + } + } + if(algo != NULL) { + iRet = rsgcrySetAlgo(pThis->ctx, algo); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "cry.algo '%s' is not know/supported", algo); + FINALIZE; + } + } + if(mode != NULL) { + iRet = rsgcrySetMode(pThis->ctx, mode); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "cry.mode '%s' is not know/supported", mode); + FINALIZE; + } + } + /* note: key must be set AFTER algo/mode is set (as it depends on them) */ + if(nKeys != 1) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "excactly one of the following " + "parameters can be specified: cry.key, cry.keyfile, cry.keyprogram\n"); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + if(key != NULL) { + errmsg.LogError(0, RS_RET_ERR, "Note: specifying an actual key directly from the " + "config file is highly insecure - DO NOT USE FOR PRODUCTION"); + keylen = strlen((char*)key); + } + if(keyfile != NULL) { + r = gcryGetKeyFromFile((char*)keyfile, (char**)&key, &keylen); + if(r != 0) { + errmsg.LogError(0, RS_RET_ERR, "error %d reading keyfile %s\n", + r, keyfile); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + } + if(keyprogram != NULL) { + r = gcryGetKeyFromProg((char*)keyprogram, (char**)&key, &keylen); + if(r != 0) { + errmsg.LogError(0, RS_RET_ERR, "error %d obtaining key from program %s\n", + r, keyprogram); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + } + + /* if we reach this point, we have a valid key */ + r = rsgcrySetKey(pThis->ctx, key, keylen); + if(r > 0) { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "Key length %d expected, but " + "key of length %d given", r, keylen); + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + cnfparamvalsDestruct(pvals, pblk); + if(key != NULL) { + memset(key, 0, strlen((char*)key)); + free(key); + } + free(keyfile); + free(algo); + free(mode); +finalize_it: + RETiRet; +} + +static void +SetDeleteOnClose(void *pF, int val) +{ + gcryfileSetDeleteOnClose(pF, val); +} + +static rsRetVal +GetBytesLeftInBlock(void *pF, ssize_t *left) +{ + return gcryfileGetBytesLeftInBlock((gcryfile) pF, left); +} + +static rsRetVal +DeleteStateFiles(uchar *logfn) +{ + return gcryfileDeleteState(logfn); +} + +static rsRetVal +OnFileOpen(void *pT, uchar *fn, void *pGF, char openMode) +{ + lmcry_gcry_t *pThis = (lmcry_gcry_t*) pT; + gcryfile *pgf = (gcryfile*) pGF; + DEFiRet; + DBGPRINTF("lmcry_gcry: open file '%s', mode '%c'\n", fn, openMode); + + CHKiRet(rsgcryInitCrypt(pThis->ctx, pgf, fn, openMode)); +finalize_it: + /* TODO: enable this error message (need to cleanup loop first ;)) + errmsg.LogError(0, iRet, "Encryption Provider" + "Error: cannot open .encinfo file - disabling log file"); + */ + RETiRet; +} + +static rsRetVal +Decrypt(void *pF, uchar *rec, size_t *lenRec) +{ + DEFiRet; + iRet = rsgcryDecrypt(pF, rec, lenRec); + + RETiRet; +} + + +static rsRetVal +Encrypt(void *pF, uchar *rec, size_t *lenRec) +{ + DEFiRet; + iRet = rsgcryEncrypt(pF, rec, lenRec); + + RETiRet; +} + +static rsRetVal +OnFileClose(void *pF, off64_t offsLogfile) +{ + DEFiRet; + gcryfileDestruct(pF, offsLogfile); + + RETiRet; +} + +BEGINobjQueryInterface(lmcry_gcry) +CODESTARTobjQueryInterface(lmcry_gcry) + if(pIf->ifVersion != cryprovCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + pIf->Construct = (rsRetVal(*)(void*)) lmcry_gcryConstruct; + pIf->SetCnfParam = SetCnfParam; + pIf->SetDeleteOnClose = SetDeleteOnClose; + pIf->Destruct = (rsRetVal(*)(void*)) lmcry_gcryDestruct; + pIf->OnFileOpen = OnFileOpen; + pIf->Encrypt = Encrypt; + pIf->Decrypt = Decrypt; + pIf->OnFileClose = OnFileClose; + pIf->DeleteStateFiles = DeleteStateFiles; + pIf->GetBytesLeftInBlock = GetBytesLeftInBlock; +finalize_it: +ENDobjQueryInterface(lmcry_gcry) + + +BEGINObjClassExit(lmcry_gcry, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(lmcry_gcry) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + + rsgcryExit(); +ENDObjClassExit(lmcry_gcry) + + +BEGINObjClassInit(lmcry_gcry, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + if(rsgcryInit() != 0) { + errmsg.LogError(0, RS_RET_CRYPROV_ERR, "error initializing " + "crypto provider - cannot encrypt"); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } +ENDObjClassInit(lmcry_gcry) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + lmcry_gcryClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(lmcry_gcryClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit diff --git a/runtime/lmcry_gcry.h b/runtime/lmcry_gcry.h new file mode 100644 index 00000000..c0205ab9 --- /dev/null +++ b/runtime/lmcry_gcry.h @@ -0,0 +1,39 @@ +/* An implementation of the cryprov interface for libgcrypt. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_LMCRY_GCRY_H +#define INCLUDED_LMCRY_GCRY_H +#include "cryprov.h" + +/* interface is defined in cryprov.h, we just implement it! */ +#define lmcry_gcryCURR_IF_VERSION cryprovCURR_IF_VERSION +typedef cryprov_if_t lmcry_gcry_if_t; + +/* the lmcry_gcry object */ +struct lmcry_gcry_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + gcryctx ctx; +}; +typedef struct lmcry_gcry_s lmcry_gcry_t; + +/* prototypes */ +PROTOTYPEObj(lmcry_gcry); + +#endif /* #ifndef INCLUDED_LMCRY_GCRY_H */ diff --git a/runtime/lmsig_gt.c b/runtime/lmsig_gt.c new file mode 100644 index 00000000..e9194c76 --- /dev/null +++ b/runtime/lmsig_gt.c @@ -0,0 +1,227 @@ +/* lmsig_gt.c + * + * An implementation of the sigprov interface for GuardTime. + * + * Copyright 2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "sigprov.h" +#include "lmsig_gt.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +/* tables for interfacing with the v6 config system */ +static struct cnfparamdescr cnfpdescr[] = { + { "sig.hashfunction", eCmdHdlrGetWord, 0 }, + { "sig.timestampservice", eCmdHdlrGetWord, 0 }, + { "sig.block.sizelimit", eCmdHdlrSize, 0 }, + { "sig.keeprecordhashes", eCmdHdlrBinary, 0 }, + { "sig.keeptreehashes", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk pblk = + { CNFPARAMBLK_VERSION, + sizeof(cnfpdescr)/sizeof(struct cnfparamdescr), + cnfpdescr + }; + + +static void +errfunc(__attribute__((unused)) void *usrptr, uchar *emsg) +{ + errmsg.LogError(0, RS_RET_SIGPROV_ERR, "Signature Provider" + "Error: %s - disabling signatures", emsg); +} + +/* Standard-Constructor + */ +BEGINobjConstruct(lmsig_gt) + pThis->ctx = rsgtCtxNew(); + rsgtsetErrFunc(pThis->ctx, errfunc, NULL); +ENDobjConstruct(lmsig_gt) + + +/* destructor for the lmsig_gt object */ +BEGINobjDestruct(lmsig_gt) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(lmsig_gt) + rsgtCtxDel(pThis->ctx); +ENDobjDestruct(lmsig_gt) + + +/* apply all params from param block to us. This must be called + * after construction, but before the OnFileOpen() entry point. + * Defaults are expected to have been set during construction. + */ +rsRetVal +SetCnfParam(void *pT, struct nvlst *lst) +{ + lmsig_gt_t *pThis = (lmsig_gt_t*) pT; + int i; + uchar *cstr; + struct cnfparamvals *pvals; + pvals = nvlstGetParams(lst, &pblk, NULL); + if(Debug) { + dbgprintf("sig param blk in lmsig_gt:\n"); + cnfparamsPrint(&pblk, pvals); + } + + for(i = 0 ; i < pblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblk.descr[i].name, "sig.hashfunction")) { + cstr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + if(rsgtSetHashFunction(pThis->ctx, (char*)cstr) != 0) { + errmsg.LogError(0, RS_RET_ERR, "Hash function " + "'%s' unknown - using default", cstr); + } + free(cstr); + } else if(!strcmp(pblk.descr[i].name, "sig.timestampservice")) { + cstr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + rsgtSetTimestamper(pThis->ctx, (char*) cstr); + free(cstr); + } else if(!strcmp(pblk.descr[i].name, "sig.block.sizelimit")) { + rsgtSetBlockSizeLimit(pThis->ctx, pvals[i].val.d.n); + } else if(!strcmp(pblk.descr[i].name, "sig.keeprecordhashes")) { + rsgtSetKeepRecordHashes(pThis->ctx, pvals[i].val.d.n); + } else if(!strcmp(pblk.descr[i].name, "sig.keeptreehashes")) { + rsgtSetKeepTreeHashes(pThis->ctx, pvals[i].val.d.n); + } else { + DBGPRINTF("lmsig_gt: program error, non-handled " + "param '%s'\n", pblk.descr[i].name); + } + } + cnfparamvalsDestruct(pvals, &pblk); + return RS_RET_OK; +} + + +static rsRetVal +OnFileOpen(void *pT, uchar *fn, void *pGF) +{ + lmsig_gt_t *pThis = (lmsig_gt_t*) pT; + gtfile *pgf = (gtfile*) pGF; + DEFiRet; + DBGPRINTF("lmsig_gt: onFileOpen: %s\n", fn); + /* note: if *pgf is set to NULL, this auto-disables GT functions */ + *pgf = rsgtCtxOpenFile(pThis->ctx, fn); + sigblkInit(*pgf); + RETiRet; +} + +/* Note: we assume that the record is terminated by a \n. + * As of the GuardTime paper, \n is not part of the signed + * message, so we subtract one from the record size. This + * may cause issues with non-standard formats, but let's + * see how things evolve (the verifier will not work in + * any case when the records are not \n delimited...). + * rgerhards, 2013-03-17 + */ +static rsRetVal +OnRecordWrite(void *pF, uchar *rec, rs_size_t lenRec) +{ + DEFiRet; + DBGPRINTF("lmsig_gt: onRecordWrite (%d): %s\n", lenRec-1, rec); + sigblkAddRecord(pF, rec, lenRec-1); + + RETiRet; +} + +static rsRetVal +OnFileClose(void *pF) +{ + DEFiRet; + DBGPRINTF("lmsig_gt: onFileClose\n"); + rsgtfileDestruct(pF); + + RETiRet; +} + +BEGINobjQueryInterface(lmsig_gt) +CODESTARTobjQueryInterface(lmsig_gt) + if(pIf->ifVersion != sigprovCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + pIf->Construct = (rsRetVal(*)(void*)) lmsig_gtConstruct; + pIf->SetCnfParam = SetCnfParam; + pIf->Destruct = (rsRetVal(*)(void*)) lmsig_gtDestruct; + pIf->OnFileOpen = OnFileOpen; + pIf->OnRecordWrite = OnRecordWrite; + pIf->OnFileClose = OnFileClose; +finalize_it: +ENDobjQueryInterface(lmsig_gt) + + +BEGINObjClassExit(lmsig_gt, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(lmsig_gt) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + + rsgtExit(); +ENDObjClassExit(lmsig_gt) + + +BEGINObjClassInit(lmsig_gt, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + if(rsgtInit("rsyslogd " VERSION) != 0) { + errmsg.LogError(0, RS_RET_SIGPROV_ERR, "error initializing " + "signature provider - cannot sign"); + ABORT_FINALIZE(RS_RET_SIGPROV_ERR); + } +ENDObjClassInit(lmsig_gt) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + lmsig_gtClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(lmsig_gtClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit diff --git a/runtime/lmsig_gt.h b/runtime/lmsig_gt.h new file mode 100644 index 00000000..665e6a8e --- /dev/null +++ b/runtime/lmsig_gt.h @@ -0,0 +1,40 @@ +/* An implementation of the sigprov interface for GuardTime. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_LMSIG_GT_H +#define INCLUDED_LMSIG_GT_H +#include "sigprov.h" +#include "librsgt.h" + +/* interface is defined in sigprov.h, we just implement it! */ +#define lmsig_gtCURR_IF_VERSION sigprovCURR_IF_VERSION +typedef sigprov_if_t lmsig_gt_if_t; + +/* the lmsig_gt object */ +struct lmsig_gt_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + gtctx ctx; /* librsgt context - contains all we need */ +}; +typedef struct lmsig_gt_s lmsig_gt_t; + +/* prototypes */ +PROTOTYPEObj(lmsig_gt); + +#endif /* #ifndef INCLUDED_LMSIG_GT_H */ diff --git a/runtime/module-template.h b/runtime/module-template.h new file mode 100644 index 00000000..8a958f90 --- /dev/null +++ b/runtime/module-template.h @@ -0,0 +1,1019 @@ +/* module-template.h + * This header contains macros that can be used to implement the + * plumbing of modules. + * + * File begun on 2007-07-25 by RGerhards + * + * Copyright 2007-2012 Adiscon GmbH. This is Adiscon-exclusive code without any other + * contributions. *** GPLv3 *** + * + * This file is part of the rsyslog runtime library. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef MODULE_TEMPLATE_H_INCLUDED +#define MODULE_TEMPLATE_H_INCLUDED 1 + +#include "modules.h" +#include "obj.h" +#include "objomsr.h" +#include "threads.h" + +/* macro to define standard output-module static data members + */ +#define DEF_MOD_STATIC_DATA \ + static __attribute__((unused)) rsRetVal (*omsdRegCFSLineHdlr)(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie); + +#define DEF_OMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) \ + static __attribute__((unused)) int bCoreSupportsBatching; +#define DEF_IMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_LMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA +#define DEF_PMOD_STATIC_DATA \ + DEFobjCurrIf(obj) \ + DEF_MOD_STATIC_DATA +#define DEF_SMOD_STATIC_DATA \ + DEFobjCurrIf(obj) \ + DEF_MOD_STATIC_DATA + + +/* Macro to define the module type. Each module can only have a single type. If + * a module provides multiple types, several separate modules must be created which + * then should share a single library containing the majority of code. This macro + * must be present in each module. -- rgerhards, 2007-12-14 + * Note that MODULE_TYPE_TESTBENCH is reserved for testbenches, but + * declared in their own header files (because the rest does not need these + * defines). -- rgerhards, 2008-06-13 + */ +#define MODULE_TYPE(x)\ +static rsRetVal modGetType(eModType_t *modType) \ + { \ + *modType = x; \ + return RS_RET_OK;\ + } + +#define MODULE_TYPE_INPUT MODULE_TYPE(eMOD_IN) +#define MODULE_TYPE_OUTPUT MODULE_TYPE(eMOD_OUT) +#define MODULE_TYPE_PARSER MODULE_TYPE(eMOD_PARSER) +#define MODULE_TYPE_STRGEN MODULE_TYPE(eMOD_STRGEN) +#define MODULE_TYPE_LIB \ + DEF_LMOD_STATIC_DATA \ + MODULE_TYPE(eMOD_LIB) + +/* Macro to define whether the module should be kept dynamically linked. + */ +#define MODULE_KEEP_TYPE(x)\ +static rsRetVal modGetKeepType(eModKeepType_t *modKeepType) \ + { \ + *modKeepType = x; \ + return RS_RET_OK;\ + } +#define MODULE_TYPE_NOKEEP MODULE_KEEP_TYPE(eMOD_NOKEEP) +#define MODULE_TYPE_KEEP MODULE_KEEP_TYPE(eMOD_KEEP) + +/* macro to define a unique module id. This must be able to fit in a void*. The + * module id must be unique inside a running rsyslogd application. It is used to + * track ownership of several objects. Most importantly, when the module is + * unloaded the module id value is used to find what needs to be destroyed. + * We currently use a pointer to modExit() as the module id. This sounds to be + * reasonable save, as each module must have this entry point AND there is no valid + * reason for twice this entry point being in memory. + * rgerhards, 2007-11-21 + */ +#define STD_LOADABLE_MODULE_ID ((void*) modExit) + + +/* macro to implement the "modGetID()" interface function + * rgerhards 2007-11-21 + */ +#define DEFmodGetID \ +static rsRetVal modGetID(void **pID) \ + { \ + *pID = STD_LOADABLE_MODULE_ID;\ + return RS_RET_OK;\ + } + +/* macro to provide the v6 config system module name + */ +#define MODULE_CNFNAME(name) \ +static rsRetVal modGetCnfName(uchar **cnfName) \ + { \ + *cnfName = (uchar*) name; \ + return RS_RET_OK;\ + } + + +/* to following macros are used to generate function headers and standard + * functionality. It works as follows (described on the sample case of + * createInstance()): + * + * BEGINcreateInstance + * ... custom variable definitions (on stack) ... (if any) + * CODESTARTcreateInstance + * ... custom code ... (if any) + * ENDcreateInstance + */ + +/* createInstance() + */ +#define BEGINcreateInstance \ +static rsRetVal createInstance(instanceData **ppData)\ + {\ + DEFiRet; /* store error code here */\ + instanceData *pData; /* use this to point to data elements */ + +#define CODESTARTcreateInstance \ + if((pData = calloc(1, sizeof(instanceData))) == NULL) {\ + *ppData = NULL;\ + ENDfunc \ + return RS_RET_OUT_OF_MEMORY;\ + } + +#define ENDcreateInstance \ + *ppData = pData;\ + RETiRet;\ +} + +/* freeInstance() + * This is the cleanup function for the module instance. It is called immediately before + * the module instance is destroyed (unloaded). The module should do any cleanup + * here, e.g. close file, free instantance heap memory and the like. Control will + * not be passed back to the module once this function is finished. Keep in mind, + * however, that other instances may still be loaded and used. So do not destroy + * anything that may be used by another instance. If you have such a ressource, you + * currently need to do the instance counting yourself. + */ +#define BEGINfreeInstance \ +static rsRetVal freeInstance(void* pModData)\ +{\ + DEFiRet;\ + instanceData *pData; + +#define CODESTARTfreeInstance \ + pData = (instanceData*) pModData; + +#define ENDfreeInstance \ + if(pData != NULL)\ + free(pData); /* we need to free this in any case */\ + RETiRet;\ +} + +/* isCompatibleWithFeature() + */ +#define BEGINisCompatibleWithFeature \ +static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\ +{\ + rsRetVal iRet = RS_RET_INCOMPATIBLE; \ + BEGINfunc + +#define CODESTARTisCompatibleWithFeature + +#define ENDisCompatibleWithFeature \ + RETiRet;\ +} + + +/* beginTransaction() + * introduced in v4.3.3 -- rgerhards, 2009-04-27 + */ +#define BEGINbeginTransaction \ +static rsRetVal beginTransaction(instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTbeginTransaction /* currently empty, but may be extended */ + +#define ENDbeginTransaction \ + RETiRet;\ +} + + +/* endTransaction() + * introduced in v4.3.3 -- rgerhards, 2009-04-27 + */ +#define BEGINendTransaction \ +static rsRetVal endTransaction(instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTendTransaction /* currently empty, but may be extended */ + +#define ENDendTransaction \ + RETiRet;\ +} + + +/* doAction() + */ +#define BEGINdoAction \ +static rsRetVal doAction(uchar __attribute__((unused)) **ppString, unsigned __attribute__((unused)) iMsgOpts, instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTdoAction \ + /* ppString may be NULL if the output module requested no strings */ + +#define ENDdoAction \ + RETiRet;\ +} + + +/* dbgPrintInstInfo() + * Extra comments: + * Print debug information about this instance. + */ +#define BEGINdbgPrintInstInfo \ +static rsRetVal dbgPrintInstInfo(void *pModData)\ +{\ + DEFiRet;\ + instanceData *pData = NULL; + +#define CODESTARTdbgPrintInstInfo \ + pData = (instanceData*) pModData; \ + (void)pData; /* prevent compiler warning if unused! */ + +#define ENDdbgPrintInstInfo \ + RETiRet;\ +} + + +/* parseSelectorAct() + * Extra comments: + * try to process a selector action line. Checks if the action + * applies to this module and, if so, processed it. If not, it + * is left untouched. The driver will then call another module. + * On exit, ppModData must point to instance data. Also, a string + * request object must be created and filled. A macro is defined + * for that. + * For the most usual case, we have defined a macro below. + * If more than one string is requested, the macro can be used together + * with own code that overwrites the entry count. In this case, the + * macro must come before the own code. It is recommended to be + * placed right after CODESTARTparseSelectorAct. + */ +#define BEGINparseSelectorAct \ +static rsRetVal parseSelectorAct(uchar **pp, void **ppModData, omodStringRequest_t **ppOMSR)\ +{\ + DEFiRet;\ + uchar *p;\ + instanceData *pData = NULL; + +#define CODESTARTparseSelectorAct \ + assert(pp != NULL);\ + assert(ppModData != NULL);\ + assert(ppOMSR != NULL);\ + p = *pp; + +#define CODE_STD_STRING_REQUESTparseSelectorAct(NumStrReqEntries) \ + CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries)); + +#define CODE_STD_FINALIZERparseSelectorAct \ +finalize_it:\ + if(iRet == RS_RET_OK || iRet == RS_RET_OK_WARN || iRet == RS_RET_SUSPENDED) {\ + *ppModData = pData;\ + *pp = p;\ + } else {\ + /* cleanup, we failed */\ + if(*ppOMSR != NULL) {\ + OMSRdestruct(*ppOMSR);\ + *ppOMSR = NULL;\ + }\ + if(pData != NULL) {\ + freeInstance(pData);\ + } \ + } + +#define ENDparseSelectorAct \ + RETiRet;\ +} + + +/* newActInst() + * Extra comments: + * This creates a new instance of a the action that implements the call. + * This is part of the conf2 (rsyslog v6) config system. It is called by + * the core when an action object has been obtained. The output module + * must then verify parameters and create a new action instance (if + * parameters are acceptable) or return an error code. + * On exit, ppModData must point to instance data. Also, a string + * request object must be created and filled. A macro is defined + * for that. + * For the most usual case, we have defined a macro below. + * If more than one string is requested, the macro can be used together + * with own code that overwrites the entry count. In this case, the + * macro must come before the own code. It is recommended to be + * placed right after CODESTARTnewActInst. + */ +#define BEGINnewActInst \ +static rsRetVal newActInst(uchar __attribute__((unused)) *modName, \ + struct nvlst *lst, void **ppModData, omodStringRequest_t **ppOMSR)\ +{\ + DEFiRet;\ + instanceData *pData = NULL; \ + *ppOMSR = NULL; + +#define CODESTARTnewActInst \ + +#define CODE_STD_STRING_REQUESTnewActInst(NumStrReqEntries) \ + CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries)); + +#define CODE_STD_FINALIZERnewActInst \ +finalize_it:\ + if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) {\ + *ppModData = pData;\ + } else {\ + /* cleanup, we failed */\ + if(*ppOMSR != NULL) {\ + OMSRdestruct(*ppOMSR);\ + *ppOMSR = NULL;\ + }\ + if(pData != NULL) {\ + freeInstance(pData);\ + } \ + } + +#define ENDnewActInst \ + RETiRet;\ +} + + +/* newInpInst() + * This is basically the equivalent to newActInst() for creating input + * module (listener) instances. + */ +#define BEGINnewInpInst \ +static rsRetVal newInpInst(struct nvlst *lst)\ +{\ + DEFiRet; + +#define CODESTARTnewInpInst \ + +#define CODE_STD_FINALIZERnewInpInst + +#define ENDnewInpInst \ + RETiRet;\ +} + + +/* tryResume() + * This entry point is called to check if a module can resume operations. This + * happens when a module requested that it be suspended. In suspended state, + * the engine periodically tries to resume the module. If that succeeds, normal + * processing continues. If not, the module will not be called unless a + * tryResume() call succeeds. + * Returns RS_RET_OK, if resumption succeeded, RS_RET_SUSPENDED otherwise + * rgerhard, 2007-08-02 + */ +#define BEGINtryResume \ +static rsRetVal tryResume(instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTtryResume \ + assert(pData != NULL); + +#define ENDtryResume \ + RETiRet;\ +} + + +/* initConfVars() - initialize pre-v6.3-config variables + */ +#define BEGINinitConfVars \ +static rsRetVal initConfVars(void)\ +{\ + DEFiRet; + +#define CODESTARTinitConfVars + +#define ENDinitConfVars \ + RETiRet;\ +} + + +/* queryEtryPt() + */ +#define BEGINqueryEtryPt \ +DEFmodGetID \ +static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\ +{\ + DEFiRet; + +#define CODESTARTqueryEtryPt \ + if((name == NULL) || (pEtryPoint == NULL)) {\ + ENDfunc \ + return RS_RET_PARAM_ERROR;\ + } \ + *pEtryPoint = NULL; + +#define ENDqueryEtryPt \ + if(iRet == RS_RET_OK)\ + if(*pEtryPoint == NULL) { \ + dbgprintf("entry point '%s' not present in module\n", name); \ + iRet = RS_RET_MODULE_ENTRY_POINT_NOT_FOUND;\ + } \ + RETiRet;\ +} + +/* the following definition is the standard block for queryEtryPt for all types + * of modules. It should be included in any module, and typically is so by calling + * the module-type specific macros. + */ +#define CODEqueryEtryPt_STD_MOD_QUERIES \ + if(!strcmp((char*) name, "modExit")) {\ + *pEtryPoint = modExit;\ + } else if(!strcmp((char*) name, "modGetID")) {\ + *pEtryPoint = modGetID;\ + } else if(!strcmp((char*) name, "getType")) {\ + *pEtryPoint = modGetType;\ + } else if(!strcmp((char*) name, "getKeepType")) {\ + *pEtryPoint = modGetKeepType;\ + } + +/* the following definition is the standard block for queryEtryPt for output + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_OMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "doAction")) {\ + *pEtryPoint = doAction;\ + } else if(!strcmp((char*) name, "dbgPrintInstInfo")) {\ + *pEtryPoint = dbgPrintInstInfo;\ + } else if(!strcmp((char*) name, "freeInstance")) {\ + *pEtryPoint = freeInstance;\ + } else if(!strcmp((char*) name, "parseSelectorAct")) {\ + *pEtryPoint = parseSelectorAct;\ + } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ + *pEtryPoint = isCompatibleWithFeature;\ + } else if(!strcmp((char*) name, "tryResume")) {\ + *pEtryPoint = tryResume;\ + } + + +/* the following definition is queryEtryPt block that must be added + * if an output module supports the transactional interface. + * rgerhards, 2009-04-27 + */ +#define CODEqueryEtryPt_TXIF_OMOD_QUERIES \ + else if(!strcmp((char*) name, "beginTransaction")) {\ + *pEtryPoint = beginTransaction;\ + } else if(!strcmp((char*) name, "endTransaction")) {\ + *pEtryPoint = endTransaction;\ + } + + +/* the following definition is a queryEtryPt block that must be added + * if a non-output module supports "isCompatibleWithFeature". + * rgerhards, 2009-07-20 + */ +#define CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES \ + else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ + *pEtryPoint = isCompatibleWithFeature;\ + } + + +/* the following definition is the standard block for queryEtryPt for INPUT + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_IMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "runInput")) {\ + *pEtryPoint = runInput;\ + } else if(!strcmp((char*) name, "willRun")) {\ + *pEtryPoint = willRun;\ + } else if(!strcmp((char*) name, "afterRun")) {\ + *pEtryPoint = afterRun;\ + } + + +/* the following block is to be added for modules that support the v2 + * config system. The config name is also provided. + */ +#define CODEqueryEtryPt_STD_CONF2_QUERIES \ + else if(!strcmp((char*) name, "beginCnfLoad")) {\ + *pEtryPoint = beginCnfLoad;\ + } else if(!strcmp((char*) name, "endCnfLoad")) {\ + *pEtryPoint = endCnfLoad;\ + } else if(!strcmp((char*) name, "checkCnf")) {\ + *pEtryPoint = checkCnf;\ + } else if(!strcmp((char*) name, "activateCnf")) {\ + *pEtryPoint = activateCnf;\ + } else if(!strcmp((char*) name, "freeCnf")) {\ + *pEtryPoint = freeCnf;\ + } \ + CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES + +/* the following block is to be added for modules that support v2 + * module global parameters [module(...)] + */ +#define CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES \ + else if(!strcmp((char*) name, "setModCnf")) {\ + *pEtryPoint = setModCnf;\ + } \ + +/* the following block is to be added for output modules that support the v2 + * config system. The config name is also provided. + */ +#define CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES \ + else if(!strcmp((char*) name, "newActInst")) {\ + *pEtryPoint = newActInst;\ + } \ + CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES + + +/* the following block is to be added for input modules that support the v2 + * config system. The config name is also provided. + */ +#define CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES \ + else if(!strcmp((char*) name, "newInpInst")) {\ + *pEtryPoint = newInpInst;\ + } \ + CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES + + +/* the following block is to be added for modules that require + * pre priv drop activation support. + */ +#define CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES \ + else if(!strcmp((char*) name, "activateCnfPrePrivDrop")) {\ + *pEtryPoint = activateCnfPrePrivDrop;\ + } + +/* the following block is to be added for modules that support + * their config name. This is required for the rsyslog v6 config + * system, especially for outout modules which do not require + * the new set of begin/end config settings. + */ +#define CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES \ + else if(!strcmp((char*) name, "getModCnfName")) {\ + *pEtryPoint = modGetCnfName;\ + } + +/* the following definition is the standard block for queryEtryPt for LIBRARY + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_LIB_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES + +/* the following definition is the standard block for queryEtryPt for PARSER + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_PMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "parse")) {\ + *pEtryPoint = parse;\ + } else if(!strcmp((char*) name, "GetParserName")) {\ + *pEtryPoint = GetParserName;\ + } + +/* the following definition is the standard block for queryEtryPt for Strgen + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_SMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "strgen")) {\ + *pEtryPoint = strgen;\ + } else if(!strcmp((char*) name, "GetName")) {\ + *pEtryPoint = GetStrgenName;\ + } + +/* modInit() + * This has an extra parameter, which is the specific name of the modInit + * function. That is needed for built-in modules, which must have unique + * names in order to link statically. Please note that this is always only + * the case with modInit() and NO other entry point. The reason is that only + * modInit() is visible form a linker/loader point of view. All other entry + * points are passed via rsyslog-internal query functions and are defined + * static inside the modules source. This is an important concept, as it allows + * us to support different interface versions within a single module. (Granted, + * we do not currently have different interface versions, so we can not put + * it to a test - but our firm believe is that we can do all abstraction needed...) + * + * Extra Comments: + * initialize the module + * + * Later, much more must be done. So far, we only return a pointer + * to the queryEtryPt() function + * TODO: do interface version checking & handshaking + * iIfVersRequetsed is the version of the interface specification that the + * caller would like to see being used. ipIFVersProvided is what we + * decide to provide. + * rgerhards, 2007-11-21: see modExit() comment below for important information + * on the need to initialize static data with code. modInit() may be called on a + * cached, left-in-memory copy of a previous incarnation. + */ +#define BEGINmodInit(uniqName) \ +rsRetVal modInit##uniqName(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t __attribute__((unused)) *pModInfo)\ +{\ + DEFiRet; \ + rsRetVal (*pObjGetObjInterface)(obj_if_t *pIf); + +#define CODESTARTmodInit \ + assert(pHostQueryEtryPt != NULL);\ + iRet = pHostQueryEtryPt((uchar*)"objGetObjInterface", &pObjGetObjInterface); \ + if((iRet != RS_RET_OK) || (pQueryEtryPt == NULL) || (ipIFVersProvided == NULL) || (pObjGetObjInterface == NULL)) { \ + ENDfunc \ + return (iRet == RS_RET_OK) ? RS_RET_PARAM_ERROR : iRet; \ + } \ + /* now get the obj interface so that we can access other objects */ \ + CHKiRet(pObjGetObjInterface(&obj)); + +/* do those initializations necessary for legacy config variables */ +#define INITLegCnfVars \ + initConfVars(); + +#define ENDmodInit \ +finalize_it:\ + *pQueryEtryPt = queryEtryPt;\ + RETiRet;\ +} + + +/* now come some check functions, which enable a standard way of obtaining feature + * information from the core. feat is the to-be-tested feature and featVar is a + * variable that receives the result (0-not support, 1-supported). + * This must be a macro, so that it is put into the output's code. Otherwise, we + * would need to rely on a library entry point, which is what we intend to avoid ;) + * rgerhards, 2009-04-27 + */ +#define INITChkCoreFeature(featVar, feat) \ +{ \ + rsRetVal MACRO_Ret; \ + rsRetVal (*pQueryCoreFeatureSupport)(int*, unsigned); \ + int bSupportsIt; \ + featVar = 0; \ + MACRO_Ret = pHostQueryEtryPt((uchar*)"queryCoreFeatureSupport", &pQueryCoreFeatureSupport); \ + if(MACRO_Ret == RS_RET_OK) { \ + /* found entry point, so let's see if core supports it */ \ + CHKiRet((*pQueryCoreFeatureSupport)(&bSupportsIt, feat)); \ + if(bSupportsIt) \ + featVar = 1; \ + } else if(MACRO_Ret != RS_RET_ENTRY_POINT_NOT_FOUND) { \ + ABORT_FINALIZE(MACRO_Ret); /* Something else went wrong, what is not acceptable */ \ + } \ +} + + + +/* definitions for host API queries */ +#define CODEmodInit_QueryRegCFSLineHdlr \ + CHKiRet(pHostQueryEtryPt((uchar*)"regCfSysLineHdlr", &omsdRegCFSLineHdlr)); + + +/* modExit() + * This is the counterpart to modInit(). It destroys a module and makes it ready for + * unloading. It is similiar to freeInstance() for the instance data. Please note that + * this entry point needs to free any module-global data structures and registrations. + * For example, the CfSysLineHandlers a module has registered need to be unregistered + * here. This entry point is only called immediately before unloading of the module. So + * it is likely to be destroyed. HOWEVER, the caller may decide to keep the module cached. + * So a module must never assume that it is actually destroyed. A call to modInit() may + * happen immediately after modExit(). So a module can NOT assume that static data elements + * are being re-initialized by the loader - this must always be done by module code itself. + * It is suggested to do this in modInit(). - rgerhards, 2007-11-21 + */ +#define BEGINmodExit \ +static rsRetVal modExit(void)\ +{\ + DEFiRet; + +#define CODESTARTmodExit + +#define ENDmodExit \ + RETiRet;\ +} + + +/* beginCnfLoad() + * This is a function tells an input module that a new config load begins. + * The core passes in a handle to the new module-specific module conf to + * the module. -- rgerards, 2011-05-03 + */ +#define BEGINbeginCnfLoad \ +static rsRetVal beginCnfLoad(modConfData_t **ptr, __attribute__((unused)) rsconf_t *pConf)\ +{\ + modConfData_t *pModConf; \ + DEFiRet; + +#define CODESTARTbeginCnfLoad \ + if((pModConf = calloc(1, sizeof(modConfData_t))) == NULL) {\ + *ptr = NULL;\ + ENDfunc \ + return RS_RET_OUT_OF_MEMORY;\ + } + +#define ENDbeginCnfLoad \ + *ptr = pModConf;\ + RETiRet;\ +} + + +/* setModCnf() + * This function permits to set module global parameters via the v2 config + * interface. It may be called multiple times, but parameters must not be + * set in a conflicting way. The module must use its current config load + * context when processing the directives. + * Note that lst may be NULL, especially if the module is loaded via the + * legacy config system. The module must check for this. + * NOTE: This entry point must only be implemented if module global + * parameters are actually required. + */ +#define BEGINsetModCnf \ +static rsRetVal setModCnf(struct nvlst *lst)\ +{\ + DEFiRet; + +#define CODESTARTsetModCnf + +#define ENDsetModCnf \ + RETiRet;\ +} + + +/* endCnfLoad() + * This is a function tells an input module that the current config load ended. + * It gets a last chance to make changes to its in-memory config object. After + * this call, the config object must no longer be changed. + * The pModConf pointer passed into the module must no longer be used. + * rgerards, 2011-05-03 + */ +#define BEGINendCnfLoad \ +static rsRetVal endCnfLoad(modConfData_t *ptr)\ +{\ + modConfData_t __attribute__((unused)) *pModConf = (modConfData_t*) ptr; \ + DEFiRet; + +#define CODESTARTendCnfLoad + +#define ENDendCnfLoad \ + RETiRet;\ +} + + +/* checkCnf() + * Check the provided config object for errors, inconsistencies and other things + * that do not work out. + * NOTE: no part of the config must be activated, so some checks that require + * activation can not be done in this entry point. They must be done in the + * activateConf() stage, where the caller must also be prepared for error + * returns. + * rgerhards, 2011-05-03 + */ +#define BEGINcheckCnf \ +static rsRetVal checkCnf(modConfData_t *ptr)\ +{\ + modConfData_t __attribute__((unused)) *pModConf = (modConfData_t*) ptr; \ + DEFiRet; + +#define CODESTARTcheckCnf + +#define ENDcheckCnf \ + RETiRet;\ +} + + +/* activateCnfPrePrivDrop() + * Initial config activation, before dropping privileges. This is an optional + * entry points that should only be implemented by those module that really need + * it. Processing should be limited to the minimum possible. Main activation + * should happen in the normal activateCnf() call. + * rgerhards, 2011-05-06 + */ +#define BEGINactivateCnfPrePrivDrop \ +static rsRetVal activateCnfPrePrivDrop(modConfData_t *ptr)\ +{\ + modConfData_t *pModConf = (modConfData_t*) ptr; \ + DEFiRet; + +#define CODESTARTactivateCnfPrePrivDrop + +#define ENDactivateCnfPrePrivDrop \ + RETiRet;\ +} + + +/* activateCnf() + * This activates the provided config, and may report errors if they are detected + * during activation. + * rgerhards, 2011-05-03 + */ +#define BEGINactivateCnf \ +static rsRetVal activateCnf(modConfData_t *ptr)\ +{\ + modConfData_t __attribute__((unused)) *pModConf = (modConfData_t*) ptr; \ + DEFiRet; + +#define CODESTARTactivateCnf + +#define ENDactivateCnf \ + RETiRet;\ +} + + +/* freeCnf() + * This is a function tells an input module that it must free all data + * associated with the passed-in module config. + * rgerhards, 2011-05-03 + */ +#define BEGINfreeCnf \ +static rsRetVal freeCnf(void *ptr)\ +{\ + modConfData_t *pModConf = (modConfData_t*) ptr; \ + DEFiRet; + +#define CODESTARTfreeCnf + +#define ENDfreeCnf \ + if(pModConf != NULL)\ + free(pModConf); /* we need to free this in any case */\ + RETiRet;\ +} + + +/* runInput() + * This is the main function for input modules. It is used to gather data from the + * input source and submit it to the message queue. Each runInput() instance has its own + * thread. This is handled by the rsyslog engine. It needs to spawn off new threads only + * if there is a module-internal need to do so. + */ +#define BEGINrunInput \ +static rsRetVal runInput(thrdInfo_t __attribute__((unused)) *pThrd)\ +{\ + DEFiRet; + +#define CODESTARTrunInput \ + dbgSetThrdName((uchar*)__FILE__); /* we need to provide something better later */ + +#define ENDrunInput \ + RETiRet;\ +} + + +/* willRun() + * This is a function that will be replaced in the longer term. It is used so + * that a module can tell the caller if it will run or not. This is to be replaced + * when we introduce input module instances. However, these require config syntax + * changes and I may (or may not... ;)) hold that until another config file + * format is available. -- rgerhards, 2007-12-17 + * returns RS_RET_NO_RUN if it will not run (RS_RET_OK or error otherwise) + */ +#define BEGINwillRun \ +static rsRetVal willRun(void)\ +{\ + DEFiRet; + +#define CODESTARTwillRun + +#define ENDwillRun \ + RETiRet;\ +} + + +/* afterRun() + * This function is called after an input module has been run and its thread has + * been terminated. It shall do any necessary cleanup. + * This is expected to evolve into a freeInstance type of call once the input module + * interface evolves to support multiple instances. + * rgerhards, 2007-12-17 + */ +#define BEGINafterRun \ +static rsRetVal afterRun(void)\ +{\ + DEFiRet; + +#define CODESTARTafterRun + +#define ENDafterRun \ + RETiRet;\ +} + + +/* doHUP() + * This function is optional. Currently, it is available to output plugins + * only, but may be made available to other types of plugins in the future. + * A plugin does not need to define this entry point. If if does, it gets + * called when a non-restart type of HUP is done. A plugin should register + * this function so that it can close files, connection or other ressources + * on HUP - if it can be assume the user wanted to do this as a part of HUP + * processing. Note that the name "HUP" has historical reasons, it stems back + * to the infamous SIGHUP which was sent to restart a syslogd. We still retain + * that legacy, but may move this to a different signal. + * rgerhards, 2008-10-22 + */ +#define CODEqueryEtryPt_doHUP \ + else if(!strcmp((char*) name, "doHUP")) {\ + *pEtryPoint = doHUP;\ + } +#define BEGINdoHUP \ +static rsRetVal doHUP(instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTdoHUP + +#define ENDdoHUP \ + RETiRet;\ +} + + +/* SetShutdownImmdtPtr() + * This function is optional. If defined by an output plugin, it is called + * each time the action is invoked to set the "ShutdownImmediate" pointer, + * which is used during termination to indicate the action should shutdown + * as quickly as possible. + */ +#define CODEqueryEtryPt_SetShutdownImmdtPtr \ + else if(!strcmp((char*) name, "SetShutdownImmdtPtr")) {\ + *pEtryPoint = SetShutdownImmdtPtr;\ + } +#define BEGINSetShutdownImmdtPtr \ +static rsRetVal SetShutdownImmdtPtr(instanceData __attribute__((unused)) *pData, int *pPtr)\ +{\ + DEFiRet; + +#define CODESTARTSetShutdownImmdtPtr + +#define ENDSetShutdownImmdtPtr \ + RETiRet;\ +} + + +/* parse() - main entry point of parser modules + */ +#define BEGINparse \ +static rsRetVal parse(msg_t *pMsg)\ +{\ + DEFiRet; + +#define CODESTARTparse \ + assert(pMsg != NULL); + +#define ENDparse \ + RETiRet;\ +} + + +/* strgen() - main entry point of parser modules + */ +#define BEGINstrgen \ +static rsRetVal strgen(msg_t *pMsg, uchar **ppBuf, size_t *pLenBuf) \ +{\ + DEFiRet; + +#define CODESTARTstrgen \ + assert(pMsg != NULL); + +#define ENDstrgen \ + RETiRet;\ +} + + +/* function to specify the parser name. This is done via a single command which + * receives a ANSI string as parameter. + */ +#define PARSER_NAME(x) \ +static rsRetVal GetParserName(uchar **ppSz)\ +{\ + *ppSz = UCHAR_CONSTANT(x);\ + return RS_RET_OK;\ +} + + + +/* function to specify the strgen name. This is done via a single command which + * receives a ANSI string as parameter. + */ +#define STRGEN_NAME(x) \ +static rsRetVal GetStrgenName(uchar **ppSz)\ +{\ + *ppSz = UCHAR_CONSTANT(x);\ + return RS_RET_OK;\ +} + + +#endif /* #ifndef MODULE_TEMPLATE_H_INCLUDED */ + +/* vim:set ai: + */ diff --git a/runtime/modules.c b/runtime/modules.c new file mode 100644 index 00000000..56606306 --- /dev/null +++ b/runtime/modules.c @@ -0,0 +1,1395 @@ +/* modules.c + * This is the implementation of syslogd modules object. + * This object handles plug-ins and build-in modules of all kind. + * + * Modules are reference-counted. Anyone who access a module must call + * Use() before any function is accessed and Release() when he is done. + * When the reference count reaches 0, rsyslog unloads the module (that + * may be changed in the future to cache modules). Rsyslog does NOT + * unload modules with a reference count > 0, even if the unload + * method is called! + * + * File begun on 2007-07-22 by RGerhards + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#ifdef OS_BSD +# include "libgen.h" +#endif + +#include <dlfcn.h> /* TODO: replace this with the libtools equivalent! */ + +#include <unistd.h> +#include <sys/file.h> + +#ifdef OS_SOLARIS +# define PATH_MAX MAXPATHLEN +#endif + +#include "cfsysline.h" +#include "rsconf.h" +#include "modules.h" +#include "errmsg.h" +#include "parser.h" +#include "strgen.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(parser) +DEFobjCurrIf(strgen) + +static modInfo_t *pLoadedModules = NULL; /* list of currently-loaded modules */ +static modInfo_t *pLoadedModulesLast = NULL; /* tail-pointer */ + +/* already dlopen()-ed libs */ +static struct dlhandle_s *pHandles = NULL; + +static uchar *pModDir; /* directory where loadable modules are found */ + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "load", eCmdHdlrGetWord, 1 } +}; +static struct cnfparamblk pblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + + +/* we provide a set of dummy functions for modules that do not support the + * some interfaces. + * On the commit feature: As the modules do not support it, they commit each message they + * receive, and as such the dummies can always return RS_RET_OK without causing + * harm. This simplifies things as in action processing we do not need to check + * if the transactional entry points exist. + */ +static rsRetVal +dummyBeginTransaction() +{ + return RS_RET_OK; +} +static rsRetVal +dummyEndTransaction() +{ + return RS_RET_OK; +} +static rsRetVal +dummyIsCompatibleWithFeature() +{ + return RS_RET_INCOMPATIBLE; +} +static rsRetVal +dummynewActInst(uchar *modName, struct nvlst __attribute__((unused)) *dummy1, + void __attribute__((unused)) **dummy2, omodStringRequest_t __attribute__((unused)) **dummy3) +{ + errmsg.LogError(0, RS_RET_CONFOBJ_UNSUPPORTED, "config objects are not " + "supported by module '%s' -- legacy config options " + "MUST be used instead", modName); + return RS_RET_CONFOBJ_UNSUPPORTED; +} + +#ifdef DEBUG +/* we add some home-grown support to track our users (and detect who does not free us). In + * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 + */ + +/* add a user to the current list of users (always at the root) */ +static void +modUsrAdd(modInfo_t *pThis, char *pszUsr) +{ + modUsr_t *pUsr; + + BEGINfunc + if((pUsr = calloc(1, sizeof(modUsr_t))) == NULL) + goto finalize_it; + + if((pUsr->pszFile = strdup(pszUsr)) == NULL) { + free(pUsr); + goto finalize_it; + } + + if(pThis->pModUsrRoot != NULL) { + pUsr->pNext = pThis->pModUsrRoot; + } + pThis->pModUsrRoot = pUsr; + +finalize_it: + ENDfunc; +} + + +/* remove a user from the current user list + * rgerhards, 2008-03-11 + */ +static void +modUsrDel(modInfo_t *pThis, char *pszUsr) +{ + modUsr_t *pUsr; + modUsr_t *pPrev = NULL; + + for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { + if(!strcmp(pUsr->pszFile, pszUsr)) + break; + else + pPrev = pUsr; + } + + if(pUsr == NULL) { + dbgprintf("oops - tried to delete user %s from module %s and it wasn't registered as one...\n", + pszUsr, pThis->pszName); + } else { + if(pPrev == NULL) { + /* This was at the root! */ + pThis->pModUsrRoot = pUsr->pNext; + } else { + pPrev->pNext = pUsr->pNext; + } + /* free ressources */ + free(pUsr->pszFile); + free(pUsr); + pUsr = NULL; /* just to make sure... */ + } +} + + +/* print a short list all all source files using the module in question + * rgerhards, 2008-03-11 + */ +static void +modUsrPrint(modInfo_t *pThis) +{ + modUsr_t *pUsr; + + for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { + dbgprintf("\tmodule %s is currently in use by file %s\n", + pThis->pszName, pUsr->pszFile); + } +} + + +/* print all loaded modules and who is accessing them. This is primarily intended + * to be called at end of run to detect "module leaks" and who is causing them. + * rgerhards, 2008-03-11 + */ +//static void +void +modUsrPrintAll(void) +{ + modInfo_t *pMod; + + BEGINfunc + for(pMod = pLoadedModules ; pMod != NULL ; pMod = pMod->pNext) { + dbgprintf("printing users of loadable module %s, refcount %u, ptr %p, type %d\n", pMod->pszName, pMod->uRefCnt, pMod, pMod->eType); + modUsrPrint(pMod); + } + ENDfunc +} + +#endif /* #ifdef DEBUG */ + + +/* Construct a new module object + */ +static rsRetVal moduleConstruct(modInfo_t **pThis) +{ + modInfo_t *pNew; + + if((pNew = calloc(1, sizeof(modInfo_t))) == NULL) + return RS_RET_OUT_OF_MEMORY; + + /* OK, we got the element, now initialize members that should + * not be zero-filled. + */ + + *pThis = pNew; + return RS_RET_OK; +} + + +/* Destructs a module object. The object must not be linked to the + * linked list of modules. Please note that all other dependencies on this + * modules must have been removed before (e.g. CfSysLineHandlers!) + */ +static void moduleDestruct(modInfo_t *pThis) +{ + assert(pThis != NULL); + free(pThis->pszName); + free(pThis->cnfName); + if(pThis->pModHdlr != NULL) { +# ifdef VALGRIND +# warning "dlclose disabled for valgrind" +# else + if (pThis->eKeepType == eMOD_NOKEEP) { + dlclose(pThis->pModHdlr); + } +# endif + } + + free(pThis); +} + + +/* This enables a module to query the core for specific features. + * rgerhards, 2009-04-22 + */ +static rsRetVal queryCoreFeatureSupport(int *pBool, unsigned uFeat) +{ + DEFiRet; + + if((pBool == NULL)) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + *pBool = (uFeat & CORE_FEATURE_BATCHING) ? 1 : 0; + +finalize_it: + RETiRet; +} + + +/* The following function is the queryEntryPoint for host-based entry points. + * Modules may call it to get access to core interface functions. Please note + * that utility functions can be accessed via shared libraries - at least this + * is my current shool of thinking. + * Please note that the implementation as a query interface allows to take + * care of plug-in interface version differences. -- rgerhards, 2007-07-31 + * ... but often it better not to use a new interface. So we now add core + * functions here that a plugin may request. -- rgerhards, 2009-04-22 + */ +static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) +{ + DEFiRet; + + if((name == NULL) || (pEtryPoint == NULL)) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + if(!strcmp((char*) name, "regCfSysLineHdlr")) { + *pEtryPoint = regCfSysLineHdlr; + } else if(!strcmp((char*) name, "objGetObjInterface")) { + *pEtryPoint = objGetObjInterface; + } else if(!strcmp((char*) name, "OMSRgetSupportedTplOpts")) { + *pEtryPoint = OMSRgetSupportedTplOpts; + } else if(!strcmp((char*) name, "queryCoreFeatureSupport")) { + *pEtryPoint = queryCoreFeatureSupport; + } else { + *pEtryPoint = NULL; /* to be on the safe side */ + ABORT_FINALIZE(RS_RET_ENTRY_POINT_NOT_FOUND); + } + +finalize_it: + RETiRet; +} + + +/* get the name of a module + */ +uchar * +modGetName(modInfo_t *pThis) +{ + return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); +} + + +/* get the state-name of a module. The state name is its name + * together with a short description of the module state (which + * is pulled from the module itself. + * rgerhards, 2007-07-24 + * TODO: the actual state name is not yet pulled + */ +static uchar *modGetStateName(modInfo_t *pThis) +{ + return(modGetName(pThis)); +} + + +/* Add a module to the loaded module linked list + */ +static inline void +addModToGlblList(modInfo_t *pThis) +{ + assert(pThis != NULL); + + if(pLoadedModules == NULL) { + pLoadedModules = pLoadedModulesLast = pThis; + } else { + /* there already exist entries */ + pThis->pPrev = pLoadedModulesLast; + pLoadedModulesLast->pNext = pThis; + pLoadedModulesLast = pThis; + } +} + + +/* ready module for config processing. this includes checking if the module + * is already in the config, so this function may return errors. Returns a + * pointer to the last module inthe current config. That pointer needs to + * be passed to addModToCnfLst() when it is called later in the process. + */ +rsRetVal +readyModForCnf(modInfo_t *pThis, cfgmodules_etry_t **ppNew, cfgmodules_etry_t **ppLast) +{ + cfgmodules_etry_t *pNew; + cfgmodules_etry_t *pLast; + DEFiRet; + assert(pThis != NULL); + + if(loadConf == NULL) { + FINALIZE; /* we are in an early init state */ + } + + /* check for duplicates and, as a side-activity, identify last node */ + pLast = loadConf->modules.root; + if(pLast != NULL) { + while(1) { /* loop broken inside */ + if(pLast->pMod == pThis) { + DBGPRINTF("module '%s' already in this config\n", modGetName(pThis)); + if(strncmp((char*)modGetName(pThis), "builtin:", sizeof("builtin:")-1)) { + errmsg.LogError(0, RS_RET_MODULE_ALREADY_IN_CONF, + "module '%s' already in this config, cannot be added\n", modGetName(pThis)); + ABORT_FINALIZE(RS_RET_MODULE_ALREADY_IN_CONF); + } + FINALIZE; + } + if(pLast->next == NULL) + break; + pLast = pLast->next; + } + } + + /* if we reach this point, pLast is the tail pointer and this module is new + * inside the currently loaded config. So, iff it is an input module, let's + * pass it a pointer which it can populate with a pointer to its module conf. + */ + + CHKmalloc(pNew = MALLOC(sizeof(cfgmodules_etry_t))); + pNew->canActivate = 1; + pNew->next = NULL; + pNew->pMod = pThis; + + if(pThis->beginCnfLoad != NULL) { + CHKiRet(pThis->beginCnfLoad(&pNew->modCnf, loadConf)); + } + + *ppLast = pLast; + *ppNew = pNew; +finalize_it: + RETiRet; +} + + +/* abort the creation of a module entry without adding it to the + * module list. Needed to prevent mem leaks. + */ +static inline void +abortCnfUse(cfgmodules_etry_t *pNew) +{ + free(pNew); +} + + +/* Add a module to the config module list for current loadConf. + * Requires last pointer obtained by readyModForCnf(). + */ +rsRetVal +addModToCnfList(cfgmodules_etry_t *pNew, cfgmodules_etry_t *pLast) +{ + DEFiRet; + assert(pNew != NULL); + + if(loadConf == NULL) { + FINALIZE; /* we are in an early init state */ + } + + if(pLast == NULL) { + loadConf->modules.root = pNew; + } else { + /* there already exist entries */ + pLast->next = pNew; + } + +finalize_it: + RETiRet; +} + + +/* Get the next module pointer - this is used to traverse the list. + * The function returns the next pointer or NULL, if there is no next one. + * The last object must be provided to the function. If NULL is provided, + * it starts at the root of the list. Even in this case, NULL may be + * returned - then, the list is empty. + * rgerhards, 2007-07-23 + */ +static modInfo_t *GetNxt(modInfo_t *pThis) +{ + modInfo_t *pNew; + + if(pThis == NULL) + pNew = pLoadedModules; + else + pNew = pThis->pNext; + + return(pNew); +} + + +/* this function is like GetNxt(), but it returns pointers to + * the configmodules entry, which than can be used to obtain the + * actual module pointer. Note that it returns those for + * modules of specific type only. Only modules from the provided + * config are returned. Note that processing speed could be improved, + * but this is really not relevant, as config file loading is not really + * something we are concerned about in regard to runtime. + */ +static cfgmodules_etry_t +*GetNxtCnfType(rsconf_t *cnf, cfgmodules_etry_t *node, eModType_t rqtdType) +{ + if(node == NULL) { /* start at beginning of module list */ + node = cnf->modules.root; + } else { + node = node->next; + } + + if(rqtdType != eMOD_ANY) { /* if any, we already have the right one! */ + while(node != NULL && node->pMod->eType != rqtdType) { + node = node->next; + } + } + + return node; +} + + +/* Find a module with the given conf name and type. Returns NULL if none + * can be found, otherwise module found. + */ +static modInfo_t * +FindWithCnfName(rsconf_t *cnf, uchar *name, eModType_t rqtdType) +{ + cfgmodules_etry_t *node; + + ; + for( node = cnf->modules.root + ; node != NULL + ; node = node->next) { + if(node->pMod->eType != rqtdType || node->pMod->cnfName == NULL) + continue; + if(!strcasecmp((char*)node->pMod->cnfName, (char*)name)) + break; + } + + return node == NULL ? NULL : node->pMod; +} + + +/* Prepare a module for unloading. + * This is currently a dummy, to be filled when we have a plug-in + * interface - rgerhards, 2007-08-09 + * rgerhards, 2007-11-21: + * When this function is called, all instance-data must already have + * been destroyed. In the case of output modules, this happens when the + * rule set is being destroyed. When we implement other module types, we + * need to think how we handle it there (and if we have any instance data). + * rgerhards, 2008-03-10: reject unload request if the module has a reference + * count > 0. + */ +static rsRetVal +modPrepareUnload(modInfo_t *pThis) +{ + DEFiRet; + void *pModCookie; + + assert(pThis != NULL); + + if(pThis->uRefCnt > 0) { + dbgprintf("rejecting unload of module '%s' because it has a refcount of %d\n", + pThis->pszName, pThis->uRefCnt); + ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); + } + + CHKiRet(pThis->modGetID(&pModCookie)); + pThis->modExit(); /* tell the module to get ready for unload */ + CHKiRet(unregCfSysLineHdlrs4Owner(pModCookie)); + +finalize_it: + RETiRet; +} + + +/* Add an already-loaded module to the module linked list. This function does + * everything needed to fully initialize the module. + */ +static rsRetVal +doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*), + uchar *name, void *pModHdlr, modInfo_t **pNewModule) +{ + rsRetVal localRet; + modInfo_t *pNew = NULL; + uchar *pName; + parser_t *pParser; /* used for parser modules */ + strgen_t *pStrgen; /* used for strgen modules */ + rsRetVal (*GetName)(uchar**); + rsRetVal (*modGetType)(eModType_t *pType); + rsRetVal (*modGetKeepType)(eModKeepType_t *pKeepType); + struct dlhandle_s *pHandle = NULL; + rsRetVal (*getModCnfName)(uchar **cnfName); + uchar *cnfName; + DEFiRet; + + assert(modInit != NULL); + + if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) { + pNew = NULL; + ABORT_FINALIZE(iRet); + } + + CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew)); + + if(pNew->iIFVers != CURR_MOD_IF_VERSION) { + ABORT_FINALIZE(RS_RET_MISSING_INTERFACE); + } + + /* We now poll the module to see what type it is. We do this only once as this + * can never change in the lifetime of an module. -- rgerhards, 2007-12-14 + */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getType", &modGetType)); + CHKiRet((*modGetType)(&pNew->eType)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getKeepType", &modGetKeepType)); + CHKiRet((*modGetKeepType)(&pNew->eKeepType)); + dbgprintf("module %s of type %d being loaded (keepType=%d).\n", name, pNew->eType, pNew->eKeepType); + + /* OK, we know we can successfully work with the module. So we now fill the + * rest of the data elements. First we load the interfaces common to all + * module types. + */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)); + localRet = (*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature", &pNew->isCompatibleWithFeature); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) + pNew->isCompatibleWithFeature = dummyIsCompatibleWithFeature; + else if(localRet != RS_RET_OK) + ABORT_FINALIZE(localRet); + localRet = (*pNew->modQueryEtryPt)((uchar*)"setModCnf", &pNew->setModCnf); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) + pNew->setModCnf = NULL; + else if(localRet != RS_RET_OK) + ABORT_FINALIZE(localRet); + + /* optional calls for new config system */ + localRet = (*pNew->modQueryEtryPt)((uchar*)"getModCnfName", &getModCnfName); + if(localRet == RS_RET_OK) { + if(getModCnfName(&cnfName) == RS_RET_OK) + pNew->cnfName = (uchar*) strdup((char*)cnfName); + /**< we do not care if strdup() fails, we can accept that */ + else + pNew->cnfName = NULL; + dbgprintf("module config name is '%s'\n", cnfName); + } + localRet = (*pNew->modQueryEtryPt)((uchar*)"beginCnfLoad", &pNew->beginCnfLoad); + if(localRet == RS_RET_OK) { + dbgprintf("module %s supports rsyslog v6 config interface\n", name); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"endCnfLoad", &pNew->endCnfLoad)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeCnf", &pNew->freeCnf)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"checkCnf", &pNew->checkCnf)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"activateCnf", &pNew->activateCnf)); + localRet = (*pNew->modQueryEtryPt)((uchar*)"activateCnfPrePrivDrop", &pNew->activateCnfPrePrivDrop); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) { + pNew->activateCnfPrePrivDrop = NULL; + } else { + CHKiRet(localRet); + } + } else if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) { + pNew->beginCnfLoad = NULL; /* flag as non-present */ + } else { + ABORT_FINALIZE(localRet); + } + /* ... and now the module-specific interfaces */ + switch(pNew->eType) { + case eMOD_IN: + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"runInput", &pNew->mod.im.runInput)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"willRun", &pNew->mod.im.willRun)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"afterRun", &pNew->mod.im.afterRun)); + pNew->mod.im.bCanRun = 0; + localRet = (*pNew->modQueryEtryPt)((uchar*)"newInpInst", &pNew->mod.im.newInpInst); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) { + pNew->mod.om.newActInst = NULL; + } else if(localRet != RS_RET_OK) { + ABORT_FINALIZE(localRet); + } + break; + case eMOD_OUT: + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo", &pNew->dbgPrintInstInfo)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)); + /* try load optional interfaces */ + localRet = (*pNew->modQueryEtryPt)((uchar*)"doHUP", &pNew->doHUP); + if(localRet != RS_RET_OK && localRet != RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) + ABORT_FINALIZE(localRet); + + localRet = (*pNew->modQueryEtryPt)((uchar*)"SetShutdownImmdtPtr", &pNew->mod.om.SetShutdownImmdtPtr); + if(localRet != RS_RET_OK && localRet != RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) + ABORT_FINALIZE(localRet); + + localRet = (*pNew->modQueryEtryPt)((uchar*)"beginTransaction", &pNew->mod.om.beginTransaction); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) + pNew->mod.om.beginTransaction = dummyBeginTransaction; + else if(localRet != RS_RET_OK) + ABORT_FINALIZE(localRet); + + localRet = (*pNew->modQueryEtryPt)((uchar*)"endTransaction", + &pNew->mod.om.endTransaction); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) { + pNew->mod.om.endTransaction = dummyEndTransaction; + } else if(localRet != RS_RET_OK) { + ABORT_FINALIZE(localRet); + } + + localRet = (*pNew->modQueryEtryPt)((uchar*)"newActInst", &pNew->mod.om.newActInst); + if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) { + pNew->mod.om.newActInst = dummynewActInst; + } else if(localRet != RS_RET_OK) { + ABORT_FINALIZE(localRet); + } + break; + case eMOD_LIB: + break; + case eMOD_PARSER: + /* first, we need to obtain the parser object. We could not do that during + * init as that would have caused class bootstrap issues which are not + * absolutely necessary. Note that we can call objUse() multiple times, it + * handles that. + */ + CHKiRet(objUse(parser, CORE_COMPONENT)); + /* here, we create a new parser object */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parse", &pNew->mod.pm.parse)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"GetParserName", &GetName)); + CHKiRet(GetName(&pName)); + CHKiRet(parser.Construct(&pParser)); + + /* check some features */ + localRet = pNew->isCompatibleWithFeature(sFEATUREAutomaticSanitazion); + if(localRet == RS_RET_OK){ + CHKiRet(parser.SetDoSanitazion(pParser, RSTRUE)); + } + localRet = pNew->isCompatibleWithFeature(sFEATUREAutomaticPRIParsing); + if(localRet == RS_RET_OK){ + CHKiRet(parser.SetDoPRIParsing(pParser, RSTRUE)); + } + + CHKiRet(parser.SetName(pParser, pName)); + CHKiRet(parser.SetModPtr(pParser, pNew)); + CHKiRet(parser.ConstructFinalize(pParser)); + break; + case eMOD_STRGEN: + /* first, we need to obtain the strgen object. We could not do that during + * init as that would have caused class bootstrap issues which are not + * absolutely necessary. Note that we can call objUse() multiple times, it + * handles that. + */ + CHKiRet(objUse(strgen, CORE_COMPONENT)); + /* here, we create a new parser object */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"strgen", &pNew->mod.sm.strgen)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"GetName", &GetName)); + CHKiRet(GetName(&pName)); + CHKiRet(strgen.Construct(&pStrgen)); + CHKiRet(strgen.SetName(pStrgen, pName)); + CHKiRet(strgen.SetModPtr(pStrgen, pNew)); + CHKiRet(strgen.ConstructFinalize(pStrgen)); + break; + case eMOD_ANY: /* this is mostly to keep the compiler happy! */ + DBGPRINTF("PROGRAM ERROR: eMOD_ANY set as module type\n"); + assert(0); + break; + } + + pNew->pszName = (uchar*) strdup((char*)name); /* we do not care if strdup() fails, we can accept that */ + pNew->pModHdlr = pModHdlr; + if(pModHdlr == NULL) { + pNew->eLinkType = eMOD_LINK_STATIC; + } else { + pNew->eLinkType = eMOD_LINK_DYNAMIC_LOADED; + + /* if we need to keep the linked module, save it */ + if (pNew->eKeepType == eMOD_KEEP) { + /* see if we have this one already */ + for (pHandle = pHandles; pHandle; pHandle = pHandle->next) { + if (!strcmp((char *)name, (char *)pHandle->pszName)) + break; + } + + /* not found, create it */ + if (!pHandle) { + if((pHandle = malloc(sizeof (*pHandle))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + if((pHandle->pszName = (uchar*) strdup((char*)name)) == NULL) { + free(pHandle); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + pHandle->pModHdlr = pModHdlr; + pHandle->next = pHandles; + + pHandles = pHandle; + } + } + } + + /* we initialized the structure, now let's add it to the linked list of modules */ + addModToGlblList(pNew); + *pNewModule = pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + moduleDestruct(pNew); + *pNewModule = NULL; + } + + RETiRet; +} + +/* Print loaded modules. This is more or less a + * debug or test aid, but anyhow I think it's worth it... + * This only works if the dbgprintf() subsystem is initialized. + * TODO: update for new input modules! + */ +static void modPrintList(void) +{ + modInfo_t *pMod; + + pMod = GetNxt(NULL); + while(pMod != NULL) { + dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ", + (char*) modGetName(pMod), pMod->iIFVers); + dbgprintf("type="); + switch(pMod->eType) { + case eMOD_OUT: + dbgprintf("output"); + break; + case eMOD_IN: + dbgprintf("input"); + break; + case eMOD_LIB: + dbgprintf("library"); + break; + case eMOD_PARSER: + dbgprintf("parser"); + break; + case eMOD_STRGEN: + dbgprintf("strgen"); + break; + case eMOD_ANY: /* this is mostly to keep the compiler happy! */ + DBGPRINTF("PROGRAM ERROR: eMOD_ANY set as module type\n"); + assert(0); + break; + } + dbgprintf(" module.\n"); + dbgprintf("Entry points:\n"); + dbgprintf("\tqueryEtryPt: 0x%lx\n", (unsigned long) pMod->modQueryEtryPt); + dbgprintf("\tdbgPrintInstInfo: 0x%lx\n", (unsigned long) pMod->dbgPrintInstInfo); + dbgprintf("\tfreeInstance: 0x%lx\n", (unsigned long) pMod->freeInstance); + dbgprintf("\tbeginCnfLoad: 0x%lx\n", (unsigned long) pMod->beginCnfLoad); + dbgprintf("\tSetModCnf: 0x%lx\n", (unsigned long) pMod->setModCnf); + dbgprintf("\tcheckCnf: 0x%lx\n", (unsigned long) pMod->checkCnf); + dbgprintf("\tactivateCnfPrePrivDrop: 0x%lx\n", (unsigned long) pMod->activateCnfPrePrivDrop); + dbgprintf("\tactivateCnf: 0x%lx\n", (unsigned long) pMod->activateCnf); + dbgprintf("\tfreeCnf: 0x%lx\n", (unsigned long) pMod->freeCnf); + switch(pMod->eType) { + case eMOD_OUT: + dbgprintf("Output Module Entry Points:\n"); + dbgprintf("\tdoAction: %p\n", pMod->mod.om.doAction); + dbgprintf("\tparseSelectorAct: %p\n", pMod->mod.om.parseSelectorAct); + dbgprintf("\tnewActInst: %p\n", (pMod->mod.om.newActInst == dummynewActInst) ? + NULL : pMod->mod.om.newActInst); + dbgprintf("\ttryResume: %p\n", pMod->tryResume); + dbgprintf("\tdoHUP: %p\n", pMod->doHUP); + dbgprintf("\tBeginTransaction: %p\n", ((pMod->mod.om.beginTransaction == dummyBeginTransaction) ? + NULL : pMod->mod.om.beginTransaction)); + dbgprintf("\tEndTransaction: %p\n", ((pMod->mod.om.endTransaction == dummyEndTransaction) ? + NULL : pMod->mod.om.endTransaction)); + break; + case eMOD_IN: + dbgprintf("Input Module Entry Points\n"); + dbgprintf("\trunInput: 0x%lx\n", (unsigned long) pMod->mod.im.runInput); + dbgprintf("\twillRun: 0x%lx\n", (unsigned long) pMod->mod.im.willRun); + dbgprintf("\tafterRun: 0x%lx\n", (unsigned long) pMod->mod.im.afterRun); + break; + case eMOD_LIB: + break; + case eMOD_PARSER: + dbgprintf("Parser Module Entry Points\n"); + dbgprintf("\tparse: 0x%lx\n", (unsigned long) pMod->mod.pm.parse); + break; + case eMOD_STRGEN: + dbgprintf("Strgen Module Entry Points\n"); + dbgprintf("\tstrgen: 0x%lx\n", (unsigned long) pMod->mod.sm.strgen); + break; + case eMOD_ANY: /* this is mostly to keep the compiler happy! */ + break; + } + dbgprintf("\n"); + pMod = GetNxt(pMod); /* done, go next */ + } +} + + +/* unlink and destroy a module. The caller must provide a pointer to the module + * itself as well as one to its immediate predecessor. + * rgerhards, 2008-02-26 + */ +static rsRetVal +modUnlinkAndDestroy(modInfo_t **ppThis) +{ + DEFiRet; + modInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + pthread_mutex_lock(&mutObjGlobalOp); + + /* first check if we are permitted to unload */ + if(pThis->eType == eMOD_LIB) { + if(pThis->uRefCnt > 0) { + dbgprintf("module %s NOT unloaded because it still has a refcount of %u\n", + pThis->pszName, pThis->uRefCnt); +# ifdef DEBUG + //modUsrPrintAll(); +# endif + ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); + } + } + + /* we need to unlink the module before we can destruct it -- rgerhards, 2008-02-26 */ + if(pThis->pPrev == NULL) { + /* module is root, so we need to set a new root */ + pLoadedModules = pThis->pNext; + } else { + pThis->pPrev->pNext = pThis->pNext; + } + + if(pThis->pNext == NULL) { + pLoadedModulesLast = pThis->pPrev; + } else { + pThis->pNext->pPrev = pThis->pPrev; + } + + /* finally, we are ready for the module to go away... */ + dbgprintf("Unloading module %s\n", modGetName(pThis)); + CHKiRet(modPrepareUnload(pThis)); + *ppThis = pThis->pNext; + + moduleDestruct(pThis); + +finalize_it: + pthread_mutex_unlock(&mutObjGlobalOp); + RETiRet; +} + + +/* unload all loaded modules of a specific type (use eMOD_ALL if you want to + * unload all module types). The unload happens only if the module is no longer + * referenced. So some modules may survive this call. + * rgerhards, 2008-03-11 + */ +static rsRetVal +modUnloadAndDestructAll(eModLinkType_t modLinkTypesToUnload) +{ + DEFiRet; + modInfo_t *pModCurr; /* module currently being processed */ + + pModCurr = GetNxt(NULL); + while(pModCurr != NULL) { + if(modLinkTypesToUnload == eMOD_LINK_ALL || pModCurr->eLinkType == modLinkTypesToUnload) { + if(modUnlinkAndDestroy(&pModCurr) == RS_RET_MODULE_STILL_REFERENCED) { + pModCurr = GetNxt(pModCurr); + } else { + /* Note: if the module was successfully unloaded, it has updated the + * pModCurr pointer to the next module. However, the unload process may + * still have indirectly referenced the pointer list in a way that the + * unloaded module is not aware of. So we restart the unload process + * to make sure we do not fall into a trap (what we did ;)). The + * performance toll is minimal. -- rgerhards, 2008-04-28 + */ + pModCurr = GetNxt(NULL); + } + } else { + pModCurr = GetNxt(pModCurr); + } + } + +# ifdef DEBUG + /* DEV DEBUG only! + if(pLoadedModules != NULL) { + dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n"); + modUsrPrintAll(); + } + */ +# endif + + RETiRet; +} + +/* find module with given name in global list */ +static inline rsRetVal +findModule(uchar *pModName, int iModNameLen, modInfo_t **pMod) +{ + modInfo_t *pModInfo; + uchar *pModNameCmp; + DEFiRet; + + pModInfo = GetNxt(NULL); + while(pModInfo != NULL) { + if(!strncmp((char *) pModName, (char *) (pModNameCmp = modGetName(pModInfo)), iModNameLen) && + (!*(pModNameCmp + iModNameLen) || !strcmp((char *) pModNameCmp + iModNameLen, ".so"))) { + dbgprintf("Module '%s' found\n", pModName); + break; + } + pModInfo = GetNxt(pModInfo); + } + *pMod = pModInfo; + RETiRet; +} + + +/* load a module and initialize it, based on doModLoad() from conf.c + * rgerhards, 2008-03-05 + * varmojfekoj added support for dynamically loadable modules on 2007-08-13 + * rgerhards, 2007-09-25: please note that the non-threadsafe function dlerror() is + * called below. This is ok because modules are currently only loaded during + * configuration file processing, which is executed on a single thread. Should we + * change that design at any stage (what is unlikely), we need to find a + * replacement. + * rgerhards, 2011-04-27: + * Parameter "bConfLoad" tells us if the load was triggered by a config handler, in + * which case we need to tie the loaded module to the current config. If bConfLoad == 0, + * the system loads a module for internal reasons, this is not directly tied to a + * configuration. We could also think if it would be useful to add only certain types + * of modules, but the current implementation at least looks simpler. + * Note: pvals = NULL means legacy config system + */ +static rsRetVal +Load(uchar *pModName, sbool bConfLoad, struct nvlst *lst) +{ + size_t iPathLen, iModNameLen; + int bHasExtension; + void *pModHdlr, *pModInit; + modInfo_t *pModInfo; + cfgmodules_etry_t *pNew; + cfgmodules_etry_t *pLast; + uchar *pModDirCurr, *pModDirNext; + int iLoadCnt; + struct dlhandle_s *pHandle = NULL; +# ifdef PATH_MAX + uchar pathBuf[PATH_MAX+1]; +# else + uchar pathBuf[4096]; +# endif + uchar *pPathBuf = pathBuf; + size_t lenPathBuf = sizeof(pathBuf); + rsRetVal localRet; + DEFiRet; + + assert(pModName != NULL); + DBGPRINTF("Requested to load module '%s'\n", pModName); + + iModNameLen = strlen((char*)pModName); + /* overhead for a full path is potentially 1 byte for a slash, + * three bytes for ".so" and one byte for '\0'. + */ +# define PATHBUF_OVERHEAD 1 + iModNameLen + 3 + 1 + + pthread_mutex_lock(&mutObjGlobalOp); + + if(iModNameLen > 3 && !strcmp((char *) pModName + iModNameLen - 3, ".so")) { + iModNameLen -= 3; + bHasExtension = RSTRUE; + } else + bHasExtension = RSFALSE; + + CHKiRet(findModule(pModName, iModNameLen, &pModInfo)); + if(pModInfo != NULL) { + DBGPRINTF("Module '%s' already loaded\n", pModName); + if(bConfLoad) { + localRet = readyModForCnf(pModInfo, &pNew, &pLast); + if(pModInfo->setModCnf != NULL && localRet == RS_RET_OK) { + if(!strncmp((char*)pModName, "builtin:", sizeof("builtin:")-1)) { + if(pModInfo->bSetModCnfCalled) { + errmsg.LogError(0, RS_RET_DUP_PARAM, + "parameters for built-in module %s already set - ignored\n", + pModName); + ABORT_FINALIZE(RS_RET_DUP_PARAM); + } else { + /* for built-in moules, we need to call setModConf, + * because there is no way to set parameters at load + * time for obvious reasons... + */ + if(lst != NULL) + pModInfo->setModCnf(lst); + pModInfo->bSetModCnfCalled = 1; + } + } else { + /* regular modules need to be added to conf list (for + * builtins, this happend during initial load). + */ + addModToCnfList(pNew, pLast); + } + } + } + FINALIZE; + } + + pModDirCurr = (uchar *)((pModDir == NULL) ? + _PATH_MODDIR : (char *)pModDir); + pModDirNext = NULL; + pModHdlr = NULL; + iLoadCnt = 0; + do { /* now build our load module name */ + if(*pModName == '/' || *pModName == '.') { + if(lenPathBuf < PATHBUF_OVERHEAD) { + if(pPathBuf != pathBuf) /* already malloc()ed memory? */ + free(pPathBuf); + /* we always alloc enough memory for everything we potentiall need to add */ + lenPathBuf = PATHBUF_OVERHEAD; + CHKmalloc(pPathBuf = malloc(sizeof(char)*lenPathBuf)); + } + *pPathBuf = '\0'; /* we do not need to append the path - its already in the module name */ + iPathLen = 0; + } else { + *pPathBuf = '\0'; + + iPathLen = strlen((char *)pModDirCurr); + pModDirNext = (uchar *)strchr((char *)pModDirCurr, ':'); + if(pModDirNext) + iPathLen = (size_t)(pModDirNext - pModDirCurr); + + if(iPathLen == 0) { + if(pModDirNext) { + pModDirCurr = pModDirNext + 1; + continue; + } + break; + } else if(iPathLen > lenPathBuf - PATHBUF_OVERHEAD) { + if(pPathBuf != pathBuf) /* already malloc()ed memory? */ + free(pPathBuf); + /* we always alloc enough memory for everything we potentiall need to add */ + lenPathBuf = iPathLen + PATHBUF_OVERHEAD; + CHKmalloc(pPathBuf = malloc(sizeof(char)*lenPathBuf)); + } + + memcpy((char *) pPathBuf, (char *)pModDirCurr, iPathLen); + if((pPathBuf[iPathLen - 1] != '/')) { + /* we have space, made sure in previous check */ + pPathBuf[iPathLen++] = '/'; + } + pPathBuf[iPathLen] = '\0'; + + if(pModDirNext) + pModDirCurr = pModDirNext + 1; + } + + /* ... add actual name ... */ + strncat((char *) pPathBuf, (char *) pModName, lenPathBuf - iPathLen - 1); + + /* now see if we have an extension and, if not, append ".so" */ + if(!bHasExtension) { + /* we do not have an extension and so need to add ".so" + * TODO: I guess this is highly importable, so we should change the + * algo over time... -- rgerhards, 2008-03-05 + */ + strncat((char *) pPathBuf, ".so", lenPathBuf - strlen((char*) pPathBuf) - 1); + iPathLen += 3; + } + + /* complete load path constructed, so ... GO! */ + dbgprintf("loading module '%s'\n", pPathBuf); + + /* see if we have this one already */ + for (pHandle = pHandles; pHandle; pHandle = pHandle->next) { + if (!strcmp((char *)pModName, (char *)pHandle->pszName)) { + pModHdlr = pHandle->pModHdlr; + break; + } + } + + /* not found, try to dynamically link it */ + if (!pModHdlr) { + pModHdlr = dlopen((char *) pPathBuf, RTLD_NOW); + } + + iLoadCnt++; + + } while(pModHdlr == NULL && *pModName != '/' && pModDirNext); + + if(!pModHdlr) { + if(iLoadCnt) { + errmsg.LogError(0, RS_RET_MODULE_LOAD_ERR_DLOPEN, "could not load module '%s', dlopen: %s\n", + pPathBuf, dlerror()); + } else { + errmsg.LogError(0, NO_ERRCODE, "could not load module '%s', ModDir was '%s'\n", pPathBuf, + ((pModDir == NULL) ? _PATH_MODDIR : (char *)pModDir)); + } + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN); + } + if(!(pModInit = dlsym(pModHdlr, "modInit"))) { + errmsg.LogError(0, RS_RET_MODULE_LOAD_ERR_NO_INIT, + "could not load module '%s', dlsym: %s\n", pPathBuf, dlerror()); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_NO_INIT); + } + if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr, &pModInfo)) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_MODULE_LOAD_ERR_INIT_FAILED, + "could not load module '%s', rsyslog error %d\n", pPathBuf, iRet); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED); + } + + if(bConfLoad) { + readyModForCnf(pModInfo, &pNew, &pLast); + if(pModInfo->setModCnf != NULL) { + if(lst != NULL) { + localRet = pModInfo->setModCnf(lst); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, + "module '%s', failed processing config parameters", + pPathBuf); + abortCnfUse(pNew); + ABORT_FINALIZE(localRet); + } + } + pModInfo->bSetModCnfCalled = 1; + } + addModToCnfList(pNew, pLast); + } + +finalize_it: + if(pPathBuf != pathBuf) /* used malloc()ed memory? */ + free(pPathBuf); + pthread_mutex_unlock(&mutObjGlobalOp); + RETiRet; +} + + +/* the v6+ way of loading modules: process a "module(...)" directive. + * rgerhards, 2012-06-20 + */ +rsRetVal +modulesProcessCnf(struct cnfobj *o) +{ + struct cnfparamvals *pvals; + uchar *cnfModName = NULL; + int typeIdx; + DEFiRet; + + pvals = nvlstGetParams(o->nvlst, &pblk, NULL); + if(pvals == NULL) { + ABORT_FINALIZE(RS_RET_ERR); + } + DBGPRINTF("modulesProcessCnf params:\n"); + cnfparamsPrint(&pblk, pvals); + typeIdx = cnfparamGetIdx(&pblk, "load"); + if(pvals[typeIdx].bUsed == 0) { + errmsg.LogError(0, RS_RET_CONF_RQRD_PARAM_MISSING, "module type missing"); + ABORT_FINALIZE(RS_RET_CONF_RQRD_PARAM_MISSING); + } + + cnfModName = (uchar*)es_str2cstr(pvals[typeIdx].val.d.estr, NULL); + iRet = Load(cnfModName, 1, o->nvlst); + +finalize_it: + free(cnfModName); + cnfparamvalsDestruct(pvals, &pblk); + RETiRet; +} + + +/* set the default module load directory. A NULL value may be provided, in + * which case any previous value is deleted but no new one set. The caller-provided + * string is duplicated. If it needs to be freed, that's the caller's duty. + * rgerhards, 2008-03-07 + */ +static rsRetVal +SetModDir(uchar *pszModDir) +{ + DEFiRet; + + dbgprintf("setting default module load directory '%s'\n", pszModDir); + if(pModDir != NULL) { + free(pModDir); + } + + pModDir = (uchar*) strdup((char*)pszModDir); + + RETiRet; +} + + +/* Reference-Counting object access: add 1 to the current reference count. Must be + * called by anyone interested in using a module. -- rgerhards, 20080-03-10 + */ +static rsRetVal +Use(char *srcFile, modInfo_t *pThis) +{ + DEFiRet; + + assert(pThis != NULL); + pThis->uRefCnt++; + dbgprintf("source file %s requested reference for module '%s', reference count now %u\n", + srcFile, pThis->pszName, pThis->uRefCnt); + +# ifdef DEBUG + modUsrAdd(pThis, srcFile); +# endif + + RETiRet; + +} + + +/* Reference-Counting object access: subract one from the current refcount. Must + * by called by anyone who no longer needs a module. If count reaches 0, the + * module is unloaded. -- rgerhards, 20080-03-10 + */ +static rsRetVal +Release(char *srcFile, modInfo_t **ppThis) +{ + DEFiRet; + modInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + if(pThis->uRefCnt == 0) { + /* oops, we are already at 0? */ + dbgprintf("internal error: module '%s' already has a refcount of 0 (released by %s)!\n", + pThis->pszName, srcFile); + } else { + --pThis->uRefCnt; + dbgprintf("file %s released module '%s', reference count now %u\n", + srcFile, pThis->pszName, pThis->uRefCnt); +# ifdef DEBUG + modUsrDel(pThis, srcFile); + modUsrPrint(pThis); +# endif + } + + if(pThis->uRefCnt == 0) { + /* we have a zero refcount, so we must unload the module */ + dbgprintf("module '%s' has zero reference count, unloading...\n", pThis->pszName); + modUnlinkAndDestroy(&pThis); + /* we must NOT do a *ppThis = NULL, because ppThis now points into freed memory! + * If in doubt, see obj.c::ReleaseObj() for how we are called. + */ + } + + RETiRet; + +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +BEGINObjClassExit(module, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(module) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + free(pModDir); +# ifdef DEBUG + modUsrPrintAll(); /* debug aid - TODO: integrate with debug.c, at least the settings! */ +# endif +ENDObjClassExit(module) + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(module) +CODESTARTobjQueryInterface(module) + if(pIf->ifVersion != moduleCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->GetNxt = GetNxt; + pIf->GetNxtCnfType = GetNxtCnfType; + pIf->GetName = modGetName; + pIf->GetStateName = modGetStateName; + pIf->PrintList = modPrintList; + pIf->FindWithCnfName = FindWithCnfName; + pIf->UnloadAndDestructAll = modUnloadAndDestructAll; + pIf->doModInit = doModInit; + pIf->SetModDir = SetModDir; + pIf->Load = Load; + pIf->Use = Use; + pIf->Release = Release; +finalize_it: +ENDobjQueryInterface(module) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-03-05 + */ +BEGINAbstractObjClassInit(module, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + uchar *pModPath; + + /* use any module load path specified in the environment */ + if((pModPath = (uchar*) getenv("RSYSLOG_MODDIR")) != NULL) { + SetModDir(pModPath); + } + + /* now check if another module path was set via the command line (-M) + * if so, that overrides the environment. Please note that we must use + * a global setting here because the command line parser can NOT call + * into the module object, because it is not initialized at that point. So + * instead a global setting is changed and we pick it up as soon as we + * initialize -- rgerhards, 2008-04-04 + */ + if(glblModPath != NULL) { + SetModDir(glblModPath); + } + + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDObjClassInit(module) + +/* vi:set ai: + */ diff --git a/runtime/modules.h b/runtime/modules.h new file mode 100644 index 00000000..64644be2 --- /dev/null +++ b/runtime/modules.h @@ -0,0 +1,201 @@ +/* modules.h + * + * Definition for build-in and plug-ins module handler. This file is the base + * for all dynamically loadable module support. In theory, in v3 all modules + * are dynamically loaded, in practice we currently do have a few build-in + * once. This may become removed. + * + * The loader keeps track of what is loaded. For library modules, it is also + * used to find objects (libraries) and to obtain the queryInterface function + * for them. A reference count is maintened for libraries, so that they are + * unloaded only when nobody still accesses them. + * + * File begun on 2007-07-22 by RGerhards + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef MODULES_H_INCLUDED +#define MODULES_H_INCLUDED 1 + +#include "objomsr.h" +#include "rainerscript.h" + +/* the following define defines the current version of the module interface. + * It can be used by any module which want's to simply prevent version conflicts + * and does not intend to do specific old-version emulations. + * rgerhards, 2008-03-04 + * version 3 adds modInfo_t ptr to call of modInit -- rgerhards, 2008-03-10 + * version 4 removes needUDPSocket OM callback -- rgerhards, 2008-03-22 + * version 5 changes the way parsing works for input modules. This is + * an important change, parseAndSubmitMessage() goes away. Other + * module types are not affected. -- rgerhards, 2008-10-09 + * version 6 introduces scoping support (starting with the output + * modules) -- rgerhards, 2010-07-27 + */ +#define CURR_MOD_IF_VERSION 6 + +typedef enum eModType_ { + eMOD_IN = 0, /* input module */ + eMOD_OUT = 1, /* output module */ + eMOD_LIB = 2, /* library module */ + eMOD_PARSER = 3,/* parser module */ + eMOD_STRGEN = 4,/* strgen module */ + eMOD_ANY = 5 /* meta-name for "any type of module" -- to be used in function calls */ +} eModType_t; + + +#ifdef DEBUG +typedef struct modUsr_s { + struct modUsr_s *pNext; + char *pszFile; +} modUsr_t; +#endif + + +/* how is this module linked? */ +typedef enum eModLinkType_ { + eMOD_LINK_STATIC, + eMOD_LINK_DYNAMIC_UNLOADED, /* dynalink module, currently not loaded */ + eMOD_LINK_DYNAMIC_LOADED, /* dynalink module, currently loaded */ + eMOD_LINK_ALL /* special: all linkage types, e.g. for unload */ +} eModLinkType_t; + +/* remember which shared libs we dlopen()-ed */ +struct dlhandle_s { + uchar *pszName; + void *pModHdlr; + struct dlhandle_s *next; +}; + +/* should this module be kept linked? */ +typedef enum eModKeepType_ { + eMOD_NOKEEP, + eMOD_KEEP +} eModKeepType_t; + +struct modInfo_s { + struct modInfo_s *pPrev; /* support for creating a double linked module list */ + struct modInfo_s *pNext; /* support for creating a linked module list */ + int iIFVers; /* Interface version of module */ + eModType_t eType; /* type of this module */ + eModLinkType_t eLinkType; + eModKeepType_t eKeepType; /* keep the module dynamically linked on unload */ + uchar* pszName; /* printable module name, e.g. for dbgprintf */ + uchar* cnfName; /* name to be used in config statements (e.g. 'name="omusrmsg"') */ + unsigned uRefCnt; /* reference count for this module; 0 -> may be unloaded */ + sbool bSetModCnfCalled;/* is setModCnf already called? Needed for built-in modules */ + /* functions supported by all types of modules */ + rsRetVal (*modInit)(int, int*, rsRetVal(**)()); /* initialize the module */ + /* be sure to support version handshake! */ + rsRetVal (*modQueryEtryPt)(uchar *name, rsRetVal (**EtryPoint)()); /* query entry point addresses */ + rsRetVal (*isCompatibleWithFeature)(syslogFeature); + rsRetVal (*freeInstance)(void*);/* called before termination or module unload */ + rsRetVal (*dbgPrintInstInfo)(void*);/* called before termination or module unload */ + rsRetVal (*tryResume)(void*);/* called to see if module actin can be resumed now */ + rsRetVal (*modExit)(void); /* called before termination or module unload */ + rsRetVal (*modGetID)(void **); /* get its unique ID from module */ + rsRetVal (*doHUP)(void *); /* non-restart type HUP handler */ + /* v2 config system specific */ + rsRetVal (*beginCnfLoad)(void*newCnf, rsconf_t *pConf); + rsRetVal (*setModCnf)(struct nvlst *lst); + rsRetVal (*endCnfLoad)(void*Cnf); + rsRetVal (*checkCnf)(void*Cnf); + rsRetVal (*activateCnfPrePrivDrop)(void*Cnf); + rsRetVal (*activateCnf)(void*Cnf); /* make provided config the running conf */ + rsRetVal (*freeCnf)(void*Cnf); + /* end v2 config system specific */ + /* below: create an instance of this module. Most importantly the module + * can allocate instance memory in this call. + */ + rsRetVal (*createInstance)(); + union { + struct {/* data for input modules */ +/* TODO: remove? */rsRetVal (*willRun)(void); /* check if the current config will be able to run*/ + rsRetVal (*runInput)(thrdInfo_t*); /* function to gather input and submit to queue */ + rsRetVal (*afterRun)(thrdInfo_t*); /* function to gather input and submit to queue */ + rsRetVal (*newInpInst)(struct nvlst *lst); + int bCanRun; /* cached value of whether willRun() succeeded */ + } im; + struct {/* data for output modules */ + /* below: perform the configured action + */ + rsRetVal (*beginTransaction)(void*); + rsRetVal (*doAction)(uchar**, unsigned, void*); + rsRetVal (*endTransaction)(void*); + rsRetVal (*parseSelectorAct)(uchar**, void**,omodStringRequest_t**); + rsRetVal (*newActInst)(uchar *modName, struct nvlst *lst, void **, omodStringRequest_t **); + rsRetVal (*SetShutdownImmdtPtr)(void *pData, void *pPtr); + } om; + struct { /* data for library modules */ + char dummy; + } lm; + struct { /* data for parser modules */ + rsRetVal (*parse)(msg_t*); + } pm; + struct { /* data for strgen modules */ + rsRetVal (*strgen)(msg_t*, uchar**, size_t *); + } sm; + } mod; + void *pModHdlr; /* handler to the dynamic library holding the module */ +# ifdef DEBUG + /* we add some home-grown support to track our users (and detect who does not free us). */ + modUsr_t *pModUsrRoot; +# endif +}; + + +/* interfaces */ +BEGINinterface(module) /* name must also be changed in ENDinterface macro! */ + modInfo_t *(*GetNxt)(modInfo_t *pThis); + cfgmodules_etry_t *(*GetNxtCnfType)(rsconf_t *cnf, cfgmodules_etry_t *pThis, eModType_t rqtdType); + uchar *(*GetName)(modInfo_t *pThis); + uchar *(*GetStateName)(modInfo_t *pThis); + rsRetVal (*Use)(char *srcFile, modInfo_t *pThis); /**< must be called before a module is used (ref counting) */ + rsRetVal (*Release)(char *srcFile, modInfo_t **ppThis); /**< release a module (ref counting) */ + void (*PrintList)(void); + rsRetVal (*UnloadAndDestructAll)(eModLinkType_t modLinkTypesToUnload); + rsRetVal (*doModInit)(rsRetVal (*modInit)(), uchar *name, void *pModHdlr, modInfo_t **pNew); + rsRetVal (*Load)(uchar *name, sbool bConfLoad, struct nvlst *lst); + rsRetVal (*SetModDir)(uchar *name); + modInfo_t *(*FindWithCnfName)(rsconf_t *cnf, uchar *name, eModType_t rqtdType); /* added v3, 2011-07-19 */ +ENDinterface(module) +#define moduleCURR_IF_VERSION 4 /* increment whenever you change the interface structure! */ +/* Changes: + * v2 + * - added param bCondLoad to Load call - 2011-04-27 + * - removed GetNxtType, added GetNxtCnfType - 2011-04-27 + * v3 (see above) + * v4 + * - added third parameter to Load() - 2012-06-20 + */ + +/* prototypes */ +PROTOTYPEObj(module); +/* in v6, we go back to in-core static link for core objects, at least those + * that are not called from plugins. + * ... and we need to know that none of the module functions are called from plugins! + * rgerhards, 2012-09-24 + */ +rsRetVal modulesProcessCnf(struct cnfobj *o); +uchar *modGetName(modInfo_t *pThis); +rsRetVal addModToCnfList(cfgmodules_etry_t *pNew, cfgmodules_etry_t *pLast); +rsRetVal readyModForCnf(modInfo_t *pThis, cfgmodules_etry_t **ppNew, cfgmodules_etry_t **ppLast); +#endif /* #ifndef MODULES_H_INCLUDED */ diff --git a/runtime/msg.c b/runtime/msg.c new file mode 100644 index 00000000..a5c52810 --- /dev/null +++ b/runtime/msg.c @@ -0,0 +1,4085 @@ +/* msg.c + * The msg object. Implementation of all msg-related functions + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#define SYSLOG_NAMES +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include <sys/socket.h> +#if HAVE_SYSINFO_UPTIME +#include <sys/sysinfo.h> +#endif +#include <netdb.h> +#include <libestr.h> +#include <json/json.h> +/* For struct json_object_iter, should not be necessary in future versions */ +#include <json/json_object_private.h> +#if HAVE_MALLOC_H +# include <malloc.h> +#endif +#ifdef USE_LIBUUID + #include <uuid/uuid.h> +#endif +#include "rsyslog.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "template.h" +#include "msg.h" +#include "datetime.h" +#include "glbl.h" +#include "regexp.h" +#include "atomic.h" +#include "unicode-helper.h" +#include "ruleset.h" +#include "prop.h" +#include "net.h" +#include "var.h" +#include "rsconf.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(datetime) +DEFobjCurrIf(glbl) +DEFobjCurrIf(regexp) +DEFobjCurrIf(prop) +DEFobjCurrIf(net) +DEFobjCurrIf(var) + +static char *two_digits[100] = { + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"}; + +static struct { + uchar *pszName; + short lenName; +} syslog_pri_names[192] = { + { UCHAR_CONSTANT("0"), 3}, + { UCHAR_CONSTANT("1"), 3}, + { UCHAR_CONSTANT("2"), 3}, + { UCHAR_CONSTANT("3"), 3}, + { UCHAR_CONSTANT("4"), 3}, + { UCHAR_CONSTANT("5"), 3}, + { UCHAR_CONSTANT("6"), 3}, + { UCHAR_CONSTANT("7"), 3}, + { UCHAR_CONSTANT("8"), 3}, + { UCHAR_CONSTANT("9"), 3}, + { UCHAR_CONSTANT("10"), 4}, + { UCHAR_CONSTANT("11"), 4}, + { UCHAR_CONSTANT("12"), 4}, + { UCHAR_CONSTANT("13"), 4}, + { UCHAR_CONSTANT("14"), 4}, + { UCHAR_CONSTANT("15"), 4}, + { UCHAR_CONSTANT("16"), 4}, + { UCHAR_CONSTANT("17"), 4}, + { UCHAR_CONSTANT("18"), 4}, + { UCHAR_CONSTANT("19"), 4}, + { UCHAR_CONSTANT("20"), 4}, + { UCHAR_CONSTANT("21"), 4}, + { UCHAR_CONSTANT("22"), 4}, + { UCHAR_CONSTANT("23"), 4}, + { UCHAR_CONSTANT("24"), 4}, + { UCHAR_CONSTANT("25"), 4}, + { UCHAR_CONSTANT("26"), 4}, + { UCHAR_CONSTANT("27"), 4}, + { UCHAR_CONSTANT("28"), 4}, + { UCHAR_CONSTANT("29"), 4}, + { UCHAR_CONSTANT("30"), 4}, + { UCHAR_CONSTANT("31"), 4}, + { UCHAR_CONSTANT("32"), 4}, + { UCHAR_CONSTANT("33"), 4}, + { UCHAR_CONSTANT("34"), 4}, + { UCHAR_CONSTANT("35"), 4}, + { UCHAR_CONSTANT("36"), 4}, + { UCHAR_CONSTANT("37"), 4}, + { UCHAR_CONSTANT("38"), 4}, + { UCHAR_CONSTANT("39"), 4}, + { UCHAR_CONSTANT("40"), 4}, + { UCHAR_CONSTANT("41"), 4}, + { UCHAR_CONSTANT("42"), 4}, + { UCHAR_CONSTANT("43"), 4}, + { UCHAR_CONSTANT("44"), 4}, + { UCHAR_CONSTANT("45"), 4}, + { UCHAR_CONSTANT("46"), 4}, + { UCHAR_CONSTANT("47"), 4}, + { UCHAR_CONSTANT("48"), 4}, + { UCHAR_CONSTANT("49"), 4}, + { UCHAR_CONSTANT("50"), 4}, + { UCHAR_CONSTANT("51"), 4}, + { UCHAR_CONSTANT("52"), 4}, + { UCHAR_CONSTANT("53"), 4}, + { UCHAR_CONSTANT("54"), 4}, + { UCHAR_CONSTANT("55"), 4}, + { UCHAR_CONSTANT("56"), 4}, + { UCHAR_CONSTANT("57"), 4}, + { UCHAR_CONSTANT("58"), 4}, + { UCHAR_CONSTANT("59"), 4}, + { UCHAR_CONSTANT("60"), 4}, + { UCHAR_CONSTANT("61"), 4}, + { UCHAR_CONSTANT("62"), 4}, + { UCHAR_CONSTANT("63"), 4}, + { UCHAR_CONSTANT("64"), 4}, + { UCHAR_CONSTANT("65"), 4}, + { UCHAR_CONSTANT("66"), 4}, + { UCHAR_CONSTANT("67"), 4}, + { UCHAR_CONSTANT("68"), 4}, + { UCHAR_CONSTANT("69"), 4}, + { UCHAR_CONSTANT("70"), 4}, + { UCHAR_CONSTANT("71"), 4}, + { UCHAR_CONSTANT("72"), 4}, + { UCHAR_CONSTANT("73"), 4}, + { UCHAR_CONSTANT("74"), 4}, + { UCHAR_CONSTANT("75"), 4}, + { UCHAR_CONSTANT("76"), 4}, + { UCHAR_CONSTANT("77"), 4}, + { UCHAR_CONSTANT("78"), 4}, + { UCHAR_CONSTANT("79"), 4}, + { UCHAR_CONSTANT("80"), 4}, + { UCHAR_CONSTANT("81"), 4}, + { UCHAR_CONSTANT("82"), 4}, + { UCHAR_CONSTANT("83"), 4}, + { UCHAR_CONSTANT("84"), 4}, + { UCHAR_CONSTANT("85"), 4}, + { UCHAR_CONSTANT("86"), 4}, + { UCHAR_CONSTANT("87"), 4}, + { UCHAR_CONSTANT("88"), 4}, + { UCHAR_CONSTANT("89"), 4}, + { UCHAR_CONSTANT("90"), 4}, + { UCHAR_CONSTANT("91"), 4}, + { UCHAR_CONSTANT("92"), 4}, + { UCHAR_CONSTANT("93"), 4}, + { UCHAR_CONSTANT("94"), 4}, + { UCHAR_CONSTANT("95"), 4}, + { UCHAR_CONSTANT("96"), 4}, + { UCHAR_CONSTANT("97"), 4}, + { UCHAR_CONSTANT("98"), 4}, + { UCHAR_CONSTANT("99"), 4}, + { UCHAR_CONSTANT("100"), 5}, + { UCHAR_CONSTANT("101"), 5}, + { UCHAR_CONSTANT("102"), 5}, + { UCHAR_CONSTANT("103"), 5}, + { UCHAR_CONSTANT("104"), 5}, + { UCHAR_CONSTANT("105"), 5}, + { UCHAR_CONSTANT("106"), 5}, + { UCHAR_CONSTANT("107"), 5}, + { UCHAR_CONSTANT("108"), 5}, + { UCHAR_CONSTANT("109"), 5}, + { UCHAR_CONSTANT("110"), 5}, + { UCHAR_CONSTANT("111"), 5}, + { UCHAR_CONSTANT("112"), 5}, + { UCHAR_CONSTANT("113"), 5}, + { UCHAR_CONSTANT("114"), 5}, + { UCHAR_CONSTANT("115"), 5}, + { UCHAR_CONSTANT("116"), 5}, + { UCHAR_CONSTANT("117"), 5}, + { UCHAR_CONSTANT("118"), 5}, + { UCHAR_CONSTANT("119"), 5}, + { UCHAR_CONSTANT("120"), 5}, + { UCHAR_CONSTANT("121"), 5}, + { UCHAR_CONSTANT("122"), 5}, + { UCHAR_CONSTANT("123"), 5}, + { UCHAR_CONSTANT("124"), 5}, + { UCHAR_CONSTANT("125"), 5}, + { UCHAR_CONSTANT("126"), 5}, + { UCHAR_CONSTANT("127"), 5}, + { UCHAR_CONSTANT("128"), 5}, + { UCHAR_CONSTANT("129"), 5}, + { UCHAR_CONSTANT("130"), 5}, + { UCHAR_CONSTANT("131"), 5}, + { UCHAR_CONSTANT("132"), 5}, + { UCHAR_CONSTANT("133"), 5}, + { UCHAR_CONSTANT("134"), 5}, + { UCHAR_CONSTANT("135"), 5}, + { UCHAR_CONSTANT("136"), 5}, + { UCHAR_CONSTANT("137"), 5}, + { UCHAR_CONSTANT("138"), 5}, + { UCHAR_CONSTANT("139"), 5}, + { UCHAR_CONSTANT("140"), 5}, + { UCHAR_CONSTANT("141"), 5}, + { UCHAR_CONSTANT("142"), 5}, + { UCHAR_CONSTANT("143"), 5}, + { UCHAR_CONSTANT("144"), 5}, + { UCHAR_CONSTANT("145"), 5}, + { UCHAR_CONSTANT("146"), 5}, + { UCHAR_CONSTANT("147"), 5}, + { UCHAR_CONSTANT("148"), 5}, + { UCHAR_CONSTANT("149"), 5}, + { UCHAR_CONSTANT("150"), 5}, + { UCHAR_CONSTANT("151"), 5}, + { UCHAR_CONSTANT("152"), 5}, + { UCHAR_CONSTANT("153"), 5}, + { UCHAR_CONSTANT("154"), 5}, + { UCHAR_CONSTANT("155"), 5}, + { UCHAR_CONSTANT("156"), 5}, + { UCHAR_CONSTANT("157"), 5}, + { UCHAR_CONSTANT("158"), 5}, + { UCHAR_CONSTANT("159"), 5}, + { UCHAR_CONSTANT("160"), 5}, + { UCHAR_CONSTANT("161"), 5}, + { UCHAR_CONSTANT("162"), 5}, + { UCHAR_CONSTANT("163"), 5}, + { UCHAR_CONSTANT("164"), 5}, + { UCHAR_CONSTANT("165"), 5}, + { UCHAR_CONSTANT("166"), 5}, + { UCHAR_CONSTANT("167"), 5}, + { UCHAR_CONSTANT("168"), 5}, + { UCHAR_CONSTANT("169"), 5}, + { UCHAR_CONSTANT("170"), 5}, + { UCHAR_CONSTANT("171"), 5}, + { UCHAR_CONSTANT("172"), 5}, + { UCHAR_CONSTANT("173"), 5}, + { UCHAR_CONSTANT("174"), 5}, + { UCHAR_CONSTANT("175"), 5}, + { UCHAR_CONSTANT("176"), 5}, + { UCHAR_CONSTANT("177"), 5}, + { UCHAR_CONSTANT("178"), 5}, + { UCHAR_CONSTANT("179"), 5}, + { UCHAR_CONSTANT("180"), 5}, + { UCHAR_CONSTANT("181"), 5}, + { UCHAR_CONSTANT("182"), 5}, + { UCHAR_CONSTANT("183"), 5}, + { UCHAR_CONSTANT("184"), 5}, + { UCHAR_CONSTANT("185"), 5}, + { UCHAR_CONSTANT("186"), 5}, + { UCHAR_CONSTANT("187"), 5}, + { UCHAR_CONSTANT("188"), 5}, + { UCHAR_CONSTANT("189"), 5}, + { UCHAR_CONSTANT("190"), 5}, + { UCHAR_CONSTANT("191"), 5} + }; +static char hexdigit[16] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +/*syslog facility names (as of RFC5424) */ +static char *syslog_fac_names[24] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr", + "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit", + "alert", "clock", "local0", "local1", "local2", "local3", + "local4", "local5", "local6", "local7" }; +/* length of the facility names string (for optimizatiions) */ +static short len_syslog_fac_names[24] = { 4, 4, 4, 6, 4, 6, 3, + 4, 4, 4, 8, 3, 3, 5, + 5, 5, 6, 6, 6, 6, + 6, 6, 6, 6 }; + +/* table of severity names (in numerical order)*/ +static char *syslog_severity_names[8] = { "emerg", "alert", "crit", "err", "warning", "notice", "info", "debug" }; +static short len_syslog_severity_names[8] = { 5, 5, 4, 3, 7, 6, 4, 5 }; + +/* numerical values as string - this is the most efficient approach to convert severity + * and facility values to a numerical string... -- rgerhars, 2009-06-17 + */ + +static char *syslog_number_names[24] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", + "15", "16", "17", "18", "19", "20", "21", "22", "23" }; + +/* global variables */ +#if defined(HAVE_MALLOC_TRIM) && !defined(HAVE_ATOMIC_BUILTINS) +static pthread_mutex_t mutTrimCtr; /* mutex to handle malloc trim */ +#endif + +/* some forward declarations */ +static int getAPPNAMELen(msg_t *pM, sbool bLockMutex); +static rsRetVal jsonPathFindParent(msg_t *pM, uchar *name, uchar *leaf, struct json_object **parent, int bCreate); +static uchar * jsonPathGetLeaf(uchar *name, int lenName); +static struct json_object *jsonDeepCopy(struct json_object *src); + + +/* the locking and unlocking implementations: */ +static inline void +MsgLock(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); */ + pthread_mutex_lock(&pThis->mut); +} +static inline void +MsgUnlock(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); */ + pthread_mutex_unlock(&pThis->mut); +} + + +/* set RcvFromIP name in msg object WITHOUT calling AddRef. + * rgerhards, 2013-01-22 + */ +static inline void +MsgSetRcvFromIPWithoutAddRef(msg_t *pThis, prop_t *new) +{ + if(pThis->pRcvFromIP != NULL) + prop.Destruct(&pThis->pRcvFromIP); + pThis->pRcvFromIP = new; +} + + +/* set RcvFrom name in msg object WITHOUT calling AddRef. + * rgerhards, 2013-01-22 + */ +void MsgSetRcvFromWithoutAddRef(msg_t *pThis, prop_t *new) +{ + assert(pThis != NULL); + + if(pThis->msgFlags & NEEDS_DNSRESOL) { + if(pThis->rcvFrom.pfrominet != NULL) + free(pThis->rcvFrom.pfrominet); + pThis->msgFlags &= ~NEEDS_DNSRESOL; + } else { + if(pThis->rcvFrom.pRcvFrom != NULL) + prop.Destruct(&pThis->rcvFrom.pRcvFrom); + } + pThis->rcvFrom.pRcvFrom = new; +} + + +/* rgerhards 2012-04-18: set associated ruleset (by ruleset name) + * If ruleset cannot be found, no update is done. + */ +static void +MsgSetRulesetByName(msg_t *pMsg, cstr_t *rulesetName) +{ + rulesetGetRuleset(runConf, &(pMsg->pRuleset), rsCStrGetSzStrNoNULL(rulesetName)); +} + + +static inline int getProtocolVersion(msg_t *pM) +{ + return(pM->iProtocolVersion); +} + + +/* do a DNS reverse resolution, if not already done, reflect status + * rgerhards, 2009-11-16 + */ +static inline rsRetVal +resolveDNS(msg_t *pMsg) { + rsRetVal localRet; + prop_t *propFromHost = NULL; + prop_t *ip; + prop_t *localName; + DEFiRet; + + MsgLock(pMsg); + CHKiRet(objUse(net, CORE_COMPONENT)); + if(pMsg->msgFlags & NEEDS_DNSRESOL) { + localRet = net.cvthname(pMsg->rcvFrom.pfrominet, &localName, NULL, &ip); + if(localRet == RS_RET_OK) { + /* we pass down the props, so no need for AddRef */ + MsgSetRcvFromWithoutAddRef(pMsg, localName); + MsgSetRcvFromIPWithoutAddRef(pMsg, ip); + } + } +finalize_it: + if(iRet != RS_RET_OK) { + /* best we can do: remove property */ + MsgSetRcvFromStr(pMsg, UCHAR_CONSTANT(""), 0, &propFromHost); + prop.Destruct(&propFromHost); + } + MsgUnlock(pMsg); + if(propFromHost != NULL) + prop.Destruct(&propFromHost); + RETiRet; +} + + +static inline void +getInputName(msg_t *pM, uchar **ppsz, int *plen) +{ + BEGINfunc + if(pM == NULL || pM->pInputName == NULL) { + *ppsz = UCHAR_CONSTANT(""); + *plen = 0; + } else { + prop.GetString(pM->pInputName, ppsz, plen); + } + ENDfunc +} + + +static inline uchar* +getRcvFromIP(msg_t *pM) +{ + uchar *psz; + int len; + BEGINfunc + if(pM == NULL) { + psz = UCHAR_CONSTANT(""); + } else { + resolveDNS(pM); /* make sure we have a resolved entry */ + if(pM->pRcvFromIP == NULL) + psz = UCHAR_CONSTANT(""); + else + prop.GetString(pM->pRcvFromIP, &psz, &len); + } + ENDfunc + return psz; +} + + +/* map a property name (C string) to a property ID */ +rsRetVal +propNameStrToID(uchar *pName, propid_t *pPropID) +{ + DEFiRet; + + assert(pName != NULL); + + /* sometimes there are aliases to the original MonitoWare + * property names. These come after || in the ifs below. */ + if(!strcmp((char*) pName, "msg")) { + *pPropID = PROP_MSG; + } else if(!strcmp((char*) pName, "timestamp") + || !strcmp((char*) pName, "timereported")) { + *pPropID = PROP_TIMESTAMP; + } else if(!strcmp((char*) pName, "hostname") || !strcmp((char*) pName, "source")) { + *pPropID = PROP_HOSTNAME; + } else if(!strcmp((char*) pName, "syslogtag")) { + *pPropID = PROP_SYSLOGTAG; + } else if(!strcmp((char*) pName, "rawmsg")) { + *pPropID = PROP_RAWMSG; + } else if(!strcmp((char*) pName, "inputname")) { + *pPropID = PROP_INPUTNAME; + } else if(!strcmp((char*) pName, "fromhost")) { + *pPropID = PROP_FROMHOST; + } else if(!strcmp((char*) pName, "fromhost-ip")) { + *pPropID = PROP_FROMHOST_IP; + } else if(!strcmp((char*) pName, "pri")) { + *pPropID = PROP_PRI; + } else if(!strcmp((char*) pName, "pri-text")) { + *pPropID = PROP_PRI_TEXT; + } else if(!strcmp((char*) pName, "iut")) { + *pPropID = PROP_IUT; + } else if(!strcmp((char*) pName, "syslogfacility")) { + *pPropID = PROP_SYSLOGFACILITY; + } else if(!strcmp((char*) pName, "syslogfacility-text")) { + *pPropID = PROP_SYSLOGFACILITY_TEXT; + } else if(!strcmp((char*) pName, "syslogseverity") || !strcmp((char*) pName, "syslogpriority")) { + *pPropID = PROP_SYSLOGSEVERITY; + } else if(!strcmp((char*) pName, "syslogseverity-text") || !strcmp((char*) pName, "syslogpriority-text")) { + *pPropID = PROP_SYSLOGSEVERITY_TEXT; + } else if(!strcmp((char*) pName, "timegenerated")) { + *pPropID = PROP_TIMEGENERATED; + } else if(!strcmp((char*) pName, "programname")) { + *pPropID = PROP_PROGRAMNAME; + } else if(!strcmp((char*) pName, "protocol-version")) { + *pPropID = PROP_PROTOCOL_VERSION; + } else if(!strcmp((char*) pName, "structured-data")) { + *pPropID = PROP_STRUCTURED_DATA; + } else if(!strcmp((char*) pName, "app-name")) { + *pPropID = PROP_APP_NAME; + } else if(!strcmp((char*) pName, "procid")) { + *pPropID = PROP_PROCID; + } else if(!strcmp((char*) pName, "msgid")) { + *pPropID = PROP_MSGID; + } else if(!strcmp((char*) pName, "parsesuccess")) { + *pPropID = PROP_PARSESUCCESS; +#ifdef USE_LIBUUID + } else if(!strcmp((char*) pName, "uuid")) { + *pPropID = PROP_UUID; +#endif + /* here start system properties (those, that do not relate to the message itself */ + } else if(!strcmp((char*) pName, "$now")) { + *pPropID = PROP_SYS_NOW; + } else if(!strcmp((char*) pName, "$year")) { + *pPropID = PROP_SYS_YEAR; + } else if(!strcmp((char*) pName, "$month")) { + *pPropID = PROP_SYS_MONTH; + } else if(!strcmp((char*) pName, "$day")) { + *pPropID = PROP_SYS_DAY; + } else if(!strcmp((char*) pName, "$hour")) { + *pPropID = PROP_SYS_HOUR; + } else if(!strcmp((char*) pName, "$hhour")) { + *pPropID = PROP_SYS_HHOUR; + } else if(!strcmp((char*) pName, "$qhour")) { + *pPropID = PROP_SYS_QHOUR; + } else if(!strcmp((char*) pName, "$minute")) { + *pPropID = PROP_SYS_MINUTE; + } else if(!strcmp((char*) pName, "$myhostname")) { + *pPropID = PROP_SYS_MYHOSTNAME; + } else if(!strcmp((char*) pName, "$!all-json")) { + *pPropID = PROP_CEE_ALL_JSON; + } else if(!strncmp((char*) pName, "$!", 2)) { + *pPropID = PROP_CEE; + } else if(!strcmp((char*) pName, "$bom")) { + *pPropID = PROP_SYS_BOM; + } else if(!strcmp((char*) pName, "$uptime")) { + *pPropID = PROP_SYS_UPTIME; + } else { + *pPropID = PROP_INVALID; + iRet = RS_RET_VAR_NOT_FOUND; + } + + RETiRet; +} + + +/* map a property name (string) to a property ID */ +rsRetVal +propNameToID(cstr_t *pCSPropName, propid_t *pPropID) +{ + uchar *pName; + DEFiRet; + + assert(pCSPropName != NULL); + assert(pPropID != NULL); + pName = rsCStrGetSzStrNoNULL(pCSPropName); + iRet = propNameStrToID(pName, pPropID); + RETiRet; +} + + +/* map a property ID to a name string (useful for displaying) */ +uchar *propIDToName(propid_t propID) +{ + switch(propID) { + case PROP_MSG: + return UCHAR_CONSTANT("msg"); + case PROP_TIMESTAMP: + return UCHAR_CONSTANT("timestamp"); + case PROP_HOSTNAME: + return UCHAR_CONSTANT("hostname"); + case PROP_SYSLOGTAG: + return UCHAR_CONSTANT("syslogtag"); + case PROP_RAWMSG: + return UCHAR_CONSTANT("rawmsg"); + case PROP_INPUTNAME: + return UCHAR_CONSTANT("inputname"); + case PROP_FROMHOST: + return UCHAR_CONSTANT("fromhost"); + case PROP_FROMHOST_IP: + return UCHAR_CONSTANT("fromhost-ip"); + case PROP_PRI: + return UCHAR_CONSTANT("pri"); + case PROP_PRI_TEXT: + return UCHAR_CONSTANT("pri-text"); + case PROP_IUT: + return UCHAR_CONSTANT("iut"); + case PROP_SYSLOGFACILITY: + return UCHAR_CONSTANT("syslogfacility"); + case PROP_SYSLOGFACILITY_TEXT: + return UCHAR_CONSTANT("syslogfacility-text"); + case PROP_SYSLOGSEVERITY: + return UCHAR_CONSTANT("syslogseverity"); + case PROP_SYSLOGSEVERITY_TEXT: + return UCHAR_CONSTANT("syslogseverity-text"); + case PROP_TIMEGENERATED: + return UCHAR_CONSTANT("timegenerated"); + case PROP_PROGRAMNAME: + return UCHAR_CONSTANT("programname"); + case PROP_PROTOCOL_VERSION: + return UCHAR_CONSTANT("protocol-version"); + case PROP_STRUCTURED_DATA: + return UCHAR_CONSTANT("structured-data"); + case PROP_APP_NAME: + return UCHAR_CONSTANT("app-name"); + case PROP_PROCID: + return UCHAR_CONSTANT("procid"); + case PROP_MSGID: + return UCHAR_CONSTANT("msgid"); + case PROP_PARSESUCCESS: + return UCHAR_CONSTANT("parsesuccess"); + case PROP_SYS_NOW: + return UCHAR_CONSTANT("$NOW"); + case PROP_SYS_YEAR: + return UCHAR_CONSTANT("$YEAR"); + case PROP_SYS_MONTH: + return UCHAR_CONSTANT("$MONTH"); + case PROP_SYS_DAY: + return UCHAR_CONSTANT("$DAY"); + case PROP_SYS_HOUR: + return UCHAR_CONSTANT("$HOUR"); + case PROP_SYS_HHOUR: + return UCHAR_CONSTANT("$HHOUR"); + case PROP_SYS_QHOUR: + return UCHAR_CONSTANT("$QHOUR"); + case PROP_SYS_MINUTE: + return UCHAR_CONSTANT("$MINUTE"); + case PROP_SYS_MYHOSTNAME: + return UCHAR_CONSTANT("$MYHOSTNAME"); + case PROP_CEE: + return UCHAR_CONSTANT("*CEE-based property*"); + case PROP_CEE_ALL_JSON: + return UCHAR_CONSTANT("$!all-json"); + case PROP_SYS_BOM: + return UCHAR_CONSTANT("$BOM"); + case PROP_UUID: + return UCHAR_CONSTANT("uuid"); + default: + return UCHAR_CONSTANT("*invalid property id*"); + } +} + + +/* This is common code for all Constructors. It is defined in an + * inline'able function so that we can save a function call in the + * actual constructors (otherwise, the msgConstruct would need + * to call msgConstructWithTime(), which would require a + * function call). Now, both can use this inline function. This + * enables us to be optimal, but still have the code just once. + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". This constructor does not query system time + * itself but rather uses a user-supplied value. This enables the caller + * to do some tricks to save processing time (done, for example, in the + * udp input). + * NOTE: this constructor does NOT call calloc(), as we have many bytes + * inside the structure which do not need to be cleared. bzero() will + * heavily thrash the cache, so we do the init manually (which also + * is the right thing to do with pointers, as they are not neccessarily + * a binary 0 on all machines [but today almost always...]). + * rgerhards, 2008-10-06 + */ +static inline rsRetVal msgBaseConstruct(msg_t **ppThis) +{ + DEFiRet; + msg_t *pM; + + assert(ppThis != NULL); + CHKmalloc(pM = MALLOC(sizeof(msg_t))); + objConstructSetObjInfo(pM); /* intialize object helper entities */ + + /* initialize members in ORDER they appear in structure (think "cache line"!) */ + pM->flowCtlType = 0; + pM->bParseSuccess = 0; + pM->iRefCount = 1; + pM->iSeverity = -1; + pM->iFacility = -1; + pM->iLenPROGNAME = -1; + pM->offAfterPRI = 0; + pM->offMSG = -1; + pM->iProtocolVersion = 0; + pM->msgFlags = 0; + pM->iLenRawMsg = 0; + pM->iLenMSG = 0; + pM->iLenTAG = 0; + pM->iLenHOSTNAME = 0; + pM->pszRawMsg = NULL; + pM->pszHOSTNAME = NULL; + pM->pszRcvdAt3164 = NULL; + pM->pszRcvdAt3339 = NULL; + pM->pszRcvdAt_MySQL = NULL; + pM->pszRcvdAt_PgSQL = NULL; + pM->pszTIMESTAMP3164 = NULL; + pM->pszTIMESTAMP3339 = NULL; + pM->pszTIMESTAMP_MySQL = NULL; + pM->pszTIMESTAMP_PgSQL = NULL; + pM->pCSStrucData = NULL; + pM->pCSAPPNAME = NULL; + pM->pCSPROCID = NULL; + pM->pCSMSGID = NULL; + pM->pInputName = NULL; + pM->pRcvFromIP = NULL; + pM->rcvFrom.pRcvFrom = NULL; + pM->pRuleset = NULL; + pM->json = NULL; + memset(&pM->tRcvdAt, 0, sizeof(pM->tRcvdAt)); + memset(&pM->tTIMESTAMP, 0, sizeof(pM->tTIMESTAMP)); + pM->TAG.pszTAG = NULL; + pM->pszTimestamp3164[0] = '\0'; + pM->pszTimestamp3339[0] = '\0'; + pM->pszTIMESTAMP_SecFrac[0] = '\0'; + pM->pszRcvdAt_SecFrac[0] = '\0'; + pM->pszTIMESTAMP_Unix[0] = '\0'; + pM->pszRcvdAt_Unix[0] = '\0'; + pM->pszUUID = NULL; + pthread_mutex_init(&pM->mut, NULL); + + /* DEV debugging only! dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);*/ + + *ppThis = pM; + +finalize_it: + RETiRet; +} + + +/* "Constructor" for a msg "object". Returns a pointer to + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". This constructor does not query system time + * itself but rather uses a user-supplied value. This enables the caller + * to do some tricks to save processing time (done, for example, in the + * udp input). + * rgerhards, 2008-10-06 + */ +rsRetVal msgConstructWithTime(msg_t **ppThis, struct syslogTime *stTime, time_t ttGenTime) +{ + DEFiRet; + + CHKiRet(msgBaseConstruct(ppThis)); + (*ppThis)->ttGenTime = ttGenTime; + memcpy(&(*ppThis)->tRcvdAt, stTime, sizeof(struct syslogTime)); + memcpy(&(*ppThis)->tTIMESTAMP, stTime, sizeof(struct syslogTime)); + +finalize_it: + RETiRet; +} + + +/* "Constructor" for a msg "object". Returns a pointer to + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". This constructor, for historical reasons, + * also sets the two timestamps to the current time. + */ +rsRetVal msgConstruct(msg_t **ppThis) +{ + DEFiRet; + + CHKiRet(msgBaseConstruct(ppThis)); + /* we initialize both timestamps to contain the current time, so that they + * are consistent. Also, this saves us from doing any further time calls just + * to obtain a timestamp. The memcpy() should not really make a difference, + * especially as I think there is no codepath currently where it would not be + * required (after I have cleaned up the pathes ;)). -- rgerhards, 2008-10-02 + */ + datetime.getCurrTime(&((*ppThis)->tRcvdAt), &((*ppThis)->ttGenTime)); + memcpy(&(*ppThis)->tTIMESTAMP, &(*ppThis)->tRcvdAt, sizeof(struct syslogTime)); + +finalize_it: + RETiRet; +} + + +/* Special msg constructor, to be used when an object is deserialized. + * we do only the base init as we know the properties will be set in + * any case by the deserializer. We still do the "inexpensive" inits + * just to be on the safe side. The whole process needs to be + * refactored together with the msg serialization subsystem. + */ +rsRetVal +msgConstructForDeserializer(msg_t **ppThis) +{ + return msgBaseConstruct(ppThis); +} + + +/* some free handlers for (slightly) complicated cases... All of them may be called + * with an empty element. + */ +static inline void freeTAG(msg_t *pThis) +{ + if(pThis->iLenTAG >= CONF_TAG_BUFSIZE) + free(pThis->TAG.pszTAG); +} +static inline void freeHOSTNAME(msg_t *pThis) +{ + if(pThis->iLenHOSTNAME >= CONF_HOSTNAME_BUFSIZE) + free(pThis->pszHOSTNAME); +} + + +BEGINobjDestruct(msg) /* be sure to specify the object type also in END and CODESTART macros! */ + int currRefCount; +# if HAVE_MALLOC_TRIM + int currCnt; +# endif +CODESTARTobjDestruct(msg) + /* DEV Debugging only ! dbgprintf("msgDestruct\t0x%lx, Ref now: %d\n", (unsigned long)pThis, pThis->iRefCount - 1); */ +# ifdef HAVE_ATOMIC_BUILTINS + currRefCount = ATOMIC_DEC_AND_FETCH(&pThis->iRefCount, NULL); +# else + MsgLock(pThis); + currRefCount = --pThis->iRefCount; +# endif + if(currRefCount == 0) + { + /* DEV Debugging Only! dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", (unsigned long)pThis); */ + if(pThis->pszRawMsg != pThis->szRawMsg) + free(pThis->pszRawMsg); + freeTAG(pThis); + freeHOSTNAME(pThis); + if(pThis->pInputName != NULL) + prop.Destruct(&pThis->pInputName); + if((pThis->msgFlags & NEEDS_DNSRESOL) == 0) { + if(pThis->rcvFrom.pRcvFrom != NULL) + prop.Destruct(&pThis->rcvFrom.pRcvFrom); + } else { + free(pThis->rcvFrom.pfrominet); + } + if(pThis->pRcvFromIP != NULL) + prop.Destruct(&pThis->pRcvFromIP); + free(pThis->pszRcvdAt3164); + free(pThis->pszRcvdAt3339); + free(pThis->pszRcvdAt_MySQL); + free(pThis->pszRcvdAt_PgSQL); + free(pThis->pszTIMESTAMP_MySQL); + free(pThis->pszTIMESTAMP_PgSQL); + if(pThis->iLenPROGNAME >= CONF_PROGNAME_BUFSIZE) + free(pThis->PROGNAME.ptr); + if(pThis->pCSStrucData != NULL) + rsCStrDestruct(&pThis->pCSStrucData); + if(pThis->pCSAPPNAME != NULL) + rsCStrDestruct(&pThis->pCSAPPNAME); + if(pThis->pCSPROCID != NULL) + rsCStrDestruct(&pThis->pCSPROCID); + if(pThis->pCSMSGID != NULL) + rsCStrDestruct(&pThis->pCSMSGID); + if(pThis->json != NULL) + json_object_put(pThis->json); + if(pThis->pszUUID != NULL) + free(pThis->pszUUID); +# ifndef HAVE_ATOMIC_BUILTINS + MsgUnlock(pThis); +# endif + pthread_mutex_destroy(&pThis->mut); + /* now we need to do our own optimization. Testing has shown that at least the glibc + * malloc() subsystem returns memory to the OS far too late in our case. So we need + * to help it a bit, by calling malloc_trim(), which will tell the alloc subsystem + * to consolidate and return to the OS. We keep 128K for our use, as a safeguard + * to too-frequent reallocs. But more importantly, we call this hook only every + * 100,000 messages (which is an approximation, as we do not work with atomic + * operations on the counter. --- rgerhards, 2009-06-22. + */ +# if HAVE_MALLOC_TRIM + { /* standard C requires a new block for a new variable definition! + * To simplify matters, we use modulo arithmetic and live with the fact + * that we trim too often when the counter wraps. + */ + static unsigned iTrimCtr = 1; + currCnt = ATOMIC_INC_AND_FETCH_unsigned(&iTrimCtr, &mutTrimCtr); + if(currCnt % 100000 == 0) { + malloc_trim(128*1024); + } + } +# endif + } else { +# ifndef HAVE_ATOMIC_BUILTINS + MsgUnlock(pThis); +# endif + pThis = NULL; /* tell framework not to destructing the object! */ + } +ENDobjDestruct(msg) + + +/* The macros below are used in MsgDup(). I use macros + * to keep the fuction code somewhat more readyble. It is my + * replacement for inline functions in CPP + */ +#define tmpCOPYSZ(name) \ + if(pOld->psz##name != NULL) { \ + if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + pNew->iLen##name = pOld->iLen##name;\ + } + +/* copy the CStr objects. + * if the old value is NULL, we do not need to do anything because we + * initialized the new value to NULL via calloc(). + */ +#define tmpCOPYCSTR(name) \ + if(pOld->pCS##name != NULL) {\ + if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + } +/* Constructs a message object by duplicating another one. + * Returns NULL if duplication failed. We do not need to lock the + * message object here, because a fully-created msg object is never + * allowed to be manipulated. For this, MsgDup() must be used, so MsgDup() + * can never run into a situation where the message object is being + * modified while its content is copied - it's forbidden by definition. + * rgerhards, 2007-07-10 + */ +msg_t* MsgDup(msg_t* pOld) +{ + msg_t* pNew; + rsRetVal localRet; + + assert(pOld != NULL); + + BEGINfunc + if(msgConstructWithTime(&pNew, &pOld->tTIMESTAMP, pOld->ttGenTime) != RS_RET_OK) { + return NULL; + } + + /* now copy the message properties */ + pNew->iRefCount = 1; + pNew->iSeverity = pOld->iSeverity; + pNew->iFacility = pOld->iFacility; + pNew->msgFlags = pOld->msgFlags; + pNew->iProtocolVersion = pOld->iProtocolVersion; + pNew->ttGenTime = pOld->ttGenTime; + pNew->offMSG = pOld->offMSG; + pNew->iLenRawMsg = pOld->iLenRawMsg; + pNew->iLenMSG = pOld->iLenMSG; + pNew->iLenTAG = pOld->iLenTAG; + pNew->iLenHOSTNAME = pOld->iLenHOSTNAME; + if((pOld->msgFlags & NEEDS_DNSRESOL)) { + localRet = msgSetFromSockinfo(pNew, pOld->rcvFrom.pfrominet); + if(localRet != RS_RET_OK) { + /* if something fails, we accept loss of this property, it is + * better than losing the whole message. + */ + pNew->msgFlags &= ~NEEDS_DNSRESOL; + pNew->rcvFrom.pRcvFrom = NULL; /* make sure no dangling values */ + } + } else { + if(pOld->rcvFrom.pRcvFrom != NULL) { + pNew->rcvFrom.pRcvFrom = pOld->rcvFrom.pRcvFrom; + prop.AddRef(pNew->rcvFrom.pRcvFrom); + } + } + if(pOld->pRcvFromIP != NULL) { + pNew->pRcvFromIP = pOld->pRcvFromIP; + prop.AddRef(pNew->pRcvFromIP); + } + if(pOld->pInputName != NULL) { + pNew->pInputName = pOld->pInputName; + prop.AddRef(pNew->pInputName); + } + if(pOld->iLenTAG > 0) { + if(pOld->iLenTAG < CONF_TAG_BUFSIZE) { + memcpy(pNew->TAG.szBuf, pOld->TAG.szBuf, pOld->iLenTAG + 1); + } else { + if((pNew->TAG.pszTAG = srUtilStrDup(pOld->TAG.pszTAG, pOld->iLenTAG)) == NULL) { + msgDestruct(&pNew); + return NULL; + } + pNew->iLenTAG = pOld->iLenTAG; + } + } + if(pOld->iLenRawMsg < CONF_RAWMSG_BUFSIZE) { + memcpy(pNew->szRawMsg, pOld->szRawMsg, pOld->iLenRawMsg + 1); + pNew->pszRawMsg = pNew->szRawMsg; + } else { + tmpCOPYSZ(RawMsg); + } + if(pOld->pszHOSTNAME == NULL) { + pNew->pszHOSTNAME = NULL; + } else { + if(pOld->iLenHOSTNAME < CONF_HOSTNAME_BUFSIZE) { + memcpy(pNew->szHOSTNAME, pOld->szHOSTNAME, pOld->iLenHOSTNAME + 1); + pNew->pszHOSTNAME = pNew->szHOSTNAME; + } else { + tmpCOPYSZ(HOSTNAME); + } + } + + tmpCOPYCSTR(StrucData); + tmpCOPYCSTR(APPNAME); + tmpCOPYCSTR(PROCID); + tmpCOPYCSTR(MSGID); + + if(pOld->json != NULL) + pNew->json = jsonDeepCopy(pOld->json); + + /* we do not copy all other cache properties, as we do not even know + * if they are needed once again. So we let them re-create if needed. + */ + + ENDfunc + return pNew; +} +#undef tmpCOPYSZ +#undef tmpCOPYCSTR + + +/* This method serializes a message object. That means the whole + * object is modified into text form. That text form is suitable for + * later reconstruction of the object by calling MsgDeSerialize(). + * The most common use case for this method is the creation of an + * on-disk representation of the message object. + * We do not serialize the cache properties. We re-create them when needed. + * This saves us a lot of memory. Performance is no concern, as serializing + * is a so slow operation that recration of the caches does not count. Also, + * we do not serialize --currently none--, as this is only a helper variable + * during msg construction - and never again used later. + * rgerhards, 2008-01-03 + */ +static rsRetVal MsgSerialize(msg_t *pThis, strm_t *pStrm) +{ + uchar *psz; + int len; + DEFiRet; + + assert(pThis != NULL); + assert(pStrm != NULL); + + /* then serialize elements */ + CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); + objSerializeSCALAR(pStrm, iProtocolVersion, SHORT); + objSerializeSCALAR(pStrm, iSeverity, SHORT); + objSerializeSCALAR(pStrm, iFacility, SHORT); + objSerializeSCALAR(pStrm, msgFlags, INT); + objSerializeSCALAR(pStrm, ttGenTime, INT); + objSerializeSCALAR(pStrm, tRcvdAt, SYSLOGTIME); + objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME); + + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszTAG"), PROPTYPE_PSZ, (void*) + ((pThis->iLenTAG < CONF_TAG_BUFSIZE) ? pThis->TAG.szBuf : pThis->TAG.pszTAG))); + + objSerializePTR(pStrm, pszRawMsg, PSZ); + objSerializePTR(pStrm, pszHOSTNAME, PSZ); + getInputName(pThis, &psz, &len); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszInputName"), PROPTYPE_PSZ, (void*) psz)); + psz = getRcvFrom(pThis); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRcvFrom"), PROPTYPE_PSZ, (void*) psz)); + psz = getRcvFromIP(pThis); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRcvFromIP"), PROPTYPE_PSZ, (void*) psz)); + if(pThis->json != NULL) { + psz = (uchar*) json_object_get_string(pThis->json); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("json"), PROPTYPE_PSZ, (void*) psz)); + } + + objSerializePTR(pStrm, pCSStrucData, CSTR); + objSerializePTR(pStrm, pCSAPPNAME, CSTR); + objSerializePTR(pStrm, pCSPROCID, CSTR); + objSerializePTR(pStrm, pCSMSGID, CSTR); + + objSerializePTR(pStrm, pszUUID, PSZ); + + if(pThis->pRuleset != NULL) { + rulesetGetName(pThis->pRuleset); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRuleset"), PROPTYPE_PSZ, + rulesetGetName(pThis->pRuleset))); + } + + /* offset must be serialized after pszRawMsg, because we need that to obtain the correct + * MSG size. + */ + objSerializeSCALAR(pStrm, offMSG, SHORT); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + +/* This is a helper for MsgDeserialize that re-inits the var object. This + * whole construct should be replaced, var is really ready to be retired. + * But as an interim help during refactoring let's introduce this function + * here (and thus NOT as method of var object!). -- rgerhads, 2012-11-06 + */ +static inline void +reinitVar(var_t *pVar) +{ + rsCStrDestruct(&pVar->pcsName); /* no longer needed */ + if(pVar->varType == VARTYPE_STR) { + if(pVar->val.pStr != NULL) + rsCStrDestruct(&pVar->val.pStr); + } +} +/* deserialize the message again + * we deserialize the properties in the same order that we serialized them. Except + * for some checks to cover downlevel version, we do not need to do all these + * CPU intense name checkings. + */ +#define isProp(name) !rsCStrSzStrCmp(pVar->pcsName, (uchar*) name, sizeof(name) - 1) +rsRetVal +MsgDeserialize(msg_t *pMsg, strm_t *pStrm) +{ + prop_t *myProp; + prop_t *propRcvFrom = NULL; + prop_t *propRcvFromIP = NULL; + struct json_tokener *tokener; + struct json_object *json; + var_t *pVar = NULL; + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + CHKiRet(objDeserializeProperty(pVar, pStrm)); + if(isProp("iProtocolVersion")) { + setProtocolVersion(pMsg, pVar->val.num); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("iSeverity")) { + pMsg->iSeverity = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("iFacility")) { + pMsg->iFacility = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("msgFlags")) { + pMsg->msgFlags = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("ttGenTime")) { + pMsg->ttGenTime = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("tRcvdAt")) { + memcpy(&pMsg->tRcvdAt, &pVar->val.vSyslogTime, sizeof(struct syslogTime)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("tTIMESTAMP")) { + memcpy(&pMsg->tTIMESTAMP, &pVar->val.vSyslogTime, sizeof(struct syslogTime)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszTAG")) { + MsgSetTAG(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), cstrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRawMsg")) { + MsgSetRawMsg(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr), cstrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszHOSTNAME")) { + MsgSetHOSTNAME(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszInputName")) { + /* we need to create a property */ + CHKiRet(prop.Construct(&myProp)); + CHKiRet(prop.SetString(myProp, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr))); + CHKiRet(prop.ConstructFinalize(myProp)); + MsgSetInputName(pMsg, myProp); + prop.Destruct(&myProp); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRcvFrom")) { + MsgSetRcvFromStr(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr), &propRcvFrom); + prop.Destruct(&propRcvFrom); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRcvFromIP")) { + MsgSetRcvFromIPStr(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr), &propRcvFromIP); + prop.Destruct(&propRcvFromIP); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("json")) { + tokener = json_tokener_new(); + json = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pVar->val.pStr), + cstrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSStrucData")) { + MsgSetStructuredData(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSAPPNAME")) { + MsgSetAPPNAME(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSPROCID")) { + MsgSetPROCID(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSMSGID")) { + MsgSetMSGID(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszUUID")) { + pMsg->pszUUID = ustrdup(rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRuleset")) { + MsgSetRulesetByName(pMsg, pVar->val.pStr); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + /* "offMSG" must always be our last field, so we use this as an + * indicator if the sequence is correct. This is a bit questionable, + * but on the other hand it works decently AND we will probably replace + * the whole persisted format soon in any case. -- rgerhards, 2012-11-06 + */ + if(!isProp("offMSG")) + ABORT_FINALIZE(RS_RET_DS_PROP_SEQ_ERR); + MsgSetMSGoffs(pMsg, pVar->val.num); +finalize_it: + if(pVar != NULL) + var.Destruct(&pVar); + RETiRet; +} +#undef isProp + + +/* Increment reference count - see description of the "msg" + * structure for details. As a convenience to developers, + * this method returns the msg pointer that is passed to it. + * It is recommended that it is called as follows: + * + * pSecondMsgPointer = MsgAddRef(pOrgMsgPointer); + */ +msg_t *MsgAddRef(msg_t *pM) +{ + assert(pM != NULL); +# ifdef HAVE_ATOMIC_BUILTINS + ATOMIC_INC(&pM->iRefCount, NULL); +# else + MsgLock(pM); + pM->iRefCount++; + MsgUnlock(pM); +# endif + /* DEV debugging only! dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount);*/ + return(pM); +} + + +/* This functions tries to aquire the PROCID from TAG. Its primary use is + * when a legacy syslog message has been received and should be forwarded as + * syslog-protocol (or the PROCID is requested for any other reason). + * In legacy syslog, the PROCID is considered to be the character sequence + * between the first [ and the first ]. This usually are digits only, but we + * do not check that. However, if there is no closing ], we do not assume we + * can obtain a PROCID. Take in mind that not every legacy syslog message + * actually has a PROCID. + * rgerhards, 2005-11-24 + * THIS MUST be called with the message lock locked. + */ +static rsRetVal aquirePROCIDFromTAG(msg_t *pM) +{ + register int i; + uchar *pszTag; + DEFiRet; + + assert(pM != NULL); + + if(pM->pCSPROCID != NULL) + return RS_RET_OK; /* we are already done ;) */ + + if(getProtocolVersion(pM) != 0) + return RS_RET_OK; /* we can only emulate if we have legacy format */ + + pszTag = (uchar*) ((pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG); + + /* find first '['... */ + i = 0; + while((i < pM->iLenTAG) && (pszTag[i] != '[')) + ++i; + if(!(i < pM->iLenTAG)) + return RS_RET_OK; /* no [, so can not emulate... */ + + ++i; /* skip '[' */ + + /* now obtain the PROCID string... */ + CHKiRet(cstrConstruct(&pM->pCSPROCID)); + while((i < pM->iLenTAG) && (pszTag[i] != ']')) { + CHKiRet(cstrAppendChar(pM->pCSPROCID, pszTag[i])); + ++i; + } + + if(!(i < pM->iLenTAG)) { + /* oops... it looked like we had a PROCID, but now it has + * turned out this is not true. In this case, we need to free + * the buffer and simply return. Note that this is NOT an error + * case! + */ + cstrDestruct(&pM->pCSPROCID); + FINALIZE; + } + + /* OK, finaally we could obtain a PROCID. So let's use it ;) */ + CHKiRet(cstrFinalize(pM->pCSPROCID)); + +finalize_it: + RETiRet; +} + + +/* Parse and set the "programname" for a given MSG object. Programname + * is a BSD concept, it is the tag without any instance-specific information. + * Precisely, the programname is terminated by either (whichever occurs first): + * - end of tag + * - nonprintable character + * - ':' + * - '[' + * - '/' + * The above definition has been taken from the FreeBSD syslogd sources. + * + * The program name is not parsed by default, because it is infrequently-used. + * IMPORTANT: A locked message object must be provided, else a crash will occur. + * rgerhards, 2005-10-19 + */ +static inline rsRetVal +aquireProgramName(msg_t *pM) +{ + int i; + uchar *pszTag, *pszProgName; + DEFiRet; + + assert(pM != NULL); + pszTag = (uchar*) ((pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG); + for( i = 0 + ; (i < pM->iLenTAG) && isprint((int) pszTag[i]) + && (pszTag[i] != '\0') && (pszTag[i] != ':') + && (pszTag[i] != '[') && (pszTag[i] != '/') + ; ++i) + ; /* just search end of PROGNAME */ + if(i < CONF_PROGNAME_BUFSIZE) { + pszProgName = pM->PROGNAME.szBuf; + } else { + CHKmalloc(pM->PROGNAME.ptr = malloc(i+1)); + pszProgName = pM->PROGNAME.ptr; + } + memcpy((char*)pszProgName, (char*)pszTag, i); + pszProgName[i] = '\0'; + pM->iLenPROGNAME = i; +finalize_it: + RETiRet; +} + + +/* Access methods - dumb & easy, not a comment for each ;) + */ +void setProtocolVersion(msg_t *pM, int iNewVersion) +{ + assert(pM != NULL); + if(iNewVersion != 0 && iNewVersion != 1) { + dbgprintf("Tried to set unsupported protocol version %d - changed to 0.\n", iNewVersion); + iNewVersion = 0; + } + pM->iProtocolVersion = iNewVersion; +} + +/* note: string is taken from constant pool, do NOT free */ +char *getProtocolVersionString(msg_t *pM) +{ + assert(pM != NULL); + return(pM->iProtocolVersion ? "1" : "0"); +} + +#ifdef USE_LIBUUID +/* note: libuuid seems not to be thread-safe, so we need + * to get some safeguards in place. + */ +static void msgSetUUID(msg_t *pM) +{ + size_t lenRes = sizeof(uuid_t) * 2 + 1; + char hex_char [] = "0123456789ABCDEF"; + unsigned int byte_nbr; + uuid_t uuid; + static pthread_mutex_t mutUUID = PTHREAD_MUTEX_INITIALIZER; + + dbgprintf("[MsgSetUUID] START\n"); + assert(pM != NULL); + + if((pM->pszUUID = (uchar*) MALLOC(lenRes)) == NULL) { + pM->pszUUID = (uchar *)""; + } else { + pthread_mutex_lock(&mutUUID); + uuid_generate(uuid); + pthread_mutex_unlock(&mutUUID); + for (byte_nbr = 0; byte_nbr < sizeof (uuid_t); byte_nbr++) { + pM->pszUUID[byte_nbr * 2 + 0] = hex_char[uuid [byte_nbr] >> 4]; + pM->pszUUID[byte_nbr * 2 + 1] = hex_char[uuid [byte_nbr] & 15]; + } + + dbgprintf("[MsgSetUUID] UUID : %s LEN: %d \n", pM->pszUUID, (int)lenRes); + pM->pszUUID[lenRes] = '\0'; + } + dbgprintf("[MsgSetUUID] END\n"); +} + +void getUUID(msg_t *pM, uchar **pBuf, int *piLen) +{ + dbgprintf("[getUUID] START\n"); + if(pM == NULL) { + dbgprintf("[getUUID] pM is NULL\n"); + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->pszUUID == NULL) { + dbgprintf("[getUUID] pM->pszUUID is NULL\n"); + MsgLock(pM); + /* re-query, things may have changed in the mean time... */ + if(pM->pszUUID == NULL) + msgSetUUID(pM); + MsgUnlock(pM); + } else { /* UUID already there we reuse it */ + dbgprintf("[getUUID] pM->pszUUID already exists\n"); + } + *pBuf = pM->pszUUID; + *piLen = sizeof(uuid_t) * 2; + } + dbgprintf("[getUUID] END\n"); +} +#endif + +void +getRawMsg(msg_t *pM, uchar **pBuf, int *piLen) +{ + if(pM == NULL) { + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->pszRawMsg == NULL) { + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + *pBuf = pM->pszRawMsg; + *piLen = pM->iLenRawMsg; + } + } +} + + +/* note: setMSGLen() is only for friends who really know what they + * do. Setting an invalid length can be desasterous! + */ +void setMSGLen(msg_t *pM, int lenMsg) +{ + pM->iLenMSG = lenMsg; +} + +int getMSGLen(msg_t *pM) +{ + return((pM == NULL) ? 0 : pM->iLenMSG); +} + +uchar *getMSG(msg_t *pM) +{ + uchar *ret; + if(pM == NULL) + ret = UCHAR_CONSTANT(""); + else { + if(pM->iLenMSG == 0) + ret = UCHAR_CONSTANT(""); + else + ret = pM->pszRawMsg + pM->offMSG; + } + return ret; +} + + +/* Get PRI value as integer */ +static int getPRIi(msg_t *pM) +{ + return (pM->iFacility << 3) + (pM->iSeverity); +} + + +/* Get PRI value in text form + */ +char * +getPRI(msg_t *pM) +{ + /* PRI is a number in the range 0..191. Thus, we use a simple lookup table to obtain the + * string value. It looks a bit clumpsy here in code ;) + */ + int iPRI; + + if(pM == NULL) + return ""; + + iPRI = getPRIi(pM); + return (iPRI > 191) ? "invld" : (char*)syslog_pri_names[iPRI].pszName; +} + + +char * +getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) +{ + BEGINfunc + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + case tplFmtRFC3164Date: + case tplFmtRFC3164BuggyDate: + MsgLock(pM); + if(pM->pszTIMESTAMP3164 == NULL) { + pM->pszTIMESTAMP3164 = pM->pszTimestamp3164; + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, + (eFmt == tplFmtRFC3164BuggyDate)); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_MySQL == NULL) { + if((pM->pszTIMESTAMP_MySQL = MALLOC(15)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_PgSQL == NULL) { + if((pM->pszTIMESTAMP_PgSQL = MALLOC(21)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_PgSQL); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszTIMESTAMP3339 == NULL) { + pM->pszTIMESTAMP3339 = pM->pszTimestamp3339; + datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3339); + case tplFmtUnixDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_Unix[0] == '\0') { + datetime.formatTimestampUnix(&pM->tTIMESTAMP, pM->pszTIMESTAMP_Unix); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_Unix); + case tplFmtSecFrac: + if(pM->pszTIMESTAMP_SecFrac[0] == '\0') { + MsgLock(pM); + /* re-check, may have changed while we did not hold lock */ + if(pM->pszTIMESTAMP_SecFrac[0] == '\0') { + datetime.formatTimestampSecFrac(&pM->tTIMESTAMP, pM->pszTIMESTAMP_SecFrac); + } + MsgUnlock(pM); + } + return(pM->pszTIMESTAMP_SecFrac); + } + ENDfunc + return "INVALID eFmt OPTION!"; +} + +static inline char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) +{ + BEGINfunc + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = MALLOC(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 0); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_MySQL == NULL) { + if((pM->pszRcvdAt_MySQL = MALLOC(15)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_PgSQL == NULL) { + if((pM->pszRcvdAt_PgSQL = MALLOC(21)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_PgSQL); + case tplFmtRFC3164Date: + case tplFmtRFC3164BuggyDate: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = MALLOC(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, + (eFmt == tplFmtRFC3164BuggyDate)); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszRcvdAt3339 == NULL) { + if((pM->pszRcvdAt3339 = MALLOC(33)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3339); + case tplFmtUnixDate: + MsgLock(pM); + if(pM->pszRcvdAt_Unix[0] == '\0') { + datetime.formatTimestampUnix(&pM->tRcvdAt, pM->pszRcvdAt_Unix); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_Unix); + case tplFmtSecFrac: + if(pM->pszRcvdAt_SecFrac[0] == '\0') { + MsgLock(pM); + /* re-check, may have changed while we did not hold lock */ + if(pM->pszRcvdAt_SecFrac[0] == '\0') { + datetime.formatTimestampSecFrac(&pM->tRcvdAt, pM->pszRcvdAt_SecFrac); + } + MsgUnlock(pM); + } + return(pM->pszRcvdAt_SecFrac); + } + ENDfunc + return "INVALID eFmt OPTION!"; +} + + +static inline char *getSeverity(msg_t *pM) +{ + char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iSeverity < 0 || pM->iSeverity > 7) { + name = "invld"; + } else { + name = syslog_number_names[pM->iSeverity]; + } + + return name; +} + + +static inline char *getSeverityStr(msg_t *pM) +{ + char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iSeverity < 0 || pM->iSeverity > 7) { + name = "invld"; + } else { + name = syslog_severity_names[pM->iSeverity]; + } + + return name; +} + +static inline char *getFacility(msg_t *pM) +{ + char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iFacility < 0 || pM->iFacility > 23) { + name = "invld"; + } else { + name = syslog_number_names[pM->iFacility]; + } + + return name; +} + +static inline char *getFacilityStr(msg_t *pM) +{ + char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iFacility < 0 || pM->iFacility > 23) { + name = "invld"; + } else { + name = syslog_fac_names[pM->iFacility]; + } + + return name; +} + + +/* set flow control state (if not called, the default - NO_DELAY - is used) + * This needs no locking because it is only done while the object is + * not fully constructed (which also means you must not call this + * method after the msg has been handed over to a queue). + * rgerhards, 2008-03-14 + */ +rsRetVal +MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl) +{ + DEFiRet; + assert(pMsg != NULL); + assert(eFlowCtl == eFLOWCTL_NO_DELAY || eFlowCtl == eFLOWCTL_LIGHT_DELAY || eFlowCtl == eFLOWCTL_FULL_DELAY); + + pMsg->flowCtlType = eFlowCtl; + + RETiRet; +} + +/* set offset after which PRI in raw msg starts + * rgerhards, 2009-06-16 + */ +rsRetVal +MsgSetAfterPRIOffs(msg_t *pMsg, short offs) +{ + assert(pMsg != NULL); + pMsg->offAfterPRI = offs; + return RS_RET_OK; +} + + +/* rgerhards 2004-11-24: set APP-NAME in msg object + * This is not locked, because it either is called during message + * construction (where we need no locking) or later as part of a function + * which already obtained the lock. So in general, this function here must + * only be called when it it safe to do so without it aquiring a lock. + */ +rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME) +{ + DEFiRet; + assert(pMsg != NULL); + if(pMsg->pCSAPPNAME == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME)); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME); + +finalize_it: + RETiRet; +} + + +/* rgerhards 2004-11-24: set PROCID in msg object + */ +rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSPROCID == NULL) { + /* we need to obtain the object first */ + CHKiRet(cstrConstruct(&pMsg->pCSPROCID)); + } + /* if we reach this point, we have the object */ + CHKiRet(rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID)); + CHKiRet(cstrFinalize(pMsg->pCSPROCID)); + +finalize_it: + RETiRet; +} + + +/* check if we have a procid, and, if not, try to aquire/emulate it. + * This must be called WITHOUT the message lock being held. + * rgerhards, 2009-06-26 + */ +static inline void preparePROCID(msg_t *pM, sbool bLockMutex) +{ + if(pM->pCSPROCID == NULL) { + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + /* re-query, things may have changed in the mean time... */ + if(pM->pCSPROCID == NULL) + aquirePROCIDFromTAG(pM); + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + } +} + + +#if 0 +/* rgerhards, 2005-11-24 + */ +static inline int getPROCIDLen(msg_t *pM, sbool bLockMutex) +{ + assert(pM != NULL); + preparePROCID(pM, bLockMutex); + return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID); +} +#endif + + +/* rgerhards, 2005-11-24 + */ +char *getPROCID(msg_t *pM, sbool bLockMutex) +{ + uchar *pszRet; + + ISOBJ_TYPE_assert(pM, msg); + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + preparePROCID(pM, MUTEX_ALREADY_LOCKED); + if(pM->pCSPROCID == NULL) + pszRet = UCHAR_CONSTANT("-"); + else + pszRet = rsCStrGetSzStrNoNULL(pM->pCSPROCID); + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + return (char*) pszRet; +} + + +/* rgerhards 2004-11-24: set MSGID in msg object + */ +rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSMSGID == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID)); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID); + +finalize_it: + RETiRet; +} + + +/* Return state of last parser. If it had success, "OK" is returned, else + * "FAIL". All from the constant pool. + */ +static inline char *getParseSuccess(msg_t *pM) +{ + return (pM->bParseSuccess) ? "OK" : "FAIL"; +} + + +/* al, 2011-07-26: LockMsg to avoid race conditions + */ +static inline char *getMSGID(msg_t *pM) +{ + if (pM->pCSMSGID == NULL) { + return "-"; + } + else { + MsgLock(pM); + char* pszreturn = (char*) rsCStrGetSzStrNoNULL(pM->pCSMSGID); + MsgUnlock(pM); + return pszreturn; + } +} + +/* rgerhards 2012-03-15: set parser success (an integer, acutally bool) + */ +void MsgSetParseSuccess(msg_t *pMsg, int bSuccess) +{ + assert(pMsg != NULL); + pMsg->bParseSuccess = bSuccess; +} + +/* rgerhards 2009-06-12: set associated ruleset + */ +void MsgSetRuleset(msg_t *pMsg, ruleset_t *pRuleset) +{ + assert(pMsg != NULL); + pMsg->pRuleset = pRuleset; +} + + +/* set TAG in msg object + * (rewritten 2009-06-18 rgerhards) + */ +void MsgSetTAG(msg_t *pMsg, uchar* pszBuf, size_t lenBuf) +{ + uchar *pBuf; + assert(pMsg != NULL); + + freeTAG(pMsg); + + pMsg->iLenTAG = lenBuf; + if(pMsg->iLenTAG < CONF_TAG_BUFSIZE) { + /* small enough: use fixed buffer (faster!) */ + pBuf = pMsg->TAG.szBuf; + } else { + if((pBuf = (uchar*) MALLOC(pMsg->iLenTAG + 1)) == NULL) { + /* truncate message, better than completely loosing it... */ + pBuf = pMsg->TAG.szBuf; + pMsg->iLenTAG = CONF_TAG_BUFSIZE - 1; + } else { + pMsg->TAG.pszTAG = pBuf; + } + } + + memcpy(pBuf, pszBuf, pMsg->iLenTAG); + pBuf[pMsg->iLenTAG] = '\0'; /* this also works with truncation! */ +} + + +/* This function tries to emulate the TAG if none is + * set. Its primary purpose is to provide an old-style TAG + * when a syslog-protocol message has been received. Then, + * the tag is APP-NAME "[" PROCID "]". The function first checks + * if there is a TAG and, if not, if it can emulate it. + * rgerhards, 2005-11-24 + */ +static inline void tryEmulateTAG(msg_t *pM, sbool bLockMutex) +{ + size_t lenTAG; + uchar bufTAG[CONF_TAG_MAXSIZE]; + assert(pM != NULL); + + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + if(pM->iLenTAG > 0) { + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + return; /* done, no need to emulate */ + } + + if(getProtocolVersion(pM) == 1) { + if(!strcmp(getPROCID(pM, MUTEX_ALREADY_LOCKED), "-")) { + /* no process ID, use APP-NAME only */ + MsgSetTAG(pM, (uchar*) getAPPNAME(pM, MUTEX_ALREADY_LOCKED), getAPPNAMELen(pM, MUTEX_ALREADY_LOCKED)); + } else { + /* now we can try to emulate */ + lenTAG = snprintf((char*)bufTAG, CONF_TAG_MAXSIZE, "%s[%s]", + getAPPNAME(pM, MUTEX_ALREADY_LOCKED), getPROCID(pM, MUTEX_ALREADY_LOCKED)); + bufTAG[sizeof(bufTAG)-1] = '\0'; /* just to make sure... */ + MsgSetTAG(pM, bufTAG, lenTAG); + } + } + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); +} + + +void +getTAG(msg_t *pM, uchar **ppBuf, int *piLen) +{ + if(pM == NULL) { + *ppBuf = UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->iLenTAG == 0) + tryEmulateTAG(pM, LOCK_MUTEX); + if(pM->iLenTAG == 0) { + *ppBuf = UCHAR_CONSTANT(""); + *piLen = 0; + } else { + *ppBuf = (pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG; + *piLen = pM->iLenTAG; + } + } +} + + +int getHOSTNAMELen(msg_t *pM) +{ + if(pM == NULL) + return 0; + else + if(pM->pszHOSTNAME == NULL) { + resolveDNS(pM); + if(pM->rcvFrom.pRcvFrom == NULL) + return 0; + else + return prop.GetStringLen(pM->rcvFrom.pRcvFrom); + } else + return pM->iLenHOSTNAME; +} + + +char *getHOSTNAME(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszHOSTNAME == NULL) { + resolveDNS(pM); + if(pM->rcvFrom.pRcvFrom == NULL) { + return ""; + } else { + uchar *psz; + int len; + prop.GetString(pM->rcvFrom.pRcvFrom, &psz, &len); + return (char*) psz; + } + } else { + return (char*) pM->pszHOSTNAME; + } +} + + +uchar *getRcvFrom(msg_t *pM) +{ + uchar *psz; + int len; + BEGINfunc + + if(pM == NULL) { + psz = UCHAR_CONSTANT(""); + } else { + resolveDNS(pM); + if(pM->rcvFrom.pRcvFrom == NULL) + psz = UCHAR_CONSTANT(""); + else + prop.GetString(pM->rcvFrom.pRcvFrom, &psz, &len); + } + ENDfunc + return psz; +} + + +/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object + */ +rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSStrucData == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSStrucData)); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData); + +finalize_it: + RETiRet; +} + +/* get the length of the "STRUCTURED-DATA" sz string + * rgerhards, 2005-11-24 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getStructuredDataLen(msg_t *pM) +{ + return (pM->pCSStrucData == NULL) ? 1 : rsCStrLen(pM->pCSStrucData); +} +#endif + + +/* get the "STRUCTURED-DATA" as sz string + * rgerhards, 2005-11-24 + */ +static inline char *getStructuredData(msg_t *pM) +{ + uchar *pszRet; + + MsgLock(pM); + if(pM->pCSStrucData == NULL) + pszRet = UCHAR_CONSTANT("-"); + else + pszRet = rsCStrGetSzStrNoNULL(pM->pCSStrucData); + MsgUnlock(pM); + return (char*) pszRet; +} + +/* get the "programname" as sz string + * rgerhards, 2005-10-19 + */ +uchar *getProgramName(msg_t *pM, sbool bLockMutex) +{ + if(pM->iLenPROGNAME == -1) { + if(bLockMutex == LOCK_MUTEX) { + MsgLock(pM); + /* need to re-check, things may have change in between! */ + if(pM->iLenPROGNAME == -1) + aquireProgramName(pM); + MsgUnlock(pM); + } else { + aquireProgramName(pM); + } + } + return (pM->iLenPROGNAME < CONF_PROGNAME_BUFSIZE) ? pM->PROGNAME.szBuf + : pM->PROGNAME.ptr; +} + + +/* This function tries to emulate APPNAME if it is not present. Its + * main use is when we have received a log record via legacy syslog and + * now would like to send out the same one via syslog-protocol. + * MUST be called with the Msg Lock locked! + */ +static void tryEmulateAPPNAME(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pCSAPPNAME != NULL) + return; /* we are already done */ + + if(getProtocolVersion(pM) == 0) { + /* only then it makes sense to emulate */ + MsgSetAPPNAME(pM, (char*)getProgramName(pM, MUTEX_ALREADY_LOCKED)); + } +} + + + +/* check if we have a APPNAME, and, if not, try to aquire/emulate it. + * This must be called WITHOUT the message lock being held. + * rgerhards, 2009-06-26 + */ +static inline void prepareAPPNAME(msg_t *pM, sbool bLockMutex) +{ + if(pM->pCSAPPNAME == NULL) { + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + + /* re-query as things might have changed during locking */ + if(pM->pCSAPPNAME == NULL) + tryEmulateAPPNAME(pM); + + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + } +} + +/* rgerhards, 2005-11-24 + */ +char *getAPPNAME(msg_t *pM, sbool bLockMutex) +{ + uchar *pszRet; + + assert(pM != NULL); + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + prepareAPPNAME(pM, MUTEX_ALREADY_LOCKED); + if(pM->pCSAPPNAME == NULL) + pszRet = UCHAR_CONSTANT(""); + else + pszRet = rsCStrGetSzStrNoNULL(pM->pCSAPPNAME); + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + return (char*)pszRet; +} + +/* rgerhards, 2005-11-24 + */ +static int getAPPNAMELen(msg_t *pM, sbool bLockMutex) +{ + assert(pM != NULL); + prepareAPPNAME(pM, bLockMutex); + return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); +} + +/* rgerhards 2008-09-10: set pszInputName in msg object. This calls AddRef() + * on the property, because this must be done in all current cases and there + * is no case expected where this may not be necessary. + * rgerhards, 2009-06-16 + */ +void MsgSetInputName(msg_t *pThis, prop_t *inputName) +{ + assert(pThis != NULL); + + prop.AddRef(inputName); + if(pThis->pInputName != NULL) + prop.Destruct(&pThis->pInputName); + pThis->pInputName = inputName; +} + + +/* Set the pfrominet socket store, so that we can obtain the peer at some + * later time. Note that we do not check if pRcvFrom is already set, so this + * function must only be called during message creation. + * NOTE: msgFlags is NOT set. While this is somewhat a violation of layers, + * it is done because it gains us some performance. So the caller must make + * sure the message flags are properly maintained. For all current callers, + * this is always the case and without extra effort required. + * rgerhards, 2009-11-17 + */ +rsRetVal +msgSetFromSockinfo(msg_t *pThis, struct sockaddr_storage *sa){ + DEFiRet; + assert(pThis->rcvFrom.pRcvFrom == NULL); + + CHKmalloc(pThis->rcvFrom.pfrominet = malloc(sizeof(struct sockaddr_storage))); + memcpy(pThis->rcvFrom.pfrominet, sa, sizeof(struct sockaddr_storage)); + +finalize_it: + RETiRet; +} + + +/* rgerhards 2008-09-10: set RcvFrom name in msg object. This calls AddRef() + * on the property, because this must be done in all current cases and there + * is no case expected where this may not be necessary. + * rgerhards, 2009-06-30 + */ +void MsgSetRcvFrom(msg_t *pThis, prop_t *new) +{ + prop.AddRef(new); + MsgSetRcvFromWithoutAddRef(pThis, new); +} + + +/* This is used to set the property via a string. This function should not be + * called if there is a reliable way for a caller to make sure that the + * same name can be used across multiple messages. However, if it can not + * ensure that, calling this function is the second best thing, because it + * will re-use the previously created property if it contained the same + * name (but it works only for the immediate previous). + * rgerhards, 2009-06-31 + */ +void MsgSetRcvFromStr(msg_t *pThis, uchar *psz, int len, prop_t **ppProp) +{ + assert(pThis != NULL); + assert(ppProp != NULL); + + prop.CreateOrReuseStringProp(ppProp, psz, len); + MsgSetRcvFrom(pThis, *ppProp); +} + + +/* set RcvFromIP name in msg object. This calls AddRef() + * on the property, because this must be done in all current cases and there + * is no case expected where this may not be necessary. + * rgerhards, 2009-06-30 + */ +rsRetVal MsgSetRcvFromIP(msg_t *pThis, prop_t *new) +{ + assert(pThis != NULL); + + BEGINfunc + prop.AddRef(new); + MsgSetRcvFromIPWithoutAddRef(pThis, new); + ENDfunc + return RS_RET_OK; +} + + +/* This is used to set the property via a string. This function should not be + * called if there is a reliable way for a caller to make sure that the + * same name can be used across multiple messages. However, if it can not + * ensure that, calling this function is the second best thing, because it + * will re-use the previously created property if it contained the same + * name (but it works only for the immediate previous). + * rgerhards, 2009-06-31 + */ +rsRetVal MsgSetRcvFromIPStr(msg_t *pThis, uchar *psz, int len, prop_t **ppProp) +{ + DEFiRet; + assert(pThis != NULL); + + CHKiRet(prop.CreateOrReuseStringProp(ppProp, psz, len)); + MsgSetRcvFromIP(pThis, *ppProp); + +finalize_it: + RETiRet; +} + + +/* rgerhards 2004-11-09: set HOSTNAME in msg object + * rgerhards, 2007-06-21: + * Does not return anything. If an error occurs, the hostname is + * simply not set. I have changed this behaviour. The only problem + * we can run into is memory shortage. If we have such, it is better + * to loose the hostname than the full message. So we silently ignore + * that problem and hope that memory will be available the next time + * we need it. The rest of the code already knows how to handle an + * unset HOSTNAME. + */ +void MsgSetHOSTNAME(msg_t *pThis, uchar* pszHOSTNAME, int lenHOSTNAME) +{ + assert(pThis != NULL); + + freeHOSTNAME(pThis); + + pThis->iLenHOSTNAME = lenHOSTNAME; + if(pThis->iLenHOSTNAME < CONF_HOSTNAME_BUFSIZE) { + /* small enough: use fixed buffer (faster!) */ + pThis->pszHOSTNAME = pThis->szHOSTNAME; + } else if((pThis->pszHOSTNAME = (uchar*) MALLOC(pThis->iLenHOSTNAME + 1)) == NULL) { + /* truncate message, better than completely loosing it... */ + pThis->pszHOSTNAME = pThis->szHOSTNAME; + pThis->iLenHOSTNAME = CONF_HOSTNAME_BUFSIZE - 1; + } + + memcpy(pThis->pszHOSTNAME, pszHOSTNAME, pThis->iLenHOSTNAME); + pThis->pszHOSTNAME[pThis->iLenHOSTNAME] = '\0'; /* this also works with truncation! */ +} + + +/* set the offset of the MSG part into the raw msg buffer + * Note that the offset may be higher than the length of the raw message + * (exactly by one). This can happen if we have a message that does not + * contain any MSG part. + */ +void MsgSetMSGoffs(msg_t *pMsg, short offs) +{ + ISOBJ_TYPE_assert(pMsg, msg); + pMsg->offMSG = offs; + if(offs > pMsg->iLenRawMsg) { + assert(offs - 1 == pMsg->iLenRawMsg); + pMsg->iLenMSG = 0; + } else { + pMsg->iLenMSG = pMsg->iLenRawMsg - offs; + } +} + + +/* replace the MSG part of a message. The update actually takes place inside + * rawmsg. + * There are two cases: either the new message will be larger than the new msg + * or it will be less than or equal. If it is less than or equal, we can utilize + * the previous message buffer. If it is larger, we can utilize the msg_t-included + * message buffer if it fits in there. If this is not the case, we need to alloc + * a new, larger, chunk and copy over the data to it. Note that this function is + * (hopefully) relatively seldom being called, so some performance impact is + * uncritical. In any case, pszMSG is copied, so if it was dynamically allocated, + * the caller is responsible for freeing it. + * rgerhards, 2009-06-23 + */ +rsRetVal MsgReplaceMSG(msg_t *pThis, uchar* pszMSG, int lenMSG) +{ + int lenNew; + uchar *bufNew; + DEFiRet; + ISOBJ_TYPE_assert(pThis, msg); + assert(pszMSG != NULL); + + lenNew = pThis->iLenRawMsg + lenMSG - pThis->iLenMSG; + if(lenMSG > pThis->iLenMSG && lenNew >= CONF_RAWMSG_BUFSIZE) { + /* we have lost our "bet" and need to alloc a new buffer ;) */ + CHKmalloc(bufNew = MALLOC(lenNew + 1)); + memcpy(bufNew, pThis->pszRawMsg, pThis->offMSG); + if(pThis->pszRawMsg != pThis->szRawMsg) + free(pThis->pszRawMsg); + pThis->pszRawMsg = bufNew; + } + + if(lenMSG > 0) + memcpy(pThis->pszRawMsg + pThis->offMSG, pszMSG, lenMSG); + pThis->pszRawMsg[lenNew] = '\0'; /* this also works with truncation! */ + pThis->iLenRawMsg = lenNew; + pThis->iLenMSG = lenMSG; + +finalize_it: + RETiRet; +} + +/* set raw message in message object. Size of message is provided. + * The function makes sure that the stored rawmsg is properly + * terminated by '\0'. + * rgerhards, 2009-06-16 + */ +void MsgSetRawMsg(msg_t *pThis, char* pszRawMsg, size_t lenMsg) +{ + assert(pThis != NULL); + if(pThis->pszRawMsg != pThis->szRawMsg) + free(pThis->pszRawMsg); + + pThis->iLenRawMsg = lenMsg; + if(pThis->iLenRawMsg < CONF_RAWMSG_BUFSIZE) { + /* small enough: use fixed buffer (faster!) */ + pThis->pszRawMsg = pThis->szRawMsg; + } else if((pThis->pszRawMsg = (uchar*) MALLOC(pThis->iLenRawMsg + 1)) == NULL) { + /* truncate message, better than completely loosing it... */ + pThis->pszRawMsg = pThis->szRawMsg; + pThis->iLenRawMsg = CONF_RAWMSG_BUFSIZE - 1; + } + + memcpy(pThis->pszRawMsg, pszRawMsg, pThis->iLenRawMsg); + pThis->pszRawMsg[pThis->iLenRawMsg] = '\0'; /* this also works with truncation! */ +} + + +/* set raw message in message object. Size of message is not provided. This + * function should only be used when it is unavoidable (and over time we should + * try to remove it altogether). + * rgerhards, 2009-06-16 + */ +void MsgSetRawMsgWOSize(msg_t *pMsg, char* pszRawMsg) +{ + MsgSetRawMsg(pMsg, pszRawMsg, strlen(pszRawMsg)); +} + + +/* Decode a priority into textual information like auth.emerg. + * The variable pRes must point to a user-supplied buffer. + * The pointer to the buffer + * is also returned, what makes this functiona suitable for + * use in printf-like functions. + * Note: a buffer size of 20 characters is always sufficient. + */ +char *textpri(char *pRes, int pri) +{ + assert(pRes != NULL); + memcpy(pRes, syslog_fac_names[LOG_FAC(pri)], len_syslog_fac_names[LOG_FAC(pri)]); + pRes[len_syslog_fac_names[LOG_FAC(pri)]] = '.'; + memcpy(pRes+len_syslog_fac_names[LOG_FAC(pri)]+1, + syslog_severity_names[LOG_PRI(pri)], + len_syslog_severity_names[LOG_PRI(pri)]+1 /* for \0! */); + return pRes; +} + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * Added 2007-07-10 rgerhards + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_HHOUR, NOW_QHOUR, NOW_MINUTE } eNOWType; +#define tmpBUFSIZE 16 /* size of formatting buffer */ +static uchar *getNOW(eNOWType eNow, struct syslogTime *t) +{ + uchar *pBuf; + + if((pBuf = (uchar*) MALLOC(sizeof(uchar) * tmpBUFSIZE)) == NULL) { + return NULL; + } + + if(t->year == 0) { /* not yet set! */ + datetime.getCurrTime(t, NULL); + } + + switch(eNow) { + case NOW_NOW: + memcpy(pBuf, two_digits[t->year/100], 2); + memcpy(pBuf+2, two_digits[t->year%100], 2); + pBuf[4] = '-'; + memcpy(pBuf+5, two_digits[(int)t->month], 2); + pBuf[7] = '-'; + memcpy(pBuf+8, two_digits[(int)t->day], 3); + break; + case NOW_YEAR: + memcpy(pBuf, two_digits[t->year/100], 2); + memcpy(pBuf+2, two_digits[t->year%100], 3); + break; + case NOW_MONTH: + memcpy(pBuf, two_digits[(int)t->month], 3); + break; + case NOW_DAY: + memcpy(pBuf, two_digits[(int)t->day], 3); + break; + case NOW_HOUR: + memcpy(pBuf, two_digits[(int)t->hour], 3); + break; + case NOW_HHOUR: + memcpy(pBuf, two_digits[t->hour/30], 3); + break; + case NOW_QHOUR: + memcpy(pBuf, two_digits[t->hour/15], 3); + break; + case NOW_MINUTE: + memcpy(pBuf, two_digits[(int)t->minute], 3); + break; + } + + return(pBuf); +} +#undef tmpBUFSIZE /* clean up */ + + +/* Get a CEE-Property as string value*/ +rsRetVal +getCEEPropVal(msg_t *pM, es_str_t *propName, uchar **pRes, rs_size_t *buflen, unsigned short *pbMustBeFreed) +{ + uchar *name = NULL; + uchar *leaf; + struct json_object *parent; + struct json_object *field; + DEFiRet; + + if(*pbMustBeFreed) + free(*pRes); + *pRes = NULL; + // TODO: mutex? + if(pM->json == NULL) goto finalize_it; + + if(!es_strbufcmp(propName, (uchar*)"!", 1)) { + field = pM->json; + } else { + name = (uchar*)es_str2cstr(propName, NULL); + leaf = jsonPathGetLeaf(name, ustrlen(name)); + CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1)); + field = json_object_object_get(parent, (char*)leaf); + } + if(field != NULL) { + *pRes = (uchar*) strdup(json_object_get_string(field)); + *buflen = (int) ustrlen(*pRes); + *pbMustBeFreed = 1; + } + +finalize_it: + free(name); + if(*pRes == NULL) { + /* could not find any value, so set it to empty */ + *pRes = (unsigned char*)""; + *pbMustBeFreed = 0; + } + RETiRet; +} + + +/* Get a CEE-Property as native json object + */ +rsRetVal +msgGetCEEPropJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson) +{ + uchar *name = NULL; + uchar *leaf; + struct json_object *parent; + DEFiRet; + + // TODO: mutex? + if(pM->json == NULL) { + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + if(!es_strbufcmp(propName, (uchar*)"!", 1)) { + *pjson = pM->json; + FINALIZE; + } + name = (uchar*)es_str2cstr(propName, NULL); + leaf = jsonPathGetLeaf(name, ustrlen(name)); + CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1)); + *pjson = json_object_object_get(parent, (char*)leaf); + if(*pjson == NULL) { + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + +finalize_it: + free(name); + RETiRet; +} + + +/* Encode a JSON value and add it to provided string. Note that + * the string object may be NULL. In this case, it is created + * if and only if escaping is needed. + */ +static rsRetVal +jsonAddVal(uchar *pSrc, unsigned buflen, es_str_t **dst) +{ + unsigned char c; + es_size_t i; + char numbuf[4]; + int j; + DEFiRet; + + for(i = 0 ; i < buflen ; ++i) { + c = pSrc[i]; + if( (c >= 0x23 && c <= 0x5b) + || (c >= 0x5d /* && c <= 0x10FFFF*/) + || c == 0x20 || c == 0x21) { + /* no need to escape */ + if(*dst != NULL) + es_addChar(dst, c); + } else { + if(*dst == NULL) { + if(i == 0) { + /* we hope we have only few escapes... */ + *dst = es_newStr(buflen+10); + } else { + *dst = es_newStrFromBuf((char*)pSrc, i); + } + if(*dst == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + } + /* we must escape, try RFC4627-defined special sequences first */ + switch(c) { + case '\0': + es_addBuf(dst, "\\u0000", 6); + break; + case '\"': + es_addBuf(dst, "\\\"", 2); + break; + case '/': + es_addBuf(dst, "\\/", 2); + break; + case '\\': + es_addBuf(dst, "\\\\", 2); + break; + case '\010': + es_addBuf(dst, "\\b", 2); + break; + case '\014': + es_addBuf(dst, "\\f", 2); + break; + case '\n': + es_addBuf(dst, "\\n", 2); + break; + case '\r': + es_addBuf(dst, "\\r", 2); + break; + case '\t': + es_addBuf(dst, "\\t", 2); + break; + default: + /* TODO : proper Unicode encoding (see header comment) */ + for(j = 0 ; j < 4 ; ++j) { + numbuf[3-j] = hexdigit[c % 16]; + c = c / 16; + } + es_addBuf(dst, "\\u", 2); + es_addBuf(dst, numbuf, 4); + break; + } + } + } +finalize_it: + RETiRet; +} + + +/* encode a property in JSON escaped format. This is a helper + * to MsgGetProp. It needs to update all provided parameters. + * Note: Code is borrowed from libee (my own code, so ASL 2.0 + * is fine with it); this function may later be replaced by + * some "better" and more complete implementation (maybe from + * libee or its helpers). + * For performance reasons, we begin to copy the string only + * when we recognice that we actually need to do some escaping. + * rgerhards, 2012-03-16 + */ +static rsRetVal +jsonEncode(uchar **ppRes, unsigned short *pbMustBeFreed, int *pBufLen) +{ + unsigned buflen; + uchar *pSrc; + es_str_t *dst = NULL; + DEFiRet; + + pSrc = *ppRes; + buflen = (*pBufLen == -1) ? ustrlen(pSrc) : *pBufLen; + CHKiRet(jsonAddVal(pSrc, buflen, &dst)); + + if(dst != NULL) { + /* we updated the string and need to replace the + * previous data. + */ + if(*pbMustBeFreed) + free(*ppRes); + *ppRes = (uchar*)es_str2cstr(dst, NULL); + *pbMustBeFreed = 1; + *pBufLen = -1; + es_deleteStr(dst); + } + +finalize_it: + RETiRet; +} + + +/* Format a property as JSON field, that means + * "name"="value" + * where value is JSON-escaped (here we assume that the name + * only contains characters from the valid character set). + * Note: this function duplicates code from jsonEncode(). + * TODO: these two functions should be combined, at least if + * that makes any sense from a performance PoV - definitely + * something to consider at a later stage. rgerhards, 2012-04-19 + */ +static rsRetVal +jsonField(struct templateEntry *pTpe, uchar **ppRes, unsigned short *pbMustBeFreed, int *pBufLen) +{ + unsigned buflen; + uchar *pSrc; + es_str_t *dst = NULL; + DEFiRet; + + pSrc = *ppRes; + buflen = (*pBufLen == -1) ? ustrlen(pSrc) : *pBufLen; + /* we hope we have only few escapes... */ + dst = es_newStr(buflen+pTpe->lenFieldName+15); + es_addChar(&dst, '"'); + es_addBuf(&dst, (char*)pTpe->fieldName, pTpe->lenFieldName); + es_addBufConstcstr(&dst, "\":\""); + CHKiRet(jsonAddVal(pSrc, buflen, &dst)); + es_addChar(&dst, '"'); + + if(*pbMustBeFreed) + free(*ppRes); + /* we know we do not have \0 chars - so the size does not change */ + *pBufLen = es_strlen(dst); + *ppRes = (uchar*)es_str2cstr(dst, NULL); + *pbMustBeFreed = 1; + es_deleteStr(dst); + +finalize_it: + RETiRet; +} + + +/* This function returns a string-representation of the + * requested message property. This is a generic function used + * to abstract properties so that these can be easier + * queried. Returns NULL if property could not be found. + * Actually, this function is a big if..elseif. What it does + * is simply to map property names (from MonitorWare) to the + * message object data fields. + * + * In case we need string forms of propertis we do not + * yet have in string form, we do a memory allocation that + * is sufficiently large (in all cases). Once the string + * form has been obtained, it is saved until the Msg object + * is finally destroyed. This is so that we save the processing + * time in the (likely) case that this property is requested + * again. It also saves us a lot of dynamic memory management + * issues in the upper layers, because we so can guarantee that + * the buffer will remain static AND available during the lifetime + * of the object. Please note that both the max size allocation as + * well as keeping things in memory might like look like a + * waste of memory (some might say it actually is...) - we + * deliberately accept this because performance is more important + * to us ;) + * rgerhards 2004-11-18 + * Parameter "bMustBeFreed" is set by this function. It tells the + * caller whether or not the string returned must be freed by the + * caller itself. It is is 0, the caller MUST NOT free it. If it is + * 1, the caller MUST free it. Handling this wrongly leads to either + * a memory leak of a program abort (do to double-frees or frees on + * the constant memory pool). So be careful to do it right. + * rgerhards 2004-11-23 + * regular expression support contributed by Andres Riancho merged + * on 2005-09-13 + * changed so that it now an be called without a template entry (NULL). + * In this case, only the (unmodified) property is returned. This will + * be used in selector line processing. + * rgerhards 2005-09-15 + */ +/* a quick helper to save some writing: */ +#define RET_OUT_OF_MEMORY { *pbMustBeFreed = 0;\ + *pPropLen = sizeof("**OUT OF MEMORY**") - 1; \ + return(UCHAR_CONSTANT("**OUT OF MEMORY**"));} +uchar *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + propid_t propid, es_str_t *propName, rs_size_t *pPropLen, + unsigned short *pbMustBeFreed, struct syslogTime *ttNow) +{ + uchar *pRes; /* result pointer */ + rs_size_t bufLen = -1; /* length of string or -1, if not known */ + uchar *pBufStart; + uchar *pBuf; + int iLen; + short iOffs; + enum tplFormatTypes datefmt; + + BEGINfunc + assert(pMsg != NULL); + assert(pbMustBeFreed != NULL); + +#ifdef FEATURE_REGEXP + /* Variables necessary for regular expression matching */ + size_t nmatch = 10; + regmatch_t pmatch[10]; +#endif + + *pbMustBeFreed = 0; + + switch(propid) { + case PROP_MSG: + pRes = getMSG(pMsg); + bufLen = getMSGLen(pMsg); + break; + case PROP_TIMESTAMP: + if (pTpe != NULL) + datefmt = pTpe->data.field.eDateFormat; + else + datefmt = tplFmtDefault; + pRes = (uchar*)getTimeReported(pMsg, datefmt); + break; + case PROP_HOSTNAME: + pRes = (uchar*)getHOSTNAME(pMsg); + bufLen = getHOSTNAMELen(pMsg); + break; + case PROP_SYSLOGTAG: + getTAG(pMsg, &pRes, &bufLen); + break; + case PROP_RAWMSG: + getRawMsg(pMsg, &pRes, &bufLen); + break; + case PROP_INPUTNAME: + getInputName(pMsg, &pRes, &bufLen); + break; + case PROP_FROMHOST: + pRes = getRcvFrom(pMsg); + break; + case PROP_FROMHOST_IP: + pRes = getRcvFromIP(pMsg); + break; + case PROP_PRI: + pRes = (uchar*)getPRI(pMsg); + break; + case PROP_PRI_TEXT: + pBuf = MALLOC(20 * sizeof(uchar)); + if(pBuf == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + pRes = (uchar*)textpri((char*)pBuf, getPRIi(pMsg)); + } + break; + case PROP_IUT: + pRes = UCHAR_CONSTANT("1"); /* always 1 for syslog messages (a MonitorWare thing;)) */ + bufLen = 1; + break; + case PROP_SYSLOGFACILITY: + pRes = (uchar*)getFacility(pMsg); + break; + case PROP_SYSLOGFACILITY_TEXT: + pRes = (uchar*)getFacilityStr(pMsg); + break; + case PROP_SYSLOGSEVERITY: + pRes = (uchar*)getSeverity(pMsg); + break; + case PROP_SYSLOGSEVERITY_TEXT: + pRes = (uchar*)getSeverityStr(pMsg); + break; + case PROP_TIMEGENERATED: + if (pTpe != NULL) + datefmt = pTpe->data.field.eDateFormat; + else + datefmt = tplFmtDefault; + pRes = (uchar*)getTimeGenerated(pMsg, datefmt); + break; + case PROP_PROGRAMNAME: + pRes = getProgramName(pMsg, LOCK_MUTEX); + break; + case PROP_PROTOCOL_VERSION: + pRes = (uchar*)getProtocolVersionString(pMsg); + break; + case PROP_STRUCTURED_DATA: + pRes = (uchar*)getStructuredData(pMsg); + break; + case PROP_APP_NAME: + pRes = (uchar*)getAPPNAME(pMsg, LOCK_MUTEX); + break; + case PROP_PROCID: + pRes = (uchar*)getPROCID(pMsg, LOCK_MUTEX); + break; + case PROP_MSGID: + pRes = (uchar*)getMSGID(pMsg); + break; +#ifdef USE_LIBUUID + case PROP_UUID: + getUUID(pMsg, &pRes, &bufLen); + break; +#endif + case PROP_PARSESUCCESS: + pRes = (uchar*)getParseSuccess(pMsg); + break; + case PROP_SYS_NOW: + if((pRes = getNOW(NOW_NOW, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 10; + } + break; + case PROP_SYS_YEAR: + if((pRes = getNOW(NOW_YEAR, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 4; + } + break; + case PROP_SYS_MONTH: + if((pRes = getNOW(NOW_MONTH, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_DAY: + if((pRes = getNOW(NOW_DAY, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_HOUR: + if((pRes = getNOW(NOW_HOUR, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_HHOUR: + if((pRes = getNOW(NOW_HHOUR, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_QHOUR: + if((pRes = getNOW(NOW_QHOUR, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_MINUTE: + if((pRes = getNOW(NOW_MINUTE, ttNow)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_MYHOSTNAME: + pRes = glbl.GetLocalHostName(); + break; + case PROP_CEE_ALL_JSON: + if(pMsg->json == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + pRes = (uchar*) "{}"; + bufLen = 2; + *pbMustBeFreed = 0; + } else { + pRes = (uchar*)strdup(json_object_get_string(pMsg->json)); + *pbMustBeFreed = 1; + } + break; + case PROP_CEE: + getCEEPropVal(pMsg, propName, &pRes, &bufLen, pbMustBeFreed); + break; + case PROP_SYS_BOM: + if(*pbMustBeFreed == 1) + free(pRes); + pRes = (uchar*) "\xEF\xBB\xBF"; + *pbMustBeFreed = 0; + break; + case PROP_SYS_UPTIME: +# ifndef HAVE_SYSINFO_UPTIME + /* An alternative on some systems (eg Solaris) is to scan + * /var/adm/utmpx for last boot time. + */ + pRes = (uchar*) "UPTIME NOT available on this system"; + *pbMustBeFreed = 0; +# else + { + struct sysinfo s_info; + + if((pRes = (uchar*) MALLOC(sizeof(uchar) * 32)) == NULL) { + RET_OUT_OF_MEMORY; + } + *pbMustBeFreed = 1; + + if(sysinfo(&s_info) < 0) { + *pPropLen = sizeof("**SYSCALL FAILED**") - 1; + return(UCHAR_CONSTANT("**SYSCALL FAILED**")); + } + + snprintf((char*) pRes, sizeof(uchar) * 32, "%ld", s_info.uptime); + } +# endif + break; + default: + /* there is no point in continuing, we may even otherwise render the + * error message unreadable. rgerhards, 2007-07-10 + */ + dbgprintf("invalid property id: '%d'\n", propid); + *pbMustBeFreed = 0; + *pPropLen = sizeof("**INVALID PROPERTY NAME**") - 1; + return UCHAR_CONSTANT("**INVALID PROPERTY NAME**"); + } + + /* If we did not receive a template pointer, we are already done... */ + if(pTpe == NULL || !pTpe->bComplexProcessing) { + *pPropLen = (bufLen == -1) ? ustrlen(pRes) : bufLen; + return pRes; + } + + /* Now check if we need to make "temporary" transformations (these + * are transformations that do not go back into the message - + * memory must be allocated for them!). + */ + + /* substring extraction */ + /* first we check if we need to extract by field number + * rgerhards, 2005-12-22 + */ + if(pTpe->data.field.has_fields == 1) { + size_t iCurrFld; + uchar *pFld; + uchar *pFldEnd; + /* first, skip to the field in question. The field separator + * is always one character and is stored in the template entry. + */ + iCurrFld = 1; + pFld = pRes; + while(*pFld && iCurrFld < pTpe->data.field.iFieldNr) { + /* skip fields until the requested field or end of string is found */ + while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) + ++pFld; /* skip to field terminator */ + if(*pFld == pTpe->data.field.field_delim) { + ++pFld; /* eat it */ +#ifdef STRICT_GPLV3 + if (pTpe->data.field.field_expand != 0) { + while (*pFld == pTpe->data.field.field_delim) { + ++pFld; + } + } +#endif + ++iCurrFld; + } + } + dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iFieldNr, (int) iCurrFld); + + if(iCurrFld == pTpe->data.field.iFieldNr) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = pFld; + while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) + ++pFldEnd; + --pFldEnd; /* we are already at the delimiter - so we need to + * step back a little not to copy it as part of the field. */ + /* we got our end pointer, now do the copy */ + /* TODO: code copied from below, this is a candidate for a separate function */ + iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = MALLOC((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + /* now copy */ + memcpy(pBuf, pFld, iLen); + bufLen = iLen; + pBuf[iLen] = '\0'; /* terminate it */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + if(*(pFldEnd+1) != '\0') + ++pFldEnd; /* OK, skip again over delimiter char */ + } else { + /* field not found, return error */ + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + *pPropLen = sizeof("**FIELD NOT FOUND**") - 1; + return UCHAR_CONSTANT("**FIELD NOT FOUND**"); + } +#ifdef FEATURE_REGEXP + } else { + /* Check for regular expressions */ + if (pTpe->data.field.has_regex != 0) { + if (pTpe->data.field.has_regex == 2) { + /* Could not compile regex before! */ + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + *pPropLen = sizeof("**NO MATCH** **BAD REGULAR EXPRESSION**") - 1; + return UCHAR_CONSTANT("**NO MATCH** **BAD REGULAR EXPRESSION**"); + } + + dbgprintf("string to match for regex is: %s\n", pRes); + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + short iTry = 0; + uchar bFound = 0; + iOffs = 0; + /* first see if we find a match, iterating through the series of + * potential matches over the string. + */ + while(!bFound) { + int iREstat; + iREstat = regexp.regexec(&pTpe->data.field.re, (char*)(pRes + iOffs), nmatch, pmatch, 0); + dbgprintf("regexec return is %d\n", iREstat); + if(iREstat == 0) { + if(pmatch[0].rm_so == -1) { + dbgprintf("oops ... start offset of successful regexec is -1\n"); + break; + } + if(iTry == pTpe->data.field.iMatchToUse) { + bFound = 1; + } else { + dbgprintf("regex found at offset %d, new offset %d, tries %d\n", + iOffs, (int) (iOffs + pmatch[0].rm_eo), iTry); + iOffs += pmatch[0].rm_eo; + ++iTry; + } + } else { + break; + } + } + dbgprintf("regex: end search, found %d\n", bFound); + if(!bFound) { + /* we got no match! */ + if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) { + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) { + bufLen = sizeof("**NO MATCH**") - 1; + pRes = UCHAR_CONSTANT("**NO MATCH**"); + } else if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_ZERO) { + bufLen = 1; + pRes = UCHAR_CONSTANT("0"); + } else { + bufLen = 0; + pRes = UCHAR_CONSTANT(""); + } + } + } else { + /* Match- but did it match the one we wanted? */ + /* we got no match! */ + if(pmatch[pTpe->data.field.iSubMatchToUse].rm_so == -1) { + if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) { + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) { + bufLen = sizeof("**NO MATCH**") - 1; + pRes = UCHAR_CONSTANT("**NO MATCH**"); + } else if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_ZERO) { + bufLen = 1; + pRes = UCHAR_CONSTANT("0"); + } else { + bufLen = 0; + pRes = UCHAR_CONSTANT(""); + } + } + } + /* OK, we have a usable match - we now need to malloc pB */ + int iLenBuf; + uchar *pB; + + iLenBuf = pmatch[pTpe->data.field.iSubMatchToUse].rm_eo + - pmatch[pTpe->data.field.iSubMatchToUse].rm_so; + pB = MALLOC((iLenBuf + 1) * sizeof(uchar)); + + if (pB == NULL) { + if (*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + + /* Lets copy the matched substring to the buffer */ + memcpy(pB, pRes + iOffs + pmatch[pTpe->data.field.iSubMatchToUse].rm_so, iLenBuf); + bufLen = iLenBuf; + pB[iLenBuf] = '\0';/* terminate string, did not happen before */ + + if (*pbMustBeFreed == 1) + free(pRes); + pRes = pB; + *pbMustBeFreed = 1; + } + } else { + /* we could not load regular expression support. This is quite unexpected at + * this stage of processing (after all, the config parser found it), but so + * it is. We return an error in that case. -- rgerhards, 2008-03-07 + */ + dbgprintf("could not get regexp object pointer, so regexp can not be evaluated\n"); + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + *pPropLen = sizeof("***REGEXP NOT AVAILABLE***") - 1; + return UCHAR_CONSTANT("***REGEXP NOT AVAILABLE***"); + } + } +#endif /* #ifdef FEATURE_REGEXP */ + } + + if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { + /* we need to obtain a private copy */ + int iFrom, iTo; + uchar *pSb; + iFrom = pTpe->data.field.iFromPos; + iTo = pTpe->data.field.iToPos; + if(bufLen == -1) + bufLen = ustrlen(pRes); + if(pTpe->data.field.options.bFromPosEndRelative) { + iFrom = (bufLen < iFrom) ? 0 : bufLen - iFrom; + iTo = (bufLen < iTo)? 0 : bufLen - iTo; + } else { + /* need to zero-base to and from (they are 1-based!) */ + if(iFrom > 0) + --iFrom; + if(iTo > 0) + --iTo; + } + if(iFrom == 0 && iTo >= bufLen) { + /* in this case, the requested string is a superset of what we already have, + * so there is no need to do any processing. This is a frequent case for size-limited + * fields like TAG in the default forwarding template (so it is a useful optimization + * to check for this condition ;)). -- rgerhards, 2009-07-09 + */ + ; /*DO NOTHING*/ + } else { + if(iTo > bufLen) /* iTo is very large, if no to-position is set in the template! */ + iTo = bufLen; + iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = MALLOC((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + pSb = pRes; + if(iFrom) { + /* skip to the start of the substring (can't do pointer arithmetic + * because the whole string might be smaller!!) + */ + while(*pSb && iFrom) { + --iFrom; + ++pSb; + } + } + /* OK, we are at the begin - now let's copy... */ + bufLen = iLen; + while(*pSb && iLen) { + *pBuf++ = *pSb; + ++pSb; + --iLen; + } + *pBuf = '\0'; + bufLen -= iLen; /* subtract remaining length if the string was smaller! */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + } + } + + /* now check if we need to do our "SP if first char is non-space" hack logic */ + if(*pRes && pTpe->data.field.options.bSPIffNo1stSP) { + /* here, we always destruct the buffer and return a new one */ + uchar cFirst = *pRes; /* save first char */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = (cFirst == ' ') ? UCHAR_CONSTANT("") : UCHAR_CONSTANT(" "); + bufLen = (cFirst == ' ') ? 0 : 1; + *pbMustBeFreed = 0; + } + + if(*pRes) { + /* case conversations (should go after substring, because so we are able to + * work on the smallest possible buffer). + */ + if(pTpe->data.field.eCaseConv != tplCaseConvNo) { + /* we need to obtain a private copy */ + if(bufLen == -1) + bufLen = ustrlen(pRes); + uchar *pBStart; + uchar *pB; + uchar *pSrc; + pBStart = pB = MALLOC((bufLen + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + pSrc = pRes; + while(*pSrc) { + *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? + (uchar)toupper((int)*pSrc) : (uchar)tolower((int)*pSrc); + /* currently only these two exist */ + ++pSrc; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + + /* now do control character dropping/escaping/replacement + * Only one of these can be used. If multiple options are given, the + * result is random (though currently there obviously is an order of + * preferrence, see code below. But this is NOT guaranteed. + * RGerhards, 2006-11-17 + * We must copy the strings if we modify them, because they may either + * point to static memory or may point into the message object, in which + * case we would actually modify the original property (which of course + * is wrong). + * This was found and fixed by varmojefkoj on 2007-09-11 + */ + if(pTpe->data.field.options.bDropCC) { + int iLenBuf = 0; + uchar *pSrc = pRes; + uchar *pDstStart; + uchar *pDst; + uchar bDropped = 0; + + while(*pSrc) { + if(!iscntrl((int) *pSrc++)) + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = MALLOC(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(!iscntrl((int) *pSrc)) + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + bufLen = iLenBuf; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bSpaceCC) { + uchar *pSrc; + uchar *pDstStart; + uchar *pDst; + + if(*pbMustBeFreed == 1) { + /* in this case, we already work on dynamic + * memory, so there is no need to copy it - we can + * modify it in-place without any harm. This is a + * performance optiomization. + */ + for(pDst = pRes; *pDst; pDst++) { + if(iscntrl((int) *pDst)) + *pDst = ' '; + } + } else { + if(bufLen == -1) + bufLen = ustrlen(pRes); + pDst = pDstStart = MALLOC(bufLen + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(iscntrl((int) *pSrc)) + *pDst++ = ' '; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bEscapeCC) { + /* we must first count how many control charactes are + * present, because we need this to compute the new string + * buffer length. While doing so, we also compute the string + * length. + */ + int iNumCC = 0; + int iLenBuf = 0; + uchar *pB; + + for(pB = pRes ; *pB ; ++pB) { + ++iLenBuf; + if(iscntrl((int) *pB)) + ++iNumCC; + } + + if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ + /* OK, let's do the escaping... */ + uchar *pBStart; + uchar szCCEsc[8]; /* buffer for escape sequence */ + int i; + + iLenBuf += iNumCC * 4; + pBStart = pB = MALLOC((iLenBuf + 1) * sizeof(uchar)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + while(*pRes) { + if(iscntrl((int) *pRes)) { + snprintf((char*)szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes); + for(i = 0 ; i < 4 ; ++i) + *pB++ = szCCEsc[i]; + } else { + *pB++ = *pRes; + } + ++pRes; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + bufLen = -1; + *pbMustBeFreed = 1; + } + } + } + + /* Take care of spurious characters to make the property safe + * for a path definition + */ + if(pTpe->data.field.options.bSecPathDrop || pTpe->data.field.options.bSecPathReplace) { + if(pTpe->data.field.options.bSecPathDrop) { + int iLenBuf = 0; + uchar *pSrc = pRes; + uchar *pDstStart; + uchar *pDst; + uchar bDropped = 0; + + while(*pSrc) { + if(*pSrc++ != '/') + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = MALLOC(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc != '/') + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + bufLen = -1; /* TODO: can we do better? */ + *pbMustBeFreed = 1; + } + } else { + uchar *pSrc; + uchar *pDstStart; + uchar *pDst; + + if(*pbMustBeFreed == 1) { + /* here, again, we can modify the string as we already obtained + * a private buffer. As we do not change the size of that buffer, + * in-place modification is possible. This is a performance + * enhancement. + */ + for(pDst = pRes; *pDst; pDst++) { + if(*pDst == '/') + *pDst++ = '_'; + } + } else { + if(bufLen == -1) + bufLen = ustrlen(pRes); + pDst = pDstStart = MALLOC(bufLen + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc == '/') + *pDst++ = '_'; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + /* we must NOT check if it needs to be freed, because we have done + * this in the if above. So if we come to hear, the pSrc string needs + * not to be freed (and we do not need to care about it). + */ + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } + + /* check for "." and ".." (note the parenthesis in the if condition!) */ + if(*pRes == '\0') { + if(*pbMustBeFreed == 1) + free(pRes); + pRes = UCHAR_CONSTANT("_"); + bufLen = 1; + *pbMustBeFreed = 0; + } else if((*pRes == '.') && (*(pRes + 1) == '\0' || (*(pRes + 1) == '.' && *(pRes + 2) == '\0'))) { + uchar *pTmp = pRes; + + if(*(pRes + 1) == '\0') + pRes = UCHAR_CONSTANT("_"); + else + pRes = UCHAR_CONSTANT("_.");; + if(*pbMustBeFreed == 1) + free(pTmp); + *pbMustBeFreed = 0; + } + } + + /* Now drop last LF if present (pls note that this must not be done + * if bEscapeCC was set)! + */ + if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { + int iLn; + uchar *pB; + if(bufLen == -1) + bufLen = ustrlen(pRes); + iLn = bufLen; + if(iLn > 0 && *(pRes + iLn - 1) == '\n') { + /* we have a LF! */ + /* check if we need to obtain a private copy */ + if(*pbMustBeFreed == 0) { + /* ok, original copy, need a private one */ + pB = MALLOC((iLn + 1) * sizeof(uchar)); + if(pB == NULL) { + RET_OUT_OF_MEMORY; + } + memcpy(pB, pRes, iLn - 1); + pRes = pB; + *pbMustBeFreed = 1; + } + *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ + --bufLen; + } + } + + /* finally, we need to check if the property should be formatted in CSV or JSON. + * For CSV we use RFC 4180, and always use double quotes. As of this writing, + * this should be the last action carried out on the property, but in the + * future there may be reasons to change that. -- rgerhards, 2009-04-02 + */ + if(pTpe->data.field.options.bCSV) { + /* we need to obtain a private copy, as we need to at least add the double quotes */ + int iBufLen; + uchar *pBStart; + uchar *pDst; + uchar *pSrc; + if(bufLen == -1) + bufLen = ustrlen(pRes); + iBufLen = bufLen; + /* the malloc may be optimized, we currently use the worst case... */ + pBStart = pDst = MALLOC((2 * iBufLen + 3) * sizeof(uchar)); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + pSrc = pRes; + *pDst++ = '"'; /* starting quote */ + while(*pSrc) { + if(*pSrc == '"') + *pDst++ = '"'; /* need to add double double quote (see RFC4180) */ + *pDst++ = *pSrc++; + } + *pDst++ = '"'; /* ending quote */ + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + bufLen = -1; + *pbMustBeFreed = 1; + } else if(pTpe->data.field.options.bJSON) { + jsonEncode(&pRes, pbMustBeFreed, &bufLen); + } else if(pTpe->data.field.options.bJSONf) { + jsonField(pTpe, &pRes, pbMustBeFreed, &bufLen); + } + + *pPropLen = (bufLen == -1) ? ustrlen(pRes) : bufLen; + + ENDfunc + return(pRes); +} + + +/* The function returns a cee variable suitable for use with RainerScript. + * Note: caller must free the returned string. + * Note that we need to do a lot of conversions between es_str_t and cstr -- this will go away once + * we have moved larger parts of rsyslog to es_str_t. Acceptable for the moment, especially as we intend + * to rewrite the script engine as well! + * rgerhards, 2010-12-03 + */ +es_str_t* +msgGetCEEVarNew(msg_t *pMsg, char *name) +{ + uchar *leaf; + char *val; + es_str_t *estr = NULL; + struct json_object *json, *parent; + + ISOBJ_TYPE_assert(pMsg, msg); + + if(pMsg->json == NULL) { + estr = es_newStr(1); + goto done; + } + leaf = jsonPathGetLeaf((uchar*)name, strlen(name)); + if(jsonPathFindParent(pMsg, (uchar*)name, leaf, &parent, 1) != RS_RET_OK) { + estr = es_newStr(1); + goto done; + } + json = json_object_object_get(parent, (char*)leaf); + val = (char*)json_object_get_string(json); + estr = es_newStrFromCStr(val, strlen(val)); +done: + return estr; +} + + +/* Return an es_str_t for given message property. + */ +es_str_t* +msgGetMsgVarNew(msg_t *pThis, uchar *name) +{ + rs_size_t propLen; + uchar *pszProp = NULL; + propid_t propid; + unsigned short bMustBeFreed = 0; + es_str_t *estr; + + ISOBJ_TYPE_assert(pThis, msg); + + /* always call MsgGetProp() without a template specifier */ + /* TODO: optimize propNameToID() call -- rgerhards, 2009-06-26 */ + propNameStrToID(name, &propid); + pszProp = (uchar*) MsgGetProp(pThis, NULL, propid, NULL, &propLen, &bMustBeFreed, NULL); + + estr = es_newStrFromCStr((char*)pszProp, propLen); + if(bMustBeFreed) + free(pszProp); + + return estr; +} + + +/* This function can be used as a generic way to set properties. + * We have to handle a lot of legacy, so our return value is not always + * 100% correct (called functions do not always provide one, should + * change over time). + * rgerhards, 2008-01-07 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +rsRetVal MsgSetProperty(msg_t *pThis, var_t *pProp) +{ + prop_t *myProp; + prop_t *propRcvFrom = NULL; + prop_t *propRcvFromIP = NULL; + struct json_tokener *tokener; + struct json_object *json; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, msg); + assert(pProp != NULL); + + if(isProp("iProtocolVersion")) { + setProtocolVersion(pThis, pProp->val.num); + } else if(isProp("iSeverity")) { + pThis->iSeverity = pProp->val.num; + } else if(isProp("iFacility")) { + pThis->iFacility = pProp->val.num; + } else if(isProp("msgFlags")) { + pThis->msgFlags = pProp->val.num; + } else if(isProp("offMSG")) { + MsgSetMSGoffs(pThis, pProp->val.num); + } else if(isProp("pszRawMsg")) { + MsgSetRawMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr), cstrLen(pProp->val.pStr)); + } else if(isProp("pszUxTradMsg")) { + /*IGNORE*/; /* this *was* a property, but does no longer exist */ + } else if(isProp("pszTAG")) { + MsgSetTAG(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), cstrLen(pProp->val.pStr)); + } else if(isProp("pszInputName")) { + /* we need to create a property */ + CHKiRet(prop.Construct(&myProp)); + CHKiRet(prop.SetString(myProp, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr))); + CHKiRet(prop.ConstructFinalize(myProp)); + MsgSetInputName(pThis, myProp); + prop.Destruct(&myProp); + } else if(isProp("pszRcvFromIP")) { + MsgSetRcvFromIPStr(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr), &propRcvFromIP); + prop.Destruct(&propRcvFromIP); + } else if(isProp("pszRcvFrom")) { + MsgSetRcvFromStr(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr), &propRcvFrom); + prop.Destruct(&propRcvFrom); + } else if(isProp("pszHOSTNAME")) { + MsgSetHOSTNAME(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr)); + } else if(isProp("pCSStrucData")) { + MsgSetStructuredData(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSAPPNAME")) { + MsgSetAPPNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSPROCID")) { + MsgSetPROCID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSMSGID")) { + MsgSetMSGID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("ttGenTime")) { + pThis->ttGenTime = pProp->val.num; + } else if(isProp("tRcvdAt")) { + memcpy(&pThis->tRcvdAt, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } else if(isProp("tTIMESTAMP")) { + memcpy(&pThis->tTIMESTAMP, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } else if(isProp("pszRuleset")) { + MsgSetRulesetByName(pThis, pProp->val.pStr); + } else if(isProp("pszMSG")) { + dbgprintf("no longer supported property pszMSG silently ignored\n"); + } else if(isProp("json")) { + tokener = json_tokener_new(); + json = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pProp->val.pStr), + cstrLen(pProp->val.pStr)); + json_tokener_free(tokener); + msgAddJSON(pThis, (uchar*)"!", json); + } else { + dbgprintf("unknown supported property '%s' silently ignored\n", + rsCStrGetSzStrNoNULL(pProp->pcsName)); + } + +finalize_it: + RETiRet; +} +#undef isProp + + +/* get the severity - this is an entry point that + * satisfies the base object class getSeverity semantics. + * rgerhards, 2008-01-14 + */ +rsRetVal +MsgGetSeverity(msg_t *pMsg, int *piSeverity) +{ + *piSeverity = pMsg->iSeverity; + return RS_RET_OK; +} + + +static uchar * +jsonPathGetLeaf(uchar *name, int lenName) +{ + int i; + for(i = lenName ; name[i] != '!' && i >= 0 ; --i) + /* just skip */; + if(name[i] == '!') + ++i; + return name + i; +} + + +static rsRetVal +jsonPathFindNext(struct json_object *root, uchar **name, uchar *leaf, + struct json_object **found, int bCreate) +{ + uchar namebuf[1024]; + struct json_object *json; + size_t i; + uchar *p = *name; + DEFiRet; + + if(*p == '!') + ++p; + for(i = 0 ; *p && *p != '!' && p != leaf && i < sizeof(namebuf)-1 ; ++i, ++p) + namebuf[i] = *p; + if(i > 0) { + namebuf[i] = '\0'; + dbgprintf("AAAA: next JSONPath elt: '%s'\n", namebuf); + json = json_object_object_get(root, (char*)namebuf); + } else + json = root; + if(json == NULL) { + if(!bCreate) { + ABORT_FINALIZE(RS_RET_JNAME_INVALID); + } else { + json = json_object_new_object(); + json_object_object_add(root, (char*)namebuf, json); + } + } + + *name = p; + *found = json; +finalize_it: + RETiRet; +} + +static rsRetVal +jsonPathFindParent(msg_t *pM, uchar *name, uchar *leaf, struct json_object **parent, int bCreate) +{ + DEFiRet; + *parent = pM->json; + while(name < leaf-1) { + jsonPathFindNext(*parent, &name, leaf, parent, bCreate); + } + RETiRet; +} + +static rsRetVal +jsonMerge(struct json_object *existing, struct json_object *json) +{ + /* TODO: check & handle duplicate names */ + DEFiRet; + struct json_object_iter it; + + json_object_object_foreachC(json, it) { +DBGPRINTF("AAAA jsonMerge adds '%s'\n", it.key); + json_object_object_add(existing, it.key, + json_object_get(it.val)); + } + /* note: json-c does ref counting. We added all descandants refcounts + * in the loop above. So when we now free(_put) the root object, only + * root gets freed(). + */ + json_object_put(json); + RETiRet; +} + +/* find a JSON structure element (field or container doesn't matter). */ +rsRetVal +jsonFind(msg_t *pM, es_str_t *propName, struct json_object **jsonres) +{ + uchar *name = NULL; + uchar *leaf; + struct json_object *parent; + struct json_object *field; + DEFiRet; + + if(pM->json == NULL) { + field = NULL; + goto finalize_it; + } + + if(!es_strbufcmp(propName, (uchar*)"!", 1)) { + field = pM->json; + } else { + name = (uchar*)es_str2cstr(propName, NULL); + leaf = jsonPathGetLeaf(name, ustrlen(name)); + CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 0)); + field = json_object_object_get(parent, (char*)leaf); + } + *jsonres = field; + +finalize_it: + free(name); + RETiRet; +} + +rsRetVal +msgAddJSON(msg_t *pM, uchar *name, struct json_object *json) +{ + /* TODO: error checks! This is a quick&dirty PoC! */ + struct json_object *parent, *leafnode; + uchar *leaf; + DEFiRet; + + MsgLock(pM); + if(name[0] == '!' && name[1] == '\0') { + if(pM->json == NULL) + pM->json = json; + else + CHKiRet(jsonMerge(pM->json, json)); + } else { + if(pM->json == NULL) { + /* now we need a root obj */ + pM->json = json_object_new_object(); + } + leaf = jsonPathGetLeaf(name, ustrlen(name)); + CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1)); + leafnode = json_object_object_get(parent, (char*)leaf); + if(leafnode == NULL) { + json_object_object_add(parent, (char*)leaf, json); + } else { + if(json_object_get_type(json) == json_type_object) { + CHKiRet(jsonMerge(pM->json, json)); + } else { +//dbgprintf("AAAA: leafnode already exists, type is %d, update with %d\n", (int)json_object_get_type(leafnode), (int)json_object_get_type(json)); + /* TODO: improve the code below, however, the current + * state is not really bad */ + if(json_object_get_type(leafnode) == json_type_object) { + DBGPRINTF("msgAddJSON: trying to update a container " + "node with a leaf, name is '%s' - " + "forbidden\n", name); + json_object_put(json); + ABORT_FINALIZE(RS_RET_INVLD_SETOP); + } + /* json-c code indicates we can simply replace a + * json type. Unfortunaltely, this is not documented + * as part of the interface spec. We still use it, + * because it speeds up processing. If it does not work + * at some point, use + * json_object_object_del(parent, (char*)leaf); + * before adding. rgerhards, 2012-09-17 + */ + json_object_object_add(parent, (char*)leaf, json); + } + } + } + +finalize_it: + MsgUnlock(pM); + RETiRet; +} + +rsRetVal +msgDelJSON(msg_t *pM, uchar *name) +{ + struct json_object *parent, *leafnode; + uchar *leaf; + DEFiRet; + +dbgprintf("AAAA: unset variable '%s'\n", name); + MsgLock(pM); + if(name[0] == '!' && name[1] == '\0') { + /* strange, but I think we should permit this. After all, + * we trust rsyslog.conf to be written by the admin. + */ + DBGPRINTF("unsetting JSON root object\n"); + json_object_put(pM->json); + pM->json = NULL; + } else { + if(pM->json == NULL) { + /* now we need a root obj */ + pM->json = json_object_new_object(); + } + leaf = jsonPathGetLeaf(name, ustrlen(name)); + CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1)); + leafnode = json_object_object_get(parent, (char*)leaf); +DBGPRINTF("AAAA: unset found JSON value path '%s', " "leaf '%s', leafnode %p\n", name, leaf, leafnode); + if(leafnode == NULL) { + DBGPRINTF("unset JSON: could not find '%s'\n", name); + ABORT_FINALIZE(RS_RET_JNAME_NOTFOUND); + } else { + DBGPRINTF("deleting JSON value path '%s', " + "leaf '%s', type %d\n", + name, leaf, json_object_get_type(leafnode)); + json_object_object_del(parent, (char*)leaf); + } + } + +finalize_it: + MsgUnlock(pM); + RETiRet; +} + +static struct json_object * +jsonDeepCopy(struct json_object *src) +{ + struct json_object *dst = NULL, *json; + struct json_object_iter it; + int arrayLen, i; + + if(src == NULL) goto done; + + switch(json_object_get_type(src)) { + case json_type_boolean: + dst = json_object_new_boolean(json_object_get_boolean(src)); + break; + case json_type_double: + dst = json_object_new_double(json_object_get_double(src)); + break; + case json_type_int: + dst = json_object_new_int(json_object_get_int(src)); + break; + case json_type_string: + dst = json_object_new_string(json_object_get_string(src)); + break; + case json_type_object: + dst = json_object_new_object(); + json_object_object_foreachC(src, it) { + json = jsonDeepCopy(it.val); + json_object_object_add(dst, it.key, json); + } + break; + case json_type_array: + arrayLen = json_object_array_length(src); + dst = json_object_new_array(); + for(i = 0 ; i < arrayLen ; ++i) { + json = json_object_array_get_idx(src, i); + json = jsonDeepCopy(json); + json_object_array_add(dst, json); + } + break; + default:DBGPRINTF("jsonDeepCopy(): error unknown type %d\n", + json_object_get_type(src)); + dst = NULL; + break; + } +done: return dst; +} + + +rsRetVal +msgSetJSONFromVar(msg_t *pMsg, uchar *varname, struct var *v) +{ + struct json_object *json = NULL; + char *cstr; + DEFiRet; + switch(v->datatype) { + case 'S':/* string */ + cstr = es_str2cstr(v->d.estr, NULL); + json = json_object_new_string(cstr); + free(cstr); + break; + case 'N':/* number (integer) */ + json = json_object_new_int((int) v->d.n); + break; + case 'J':/* native JSON */ + json = jsonDeepCopy(v->d.json); + break; + default:DBGPRINTF("msgSetJSONFromVar: unsupported datatype %c\n", + v->datatype); + ABORT_FINALIZE(RS_RET_ERR); + } + msgAddJSON(pMsg, varname+1, json); +finalize_it: + RETiRet; +} + +/* dummy */ +rsRetVal msgQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the message class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-04 + */ +BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize); + /* some more inits */ +# if HAVE_MALLOC_TRIM + INIT_ATOMIC_HELPER_MUT(mutTrimCtr); +# endif +ENDObjClassInit(msg) +/* vim:set ai: + */ diff --git a/runtime/msg.h b/runtime/msg.h new file mode 100644 index 00000000..6faf066a --- /dev/null +++ b/runtime/msg.h @@ -0,0 +1,247 @@ +/* msg.h + * Header file for all msg-related functions. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "template.h" /* this is a quirk, but these two are too interdependant... */ + +#ifndef MSG_H_INCLUDED +#define MSG_H_INCLUDED 1 + +#include <pthread.h> +#include <libestr.h> +#include <json/json.h> +#include "obj.h" +#include "syslogd-types.h" +#include "template.h" +#include "atomic.h" + +/* rgerhards 2004-11-08: The following structure represents a + * syslog message. + * + * Important Note: + * The message object is used for multiple purposes (once it + * has been created). Once created, it actully is a read-only + * object (though we do not specifically express this). In order + * to avoid multiple copies of the same object, we use a + * reference counter. This counter is set to 1 by the constructer + * and increased by 1 with a call to MsgAddRef(). The destructor + * checks the reference count. If it is more than 1, only the counter + * will be decremented. If it is 1, however, the object is actually + * destroyed. To make this work, it is vital that MsgAddRef() is + * called each time a "copy" is stored somewhere. + * + * WARNING: this structure is not calloc()ed, so be careful when + * adding new fields. You need to initialize them in + * msgBaseConstruct(). That function header comment also describes + * why this is the case. + */ +struct msg { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + flowControl_t flowCtlType; /**< type of flow control we can apply, for enqueueing, needs not to be persisted because + once data has entered the queue, this property is no longer needed. */ + pthread_mutex_t mut; + int iRefCount; /* reference counter (0 = unused) */ + sbool bAlreadyFreed; /* aid to help detect a well-hidden bad bug -- TODO: remove when no longer needed */ + sbool bParseSuccess; /* set to reflect state of last executed higher level parser */ + short iSeverity; /* the severity 0..7 */ + short iFacility; /* Facility code 0 .. 23*/ + short offAfterPRI; /* offset, at which raw message WITHOUT PRI part starts in pszRawMsg */ + short offMSG; /* offset at which the MSG part starts in pszRawMsg */ + short iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */ + int msgFlags; /* flags associated with this message */ + int iLenRawMsg; /* length of raw message */ + int iLenMSG; /* Length of the MSG part */ + int iLenTAG; /* Length of the TAG part */ + int iLenHOSTNAME; /* Length of HOSTNAME */ + int iLenPROGNAME; /* Length of PROGNAME (-1 = not yet set) */ + uchar *pszRawMsg; /* message as it was received on the wire. This is important in case we + * need to preserve cryptographic verifiers. */ + uchar *pszHOSTNAME; /* HOSTNAME from syslog message */ + char *pszRcvdAt3164; /* time as RFC3164 formatted string (always 15 charcters) */ + char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 charcters at most) */ + char *pszRcvdAt_MySQL; /* rcvdAt as MySQL formatted string (always 14 charcters) */ + char *pszRcvdAt_PgSQL; /* rcvdAt as PgSQL formatted string (always 21 characters) */ + char *pszTIMESTAMP3164; /* TIMESTAMP as RFC3164 formatted string (always 15 charcters) */ + char *pszTIMESTAMP3339; /* TIMESTAMP as RFC3339 formatted string (32 charcters at most) */ + char *pszTIMESTAMP_MySQL;/* TIMESTAMP as MySQL formatted string (always 14 charcters) */ + char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */ + cstr_t *pCSStrucData; /* STRUCTURED-DATA */ + cstr_t *pCSAPPNAME; /* APP-NAME */ + cstr_t *pCSPROCID; /* PROCID */ + cstr_t *pCSMSGID; /* MSGID */ + prop_t *pInputName; /* input name property */ + prop_t *pRcvFromIP; /* IP of system message was received from */ + union { + prop_t *pRcvFrom;/* name of system message was received from */ + struct sockaddr_storage *pfrominet; /* unresolved name */ + } rcvFrom; + + ruleset_t *pRuleset; /* ruleset to be used for processing this message */ + time_t ttGenTime; /* time msg object was generated, same as tRcvdAt, but a Unix timestamp. + While this field looks redundant, it is required because a Unix timestamp + is used at later processing stages (namely in the output arena). Thanks to + the subleties of how time is defined, there is no reliable way to reconstruct + the Unix timestamp from the syslogTime fields (in practice, we may be close + enough to reliable, but I prefer to leave the subtle things to the OS, where + it obviously is solved in way or another...). */ + struct syslogTime tRcvdAt;/* time the message entered this program */ + struct syslogTime tTIMESTAMP;/* (parsed) value of the timestamp */ + struct json_object *json; + /* some fixed-size buffers to save malloc()/free() for frequently used fields (from the default templates) */ + uchar szRawMsg[CONF_RAWMSG_BUFSIZE]; /* most messages are small, and these are stored here (without malloc/free!) */ + uchar szHOSTNAME[CONF_HOSTNAME_BUFSIZE]; + union { + uchar *ptr; /* pointer to progname value */ + uchar szBuf[CONF_PROGNAME_BUFSIZE]; + } PROGNAME; + union { + uchar *pszTAG; /* pointer to tag value */ + uchar szBuf[CONF_TAG_BUFSIZE]; + } TAG; + char pszTimestamp3164[CONST_LEN_TIMESTAMP_3164 + 1]; + char pszTimestamp3339[CONST_LEN_TIMESTAMP_3339 + 1]; + char pszTIMESTAMP_SecFrac[7]; /* Note: a pointer is 64 bits/8 char, so this is actually fewer than a pointer! */ + char pszRcvdAt_SecFrac[7]; /* same as above. Both are fractional seconds for their respective timestamp */ + char pszTIMESTAMP_Unix[12]; /* almost as small as a pointer! */ + char pszRcvdAt_Unix[12]; + uchar *pszUUID; /* The message's UUID */ +}; + + +/* message flags (msgFlags), not an enum for historical reasons + */ +#define NOFLAG 0x000 /* no flag is set (to be used when a flag must be specified and none is required) */ +#define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */ +/* 0x002 not used because it was previously a known value - rgerhards, 2008-10-09 */ +#define IGNDATE 0x004 /* ignore, if given, date in message and use date of reception as msg date */ +#define MARK 0x008 /* this message is a mark */ +#define NEEDS_PARSING 0x010 /* raw message, must be parsed before processing can be done */ +#define PARSE_HOSTNAME 0x020 /* parse the hostname during message parsing */ +#define NEEDS_DNSRESOL 0x040 /* fromhost address is unresolved and must be locked up via DNS reverse lookup first */ +#define NEEDS_ACLCHK_U 0x080 /* check UDP ACLs after DNS resolution has been done in main queue consumer */ +#define NO_PRI_IN_RAW 0x100 /* rawmsg does not include a PRI (Solaris!), but PRI is already set correctly in the msg object */ + + +/* function prototypes + */ +PROTOTYPEObjClassInit(msg); +rsRetVal msgConstruct(msg_t **ppThis); +rsRetVal msgConstructWithTime(msg_t **ppThis, struct syslogTime *stTime, time_t ttGenTime); +rsRetVal msgConstructForDeserializer(msg_t **ppThis); +rsRetVal msgConstructFinalizer(msg_t *pThis); +rsRetVal msgDestruct(msg_t **ppM); +msg_t* MsgDup(msg_t* pOld); +msg_t *MsgAddRef(msg_t *pM); +void setProtocolVersion(msg_t *pM, int iNewVersion); +void MsgSetInputName(msg_t *pMsg, prop_t*); +rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME); +rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID); +rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID); +void MsgSetParseSuccess(msg_t *pMsg, int bSuccess); +void MsgSetTAG(msg_t *pMsg, uchar* pszBuf, size_t lenBuf); +void MsgSetRuleset(msg_t *pMsg, ruleset_t*); +rsRetVal MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl); +rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData); +rsRetVal msgSetFromSockinfo(msg_t *pThis, struct sockaddr_storage *sa); +void MsgSetRcvFrom(msg_t *pMsg, prop_t*); +void MsgSetRcvFromStr(msg_t *pMsg, uchar* pszRcvFrom, int, prop_t **); +rsRetVal MsgSetRcvFromIP(msg_t *pMsg, prop_t*); +rsRetVal MsgSetRcvFromIPStr(msg_t *pThis, uchar *psz, int len, prop_t **ppProp); +void MsgSetHOSTNAME(msg_t *pMsg, uchar* pszHOSTNAME, int lenHOSTNAME); +rsRetVal MsgSetAfterPRIOffs(msg_t *pMsg, short offs); +void MsgSetMSGoffs(msg_t *pMsg, short offs); +void MsgSetRawMsgWOSize(msg_t *pMsg, char* pszRawMsg); +void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg, size_t lenMsg); +rsRetVal MsgReplaceMSG(msg_t *pThis, uchar* pszMSG, int lenMSG); +uchar *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + propid_t propid, es_str_t *propName, + rs_size_t *pPropLen, unsigned short *pbMustBeFreed, struct syslogTime *ttNow); +rsRetVal msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar); +es_str_t* msgGetMsgVarNew(msg_t *pThis, uchar *name); +uchar *getRcvFrom(msg_t *pM); +void getTAG(msg_t *pM, uchar **ppBuf, int *piLen); +char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt); +char *getPRI(msg_t *pMsg); +void getRawMsg(msg_t *pM, uchar **pBuf, int *piLen); +rsRetVal msgGetCEEVar(msg_t *pThis, cstr_t *propName, var_t **ppVar); +es_str_t* msgGetCEEVarNew(msg_t *pMsg, char *name); +rsRetVal msgAddJSON(msg_t *pM, uchar *name, struct json_object *json); +rsRetVal getCEEPropVal(msg_t *pM, es_str_t *propName, uchar **pRes, rs_size_t *buflen, unsigned short *pbMustBeFreed); +rsRetVal MsgGetSeverity(msg_t *pThis, int *piSeverity); +rsRetVal MsgDeserialize(msg_t *pMsg, strm_t *pStrm); + +/* TODO: remove these five (so far used in action.c) */ +uchar *getMSG(msg_t *pM); +char *getHOSTNAME(msg_t *pM); +char *getPROCID(msg_t *pM, sbool bLockMutex); +char *getAPPNAME(msg_t *pM, sbool bLockMutex); +void setMSGLen(msg_t *pM, int lenMsg); +int getMSGLen(msg_t *pM); + +char *getHOSTNAME(msg_t *pM); +int getHOSTNAMELen(msg_t *pM); +uchar *getProgramName(msg_t *pM, sbool bLockMutex); +uchar *getRcvFrom(msg_t *pM); +rsRetVal propNameToID(cstr_t *pCSPropName, propid_t *pPropID); +uchar *propIDToName(propid_t propID); +rsRetVal msgGetCEEPropJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson); +rsRetVal msgSetJSONFromVar(msg_t *pMsg, uchar *varname, struct var *var); +rsRetVal msgDelJSON(msg_t *pMsg, uchar *varname); +rsRetVal jsonFind(msg_t *pM, es_str_t *propName, struct json_object **jsonres); + +static inline rsRetVal +msgUnsetJSON(msg_t *pMsg, uchar *varname) { + return msgDelJSON(pMsg, varname+1); +} + + +/* ------------------------------ some inline functions ------------------------------ */ + +/* set raw message size. This is needed in some cases where a trunctation is necessary + * but the raw message must not be newly set. The most important (and currently only) + * use case is if we remove trailing LF or NUL characters. Note that the size can NOT + * be extended, only shrunk! + * rgerhards, 2009-08-26 + */ +static inline void +MsgSetRawMsgSize(msg_t *pMsg, size_t newLen) +{ + assert(newLen <= (size_t) pMsg->iLenRawMsg); + pMsg->iLenRawMsg = newLen; +} + + +/* get the ruleset that is associated with the ruleset. + * May be NULL. -- rgerhards, 2009-10-27 + */ +static inline ruleset_t* +MsgGetRuleset(msg_t *pMsg) +{ + return pMsg->pRuleset; +} + + +#endif /* #ifndef MSG_H_INCLUDED */ +/* vim:set ai: + */ diff --git a/runtime/net.c b/runtime/net.c new file mode 100644 index 00000000..b291213e --- /dev/null +++ b/runtime/net.c @@ -0,0 +1,1545 @@ +/* net.c + * Implementation of network-related stuff. + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" networking code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Starting 2007-12-24, I have begun to shuffle more network-related code + * from syslogd.c to over here. I am not sure if it will stay here in the + * long term, but it is good to have it out of syslogd.c. Maybe this here is + * an interim location ;) + * + * Copyright 2007-2011 Rainer Gerhards and Adiscon GmbH. + * + * rgerhards, 2008-04-16: I changed this code to LGPL today. I carefully analyzed + * that it does not borrow code from the original sysklogd and that I have + * permission to do so from all other contributors. My analysis found that all + * code from sysklogd has been superseeded by our own functionality, so it + * is OK to move this file to LGPL. Some variable sysklogd variable names + * remain, but even this will change as the net object evolves. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <ctype.h> +#include <netdb.h> +#include <fnmatch.h> +#include <fcntl.h> +#include <unistd.h> +#if HAVE_GETIFADDRS +#include <ifaddrs.h> +#else +#include "compat/ifaddrs.h" +#endif /* HAVE_GETIFADDRS */ +#include <sys/types.h> +#include <arpa/inet.h> + +#include "syslogd-types.h" +#include "module-template.h" +#include "parse.h" +#include "srUtils.h" +#include "obj.h" +#include "errmsg.h" +#include "net.h" +#include "dnscache.h" +#include "prop.h" + +#ifdef OS_SOLARIS +# define s6_addr32 _S6_un._S6_u32 + typedef unsigned int u_int32_t; +#endif + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) + +/* support for defining allowed TCP and UDP senders. We use the same + * structure to implement this (a linked list), but we define two different + * list roots, one for UDP and one for TCP. + * rgerhards, 2005-09-26 + */ +/* All of the five below are read-only after startup */ +struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */ +struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */ +static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */ +static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */ +#ifdef USE_GSSAPI +struct AllowedSenders *pAllowedSenders_GSS = NULL; +static struct AllowedSenders *pLastAllowedSenders_GSS = NULL; +#endif + +int ACLAddHostnameOnFail = 0; /* add hostname to acl when DNS resolving has failed */ +int ACLDontResolve = 0; /* add hostname to acl instead of resolving it to IP(s) */ + + +/* ------------------------------ begin permitted peers code ------------------------------ */ + + +/* sets the correct allow root pointer based on provided type + * rgerhards, 2008-12-01 + */ +static inline rsRetVal +setAllowRoot(struct AllowedSenders **ppAllowRoot, uchar *pszType) +{ + DEFiRet; + + if(!strcmp((char*)pszType, "UDP")) + *ppAllowRoot = pAllowedSenders_UDP; + else if(!strcmp((char*)pszType, "TCP")) + *ppAllowRoot = pAllowedSenders_TCP; +#ifdef USE_GSSAPI + else if(!strcmp((char*)pszType, "GSS")) + *ppAllowRoot = pAllowedSenders_GSS; +#endif + else { + dbgprintf("program error: invalid allowed sender ID '%s', denying...\n", pszType); + ABORT_FINALIZE(RS_RET_CODE_ERR); /* everything is invalid for an invalid type */ + } + +finalize_it: + RETiRet; +} +/* re-initializes (sets to NULL) the correct allow root pointer + * rgerhards, 2009-01-12 + */ +static inline rsRetVal +reinitAllowRoot(uchar *pszType) +{ + DEFiRet; + + if(!strcmp((char*)pszType, "UDP")) + pAllowedSenders_UDP = NULL; + else if(!strcmp((char*)pszType, "TCP")) + pAllowedSenders_TCP = NULL; +#ifdef USE_GSSAPI + else if(!strcmp((char*)pszType, "GSS")) + pAllowedSenders_GSS = NULL; +#endif + else { + dbgprintf("program error: invalid allowed sender ID '%s', denying...\n", pszType); + ABORT_FINALIZE(RS_RET_CODE_ERR); /* everything is invalid for an invalid type */ + } + +finalize_it: + RETiRet; +} + + +/* add a wildcard entry to this permitted peer. Entries are always + * added at the tail of the list. pszStr and lenStr identify the wildcard + * entry to be added. Note that the string is NOT \0 terminated, so + * we must rely on lenStr for when it is finished. + * rgerhards, 2008-05-27 + */ +static rsRetVal +AddPermittedPeerWildcard(permittedPeers_t *pPeer, uchar* pszStr, size_t lenStr) +{ + permittedPeerWildcard_t *pNew = NULL; + size_t iSrc; + size_t iDst; + DEFiRet; + + assert(pPeer != NULL); + assert(pszStr != NULL); + + CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); + + if(lenStr == 0) { /* empty domain components are permitted */ + pNew->wildcardType = PEER_WILDCARD_EMPTY_COMPONENT; + FINALIZE; + } else { + /* alloc memory for the domain component. We may waste a byte or + * two, but that's ok. + */ + CHKmalloc(pNew->pszDomainPart = MALLOC(lenStr +1 )); + } + + if(pszStr[0] == '*') { + pNew->wildcardType = PEER_WILDCARD_AT_START; + iSrc = 1; /* skip '*' */ + } else { + iSrc = 0; + } + + for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) { + pNew->pszDomainPart[iDst] = pszStr[iSrc]; + } + + if(iSrc < lenStr) { + if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') { + if(pNew->wildcardType == PEER_WILDCARD_AT_START) { + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } else { + pNew->wildcardType = PEER_WILDCARD_AT_END; + } + } else { + /* we have an invalid wildcard, something follows the asterisk! */ + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } + } + + if(lenStr == 1 && pNew->wildcardType == PEER_WILDCARD_AT_START) { + pNew->wildcardType = PEER_WILDCARD_MATCH_ALL; + } + + /* if we reach this point, we had a valid wildcard. We now need to + * properly terminate the domain component string. + */ + pNew->pszDomainPart[iDst] = '\0'; + pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) { + if(pNew->pszDomainPart != NULL) + free(pNew->pszDomainPart); + free(pNew); + } + } else { + /* enqueue the element */ + if(pPeer->pWildcardRoot == NULL) { + pPeer->pWildcardRoot = pNew; + } else { + pPeer->pWildcardLast->pNext = pNew; + } + pPeer->pWildcardLast = pNew; + } + + RETiRet; +} + + +/* Destruct a permitted peer's wildcard list -- rgerhards, 2008-05-27 */ +static rsRetVal +DestructPermittedPeerWildcards(permittedPeers_t *pPeer) +{ + permittedPeerWildcard_t *pCurr; + permittedPeerWildcard_t *pDel; + DEFiRet; + + assert(pPeer != NULL); + + for(pCurr = pPeer->pWildcardRoot ; pCurr != NULL ; /*EMPTY*/) { + pDel = pCurr; + pCurr = pCurr->pNext; + free(pDel->pszDomainPart); + free(pDel); + } + + pPeer->pWildcardRoot = NULL; + pPeer->pWildcardLast = NULL; + + RETiRet; +} + + +/* add a permitted peer. PermittedPeers is an interim solution until we can provide + * access control via enhanced RainerScript methods. + * Note: the provided string is handed over to this function, caller must + * no longer access it. -- rgerhards, 2008-05-19 + */ +static rsRetVal +AddPermittedPeer(permittedPeers_t **ppRootPeer, uchar* pszID) +{ + permittedPeers_t *pNew = NULL; + DEFiRet; + + assert(ppRootPeer != NULL); + assert(pszID != NULL); + + CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); /* we use calloc() for consistency with "real" objects */ + CHKmalloc(pNew->pszID = (uchar*)strdup((char*)pszID)); + + if(*ppRootPeer != NULL) { + pNew->pNext = *ppRootPeer; + } + *ppRootPeer = pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + free(pNew); + } + RETiRet; +} + + +/* Destruct a permitted peers list -- rgerhards, 2008-05-19 */ +static rsRetVal +DestructPermittedPeers(permittedPeers_t **ppRootPeer) +{ + permittedPeers_t *pCurr; + permittedPeers_t *pDel; + DEFiRet; + + assert(ppRootPeer != NULL); + + for(pCurr = *ppRootPeer ; pCurr != NULL ; /*EMPTY*/) { + pDel = pCurr; + pCurr = pCurr->pNext; + DestructPermittedPeerWildcards(pDel); + free(pDel->pszID); + free(pDel); + } + + *ppRootPeer = NULL; + + RETiRet; +} + + +/* Compile a wildcard. The function first checks if there is a wildcard + * present and compiles it only if so ;) It sets the etryType status + * accordingly. + * rgerhards, 2008-05-27 + */ +static rsRetVal +PermittedPeerWildcardCompile(permittedPeers_t *pPeer) +{ + uchar *pC; + uchar *pStart; + DEFiRet; + + assert(pPeer != NULL); + assert(pPeer->pszID != NULL); + + /* first check if we have a wildcard */ + for(pC = pPeer->pszID ; *pC != '\0' && *pC != '*' ; ++pC) + /*EMPTY, just skip*/; + + if(*pC == '\0') { + /* no wildcard found, we are mostly done */ + pPeer->etryType = PERM_PEER_TYPE_PLAIN; + FINALIZE; + } + + /* if we reach this point, the string contains wildcards. So let's + * compile the structure. To do so, we must parse from dot to dot + * and create a wildcard entry for each domain component we find. + * We must also flag problems if we have an asterisk in the middle + * of the text (it is supported at the start or end only). + */ + pPeer->etryType = PERM_PEER_TYPE_WILDCARD; + pC = pPeer->pszID; + while(*pC != '\0') { + pStart = pC; + /* find end of domain component */ + for( ; *pC != '\0' && *pC != '.' ; ++pC) + /*EMPTY, just skip*/; + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, pC - pStart)); + /* now check if we have an empty component at end of string */ + if(*pC == '.' && *(pC + 1) == '\0') { + /* pStart is a dummy, it is not used if length is 0 */ + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, 0)); + } + if(*pC != '\0') + ++pC; + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error compiling wildcard expression '%s'", + pPeer->pszID); + } + RETiRet; +} + + +/* Do a (potential) wildcard match. The function first checks if the wildcard + * has already been compiled and, if not, compiles it. If the peer entry in + * question does NOT contain a wildcard, a simple strcmp() is done. + * *pbIsMatching is set to 0 if there is no match and something else otherwise. + * rgerhards, 2008-05-27 */ +static rsRetVal +PermittedPeerWildcardMatch(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching) +{ + permittedPeerWildcard_t *pWildcard; + uchar *pC; + uchar *pStart; /* start of current domain component */ + size_t iWildcard, iName; /* work indexes for backward comparisons */ + DEFiRet; + + assert(pPeer != NULL); + assert(pszNameToMatch != NULL); + assert(pbIsMatching != NULL); + + if(pPeer->etryType == PERM_PEER_TYPE_UNDECIDED) { + PermittedPeerWildcardCompile(pPeer); + } + + if(pPeer->etryType == PERM_PEER_TYPE_PLAIN) { + *pbIsMatching = !strcmp((char*)pPeer->pszID, (char*)pszNameToMatch); + FINALIZE; + } + + /* we have a wildcard, so we need to extract the domain components and + * check then against the provided wildcards. + */ + pWildcard = pPeer->pWildcardRoot; + pC = pszNameToMatch; + while(*pC != '\0') { + if(pWildcard == NULL) { + /* we have more domain components than we have wildcards --> no match */ + *pbIsMatching = 0; + FINALIZE; + } + pStart = pC; + while(*pC != '\0' && *pC != '.') { + ++pC; + } + + /* got the component, now do the match */ + switch(pWildcard->wildcardType) { + case PEER_WILDCARD_NONE: + if( pWildcard->lenDomainPart != (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_AT_START: + /* we need to do the backwards-matching manually */ + if(pWildcard->lenDomainPart > (size_t) (pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart; + iWildcard = 0; + while(iWildcard < pWildcard->lenDomainPart) { + if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) { + *pbIsMatching = 0; + FINALIZE; + } + ++iName; + ++iWildcard; + } + break; + case PEER_WILDCARD_AT_END: + if( pWildcard->lenDomainPart > (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pWildcard->lenDomainPart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_MATCH_ALL: + /* everything is OK, just continue */ + break; + case PEER_WILDCARD_EMPTY_COMPONENT: + if(pC - pStart > 0) { + /* if it is not empty, it is no match... */ + *pbIsMatching = 0; + FINALIZE; + } + break; + } + pWildcard = pWildcard->pNext; /* we processed this entry */ + + /* skip '.' if we had it and so prepare for next iteration */ + if(*pC == '.') + ++pC; + } + + if(pWildcard != NULL) { + /* we have more domain components than in the name to be + * checked. So this is no match. + */ + *pbIsMatching = 0; + FINALIZE; + } + + *pbIsMatching = 1; /* finally... it matches ;) */ + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end permitted peers code ------------------------------ */ + + +/* Code for handling allowed/disallowed senders + */ +static inline void MaskIP6 (struct in6_addr *addr, uint8_t bits) { + register uint8_t i; + + assert (addr != NULL); + assert (bits <= 128); + + i = bits/32; + if (bits%32) + addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); + for (; i < (sizeof addr->s6_addr32)/4; i++) + addr->s6_addr32[i] = 0; +} + +static inline void MaskIP4 (struct in_addr *addr, uint8_t bits) { + + assert (addr != NULL); + assert (bits <=32 ); + + addr->s_addr &= htonl(0xffffffff << (32 - bits)); +} + +#define SIN(sa) ((struct sockaddr_in *)(void*)(sa)) +#define SIN6(sa) ((struct sockaddr_in6 *)(void*)(sa)) + + +/* This is a cancel-safe getnameinfo() version, because we learned + * (via drd/valgrind) that getnameinfo() seems to have some issues + * when being cancelled, at least if the module was dlloaded. + * rgerhards, 2008-09-30 + */ +static inline int +mygetnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ + int iCancelStateSave; + int i; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + pthread_setcancelstate(iCancelStateSave, NULL); + return i; +} + + +/* This function adds an allowed sender entry to the ACL linked list. + * In any case, a single entry is added. If an error occurs, the + * function does its error reporting itself. All validity checks + * must already have been done by the caller. + * This is a helper to AddAllowedSender(). + * rgerhards, 2007-07-17 + */ +static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, + struct NetAddr *iAllow, uint8_t iSignificantBits) +{ + struct AllowedSenders *pEntry = NULL; + + assert(ppRoot != NULL); + assert(ppLast != NULL); + assert(iAllow != NULL); + + if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) { + return RS_RET_OUT_OF_MEMORY; /* no options left :( */ + } + + memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr)); + pEntry->pNext = NULL; + pEntry->SignificantBits = iSignificantBits; + + /* enqueue */ + if(*ppRoot == NULL) { + *ppRoot = pEntry; + } else { + (*ppLast)->pNext = pEntry; + } + *ppLast = pEntry; + + return RS_RET_OK; +} + +/* function to clear the allowed sender structure in cases where + * it must be freed (occurs most often when HUPed). + * rgerhards, 2008-12-02: revamped this code when we fixed the interface + * definition. Now an iterative algorithm is used. + */ +static void +clearAllowedSenders(uchar *pszType) +{ + struct AllowedSenders *pPrev; + struct AllowedSenders *pCurr = NULL; + + if(setAllowRoot(&pCurr, pszType) != RS_RET_OK) + return; /* if something went wrong, so let's leave */ + + while(pCurr != NULL) { + pPrev = pCurr; + pCurr = pCurr->pNext; + /* now delete the entry we are right now processing */ + if(F_ISSET(pPrev->allowedSender.flags, ADDR_NAME)) + free(pPrev->allowedSender.addr.HostWildcard); + else + free(pPrev->allowedSender.addr.NetAddr); + free(pPrev); + } + + /* indicate root pointer is de-init (was forgotten previously, resulting in + * all kinds of interesting things) -- rgerhards, 2009-01-12 + */ + reinitAllowRoot(pszType); +} + + +/* function to add an allowed sender to the allowed sender list. The + * root of the list is caller-provided, so it can be used for all + * supported lists. The caller must provide a pointer to the root, + * as it eventually needs to be updated. Also, a pointer to the + * pointer to the last element must be provided (to speed up adding + * list elements). + * rgerhards, 2005-09-26 + * If a hostname is given there are possible multiple entries + * added (all addresses from that host). + */ +static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, + struct NetAddr *iAllow, uint8_t iSignificantBits) +{ + DEFiRet; + + assert(ppRoot != NULL); + assert(ppLast != NULL); + assert(iAllow != NULL); + + if (!F_ISSET(iAllow->flags, ADDR_NAME)) { + if(iSignificantBits == 0) + /* we handle this seperatly just to provide a better + * error message. + */ + errmsg.LogError(0, NO_ERRCODE, "You can not specify 0 bits of the netmask, this would " + "match ALL systems. If you really intend to do that, " + "remove all $AllowedSender directives."); + + switch (iAllow->addr.NetAddr->sa_family) { + case AF_INET: + if((iSignificantBits < 1) || (iSignificantBits > 32)) { + errmsg.LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv4 address - adjusted to 32", + (int)iSignificantBits); + iSignificantBits = 32; + } + + MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits); + break; + case AF_INET6: + if((iSignificantBits < 1) || (iSignificantBits > 128)) { + errmsg.LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv6 address - adjusted to 128", + iSignificantBits); + iSignificantBits = 128; + } + + MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits); + break; + default: + /* rgerhards, 2007-07-16: We have an internal program error in this + * case. However, there is not much we can do against it right now. Of + * course, we could abort, but that would probably cause more harm + * than good. So we continue to run. We simply do not add this line - the + * worst thing that happens is that one host will not be allowed to + * log. + */ + errmsg.LogError(0, NO_ERRCODE, "Internal error caused AllowedSender to be ignored, AF = %d", + iAllow->addr.NetAddr->sa_family); + ABORT_FINALIZE(RS_RET_ERR); + } + /* OK, entry constructed, now lets add it to the ACL list */ + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + } else { + /* we need to process a hostname ACL */ + if(glbl.GetDisableDNS()) { + errmsg.LogError(0, NO_ERRCODE, "Ignoring hostname based ACLs because DNS is disabled."); + ABORT_FINALIZE(RS_RET_OK); + } + + if (!strchr (iAllow->addr.HostWildcard, '*') && + !strchr (iAllow->addr.HostWildcard, '?') && + ACLDontResolve == 0) { + /* single host - in this case, we pull its IP addresses from DNS + * and add IP-based ACLs. + */ + struct addrinfo hints, *res, *restmp; + struct NetAddr allowIP; + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; +# ifdef AI_ADDRCONFIG /* seems not to be present on all systems */ + hints.ai_flags = AI_ADDRCONFIG; +# endif + + if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) { + errmsg.LogError(0, NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); + + if (ACLAddHostnameOnFail) { + errmsg.LogError(0, NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + FINALIZE; + } else { + errmsg.LogError(0, NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard); + ABORT_FINALIZE(RS_RET_NOENTRY); + } + } + + for (restmp = res ; res != NULL ; res = res->ai_next) { + switch (res->ai_family) { + case AF_INET: /* add IPv4 */ + iSignificantBits = 32; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = MALLOC(res->ai_addrlen)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) + != RS_RET_OK) + FINALIZE; + break; + case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */ + if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) { + /* extract & add IPv4 */ + + iSignificantBits = 32; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = MALLOC(sizeof(struct sockaddr_in))) + == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + SIN(allowIP.addr.NetAddr)->sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in); +#endif + SIN(allowIP.addr.NetAddr)->sin_port = 0; + memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr), + &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]), + sizeof (in_addr_t)); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, + iSignificantBits)) + != RS_RET_OK) + FINALIZE; + } else { + /* finally add IPv6 */ + + iSignificantBits = 128; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = MALLOC(res->ai_addrlen)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, + iSignificantBits)) + != RS_RET_OK) + FINALIZE; + } + break; + } + } + freeaddrinfo (restmp); + } else { + /* wildcards in hostname - we need to add a text-based ACL. + * For this, we already have everything ready and just need + * to pass it along... + */ + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + } + } + +finalize_it: + RETiRet; +} + + +/* Print an allowed sender list. The caller must tell us which one. + * iListToPrint = 1 means UDP, 2 means TCP + * rgerhards, 2005-09-27 + */ +void PrintAllowedSenders(int iListToPrint) +{ + struct AllowedSenders *pSender; + uchar szIP[64]; + + assert((iListToPrint == 1) || (iListToPrint == 2) +#ifdef USE_GSSAPI + || (iListToPrint == 3) +#endif + ); + + dbgprintf("Allowed %s Senders:\n", + (iListToPrint == 1) ? "UDP" : +#ifdef USE_GSSAPI + (iListToPrint == 3) ? "GSS" : +#endif + "TCP"); + + pSender = (iListToPrint == 1) ? pAllowedSenders_UDP : +#ifdef USE_GSSAPI + (iListToPrint == 3) ? pAllowedSenders_GSS : +#endif + pAllowedSenders_TCP; + if(pSender == NULL) { + dbgprintf("\tNo restrictions set.\n"); + } else { + while(pSender != NULL) { + if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME)) + dbgprintf ("\t%s\n", pSender->allowedSender.addr.HostWildcard); + else { + if(mygetnameinfo (pSender->allowedSender.addr.NetAddr, + SALEN(pSender->allowedSender.addr.NetAddr), + (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) { + dbgprintf ("\t%s/%u\n", szIP, pSender->SignificantBits); + } else { + /* getnameinfo() failed - but as this is only a + * debug function, we simply spit out an error and do + * not care much about it. + */ + dbgprintf("\tERROR in getnameinfo() - something may be wrong " + "- ignored for now\n"); + } + } + pSender = pSender->pNext; + } + } +} + + +/* parse an allowed sender config line and add the allowed senders + * (if the line is correct). + * rgerhards, 2005-09-27 + */ +rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine) +{ + struct AllowedSenders **ppRoot; + struct AllowedSenders **ppLast; + rsParsObj *pPars; + rsRetVal iRet; + struct NetAddr *uIP = NULL; + int iBits; + + assert(pName != NULL); + assert(ppRestOfConfLine != NULL); + assert(*ppRestOfConfLine != NULL); + + if(!strcasecmp(pName, "udp")) { + ppRoot = &pAllowedSenders_UDP; + ppLast = &pLastAllowedSenders_UDP; + } else if(!strcasecmp(pName, "tcp")) { + ppRoot = &pAllowedSenders_TCP; + ppLast = &pLastAllowedSenders_TCP; +#ifdef USE_GSSAPI + } else if(!strcasecmp(pName, "gss")) { + ppRoot = &pAllowedSenders_GSS; + ppLast = &pLastAllowedSenders_GSS; +#endif + } else { + errmsg.LogError(0, RS_RET_ERR, "Invalid protocol '%s' in allowed sender " + "list, line ignored", pName); + return RS_RET_ERR; + } + + /* OK, we now know the protocol and have valid list pointers. + * So let's process the entries. We are using the parse class + * for this. + */ + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) { + errmsg.LogError(0, iRet, "Error %d constructing parser object - ignoring allowed sender list", iRet); + return(iRet); + } + + while(!parsIsAtEndOfParseString(pPars)) { + if(parsPeekAtCharAtParsPtr(pPars) == '#') + break; /* a comment-sign stops processing of line */ + /* now parse a single IP address */ + if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) { + errmsg.LogError(0, iRet, "Error %d parsing address in allowed sender" + "list - ignoring.", iRet); + rsParsDestruct(pPars); + return(iRet); + } + if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) != RS_RET_OK) { + if(iRet == RS_RET_NOENTRY) { + errmsg.LogError(0, iRet, "Error %d adding allowed sender entry " + "- ignoring.", iRet); + } else { + errmsg.LogError(0, iRet, "Error %d adding allowed sender entry " + "- terminating, nothing more will be added.", iRet); + rsParsDestruct(pPars); + return(iRet); + } + } + free (uIP); /* copy stored in AllowedSenders list */ + } + + /* cleanup */ + *ppRestOfConfLine += parsGetCurrentPosition(pPars); + return rsParsDestruct(pPars); +} + + + +/* compares a host to an allowed sender list entry. Handles all subleties + * including IPv4/v6 as well as domain name wildcards. + * This is a helper to isAllowedSender. As it is only called once, it is + * declared inline. + * Returns 0 if they do not match, 1 if they match and 2 if a DNS name would have been required. + * contributed 2007-07-16 by mildew@gmail.com + */ +static inline int +MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS) +{ + assert(pAllow != NULL); + assert(pFrom != NULL); + + if(F_ISSET(pAllow->flags, ADDR_NAME)) { + if(bChkDNS == 0) + return 2; + dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard); + +# if !defined(FNM_CASEFOLD) + /* TODO: I don't know if that then works, seen on HP UX, what I have not in lab... ;) */ + return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE) == 0); +# else + return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0); +# endif + } else {/* We need to compare an IP address */ + switch (pFrom->sa_family) { + case AF_INET: + if (AF_INET == pAllow->addr.NetAddr->sa_family) + return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) ) + == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr); + else + return 0; + break; + case AF_INET6: + switch (pAllow->addr.NetAddr->sa_family) { + case AF_INET6: { + struct in6_addr ip, net; + register uint8_t i; + + memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr)); + memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr)); + + i = bits/32; + if (bits % 32) + ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); + for (; i < (sizeof ip.s6_addr32)/4; i++) + ip.s6_addr32[i] = 0; + + return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 && + (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ? + SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1)); + } + case AF_INET: { + struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr; + struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr; + + if ((ip6->s6_addr32[3] & (u_int32_t) htonl((0xffffffff << (32 - bits)))) == net->s_addr && +#if BYTE_ORDER == LITTLE_ENDIAN + (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) && +#else + (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) && +#endif + (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0)) + return 1; + else + return 0; + } + default: + /* Unsupported AF */ + return 0; + } + default: + /* Unsupported AF */ + return 0; + } + } +} + + +/* check if a sender is allowed. The root of the the allowed sender. + * list must be proveded by the caller. As such, this function can be + * used to check both UDP and TCP allowed sender lists. + * returns 1, if the sender is allowed, 0 if not and 2 if we could not + * obtain a result because we would need a dns name, which we don't have + * (2 was added rgerhards, 2009-11-16). + * rgerhards, 2005-09-26 + */ +static int isAllowedSender2(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS) +{ + struct AllowedSenders *pAllow; + struct AllowedSenders *pAllowRoot = NULL; + int bNeededDNS = 0; /* partial check because we could not resolve DNS? */ + int ret; + + assert(pFrom != NULL); + + if(setAllowRoot(&pAllowRoot, pszType) != RS_RET_OK) + return 0; /* if something went wrong, we deny access - that's the better choice... */ + + if(pAllowRoot == NULL) + return 1; /* checking disabled, everything is valid! */ + + /* now we loop through the list of allowed senders. As soon as + * we find a match, we return back (indicating allowed). We loop + * until we are out of allowed senders. If so, we fall through the + * loop and the function's terminal return statement will indicate + * that the sender is disallowed. + */ + for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) { + ret = MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost, bChkDNS); + if(ret == 1) + return 1; + else if(ret == 2) + bNeededDNS = 2; + } + return bNeededDNS; +} + + +/* legacy API, not to be used any longer */ +static int +isAllowedSender(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost) { + return isAllowedSender2(pszType, pFrom, pszFromHost, 1); +} + + +/* The following #ifdef sequence is a small compatibility + * layer. It tries to work around the different availality + * levels of SO_BSDCOMPAT on linuxes... + * I borrowed this code from + * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html + * It still needs to be a bit better adapted to rsyslog. + * rgerhards 2005-09-19 + */ +#include <sys/utsname.h> +static int +should_use_so_bsdcompat(void) +{ +#ifndef OS_BSD + static int init_done = 0; + static int so_bsdcompat_is_obsolete = 0; + + if (!init_done) { + struct utsname myutsname; + unsigned int version, patchlevel; + + init_done = 1; + if (uname(&myutsname) < 0) { + char errStr[1024]; + dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr))); + return 1; + } + /* Format is <version>.<patchlevel>.<sublevel><extraversion> + where the first three are unsigned integers and the last + is an arbitrary string. We only care about the first two. */ + if (sscanf(myutsname.release, "%u.%u", &version, &patchlevel) != 2) { + dbgprintf("uname: unexpected release '%s'\r\n", + myutsname.release); + return 1; + } + /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5 + kernels. It is a no-op in 2.4 but not in 2.2 kernels. */ + if (version > 2 || (version == 2 && patchlevel >= 5)) + so_bsdcompat_is_obsolete = 1; + } + return !so_bsdcompat_is_obsolete; +#else /* #ifndef OS_BSD */ + return 1; +#endif /* #ifndef OS_BSD */ +} +#ifndef SO_BSDCOMPAT +/* this shall prevent compiler errors due to undfined name */ +#define SO_BSDCOMPAT 0 +#endif + + +/* print out which socket we are listening on. This is only + * a debug aid. rgerhards, 2007-07-02 + */ +void debugListenInfo(int fd, char *type) +{ + char *szFamily; + int port; + struct sockaddr sa; + struct sockaddr_in *ipv4; + struct sockaddr_in6 *ipv6; + socklen_t saLen = sizeof(sa); + + if(getsockname(fd, &sa, &saLen) == 0) { + switch(sa.sa_family) { + case PF_INET: + szFamily = "IPv4"; + ipv4 = (struct sockaddr_in*)(void*) &sa; + port = ntohs(ipv4->sin_port); + break; + case PF_INET6: + szFamily = "IPv6"; + ipv6 = (struct sockaddr_in6*)(void*) &sa; + port = ntohs(ipv6->sin6_port); + break; + default: + szFamily = "other"; + port = -1; + break; + } + dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n", + type, fd, szFamily, port); + return; + } + + /* we can not obtain peer info. We are just providing + * debug info, so this is no reason to break the program + * or do any serious error reporting. + */ + dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd); +} + + +/* Return a printable representation of a host addresses. If + * a parameter is NULL, it is not set. rgerhards, 2013-01-22 + */ +rsRetVal +cvthname(struct sockaddr_storage *f, prop_t **localName, prop_t **fqdn, prop_t **ip) +{ + DEFiRet; + assert(f != NULL); + iRet = dnscacheLookup(f, NULL, fqdn, localName, ip); + RETiRet; +} + + +/* get the name of the local host. A pointer to a character pointer is passed + * in, which on exit points to the local hostname. This buffer is dynamically + * allocated and must be free()ed by the caller. If the functions returns an + * error, the pointer is NULL. This function is based on GNU/Hurd's localhostname + * function. + * rgerhards, 20080-04-10 + */ +static rsRetVal +getLocalHostname(uchar **ppName) +{ + DEFiRet; + uchar *buf = NULL; + size_t buf_len = 0; + + assert(ppName != NULL); + + do { + if(buf == NULL) { + buf_len = 128; /* Initial guess */ + CHKmalloc(buf = MALLOC(buf_len)); + } else { + buf_len += buf_len; + CHKmalloc(buf = realloc (buf, buf_len)); + } + } while((gethostname((char*)buf, buf_len) == 0 && !memchr (buf, '\0', buf_len)) || errno == ENAMETOOLONG); + + *ppName = buf; + buf = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(buf != NULL) + free(buf); + } + RETiRet; +} + + +/* closes the UDP listen sockets (if they exist) and frees + * all dynamically assigned memory. + */ +void closeUDPListenSockets(int *pSockArr) +{ + register int i; + + assert(pSockArr != NULL); + if(pSockArr != NULL) { + for (i = 0; i < *pSockArr; i++) + close(pSockArr[i+1]); + free(pSockArr); + } +} + + +/* creates the UDP listen sockets + * hostname and/or pszPort may be NULL, but not both! + * bIsServer indicates if a server socket should be created + * 1 - server, 0 - client + */ +int *create_udp_socket(uchar *hostname, uchar *pszPort, int bIsServer) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks, on = 1; + int sockflags; + + assert(!((pszPort == NULL) && (hostname == NULL))); + memset(&hints, 0, sizeof(hints)); + if(bIsServer) + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; + else + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res); + if(error) { + errmsg.LogError(0, NO_ERRCODE, "%s", gai_strerror(error)); + errmsg.LogError(0, NO_ERRCODE, "UDP message reception disabled due to error logged in last message.\n"); + return NULL; + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + socks = MALLOC((maxs+1) * sizeof(int)); + if (socks == NULL) { + errmsg.LogError(0, NO_ERRCODE, "couldn't allocate memory for UDP sockets, suspending UDP message reception"); + freeaddrinfo(res); + return NULL; + } + + *socks = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r != NULL ; r = r->ai_next) { + *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (*s < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + errmsg.LogError(errno, NO_ERRCODE, "create_udp_socket(), socket"); + /* it is debateble if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +# ifdef IPV6_V6ONLY + if (r->ai_family == AF_INET6) { + int ion = 1; + if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&ion, sizeof (ion)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "setsockopt"); + close(*s); + *s = -1; + continue; + } + } +# endif + + /* if we have an error, we "just" suspend that socket. Eventually + * other sockets will work. At the end of this function, we check + * if we managed to open at least one socket. If not, we'll write + * a "inet suspended" message and declare failure. Else we use + * what we could obtain. + * rgerhards, 2007-06-22 + */ + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)) < 0 ) { + errmsg.LogError(errno, NO_ERRCODE, "setsockopt(REUSEADDR)"); + close(*s); + *s = -1; + continue; + } + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#if !defined(OS_BSD) && !defined(__hpux) + if (should_use_so_bsdcompat()) { + if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "setsockopt(BSDCOMPAT)"); + close(*s); + *s = -1; + continue; + } + } +#endif + /* We must not block on the network socket, in case a packet + * gets lost between select and recv, otherwise the process + * will stall until the timeout, and other processes trying to + * log will also stall. + * Patch vom Colin Phipps <cph@cph.demon.co.uk> to the original + * sysklogd source. Applied to rsyslogd on 2005-10-19. + */ + if ((sockflags = fcntl(*s, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(*s, F_SETFL, sockflags); + } + if (sockflags == -1) { + errmsg.LogError(errno, NO_ERRCODE, "fcntl(O_NONBLOCK)"); + close(*s); + *s = -1; + continue; + } + + if(bIsServer) { + /* rgerhards, 2007-06-22: if we run on a kernel that does not support + * the IPV6_V6ONLY socket option, we need to use a work-around. On such + * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4 + * socket can not listen on the same port as an IPv6 socket. The only + * workaround is to ignore the "socket in use" error. This is what we + * do if we have to. + */ + if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) + # ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) + # endif + ) { + errmsg.LogError(errno, NO_ERRCODE, "bind"); + close(*s); + *s = -1; + continue; + } + } + + (*socks)++; + s++; + } + + if(res != NULL) + freeaddrinfo(res); + + if(Debug && *socks != maxs) + dbgprintf("We could initialize %d UDP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", *socks, maxs); + + if(*socks == 0) { + errmsg.LogError(0, NO_ERRCODE, "No UDP listen socket could successfully be initialized, " + "message reception via UDP disabled.\n"); + /* we do NOT need to free any sockets, because there were none... */ + free(socks); + return(NULL); + } + + return(socks); +} + + +/* check if two provided socket addresses point to the same host. Note that the + * length of the sockets must be provided as third parameter. This is necessary to + * compare non IPv4/v6 hosts, in which case we do a simple memory compare of the + * address structure (in that case, the same host may not reliably be detected). + * Note that we need to do the comparison not on the full structure, because it contains things + * like the port, which we do not need to look at when thinking about hostnames. So we look + * at the relevant fields, what means a somewhat more complicated processing. + * Also note that we use a non-standard calling interface, as this is much more natural and + * it looks extremely unlikely that we get an exception of any kind here. What we + * return is mimiced after memcmp(), and as such useful for building binary trees + * (the order relation may be a bit arbritrary, but at least it is consistent). + * rgerhards, 2009-09-03 + */ +static int CmpHost(struct sockaddr_storage *s1, struct sockaddr_storage* s2, size_t socklen) +{ + int ret; + + if(((struct sockaddr*) s1)->sa_family != ((struct sockaddr*) s2)->sa_family) { + ret = memcmp(s1, s2, socklen); + goto finalize_it; + } + + if(((struct sockaddr*) s1)->sa_family == AF_INET) { + if(((struct sockaddr_in *) s1)->sin_addr.s_addr == ((struct sockaddr_in*)s2)->sin_addr.s_addr) { + ret = 0; + } else if(((struct sockaddr_in *) s1)->sin_addr.s_addr < ((struct sockaddr_in*)s2)->sin_addr.s_addr) { + ret = -1; + } else { + ret = 1; + } + } else if(((struct sockaddr*) s1)->sa_family == AF_INET6) { + /* IPv6 addresses are always 16 octets long */ + ret = memcmp(((struct sockaddr_in6 *)s1)->sin6_addr.s6_addr, ((struct sockaddr_in6*)s2)->sin6_addr.s6_addr, 16); + } else { + ret = memcmp(s1, s2, socklen); + } + +finalize_it: + return ret; +} + + + +/* check if restrictions (ALCs) exists. The goal of this function is to disable the + * somewhat time-consuming ACL checks if no restrictions are defined (the usual case). + * This also permits to gain some speedup by using firewall-based ACLs instead of + * rsyslog ACLs (the recommended method. + * rgerhards, 2009-11-16 + */ +static rsRetVal +HasRestrictions(uchar *pszType, int *bHasRestrictions) { + struct AllowedSenders *pAllowRoot = NULL; + DEFiRet; + + CHKiRet(setAllowRoot(&pAllowRoot, pszType)); + + *bHasRestrictions = (pAllowRoot == NULL) ? 0 : 1; + +finalize_it: + if(iRet != RS_RET_OK) { + *bHasRestrictions = 1; /* in this case it is better to check individually */ + DBGPRINTF("Error %d trying to obtain ACL restriction state of '%s'\n", iRet, pszType); + } + RETiRet; +} + + +/* return the IP address (IPv4/6) for the provided interface. Returns + * RS_RET_NOT_FOUND if interface can not be found in interface list. + * The family must be correct (AF_INET vs. AF_INET6, AF_UNSPEC means + * either of *these two*). + * The function re-queries the interface list (at least in theory). + * However, it caches entries in order to avoid too-frequent requery. + * rgerhards, 2012-03-06 + */ +static rsRetVal +getIFIPAddr(uchar *szif, int family, uchar *pszbuf, int lenBuf) +{ + struct ifaddrs * ifaddrs = NULL; + struct ifaddrs * ifa; + void * pAddr; + DEFiRet; + + if(getifaddrs(&ifaddrs) != 0) { + ABORT_FINALIZE(RS_RET_ERR); + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if(strcmp(ifa->ifa_name, (char*)szif)) + continue; + if( (family == AF_INET6 || family == AF_UNSPEC) + && ifa->ifa_addr->sa_family == AF_INET6) { + pAddr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + inet_ntop(AF_INET6, pAddr, (char*)pszbuf, lenBuf); + break; + } else if(/* (family == AF_INET || family == AF_UNSPEC) + &&*/ ifa->ifa_addr->sa_family == AF_INET) { + pAddr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; + inet_ntop(AF_INET, pAddr, (char*)pszbuf, lenBuf); + break; + } + } + + if(ifaddrs != NULL) + freeifaddrs(ifaddrs); + + if(ifa == NULL) + iRet = RS_RET_NOT_FOUND; + +finalize_it: + RETiRet; + +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(net) +CODESTARTobjQueryInterface(net) + if(pIf->ifVersion != netCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->cvthname = cvthname; + /* things to go away after proper modularization */ + pIf->addAllowedSenderLine = addAllowedSenderLine; + pIf->PrintAllowedSenders = PrintAllowedSenders; + pIf->clearAllowedSenders = clearAllowedSenders; + pIf->debugListenInfo = debugListenInfo; + pIf->create_udp_socket = create_udp_socket; + pIf->closeUDPListenSockets = closeUDPListenSockets; + pIf->isAllowedSender = isAllowedSender; + pIf->isAllowedSender2 = isAllowedSender2; + pIf->should_use_so_bsdcompat = should_use_so_bsdcompat; + pIf->getLocalHostname = getLocalHostname; + pIf->AddPermittedPeer = AddPermittedPeer; + pIf->DestructPermittedPeers = DestructPermittedPeers; + pIf->PermittedPeerWildcardMatch = PermittedPeerWildcardMatch; + pIf->CmpHost = CmpHost; + pIf->HasRestrictions = HasRestrictions; + pIf->GetIFIPAddr = getIFIPAddr; + /* data members */ + pIf->pACLAddHostnameOnFail = &ACLAddHostnameOnFail; + pIf->pACLDontResolve = &ACLDontResolve; +finalize_it: +ENDobjQueryInterface(net) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(net, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(net) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(net) + + +/* Initialize the net class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(net, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(net) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + netClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(netClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/net.h b/runtime/net.h new file mode 100644 index 00000000..b196116b --- /dev/null +++ b/runtime/net.h @@ -0,0 +1,169 @@ +/* Definitions for network-related stuff. + * + * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NET_H +#define INCLUDED_NET_H + +#include <netinet/in.h> +#include <sys/socket.h> /* this is needed on HP UX -- rgerhards, 2008-03-04 */ + +typedef enum _TCPFRAMINGMODE { + TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ + TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ + } TCPFRAMINGMODE; + +#define F_SET(where, flag) (where)|=(flag) +#define F_ISSET(where, flag) ((where)&(flag))==(flag) +#define F_UNSET(where, flag) (where)&=~(flag) + +#define ADDR_NAME 0x01 /* address is hostname wildcard) */ +#define ADDR_PRI6 0x02 /* use IPv6 address prior to IPv4 when resolving */ + +#ifdef OS_BSD +# ifndef _KERNEL +# define s6_addr32 __u6_addr.__u6_addr32 +# endif +#endif + +struct NetAddr { + uint8_t flags; + union { + struct sockaddr *NetAddr; + char *HostWildcard; + } addr; +}; + +#ifndef SO_BSDCOMPAT + /* this shall prevent compiler errors due to undefined name */ +# define SO_BSDCOMPAT 0 +#endif + + +/* IPv6 compatibility layer for older platforms + * We need to handle a few things different if we are running + * on an older platform which does not support all the glory + * of IPv6. We try to limit toll on features and reliability, + * but obviously it is better to run rsyslog on a platform that + * supports everything... + * rgerhards, 2007-06-22 + */ +#ifndef AI_NUMERICSERV +# define AI_NUMERICSERV 0 +#endif + + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +#define SALEN(sa) ((sa)->sa_len) +#else +static inline size_t SALEN(struct sockaddr *sa) { + switch (sa->sa_family) { + case AF_INET: return (sizeof (struct sockaddr_in)); + case AF_INET6: return (sizeof (struct sockaddr_in6)); + default: return 0; + } +} +#endif + +struct AllowedSenders { + struct NetAddr allowedSender; /* ip address allowed */ + uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */ + struct AllowedSenders *pNext; +}; + + +/* this structure is a helper to implement wildcards in permittedPeers_t. It specifies + * the domain component and the matching mode. + * rgerhards, 2008-05-27 + */ +struct permittedPeerWildcard_s { + uchar *pszDomainPart; + size_t lenDomainPart; + enum { + PEER_WILDCARD_NONE = 0, /**< no wildcard in this entry */ + PEER_WILDCARD_AT_START = 1, /**< wildcard at start of entry (*name) */ + PEER_WILDCARD_AT_END = 2, /**< wildcard at end of entry (name*) */ + PEER_WILDCARD_MATCH_ALL = 3, /**< only * wildcard, matches all values */ + PEER_WILDCARD_EMPTY_COMPONENT = 4/**< special case: domain component empty (e.g. "..") */ + } wildcardType; + permittedPeerWildcard_t *pNext; +}; + +/* for fingerprints and hostnames, we need to have a temporary linked list of + * permitted values. Unforutnately, we must also duplicate this in the netstream + * drivers. However, this is the best interim solution (with the least effort). + * A clean implementation requires that we have more capable variables and the + * full-fledged scripting engine available. So we have opted to do the interim + * solution so that our users can begin to enjoy authenticated TLS. The next step + * (hopefully) is to enhance RainerScript. -- rgerhards, 2008-05-19 + */ +struct permittedPeers_s { + uchar *pszID; + enum { + PERM_PEER_TYPE_UNDECIDED = 0, /**< we have not yet decided the type (fine in some auth modes) */ + PERM_PEER_TYPE_PLAIN = 1, /**< just plain text contained */ + PERM_PEER_TYPE_WILDCARD = 2, /**< wildcards are contained, wildcard struture is filled */ + } etryType; + permittedPeers_t *pNext; + permittedPeerWildcard_t *pWildcardRoot; /**< root of the wildcard, NULL if not initialized */ + permittedPeerWildcard_t *pWildcardLast; /**< end of the wildcard list, NULL if not initialized */ +}; + + +/* interfaces */ +BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*cvthname)(struct sockaddr_storage *f, prop_t **localName, prop_t **fqdn, prop_t **ip); + /* things to go away after proper modularization */ + rsRetVal (*addAllowedSenderLine)(char* pName, uchar** ppRestOfConfLine); + void (*PrintAllowedSenders)(int iListToPrint); + void (*clearAllowedSenders)(uchar*); + void (*debugListenInfo)(int fd, char *type); + int *(*create_udp_socket)(uchar *hostname, uchar *LogPort, int bIsServer); + void (*closeUDPListenSockets)(int *finet); + int (*isAllowedSender)(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost); /* deprecated! */ + rsRetVal (*getLocalHostname)(uchar**); + int (*should_use_so_bsdcompat)(void); + /* permitted peer handling should be replaced by something better (see comments above) */ + rsRetVal (*AddPermittedPeer)(permittedPeers_t **ppRootPeer, uchar *pszID); + rsRetVal (*DestructPermittedPeers)(permittedPeers_t **ppRootPeer); + rsRetVal (*PermittedPeerWildcardMatch)(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching); + /* v5 interface additions */ + int (*CmpHost)(struct sockaddr_storage *, struct sockaddr_storage*, size_t); + /* v6 interface additions - 2009-11-16 */ + rsRetVal (*HasRestrictions)(uchar *, int *bHasRestrictions); + int (*isAllowedSender2)(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS); + /* v7 interface additions - 2012-03-06 */ + rsRetVal (*GetIFIPAddr)(uchar *szif, int family, uchar *pszbuf, int lenBuf); + /* data members - these should go away over time... TODO */ + int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */ + int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */ + /* v8 cvthname() signature change -- rgerhards, 2013-01-18 */ +ENDinterface(net) +#define netCURR_IF_VERSION 8 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(net); + +/* the name of our library binary */ +#define LM_NET_FILENAME "lmnet" + +#endif /* #ifndef INCLUDED_NET_H */ diff --git a/runtime/netstrm.c b/runtime/netstrm.c new file mode 100644 index 00000000..c046cf52 --- /dev/null +++ b/runtime/netstrm.c @@ -0,0 +1,381 @@ +/* netstrm.c + * + * This class implements a generic netstrmwork stream class. It supports + * sending and receiving data streams over a netstrmwork. The class abstracts + * the transport, though it is a safe assumption that TCP is being used. + * The class has a number of properties, among which are also ones to + * select privacy settings, eg by enabling TLS and/or GSSAPI. In the + * long run, this class shall provide all stream-oriented netstrmwork + * functionality inside rsyslog. + * + * It is a high-level class, which uses a number of helper objects + * to carry out its work (including, and most importantly, transport + * drivers). + * + * Work on this module begun 2008-04-17 by Rainer Gerhards. This code + * borrows from librelp's tcp.c/.h code. librelp is dual licensed and + * Rainer Gerhards and Adiscon GmbH have agreed to permit using the code + * under the terms of the GNU Lesser General Public License. + * + * Copyright 2007-2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "rsyslog.h" +#include "net.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "netstrms.h" +#include "netstrm.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(netstrms) + + +/* Standard-Constructor */ +BEGINobjConstruct(netstrm) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(netstrm) + + +/* destructor for the netstrm object */ +BEGINobjDestruct(netstrm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(netstrm) +//printf("destruct driver data %p\n", pThis->pDrvrData); + if(pThis->pDrvrData != NULL) + iRet = pThis->Drvr.Destruct(&pThis->pDrvrData); +ENDobjDestruct(netstrm) + + +/* ConstructionFinalizer */ +static rsRetVal +netstrmConstructFinalize(netstrm_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData)); +finalize_it: + RETiRet; +} + +/* abort a connection. This is much like Destruct(), but tries + * to discard any unsent data. -- rgerhards, 2008-03-24 + */ +static rsRetVal +AbortDestruct(netstrm_t **ppThis) +{ + DEFiRet; + assert(ppThis != NULL); + ISOBJ_TYPE_assert((*ppThis), netstrm); + + /* we do NOT exit on error, because that would make things worse */ + (*ppThis)->Drvr.Abort((*ppThis)->pDrvrData); + iRet = netstrmDestruct(ppThis); + + RETiRet; +} + + +/* accept an incoming connection request + * The netstrm instance that had the incoming request must be provided. If + * the connection request succeeds, a new netstrm object is created and + * passed back to the caller. The caller is responsible for destructing it. + * pReq is the nsd_t obj that has the accept request. + * rgerhards, 2008-04-21 + */ +static rsRetVal +AcceptConnReq(netstrm_t *pThis, netstrm_t **ppNew) +{ + nsd_t *pNewNsd = NULL; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, netstrm); + assert(ppNew != NULL); + + /* accept the new connection */ + CHKiRet(pThis->Drvr.AcceptConnReq(pThis->pDrvrData, &pNewNsd)); + /* construct our object so that we can use it... */ + CHKiRet(objUse(netstrms, DONT_LOAD_LIB)); /* use netstrms obj if not already done so */ + CHKiRet(netstrms.CreateStrm(pThis->pNS, ppNew)); + (*ppNew)->pDrvrData = pNewNsd; + +finalize_it: + if(iRet != RS_RET_OK) { + /* the close may be redundant, but that doesn't hurt... */ + if(pNewNsd != NULL) + pThis->Drvr.Destruct(&pNewNsd); + } + + RETiRet; +} + + +/* make the netstrm listen to specified port and IP. + * pLstnIP points to the port to listen to (NULL means "all"), + * iMaxSess has the maximum number of sessions permitted (this ist just a hint). + * pLstnPort must point to a port name or number. NULL is NOT permitted. + * rgerhards, 2008-04-22 + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pNS, netstrms); + assert(fAddLstn != NULL); + assert(pLstnPort != NULL); + + CHKiRet(pNS->Drvr.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax)); + +finalize_it: + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read (or -1 in case of error) on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. If *pLenBuf is -1, an error occured and + * errno holds the exact error cause. + * rgerhards, 2008-03-17 + */ +static rsRetVal +Rcv(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); +//printf("Rcv %p\n", pThis); + iRet = pThis->Drvr.Rcv(pThis->pDrvrData, pBuf, pLenBuf); + RETiRet; +} + +/* here follows a number of methods that shuffle authentication settings down + * to the drivers. Drivers not supporting these settings may return an error + * state. + * -------------------------------------------------------------------------- */ + +/* set the driver mode + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetDrvrMode(netstrm_t *pThis, int iMode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.SetMode(pThis->pDrvrData, iMode); + RETiRet; +} + + +/* set the driver authentication mode -- rgerhards, 2008-05-16 + */ +static rsRetVal +SetDrvrAuthMode(netstrm_t *pThis, uchar *mode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.SetAuthMode(pThis->pDrvrData, mode); + RETiRet; +} + + +/* set the driver's permitted peers -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrPermPeers(netstrm_t *pThis, permittedPeers_t *pPermPeers) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.SetPermPeers(pThis->pDrvrData, pPermPeers); + RETiRet; +} + + +/* End of methods to shuffle autentication settings to the driver. + * -------------------------------------------------------------------------- */ + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.Send(pThis->pDrvrData, pBuf, pLenBuf); + RETiRet; +} + +/* Enable Keep-Alive handling for those drivers that support it. + * rgerhards, 2009-06-02 + */ +static rsRetVal +EnableKeepAlive(netstrm_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.EnableKeepAlive(pThis->pDrvrData); + RETiRet; +} + + + +/* check connection - slim wrapper for NSD driver function */ +static rsRetVal +CheckConnection(netstrm_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrm); + return pThis->Drvr.CheckConnection(pThis->pDrvrData); +} + + +/* get remote hname - slim wrapper for NSD driver function */ +static rsRetVal +GetRemoteHName(netstrm_t *pThis, uchar **ppsz) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.GetRemoteHName(pThis->pDrvrData, ppsz); + RETiRet; +} + + +/* get remote IP - slim wrapper for NSD driver function */ +static rsRetVal +GetRemoteIP(netstrm_t *pThis, prop_t **ip) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.GetRemoteIP(pThis->pDrvrData, ip); + RETiRet; +} + + +/* get remote addr - slim wrapper for NSD driver function */ +static rsRetVal +GetRemAddr(netstrm_t *pThis, struct sockaddr_storage **ppAddr) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.GetRemAddr(pThis->pDrvrData, ppAddr); + RETiRet; +} + + +/* open a connection to a remote host (server). + * rgerhards, 2008-03-19 + */ +static rsRetVal +Connect(netstrm_t *pThis, int family, uchar *port, uchar *host) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + assert(port != NULL); + assert(host != NULL); + iRet = pThis->Drvr.Connect(pThis->pDrvrData, family, port, host); + RETiRet; +} + + +/* Provide access to the underlying OS socket. This is dirty + * and scheduled to be removed. Does not work with all nsd drivers. + * See comment in netstrm interface for details. + * rgerhards, 2008-05-05 + */ +static rsRetVal +GetSock(netstrm_t *pThis, int *pSock) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + assert(pSock != NULL); + iRet = pThis->Drvr.GetSock(pThis->pDrvrData, pSock); + RETiRet; +} + + +/* queryInterface function + */ +BEGINobjQueryInterface(netstrm) +CODESTARTobjQueryInterface(netstrm) + if(pIf->ifVersion != netstrmCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = netstrmConstruct; + pIf->ConstructFinalize = netstrmConstructFinalize; + pIf->Destruct = netstrmDestruct; + pIf->AbortDestruct = AbortDestruct; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->Connect = Connect; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; + pIf->GetRemAddr = GetRemAddr; + pIf->SetDrvrMode = SetDrvrMode; + pIf->SetDrvrAuthMode = SetDrvrAuthMode; + pIf->SetDrvrPermPeers = SetDrvrPermPeers; + pIf->CheckConnection = CheckConnection; + pIf->GetSock = GetSock; + pIf->EnableKeepAlive = EnableKeepAlive; +finalize_it: +ENDobjQueryInterface(netstrm) + + +/* exit our class + */ +BEGINObjClassExit(netstrm, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(netstrm) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrms, DONT_LOAD_LIB); +ENDObjClassExit(netstrm) + + +/* Initialize the netstrm class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(netstrm, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(netstrm) +/* vi:set ai: + */ diff --git a/runtime/netstrm.h b/runtime/netstrm.h new file mode 100644 index 00000000..4ef24229 --- /dev/null +++ b/runtime/netstrm.h @@ -0,0 +1,88 @@ +/* Definitions for the stream-based netstrmworking class. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NETSTRM_H +#define INCLUDED_NETSTRM_H + +#include "netstrms.h" + +/* the netstrm object */ +struct netstrm_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pDrvrData; /**< the driver's data elements (at most other places, this is called pNsd) */ + nsd_if_t Drvr; /**< our stream driver */ + void *pUsr; /**< pointer to user-provided data structure */ + netstrms_t *pNS; /**< pointer to our netstream subsystem object */ +}; + + +/* interface */ +BEGINinterface(netstrm) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(netstrm_t **ppThis); + rsRetVal (*ConstructFinalize)(netstrm_t *pThis); + rsRetVal (*Destruct)(netstrm_t **ppThis); + rsRetVal (*AbortDestruct)(netstrm_t **ppThis); + rsRetVal (*LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax); + rsRetVal (*AcceptConnReq)(netstrm_t *pThis, netstrm_t **ppNew); + rsRetVal (*Rcv)(netstrm_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf); + rsRetVal (*Send)(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf); + rsRetVal (*Connect)(netstrm_t *pThis, int family, unsigned char *port, unsigned char *host); + rsRetVal (*GetRemoteHName)(netstrm_t *pThis, uchar **pszName); + rsRetVal (*GetRemoteIP)(netstrm_t *pThis, prop_t **ip); + rsRetVal (*SetDrvrMode)(netstrm_t *pThis, int iMode); + rsRetVal (*SetDrvrAuthMode)(netstrm_t *pThis, uchar*); + rsRetVal (*SetDrvrPermPeers)(netstrm_t *pThis, permittedPeers_t*); + rsRetVal (*CheckConnection)(netstrm_t *pThis); /* This is a trick mostly for plain tcp syslog */ + /* the GetSock() below is a hack to make imgssapi work. In the long term, + * we should migrate imgssapi to a stream driver, which will relieve us of + * this problem. Please note that nobody else should use GetSock(). Using it + * will also tie the caller to nsd_ptcp, because other drivers may not support + * it at all. Once the imgssapi problem is solved, GetSock should be removed from + * this interface. -- rgerhards, 2008-05-05 + */ + rsRetVal (*GetSock)(netstrm_t *pThis, int *pSock); + rsRetVal (*GetRemAddr)(netstrm_t *pThis, struct sockaddr_storage **ppAddr); + /* getRemAddr() is an aid needed by the legacy ACL system. It exposes the remote + * peer's socket addr structure, so that the legacy matching functions can work on + * it. Note that this ties netstream drivers to things that can be implemented over + * sockets - not really desirable, but not the end of the world... TODO: should be + * reconsidered when a new ACL system is build. -- rgerhards, 2008-12-01 + */ + /* v4 */ + rsRetVal (*EnableKeepAlive)(netstrm_t *pThis); +ENDinterface(netstrm) +#define netstrmCURR_IF_VERSION 6 /* increment whenever you change the interface structure! */ +/* interface version 3 added GetRemAddr() + * interface version 4 added EnableKeepAlive() -- rgerhards, 2009-06-02 + * interface version 5 changed return of CheckConnection from void to rsRetVal -- alorbach, 2012-09-06 + * interface version 6 changed signature of GetRemoteIP() -- rgerhards, 2013-01-21 + * */ + +/* prototypes */ +PROTOTYPEObj(netstrm); + +/* the name of our library binary */ +#define LM_NETSTRM_FILENAME LM_NETSTRMS_FILENAME + +#endif /* #ifndef INCLUDED_NETSTRM_H */ diff --git a/runtime/netstrms.c b/runtime/netstrms.c new file mode 100644 index 00000000..0122064d --- /dev/null +++ b/runtime/netstrms.c @@ -0,0 +1,331 @@ +/* netstrms.c + * + * Work on this module begung 2008-04-23 by Rainer Gerhards. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "nsd.h" +#include "netstrm.h" +#include "nssel.h" +#include "nspoll.h" +#include "netstrms.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers +//DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(netstrm) + + +/* load our low-level driver. This must be done before any + * driver-specific functions (allmost all...) can be carried + * out. Note that the driver's .ifIsLoaded is correctly + * initialized by calloc() and we depend on that. + * WARNING: this code is mostly identical to similar code in + * nssel.c - TODO: abstract it and move it to some common place. + * rgerhards, 2008-04-18 + */ +static rsRetVal +loadDrvr(netstrms_t *pThis) +{ + DEFiRet; + uchar *pBaseDrvrName; + uchar szDrvrName[48]; /* 48 shall be large enough */ + + pBaseDrvrName = pThis->pBaseDrvrName; + if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */ + pBaseDrvrName = glbl.GetDfltNetstrmDrvr(); + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsd_%s", pBaseDrvrName) == sizeof(szDrvrName)) + ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG); + CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName)); + + pThis->Drvr.ifVersion = nsdCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean + * enough. -- rgerhards, 2008-04-18 + */ + CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, szDrvrName, (void*) &pThis->Drvr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pDrvrName != NULL) + free(pThis->pDrvrName); + pThis->pDrvrName = NULL; + } + RETiRet; +} + + +/* Standard-Constructor */ +BEGINobjConstruct(netstrms) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(netstrms) + + +/* destructor for the netstrms object */ +BEGINobjDestruct(netstrms) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(netstrms) + /* and now we must release our driver, if we got one. We use the presence of + * a driver name string as load indicator (because we also need that string + * to release the driver + */ + if(pThis->pDrvrName != NULL) { + obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, pThis->pDrvrName, (void*) &pThis->Drvr); + free(pThis->pDrvrName); + } + if(pThis->pszDrvrAuthMode != NULL) { + free(pThis->pszDrvrAuthMode); + pThis->pszDrvrAuthMode = NULL; + } + if(pThis->pBaseDrvrName != NULL) { + free(pThis->pBaseDrvrName); + pThis->pBaseDrvrName = NULL; + } +ENDobjDestruct(netstrms) + + +/* ConstructionFinalizer */ +static rsRetVal +netstrmsConstructFinalize(netstrms_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + CHKiRet(loadDrvr(pThis)); +finalize_it: + RETiRet; +} + + +/* set the base driver name. If the driver name + * is set to NULL, the previously set name is deleted but + * no name set again (which results in the system default being + * used)-- rgerhards, 2008-05-05 + */ +static rsRetVal +SetDrvrName(netstrms_t *pThis, uchar *pszName) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + if(pThis->pBaseDrvrName != NULL) { + free(pThis->pBaseDrvrName); + pThis->pBaseDrvrName = NULL; + } + + if(pszName != NULL) { + CHKmalloc(pThis->pBaseDrvrName = (uchar*) strdup((char*) pszName)); + } +finalize_it: + RETiRet; +} + + +/* set the driver's permitted peers -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrPermPeers(netstrms_t *pThis, permittedPeers_t *pPermPeers) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + pThis->pPermPeers = pPermPeers; + RETiRet; +} +/* return the driver's permitted peers + * We use non-standard calling conventions because it makes an awful lot + * of sense here. + * rgerhards, 2008-05-19 + */ +static permittedPeers_t* +GetDrvrPermPeers(netstrms_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrms); + return pThis->pPermPeers; +} + + +/* set the driver auth mode -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrAuthMode(netstrms_t *pThis, uchar *mode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + CHKmalloc(pThis->pszDrvrAuthMode = (uchar*)strdup((char*)mode)); +finalize_it: + RETiRet; +} +/* return the driver auth mode + * We use non-standard calling conventions because it makes an awful lot + * of sense here. + * rgerhards, 2008-05-19 + */ +static uchar* +GetDrvrAuthMode(netstrms_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrms); + return pThis->pszDrvrAuthMode; +} + + +/* set the driver mode -- rgerhards, 2008-04-30 */ +static rsRetVal +SetDrvrMode(netstrms_t *pThis, int iMode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + pThis->iDrvrMode = iMode; + RETiRet; +} + + +/* return the driver mode + * We use non-standard calling conventions because it makes an awful lot + * of sense here. + * rgerhards, 2008-04-30 + */ +static int +GetDrvrMode(netstrms_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrms); + return pThis->iDrvrMode; +} + + +/* create an instance of a netstrm object. It is initialized with default + * values. The current driver is used. The caller may set netstrm properties + * and must call ConstructFinalize(). + */ +static rsRetVal +CreateStrm(netstrms_t *pThis, netstrm_t **ppStrm) +{ + netstrm_t *pStrm = NULL; + DEFiRet; + + CHKiRet(objUse(netstrm, DONT_LOAD_LIB)); + CHKiRet(netstrm.Construct(&pStrm)); + /* we copy over our driver structure. We could provide a pointer to + * ourselves, but that costs some performance on each driver invocation. + * As we already have hefty indirection (and thus performance toll), I + * prefer to copy over the function pointers here. -- rgerhards, 2008-04-23 + */ + memcpy(&pStrm->Drvr, &pThis->Drvr, sizeof(pThis->Drvr)); + pStrm->pNS = pThis; + + *ppStrm = pStrm; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrm != NULL) + netstrm.Destruct(&pStrm); + } + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(netstrms) +CODESTARTobjQueryInterface(netstrms) + if(pIf->ifVersion != netstrmsCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = netstrmsConstruct; + pIf->ConstructFinalize = netstrmsConstructFinalize; + pIf->Destruct = netstrmsDestruct; + pIf->CreateStrm = CreateStrm; + pIf->SetDrvrName = SetDrvrName; + pIf->SetDrvrMode = SetDrvrMode; + pIf->GetDrvrMode = GetDrvrMode; + pIf->SetDrvrAuthMode = SetDrvrAuthMode; + pIf->GetDrvrAuthMode = GetDrvrAuthMode; + pIf->SetDrvrPermPeers = SetDrvrPermPeers; + pIf->GetDrvrPermPeers = GetDrvrPermPeers; +finalize_it: +ENDobjQueryInterface(netstrms) + + +/* exit our class */ +BEGINObjClassExit(netstrms, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(netstrms) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(netstrm, DONT_LOAD_LIB); +ENDObjClassExit(netstrms) + + +/* Initialize the netstrms class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(netstrms, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(netstrms) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + nsselClassExit(); + nspollClassExit(); + netstrmsClassExit(); + netstrmClassExit(); /* we use this object, so we must exit it after we are finished */ +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(netstrmClassInit(pModInfo)); + CHKiRet(nsselClassInit(pModInfo)); + CHKiRet(nspollClassInit(pModInfo)); + CHKiRet(netstrmsClassInit(pModInfo)); +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/netstrms.h b/runtime/netstrms.h new file mode 100644 index 00000000..3f686af6 --- /dev/null +++ b/runtime/netstrms.h @@ -0,0 +1,64 @@ +/* Definitions for the stream-based netstrmsworking class. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NETSTRMS_H +#define INCLUDED_NETSTRMS_H + +#include "nsd.h" /* we need our driver interface to be defined */ + +/* the netstrms object */ +struct netstrms_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */ + uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */ + int iDrvrMode; /**< current default driver mode */ + uchar *pszDrvrAuthMode; /**< current driver authentication mode */ + permittedPeers_t *pPermPeers;/**< current driver's permitted peers */ + + nsd_if_t Drvr; /**< our stream driver */ +}; + + +/* interface */ +BEGINinterface(netstrms) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(netstrms_t **ppThis); + rsRetVal (*ConstructFinalize)(netstrms_t *pThis); + rsRetVal (*Destruct)(netstrms_t **ppThis); + rsRetVal (*CreateStrm)(netstrms_t *pThis, netstrm_t **ppStrm); + rsRetVal (*SetDrvrName)(netstrms_t *pThis, uchar *pszName); + rsRetVal (*SetDrvrMode)(netstrms_t *pThis, int iMode); + rsRetVal (*SetDrvrAuthMode)(netstrms_t *pThis, uchar*); + rsRetVal (*SetDrvrPermPeers)(netstrms_t *pThis, permittedPeers_t*); + int (*GetDrvrMode)(netstrms_t *pThis); + uchar* (*GetDrvrAuthMode)(netstrms_t *pThis); + permittedPeers_t* (*GetDrvrPermPeers)(netstrms_t *pThis); +ENDinterface(netstrms) +#define netstrmsCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(netstrms); + +/* the name of our library binary */ +#define LM_NETSTRMS_FILENAME "lmnetstrms" + +#endif /* #ifndef INCLUDED_NETSTRMS_H */ diff --git a/runtime/nsd.h b/runtime/nsd.h new file mode 100644 index 00000000..aa3662a4 --- /dev/null +++ b/runtime/nsd.h @@ -0,0 +1,109 @@ +/* The interface definition for "NetStream Drivers" (nsd). + * + * This is just an abstract driver interface, which needs to be + * implemented by concrete classes. As such, no nsd data type itself + * is defined. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_NSD_H +#define INCLUDED_NSD_H + +#include <sys/socket.h> + +/** + * The following structure is a set of descriptors that need to be processed. + * This set will be the result of the epoll call and be used + * in the actual request processing stage. -- rgerhards, 2011-01-24 + */ +struct nsd_epworkset_s { + int id; + void *pUsr; +}; + +enum nsdsel_waitOp_e { + NSDSEL_RD = 1, + NSDSEL_WR = 2, + NSDSEL_RDWR = 3 +}; /**< the operation we wait for */ + +/* nsd_t is actually obj_t (which is somewhat better than void* but in essence + * much the same). + */ + +/* interface */ +BEGINinterface(nsd) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nsd_t **ppThis); + rsRetVal (*Destruct)(nsd_t **ppThis); + rsRetVal (*Abort)(nsd_t *pThis); + rsRetVal (*Rcv)(nsd_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf); + rsRetVal (*Send)(nsd_t *pThis, uchar *pBuf, ssize_t *pLenBuf); + rsRetVal (*Connect)(nsd_t *pThis, int family, unsigned char *port, unsigned char *host); + rsRetVal (*LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax); + rsRetVal (*AcceptConnReq)(nsd_t *pThis, nsd_t **ppThis); + rsRetVal (*GetRemoteHName)(nsd_t *pThis, uchar **pszName); + rsRetVal (*GetRemoteIP)(nsd_t *pThis, prop_t **ip); + rsRetVal (*SetMode)(nsd_t *pThis, int mode); /* sets a driver specific mode - see driver doc for details */ + rsRetVal (*SetAuthMode)(nsd_t *pThis, uchar*); /* sets a driver specific mode - see driver doc for details */ + rsRetVal (*SetPermPeers)(nsd_t *pThis, permittedPeers_t*); /* sets driver permitted peers for auth needs */ + rsRetVal (*CheckConnection)(nsd_t *pThis); /* This is a trick mostly for plain tcp syslog */ + rsRetVal (*GetSock)(nsd_t *pThis, int *pSock); + rsRetVal (*SetSock)(nsd_t *pThis, int sock); + /* GetSock() and SetSock() return an error if the driver does not use plain + * OS sockets. This interface is primarily meant as an internal aid for + * those drivers that utilize the nsd_ptcp to do some of their work. + */ + rsRetVal (*GetRemAddr)(nsd_t *pThis, struct sockaddr_storage **ppAddr); + /* getRemAddr() is an aid needed by the legacy ACL system. It exposes the remote + * peer's socket addr structure, so that the legacy matching functions can work on + * it. Note that this ties netstream drivers to things that can be implemented over + * sockets - not really desirable, but not the end of the world... TODO: should be + * reconsidered when a new ACL system is build. -- rgerhards, 2008-12-01 + */ + /* v5 */ + rsRetVal (*EnableKeepAlive)(nsd_t *pThis); +ENDinterface(nsd) +#define nsdCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */ +/* interface version 4 added GetRemAddr() + * interface version 5 added EnableKeepAlive() -- rgerhards, 2009-06-02 + * interface version 6 changed return of CheckConnection from void to rsRetVal -- alorbach, 2012-09-06 + * interface version 7 changed signature ofGetRempoteIP() -- rgerhards, 2013-01-21 + */ + +/* interface for the select call */ +BEGINinterface(nsdsel) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nsdsel_t **ppThis); + rsRetVal (*Destruct)(nsdsel_t **ppThis); + rsRetVal (*Add)(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp); + rsRetVal (*Select)(nsdsel_t *pNsdsel, int *piNumReady); + rsRetVal (*IsReady)(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady); +ENDinterface(nsdsel) +#define nsdselCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* interface for the epoll call */ +BEGINinterface(nsdpoll) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nsdpoll_t **ppThis); + rsRetVal (*Destruct)(nsdpoll_t **ppThis); + rsRetVal (*Ctl)(nsdpoll_t *pNsdpoll, nsd_t *pNsd, int id, void *pUsr, int mode, int op); + rsRetVal (*Wait)(nsdpoll_t *pNsdpoll, int timeout, int *numReady, nsd_epworkset_t workset[]); +ENDinterface(nsdpoll) +#define nsdpollCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +#endif /* #ifndef INCLUDED_NSD_H */ diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c new file mode 100644 index 00000000..6ef4feba --- /dev/null +++ b/runtime/nsd_gtls.c @@ -0,0 +1,1750 @@ +/* nsd_gtls.c + * + * An implementation of the nsd interface for GnuTLS. + * + * Copyright (C) 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#if GNUTLS_VERSION_NUMBER <= 0x020b00 +# include <gcrypt.h> +#endif +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> + +#include "rsyslog.h" +#include "syslogd-types.h" +#include "module-template.h" +#include "cfsysline.h" +#include "obj.h" +#include "stringbuf.h" +#include "errmsg.h" +#include "net.h" +#include "datetime.h" +#include "nsd_ptcp.h" +#include "nsdsel_gtls.h" +#include "nsd_gtls.h" + +/* things to move to some better place/functionality - TODO */ +#define CRLFILE "crl.pem" + + +#if GNUTLS_VERSION_NUMBER <= 0x020b00 +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif +MODULE_TYPE_LIB +MODULE_TYPE_KEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(datetime) +DEFobjCurrIf(nsd_ptcp) + +static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - already done */ + +static pthread_mutex_t mutGtlsStrerror; /**< a mutex protecting the potentially non-reentrant gtlStrerror() function */ + +/* a macro to check GnuTLS calls against unexpected errors */ +#define CHKgnutls(x) \ + if((gnuRet = (x)) != 0) { \ + uchar *pErr = gtlsStrerror(gnuRet); \ + dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); \ + free(pErr); \ + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \ + } + + +/* ------------------------------ GnuTLS specifics ------------------------------ */ +static gnutls_certificate_credentials xcred; + +#ifdef DEBUG +#if 0 /* uncomment, if needed some time again -- DEV Debug only */ +/* This defines a log function to be provided to GnuTLS. It hopefully + * helps us track down hard to find problems. + * rgerhards, 2008-06-20 + */ +static void logFunction(int level, const char *msg) +{ + dbgprintf("GnuTLS log msg, level %d: %s\n", level, msg); +} +#endif +#endif /* #ifdef DEBUG */ + + +/* read in the whole content of a file. The caller is responsible for + * freeing the buffer. To prevent DOS, this function can NOT read + * files larger than 1MB (which still is *very* large). + * rgerhards, 2008-05-26 + */ +static rsRetVal +readFile(uchar *pszFile, gnutls_datum_t *pBuf) +{ + int fd; + struct stat stat_st; + DEFiRet; + + assert(pszFile != NULL); + assert(pBuf != NULL); + + pBuf->data = NULL; + + if((fd = open((char*)pszFile, O_RDONLY)) == -1) { + errmsg.LogError(0, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + + } + + if(fstat(fd, &stat_st) == -1) { + errmsg.LogError(0, RS_RET_FILE_NO_STAT, "can not stat file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_FILE_NO_STAT); + } + + /* 1MB limit */ + if(stat_st.st_size > 1024 * 1024) { + errmsg.LogError(0, RS_RET_FILE_TOO_LARGE, "file '%s' too large, max 1MB", pszFile); + ABORT_FINALIZE(RS_RET_FILE_TOO_LARGE); + } + + CHKmalloc(pBuf->data = MALLOC(stat_st.st_size)); + pBuf->size = stat_st.st_size; + if(read(fd, pBuf->data, stat_st.st_size) != stat_st.st_size) { + errmsg.LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + close(fd); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pBuf->data != NULL) { + free(pBuf->data); + pBuf->data = NULL; + pBuf->size = 0; + } + } + RETiRet; +} + + +/* Load the certificate and the private key into our own store. We need to do + * this in the client case, to support fingerprint authentication. In that case, + * we may be presented no matching root certificate, but we must provide ours. + * The only way to do that is via the cert callback interface, but for it we + * need to load certificates into our private store. + * rgerhards, 2008-05-26 + */ +static rsRetVal +gtlsLoadOurCertKey(nsd_gtls_t *pThis) +{ + DEFiRet; + int gnuRet; + gnutls_datum_t data = { NULL, 0 }; + uchar *keyFile; + uchar *certFile; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + certFile = glbl.GetDfltNetstrmDrvrCertFile(); + keyFile = glbl.GetDfltNetstrmDrvrKeyFile(); + + if(certFile == NULL || keyFile == NULL) { + /* in this case, we can not set our certificate. If we are + * a client and the server is running in "anon" auth mode, this + * may be well acceptable. In other cases, we will see some + * more error messages down the road. -- rgerhards, 2008-07-02 + */ + dbgprintf("our certificate is not set, file name values are cert: '%s', key: '%s'\n", + certFile, keyFile); + ABORT_FINALIZE(RS_RET_CERTLESS); + } + + /* try load certificate */ + CHKiRet(readFile(certFile, &data)); + CHKgnutls(gnutls_x509_crt_init(&pThis->ourCert)); + pThis->bOurCertIsInit = 1; + CHKgnutls(gnutls_x509_crt_import(pThis->ourCert, &data, GNUTLS_X509_FMT_PEM)); + free(data.data); + data.data = NULL; + + /* try load private key */ + CHKiRet(readFile(keyFile, &data)); + CHKgnutls(gnutls_x509_privkey_init(&pThis->ourKey)); + pThis->bOurKeyIsInit = 1; + CHKgnutls(gnutls_x509_privkey_import(pThis->ourKey, &data, GNUTLS_X509_FMT_PEM)); + free(data.data); + +finalize_it: + if(iRet != RS_RET_OK) { + if(data.data != NULL) + free(data.data); + if(pThis->bOurCertIsInit) { + gnutls_x509_crt_deinit(pThis->ourCert); + pThis->bOurCertIsInit = 0; + } + if(pThis->bOurKeyIsInit) { + gnutls_x509_privkey_deinit(pThis->ourKey); + pThis->bOurKeyIsInit = 0; + } + } + RETiRet; +} + + +/* This callback must be associated with a session by calling + * gnutls_certificate_client_set_retrieve_function(session, cert_callback), + * before a handshake. We will always return the configured certificate, + * even if it does not match the peer's trusted CAs. This is necessary + * to use self-signed certs in fingerprint mode. And, yes, this usage + * of the callback is quite a hack. But it seems the only way to + * obey to the IETF -transport-tls I-D. + * Note: GnuTLS requires the function to return 0 on success and + * -1 on failure. + * rgerhards, 2008-05-27 + */ +static int +gtlsClientCertCallback(gnutls_session session, + __attribute__((unused)) const gnutls_datum* req_ca_rdn, int __attribute__((unused)) nreqs, + __attribute__((unused)) const gnutls_pk_algorithm* sign_algos, int __attribute__((unused)) sign_algos_length, + gnutls_retr_st *st) +{ + nsd_gtls_t *pThis; + + pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session); + + st->type = GNUTLS_CRT_X509; + st->ncerts = 1; + st->cert.x509 = &pThis->ourCert; + st->key.x509 = pThis->ourKey; + st->deinit_all = 0; + + return 0; +} + + +/* This function extracts some information about this session's peer + * certificate. Works for X.509 certificates only. Adds all + * of the info to a cstr_t, which is handed over to the caller. + * Caller must destruct it when no longer needed. + * rgerhards, 2008-05-21 + */ +static rsRetVal +gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) +{ + uchar szBufA[1024]; + uchar *szBuf = szBufA; + size_t szBufLen = sizeof(szBufA), tmp; + unsigned int algo, bits; + time_t expiration_time, activation_time; + const gnutls_datum *cert_list; + unsigned cert_list_size = 0; + gnutls_x509_crt cert; + cstr_t *pStr = NULL; + int gnuRet; + DEFiRet; + unsigned iAltName; + + assert(ppStr != NULL); + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509) + return RS_RET_TLS_CERT_ERR; + + cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size); + CHKiRet(rsCStrConstructFromszStrf(&pStr, "peer provided %d certificate(s). ", cert_list_size)); + + if(cert_list_size > 0) { + /* we only print information about the first certificate */ + CHKgnutls(gnutls_x509_crt_init(&cert)); + CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); + + expiration_time = gnutls_x509_crt_get_expiration_time(cert); + activation_time = gnutls_x509_crt_get_activation_time(cert); + ctime_r(&activation_time, szBuf); + szBuf[strlen(szBuf) - 1] = '\0'; /* strip linefeed */ + CHKiRet(rsCStrAppendStrf(pStr, (uchar*)"Certificate 1 info: " + "certificate valid from %s ", szBuf)); + ctime_r(&expiration_time, szBuf); + szBuf[strlen(szBuf) - 1] = '\0'; /* strip linefeed */ + CHKiRet(rsCStrAppendStrf(pStr, "to %s; ", szBuf)); + + /* Extract some of the public key algorithm's parameters */ + algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits); + CHKiRet(rsCStrAppendStrf(pStr, "Certificate public key: %s; ", + gnutls_pk_algorithm_get_name(algo))); + + /* names */ + tmp = szBufLen; + if(gnutls_x509_crt_get_dn(cert, szBuf, &tmp) + == GNUTLS_E_SHORT_MEMORY_BUFFER) { + szBufLen = tmp; + szBuf = malloc(tmp); + gnutls_x509_crt_get_dn(cert, szBuf, &tmp); + } + CHKiRet(rsCStrAppendStrf(pStr, "DN: %s; ", szBuf)); + + tmp = szBufLen; + if(gnutls_x509_crt_get_issuer_dn(cert, szBuf, &tmp) + == GNUTLS_E_SHORT_MEMORY_BUFFER) { + szBufLen = tmp; + szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp); + gnutls_x509_crt_get_issuer_dn(cert, szBuf, &tmp); + } + CHKiRet(rsCStrAppendStrf(pStr, "Issuer DN: %s; ", szBuf)); + + /* dNSName alt name */ + iAltName = 0; + while(1) { /* loop broken below */ + tmp = szBufLen; + gnuRet = gnutls_x509_crt_get_subject_alt_name(cert, iAltName, + szBuf, &tmp, NULL); + if(gnuRet == GNUTLS_E_SHORT_MEMORY_BUFFER) { + szBufLen = tmp; + szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp); + continue; + } else if(gnuRet < 0) + break; + else if(gnuRet == GNUTLS_SAN_DNSNAME) { + /* we found it! */ + CHKiRet(rsCStrAppendStrf(pStr, "SAN:DNSname: %s; ", szBuf)); + /* do NOT break, because there may be multiple dNSName's! */ + } + ++iAltName; + } + + gnutls_x509_crt_deinit(cert); + } + + CHKiRet(cstrFinalize(pStr)); + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStr != NULL) + rsCStrDestruct(&pStr); + } + if(szBuf != szBufA) + free(szBuf); + + RETiRet; +} + + + +#if 0 /* we may need this in the future - code needs to be looked at then! */ +/* This function will print some details of the + * given pThis->sess. + */ +static rsRetVal +print_info(nsd_gtls_t *pThis) +{ + const char *tmp; + gnutls_credentials_type cred; + gnutls_kx_algorithm kx; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + /* print the key exchange's algorithm name + */ + kx = gnutls_kx_get(pThis->sess); + tmp = gnutls_kx_get_name(kx); + dbgprintf("- Key Exchange: %s\n", tmp); + + /* Check the authentication type used and switch + * to the appropriate. + */ + cred = gnutls_auth_get_type(pThis->sess); + switch (cred) { + case GNUTLS_CRD_ANON: /* anonymous authentication */ + dbgprintf("- Anonymous DH using prime of %d bits\n", + gnutls_dh_get_prime_bits(pThis->sess)); + break; + case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */ + /* Check if we have been using ephemeral Diffie Hellman. + */ + if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) { + dbgprintf("\n- Ephemeral DH using prime of %d bits\n", + gnutls_dh_get_prime_bits(pThis->sess)); + } + + /* if the certificate list is available, then + * print some information about it. + */ + gtlsPrintCert(pThis); + break; + case GNUTLS_CRD_SRP: /* certificate authentication */ + dbgprintf("GNUTLS_CRD_SRP/IA"); + break; + case GNUTLS_CRD_PSK: /* certificate authentication */ + dbgprintf("GNUTLS_CRD_PSK"); + break; + case GNUTLS_CRD_IA: /* certificate authentication */ + dbgprintf("GNUTLS_CRD_IA"); + break; + } /* switch */ + + /* print the protocol's name (ie TLS 1.0) */ + tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(pThis->sess)); + dbgprintf("- Protocol: %s\n", tmp); + + /* print the certificate type of the peer. + * ie X.509 + */ + tmp = gnutls_certificate_type_get_name( + gnutls_certificate_type_get(pThis->sess)); + + dbgprintf("- Certificate Type: %s\n", tmp); + + /* print the compression algorithm (if any) + */ + tmp = gnutls_compression_get_name( gnutls_compression_get(pThis->sess)); + dbgprintf("- Compression: %s\n", tmp); + + /* print the name of the cipher used. + * ie 3DES. + */ + tmp = gnutls_cipher_get_name(gnutls_cipher_get(pThis->sess)); + dbgprintf("- Cipher: %s\n", tmp); + + /* Print the MAC algorithms name. + * ie SHA1 + */ + tmp = gnutls_mac_get_name(gnutls_mac_get(pThis->sess)); + dbgprintf("- MAC: %s\n", tmp); + + RETiRet; +} +#endif + + +/* Convert a fingerprint to printable data. The conversion is carried out + * according IETF I-D syslog-transport-tls-12. The fingerprint string is + * returned in a new cstr object. It is the caller's responsibility to + * destruct that object. + * rgerhards, 2008-05-08 + */ +static rsRetVal +GenFingerprintStr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr) +{ + cstr_t *pStr = NULL; + uchar buf[4]; + size_t i; + DEFiRet; + + CHKiRet(rsCStrConstruct(&pStr)); + CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4)); + for(i = 0 ; i < sizeFingerprint ; ++i) { + snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]); + CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3)); + } + CHKiRet(cstrFinalize(pStr)); + + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStr != NULL) + rsCStrDestruct(&pStr); + } + RETiRet; +} + + +/* a thread-safe variant of gnutls_strerror + * The caller must free the returned string. + * rgerhards, 2008-04-30 + */ +uchar *gtlsStrerror(int error) +{ + uchar *pErr; + + pthread_mutex_lock(&mutGtlsStrerror); + pErr = (uchar*) strdup(gnutls_strerror(error)); + pthread_mutex_unlock(&mutGtlsStrerror); + + return pErr; +} + + +/* try to receive a record from the remote peer. This works with + * our own abstraction and handles local buffering and EAGAIN. + * See details on local buffering in Rcv(9 header-comment. + * This function MUST only be called when the local buffer is + * empty. Calling it otherwise will cause losss of current buffer + * data. + * rgerhards, 2008-06-24 + */ +rsRetVal +gtlsRecordRecv(nsd_gtls_t *pThis) +{ + ssize_t lenRcvd; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF); + if(lenRcvd >= 0) { + pThis->lenRcvBuf = lenRcvd; + pThis->ptrRcvBuf = 0; + } else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) { + pThis->rtryCall = gtlsRtry_recv; + dbgprintf("GnuTLS receive requires a retry (this most probably is OK and no error condition)\n"); + ABORT_FINALIZE(RS_RET_RETRY); + } else { + int gnuRet; /* TODO: build a specific function for GnuTLS error reporting */ + CHKgnutls(lenRcvd); /* this will abort the function */ + } + +finalize_it: + dbgprintf("gtlsRecordRecv return. nsd %p, iRet %d, lenRcvd %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, (int) lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf); + RETiRet; +} + + +/* add our own certificate to the certificate set, so that the peer + * can identify us. Please note that we try to use mutual authentication, + * so we always add a cert, even if we are in the client role (later, + * this may be controlled by a config setting). + * rgerhards, 2008-05-15 + */ +static rsRetVal +gtlsAddOurCert(void) +{ + int gnuRet; + uchar *keyFile; + uchar *certFile; + uchar *pGnuErr; /* for GnuTLS error reporting */ + DEFiRet; + + certFile = glbl.GetDfltNetstrmDrvrCertFile(); + keyFile = glbl.GetDfltNetstrmDrvrKeyFile(); + dbgprintf("GTLS certificate file: '%s'\n", certFile); + dbgprintf("GTLS key file: '%s'\n", keyFile); + CHKgnutls(gnutls_certificate_set_x509_key_file(xcred, (char*)certFile, (char*)keyFile, GNUTLS_X509_FMT_PEM)); + +finalize_it: + if(iRet != RS_RET_OK) { + pGnuErr = gtlsStrerror(gnuRet); + errno = 0; + errmsg.LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', " + "key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile); + free(pGnuErr); + } + RETiRet; +} + + +/* globally initialize GnuTLS */ +static rsRetVal +gtlsGlblInit(void) +{ + int gnuRet; + uchar *cafile; + DEFiRet; + + /* gcry_control must be called first, so that the thread system is correctly set up */ + #if GNUTLS_VERSION_NUMBER <= 0x020b00 + gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + #endif + CHKgnutls(gnutls_global_init()); + + /* X509 stuff */ + CHKgnutls(gnutls_certificate_allocate_credentials(&xcred)); + + /* sets the trusted cas file */ + cafile = glbl.GetDfltNetstrmDrvrCAF(); + dbgprintf("GTLS CA file: '%s'\n", cafile); + gnuRet = gnutls_certificate_set_x509_trust_file(xcred, (char*)cafile, GNUTLS_X509_FMT_PEM); + if(gnuRet < 0) { + /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */ + uchar *pErr = gtlsStrerror(gnuRet); + dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); + free(pErr); + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); + } + +# ifdef DEBUG +#if 0 /* do this in special cases only. WARNING: if active, it may reveal sensitive information! */ + /* intialize log function - set a level only for hard-to-find bugs */ + gnutls_global_set_log_function(logFunction); + gnutls_global_set_log_level(10); /* 0 (no) to 9 (most), 10 everything */ +# endif +# endif + +finalize_it: + RETiRet; +} + +static rsRetVal +gtlsInitSession(nsd_gtls_t *pThis) +{ + DEFiRet; + int gnuRet; + gnutls_session session; + + gnutls_init(&session, GNUTLS_SERVER); + pThis->bHaveSess = 1; + pThis->bIsInitiator = 0; + + /* avoid calling all the priority functions, since the defaults are adequate. */ + CHKgnutls(gnutls_set_default_priority(session)); + CHKgnutls(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred)); + + /* request client certificate if any. */ + gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUEST); + + pThis->sess = session; + +finalize_it: + RETiRet; +} + + +/* set up all global things that are needed for server operations + * rgerhards, 2008-04-30 + */ +static rsRetVal +gtlsGlblInitLstn(void) +{ + DEFiRet; + + if(bGlblSrvrInitDone == 0) { + /* we do not use CRLs right now, and I doubt we'll ever do. This functionality is + * considered legacy. -- rgerhards, 2008-05-05 + */ + /*CHKgnutls(gnutls_certificate_set_x509_crl_file(xcred, CRLFILE, GNUTLS_X509_FMT_PEM));*/ + bGlblSrvrInitDone = 1; /* we are all set now */ + + /* now we need to add our certificate */ + CHKiRet(gtlsAddOurCert()); + } + +finalize_it: + RETiRet; +} + + +/* Obtain the CN from the DN field and hand it back to the caller + * (which is responsible for destructing it). We try to follow + * RFC2253 as far as it makes sense for our use-case. This function + * is considered a compromise providing good-enough correctness while + * limiting code size and complexity. If a problem occurs, we may enhance + * this function. A (pointer to a) certificate must be caller-provided. + * If no CN is contained in the cert, no string is returned + * (*ppstrCN remains NULL). *ppstrCN MUST be NULL on entry! + * rgerhards, 2008-05-22 + */ +static rsRetVal +gtlsGetCN(nsd_gtls_t *pThis, gnutls_x509_crt *pCert, cstr_t **ppstrCN) +{ + DEFiRet; + int gnuRet; + int i; + int bFound; + cstr_t *pstrCN = NULL; + size_t size; + /* big var the last, so we hope to have all we usually neeed within one mem cache line */ + uchar szDN[1024]; /* this should really be large enough for any non-malicious case... */ + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + assert(pCert != NULL); + assert(ppstrCN != NULL); + assert(*ppstrCN == NULL); + + size = sizeof(szDN); + CHKgnutls(gnutls_x509_crt_get_dn(*pCert, (char*)szDN, &size)); + + /* now search for the CN part */ + i = 0; + bFound = 0; + while(!bFound && szDN[i] != '\0') { + /* note that we do not overrun our string due to boolean shortcut + * operations. If we have '\0', the if does not match and evaluation + * stops. Order of checks is obviously important! + */ + if(szDN[i] == 'C' && szDN[i+1] == 'N' && szDN[i+2] == '=') { + bFound = 1; + i += 2; + } + i++; + + } + + if(!bFound) { + FINALIZE; /* we are done */ + } + + /* we found a common name, now extract it */ + CHKiRet(cstrConstruct(&pstrCN)); + while(szDN[i] != '\0' && szDN[i] != ',') { + if(szDN[i] == '\\') { + /* hex escapes are not implemented */ + ++i; /* escape char processed */ + if(szDN[i] == '\0') + ABORT_FINALIZE(RS_RET_CERT_INVALID_DN); + CHKiRet(cstrAppendChar(pstrCN, szDN[i])); + } else { + CHKiRet(cstrAppendChar(pstrCN, szDN[i])); + } + ++i; /* char processed */ + } + CHKiRet(cstrFinalize(pstrCN)); + + /* we got it - we ignore the rest of the DN string (if any). So we may + * not detect if it contains more than one CN + */ + + *ppstrCN = pstrCN; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pstrCN != NULL) + cstrDestruct(&pstrCN); + } + + RETiRet; +} + + +/* Check the peer's ID in fingerprint auth mode. + * rgerhards, 2008-05-22 + */ +static rsRetVal +gtlsChkPeerFingerprint(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) +{ + uchar fingerprint[20]; + size_t size; + cstr_t *pstrFingerprint = NULL; + int bFoundPositiveMatch; + permittedPeers_t *pPeer; + int gnuRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* obtain the SHA1 fingerprint */ + size = sizeof(fingerprint); + CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size)); + CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint)); + dbgprintf("peer's certificate SHA1 fingerprint: %s\n", cstrGetSzStr(pstrFingerprint)); + + /* now search through the permitted peers to see if we can find a permitted one */ + bFoundPositiveMatch = 0; + pPeer = pThis->pPermPeers; + while(pPeer != NULL && !bFoundPositiveMatch) { + if(!rsCStrSzStrCmp(pstrFingerprint, pPeer->pszID, strlen((char*) pPeer->pszID))) { + bFoundPositiveMatch = 1; + } else { + pPeer = pPeer->pNext; + } + } + + if(!bFoundPositiveMatch) { + dbgprintf("invalid peer fingerprint, not permitted to talk to it\n"); + if(pThis->bReportAuthErr == 1) { + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are " + "not permitted to talk to it", cstrGetSzStr(pstrFingerprint)); + pThis->bReportAuthErr = 0; + } + ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); + } + +finalize_it: + if(pstrFingerprint != NULL) + cstrDestruct(&pstrFingerprint); + RETiRet; +} + + +/* Perform a match on ONE peer name obtained from the certificate. This name + * is checked against the set of configured credentials. *pbFoundPositiveMatch is + * set to 1 if the ID matches. *pbFoundPositiveMatch must have been initialized + * to 0 by the caller (this is a performance enhancement as we expect to be + * called multiple times). + * TODO: implemet wildcards? + * rgerhards, 2008-05-26 + */ +static rsRetVal +gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatch) +{ + permittedPeers_t *pPeer; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + assert(pszPeerID != NULL); + assert(pbFoundPositiveMatch != NULL); + + if(pThis->pPermPeers) { /* do we have configured peer IDs? */ + pPeer = pThis->pPermPeers; + while(pPeer != NULL) { + CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch)); + if(*pbFoundPositiveMatch) + break; + pPeer = pPeer->pNext; + } + } else { + /* we do not have configured peer IDs, so we use defaults */ + if( pThis->pszConnectHost + && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) { + *pbFoundPositiveMatch = 1; + } + } + +finalize_it: + RETiRet; +} + + +/* Check the peer's ID in name auth mode. + * rgerhards, 2008-05-22 + */ +static rsRetVal +gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) +{ + uchar lnBuf[256]; + char szAltName[1024]; /* this is sufficient for the DNSNAME... */ + int iAltName; + size_t szAltNameLen; + int bFoundPositiveMatch; + cstr_t *pStr = NULL; + cstr_t *pstrCN = NULL; + int gnuRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + bFoundPositiveMatch = 0; + CHKiRet(rsCStrConstruct(&pStr)); + + /* first search through the dNSName subject alt names */ + iAltName = 0; + while(!bFoundPositiveMatch) { /* loop broken below */ + szAltNameLen = sizeof(szAltName); + gnuRet = gnutls_x509_crt_get_subject_alt_name(*pCert, iAltName, + szAltName, &szAltNameLen, NULL); + if(gnuRet < 0) + break; + else if(gnuRet == GNUTLS_SAN_DNSNAME) { + dbgprintf("subject alt dnsName: '%s'\n", szAltName); + snprintf((char*)lnBuf, sizeof(lnBuf), "DNSname: %s; ", szAltName); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + CHKiRet(gtlsChkOnePeerName(pThis, (uchar*)szAltName, &bFoundPositiveMatch)); + /* do NOT break, because there may be multiple dNSName's! */ + } + ++iAltName; + } + + if(!bFoundPositiveMatch) { + /* if we did not succeed so far, we try the CN part of the DN... */ + CHKiRet(gtlsGetCN(pThis, pCert, &pstrCN)); + if(pstrCN != NULL) { /* NULL if there was no CN present */ + dbgprintf("gtls now checking auth for CN '%s'\n", cstrGetSzStr(pstrCN)); + snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", cstrGetSzStr(pstrCN)); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + CHKiRet(gtlsChkOnePeerName(pThis, cstrGetSzStr(pstrCN), &bFoundPositiveMatch)); + } + } + + if(!bFoundPositiveMatch) { + dbgprintf("invalid peer name, not permitted to talk to it\n"); + if(pThis->bReportAuthErr == 1) { + CHKiRet(cstrFinalize(pStr)); + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized - " + "not permitted to talk to it. Names: %s", + cstrGetSzStr(pStr)); + pThis->bReportAuthErr = 0; + } + ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); + } + +finalize_it: + if(pStr != NULL) + rsCStrDestruct(&pStr); + if(pstrCN != NULL) + rsCStrDestruct(&pstrCN); + RETiRet; +} + + +/* check the ID of the remote peer - used for both fingerprint and + * name authentication. This is common code. Will call into specific + * drivers once the certificate has been obtained. + * rgerhards, 2008-05-08 + */ +static rsRetVal +gtlsChkPeerID(nsd_gtls_t *pThis) +{ + const gnutls_datum *cert_list; + unsigned int list_size = 0; + gnutls_x509_crt cert; + int bMustDeinitCert = 0; + int gnuRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* This function only works for X.509 certificates. */ + if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509) + return RS_RET_TLS_CERT_ERR; + + cert_list = gnutls_certificate_get_peers(pThis->sess, &list_size); + + if(list_size < 1) { + if(pThis->bReportAuthErr == 1) { + errno = 0; + errmsg.LogError(0, RS_RET_TLS_NO_CERT, "error: peer did not provide a certificate, " + "not permitted to talk to it"); + pThis->bReportAuthErr = 0; + } + ABORT_FINALIZE(RS_RET_TLS_NO_CERT); + } + + /* If we reach this point, we have at least one valid certificate. + * We always use only the first certificate. As of GnuTLS documentation, the + * first certificate always contains the remote peer's own certificate. All other + * certificates are issuer's certificates (up the chain). We are only interested + * in the first certificate, which is our peer. -- rgerhards, 2008-05-08 + */ + CHKgnutls(gnutls_x509_crt_init(&cert)); + bMustDeinitCert = 1; /* indicate cert is initialized and must be freed on exit */ + CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); + + /* Now we see which actual authentication code we must call. */ + if(pThis->authMode == GTLS_AUTH_CERTFINGERPRINT) { + CHKiRet(gtlsChkPeerFingerprint(pThis, &cert)); + } else { + assert(pThis->authMode == GTLS_AUTH_CERTNAME); + CHKiRet(gtlsChkPeerName(pThis, &cert)); + } + +finalize_it: + if(bMustDeinitCert) + gnutls_x509_crt_deinit(cert); + + RETiRet; +} + + +/* Verify the validity of the remote peer's certificate. + * rgerhards, 2008-05-21 + */ +static rsRetVal +gtlsChkPeerCertValidity(nsd_gtls_t *pThis) +{ + DEFiRet; + char *pszErrCause; + int gnuRet; + cstr_t *pStr; + unsigned stateCert; + const gnutls_datum *cert_list; + unsigned cert_list_size = 0; + gnutls_x509_crt cert; + unsigned i; + time_t ttCert; + time_t ttNow; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* check if we have at least one cert */ + cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size); + if(cert_list_size < 1) { + errno = 0; + errmsg.LogError(0, RS_RET_TLS_NO_CERT, "peer did not provide a certificate, not permitted to talk to it"); + ABORT_FINALIZE(RS_RET_TLS_NO_CERT); + } + + CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert)); + + if(stateCert & GNUTLS_CERT_INVALID) { + /* provide error details if we have them */ + if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) { + pszErrCause = "signer not found"; + } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) { + pszErrCause = "signer is not a CA"; + } else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) { + pszErrCause = "insecure algorithm"; + } else if(stateCert & GNUTLS_CERT_REVOKED) { + pszErrCause = "certificate revoked"; + } else { + pszErrCause = "GnuTLS returned no specific reason"; + dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate " + "status is %d\n", stateCert); + } + errmsg.LogError(0, NO_ERRCODE, "not permitted to talk to peer, certificate invalid: %s", + pszErrCause); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(0, NO_ERRCODE, "invalid cert info: %s", cstrGetSzStr(pStr)); + cstrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_INVALID); + } + + /* get current time for certificate validation */ + if(datetime.GetTime(&ttNow) == -1) + ABORT_FINALIZE(RS_RET_SYS_ERR); + + /* as it looks, we need to validate the expiration dates ourselves... + * We need to loop through all certificates as we need to make sure the + * interim certificates are also not expired. + */ + for(i = 0 ; i < cert_list_size ; ++i) { + CHKgnutls(gnutls_x509_crt_init(&cert)); + CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER)); + ttCert = gnutls_x509_crt_get_activation_time(cert); + if(ttCert == -1) + ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); + else if(ttCert > ttNow) { + errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer: certificate %d not yet active", i); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "invalid cert info: %s", cstrGetSzStr(pStr)); + cstrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE); + } + + ttCert = gnutls_x509_crt_get_expiration_time(cert); + if(ttCert == -1) + ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); + else if(ttCert < ttNow) { + errmsg.LogError(0, RS_RET_CERT_EXPIRED, "not permitted to talk to peer: certificate %d expired", i); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(0, RS_RET_CERT_EXPIRED, "invalid cert info: %s", cstrGetSzStr(pStr)); + cstrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_EXPIRED); + } + gnutls_x509_crt_deinit(cert); + } + +finalize_it: + RETiRet; +} + + +/* check if it is OK to talk to the remote peer + * rgerhards, 2008-05-21 + */ +rsRetVal +gtlsChkPeerAuth(nsd_gtls_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* call the actual function based on current auth mode */ + switch(pThis->authMode) { + case GTLS_AUTH_CERTNAME: + /* if we check the name, we must ensure the cert is valid */ + CHKiRet(gtlsChkPeerCertValidity(pThis)); + CHKiRet(gtlsChkPeerID(pThis)); + break; + case GTLS_AUTH_CERTFINGERPRINT: + CHKiRet(gtlsChkPeerID(pThis)); + break; + case GTLS_AUTH_CERTVALID: + CHKiRet(gtlsChkPeerCertValidity(pThis)); + break; + case GTLS_AUTH_CERTANON: + FINALIZE; + break; + } + +finalize_it: + RETiRet; +} + + +/* globally de-initialize GnuTLS */ +static rsRetVal +gtlsGlblExit(void) +{ + DEFiRet; + /* X509 stuff */ + gnutls_certificate_free_credentials(xcred); + gnutls_global_deinit(); /* we are done... */ + RETiRet; +} + + +/* end a GnuTLS session + * The function checks if we have a session and ends it only if so. So it can + * always be called, even if there currently is no session. + */ +static rsRetVal +gtlsEndSess(nsd_gtls_t *pThis) +{ + int gnuRet; + DEFiRet; + + if(pThis->bHaveSess) { + if(pThis->bIsInitiator) { + gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR); + while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) { + gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR); + } + } + gnutls_deinit(pThis->sess); + pThis->bHaveSess = 0; + } + RETiRet; +} + + +/* a small wrapper for gnutls_transport_set_ptr(). The main intension for + * creating this wrapper is to get the annoying "cast to pointer from different + * size" compiler warning just once. There seems to be no way around it, see: + * http://lists.gnu.org/archive/html/help-gnutls/2008-05/msg00000.html + * rgerhards, 2008.05-07 + */ +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +static inline void +gtlsSetTransportPtr(nsd_gtls_t *pThis, int sock) +{ + /* Note: the compiler warning for the next line is OK - see header comment! */ + gnutls_transport_set_ptr(pThis->sess, (gnutls_transport_ptr_t) sock); +} +#pragma GCC diagnostic warning "-Wint-to-pointer-cast" + +/* ---------------------------- end GnuTLS specifics ---------------------------- */ + + +/* Standard-Constructor */ +BEGINobjConstruct(nsd_gtls) /* be sure to specify the object type also in END macro! */ + iRet = nsd_ptcp.Construct(&pThis->pTcp); + pThis->bReportAuthErr = 1; +ENDobjConstruct(nsd_gtls) + + +/* destructor for the nsd_gtls object */ +BEGINobjDestruct(nsd_gtls) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsd_gtls) + if(pThis->iMode == 1) { + gtlsEndSess(pThis); + } + + if(pThis->pTcp != NULL) { + nsd_ptcp.Destruct(&pThis->pTcp); + } + + if(pThis->pszConnectHost != NULL) { + free(pThis->pszConnectHost); + } + + if(pThis->pszRcvBuf == NULL) { + free(pThis->pszRcvBuf); + } + + if(pThis->bOurCertIsInit) + gnutls_x509_crt_deinit(pThis->ourCert); + if(pThis->bOurKeyIsInit) + gnutls_x509_privkey_deinit(pThis->ourKey); + if(pThis->bHaveSess) + gnutls_deinit(pThis->sess); +ENDobjDestruct(nsd_gtls) + + +/* Set the driver mode. For us, this has the following meaning: + * 0 - work in plain tcp mode, without tls (e.g. before a STARTTLS) + * 1 - work in TLS mode + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetMode(nsd_t *pNsd, int mode) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + if(mode != 0 && mode != 1) { + errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by " + "gtls netstream driver", mode); + ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE); + } + + pThis->iMode = mode; + +finalize_it: + RETiRet; +} + + +/* Set the authentication mode. For us, the following is supported: + * anon - no certificate checks whatsoever (discouraged, but supported) + * x509/certvalid - (just) check certificate validity + * x509/fingerprint - certificate fingerprint + * x509/name - cerfificate name check + * mode == NULL is valid and defaults to x509/name + * rgerhards, 2008-05-16 + */ +static rsRetVal +SetAuthMode(nsd_t *pNsd, uchar *mode) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) { + pThis->authMode = GTLS_AUTH_CERTNAME; + } else if(!strcasecmp((char*) mode, "x509/fingerprint")) { + pThis->authMode = GTLS_AUTH_CERTFINGERPRINT; + } else if(!strcasecmp((char*) mode, "x509/certvalid")) { + pThis->authMode = GTLS_AUTH_CERTVALID; + } else if(!strcasecmp((char*) mode, "anon")) { + pThis->authMode = GTLS_AUTH_CERTANON; + } else { + errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by " + "gtls netstream driver", mode); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } + +/* TODO: clear stored IDs! */ + +finalize_it: + RETiRet; +} + + +/* Set permitted peers. It is depending on the auth mode if this are + * fingerprints or names. -- rgerhards, 2008-05-19 + */ +static rsRetVal +SetPermPeers(nsd_t *pNsd, permittedPeers_t *pPermPeers) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + if(pPermPeers == NULL) + FINALIZE; + + if(pThis->authMode != GTLS_AUTH_CERTFINGERPRINT && pThis->authMode != GTLS_AUTH_CERTNAME) { + errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by " + "gtls netstream driver in the configured authentication mode - ignored"); + ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE); + } + + pThis->pPermPeers = pPermPeers; + +finalize_it: + RETiRet; +} + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_gtls) who utilize ourselfs + * for some of their functionality. -- rgerhards, 2008-04-18 + */ +static rsRetVal +SetSock(nsd_t *pNsd, int sock) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + assert(sock >= 0); + + nsd_ptcp.SetSock(pThis->pTcp, sock); + + RETiRet; +} + + +/* abort a connection. This is meant to be called immediately + * before the Destruct call. -- rgerhards, 2008-03-24 + */ +static rsRetVal +Abort(nsd_t *pNsd) +{ + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + + if(pThis->iMode == 0) { + nsd_ptcp.Abort(pThis->pTcp); + } + + RETiRet; +} + + + +/* initialize the tcp socket for a listner + * Here, we use the ptcp driver - because there is nothing special + * at this point with GnuTLS. Things become special once we accept + * a session, but not during listener setup. + * gerhards, 2008-04-25 + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax) +{ + DEFiRet; + CHKiRet(gtlsGlblInitLstn()); + iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax); +finalize_it: + RETiRet; +} + + +/* This function checks if the connection is still alive - well, kind of... + * This is a dummy here. For details, check function common in ptcp driver. + * rgerhards, 2008-06-09 + */ +static rsRetVal +CheckConnection(nsd_t __attribute__((unused)) *pNsd) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + CHKiRet(nsd_ptcp.CheckConnection(pThis->pTcp)); +finalize_it: + RETiRet; +} + + +/* get the remote hostname. The returned hostname must be freed by the caller. + * rgerhards, 2008-04-25 + */ +static rsRetVal +GetRemoteHName(nsd_t *pNsd, uchar **ppszHName) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, ppszHName); + RETiRet; +} + + +/* Provide access to the sockaddr_storage of the remote peer. This + * is needed by the legacy ACL system. --- gerhards, 2008-12-01 + */ +static rsRetVal +GetRemAddr(nsd_t *pNsd, struct sockaddr_storage **ppAddr) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + iRet = nsd_ptcp.GetRemAddr(pThis->pTcp, ppAddr); + RETiRet; +} + + +/* get the remote host's IP address. Caller must Destruct the object. */ +static rsRetVal +GetRemoteIP(nsd_t *pNsd, prop_t **ip) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ip); + RETiRet; +} + + +/* accept an incoming connection request - here, we do the usual accept + * handling. TLS specific handling is done thereafter (and if we run in TLS + * mode at this time). + * rgerhards, 2008-04-25 + */ +static rsRetVal +AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew) +{ + DEFiRet; + int gnuRet; + nsd_gtls_t *pNew = NULL; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + CHKiRet(nsd_gtlsConstruct(&pNew)); // TODO: prevent construct/destruct! + CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp)); + CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp)); + + if(pThis->iMode == 0) { + /* we are in non-TLS mode, so we are done */ + *ppNew = (nsd_t*) pNew; + FINALIZE; + } + + /* if we reach this point, we are in TLS mode */ + CHKiRet(gtlsInitSession(pNew)); + gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock); + pNew->authMode = pThis->authMode; + pNew->pPermPeers = pThis->pPermPeers; + + /* we now do the handshake. This is a bit complicated, because we are + * on non-blocking sockets. Usually, the handshake will not complete + * immediately, so that we need to retry it some time later. + */ + gnuRet = gnutls_handshake(pNew->sess); + if(gnuRet == GNUTLS_E_AGAIN || gnuRet == GNUTLS_E_INTERRUPTED) { + pNew->rtryCall = gtlsRtry_handshake; + dbgprintf("GnuTLS handshake does not complete immediately - setting to retry (this is OK and normal)\n"); + } else if(gnuRet == 0) { + /* we got a handshake, now check authorization */ + CHKiRet(gtlsChkPeerAuth(pNew)); + } else { + uchar *pGnuErr = gtlsStrerror(gnuRet); + errmsg.LogError(0, RS_RET_TLS_HANDSHAKE_ERR, + "gnutls returned error on handshake: %s\n", pGnuErr); + free(pGnuErr); + ABORT_FINALIZE(RS_RET_TLS_HANDSHAKE_ERR); + } + + pNew->iMode = 1; /* this session is now in TLS mode! */ + + *ppNew = (nsd_t*) pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + nsd_gtlsDestruct(&pNew); + } + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. -- rgerhards, 2008-03-17 + * The function now follows the usual iRet calling sequence. + * With GnuTLS, we may need to restart a recv() system call. If so, we need + * to supply the SAME buffer on the retry. We can not assure this, as the + * caller is free to call us with any buffer location (and in current + * implementation, it is on the stack and extremely likely to change). To + * work-around this problem, we allocate a buffer ourselfs and always receive + * into that buffer. We pass data on to the caller only after we have received it. + * To save some space, we allocate that internal buffer only when it is actually + * needed, which means when we reach this function for the first time. To keep + * the algorithm simple, we always supply data only from the internal buffer, + * even if it is a single byte. As we have a stream, the caller must be prepared + * to accept messages in any order, so we do not need to take care about this. + * Please note that the logic also forces us to do some "faking" in select(), as + * we must provide a fake "is ready for readign" status if we have data inside our + * buffer. -- rgerhards, 2008-06-23 + */ +static rsRetVal +Rcv(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */ + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + if(pThis->bAbortConn) + ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); + + if(pThis->iMode == 0) { + CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf)); + FINALIZE; + } + + /* --- in TLS mode now --- */ + + /* Buffer logic applies only if we are in TLS mode. Here we + * assume that we will switch from plain to TLS, but never back. This + * assumption may be unsafe, but it is the model for the time being and I + * do not see any valid reason why we should switch back to plain TCP after + * we were in TLS mode. However, in that case we may lose something that + * is already in the receive buffer ... risk accepted. -- rgerhards, 2008-06-23 + */ + + if(pThis->pszRcvBuf == NULL) { + /* we have no buffer, so we need to malloc one */ + CHKmalloc(pThis->pszRcvBuf = MALLOC(NSD_GTLS_MAX_RCVBUF)); + pThis->lenRcvBuf = -1; + } + + /* now check if we have something in our buffer. If so, we satisfy + * the request from buffer contents. + */ + if(pThis->lenRcvBuf == -1) { /* no data present, must read */ + CHKiRet(gtlsRecordRecv(pThis)); + } + + if(pThis->lenRcvBuf == 0) { /* EOS */ + *pLenBuf = 0; + /* in this case, we also need to free the receive buffer, if we + * allocated one. -- rgerhards, 2008-12-03 + */ + if(pThis->pszRcvBuf != NULL) { + free(pThis->pszRcvBuf); + pThis->pszRcvBuf = NULL; + } + ABORT_FINALIZE(RS_RET_CLOSED); + } + + /* if we reach this point, data is present in the buffer and must be copied */ + iBytesCopy = pThis->lenRcvBuf - pThis->ptrRcvBuf; + if(iBytesCopy > *pLenBuf) { + iBytesCopy = *pLenBuf; + } else { + pThis->lenRcvBuf = -1; /* buffer will be emptied below */ + } + + memcpy(pBuf, pThis->pszRcvBuf + pThis->ptrRcvBuf, iBytesCopy); + pThis->ptrRcvBuf += iBytesCopy; + *pLenBuf = iBytesCopy; + +finalize_it: + dbgprintf("gtlsRcv return. nsd %p, iRet %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, pThis->lenRcvBuf, pThis->ptrRcvBuf); + RETiRet; +} + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + int iSent; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + if(pThis->bAbortConn) + ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); + + if(pThis->iMode == 0) { + CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf)); + FINALIZE; + } + + /* in TLS mode now */ + while(1) { /* loop broken inside */ + iSent = gnutls_record_send(pThis->sess, pBuf, *pLenBuf); + if(iSent >= 0) { + *pLenBuf = iSent; + break; + } + if(iSent != GNUTLS_E_INTERRUPTED && iSent != GNUTLS_E_AGAIN) { + dbgprintf("unexpected GnuTLS error %d in %s:%d\n", iSent, __FILE__, __LINE__); + gnutls_perror(iSent); /* TODO: can we do better? */ + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); + } + } + +finalize_it: + RETiRet; +} + +/* Enable KEEPALIVE handling on the socket. + * rgerhards, 2009-06-02 + */ +static rsRetVal +EnableKeepAlive(nsd_t *pNsd) +{ + return nsd_ptcp.EnableKeepAlive(pNsd); +} + + + +/* open a connection to a remote host (server). With GnuTLS, we always + * open a plain tcp socket and then, if in TLS mode, do a handshake on it. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) +{ + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + int sock; + int gnuRet; + /* TODO: later? static const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };*/ + static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 }; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + assert(port != NULL); + assert(host != NULL); + + CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host)); + + if(pThis->iMode == 0) + FINALIZE; + + /* we reach this point if in TLS mode */ + CHKgnutls(gnutls_init(&pThis->sess, GNUTLS_CLIENT)); + pThis->bHaveSess = 1; + pThis->bIsInitiator = 1; + + /* in the client case, we need to set a callback that ensures our certificate + * will be presented to the server even if it is not signed by one of the server's + * trusted roots. This is necessary to support fingerprint authentication. + */ + /* store a pointer to ourselfs (needed by callback) */ + gnutls_session_set_ptr(pThis->sess, (void*)pThis); + iRet = gtlsLoadOurCertKey(pThis); /* first load .pem files */ + if(iRet == RS_RET_OK) { + gnutls_certificate_client_set_retrieve_function(xcred, gtlsClientCertCallback); + } else if(iRet != RS_RET_CERTLESS) { + FINALIZE; /* we have an error case! */ + } + + /* Use default priorities */ + CHKgnutls(gnutls_set_default_priority(pThis->sess)); + CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority)); + + /* put the x509 credentials to the current session */ + CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, xcred)); + + /* assign the socket to GnuTls */ + CHKiRet(nsd_ptcp.GetSock(pThis->pTcp, &sock)); + gtlsSetTransportPtr(pThis, sock); + + /* we need to store the hostname as an alternate mean of authentication if no + * permitted peer names are given. Using the hostname is quite useful. It permits + * auto-configuration of security if a commen root cert is present. -- rgerhards, 2008-05-26 + */ + CHKmalloc(pThis->pszConnectHost = (uchar*)strdup((char*)host)); + + /* and perform the handshake */ + CHKgnutls(gnutls_handshake(pThis->sess)); + dbgprintf("GnuTLS handshake succeeded\n"); + + /* now check if the remote peer is permitted to talk to us - ideally, we + * should do this during the handshake, but GnuTLS does not yet provide + * the necessary callbacks -- rgerhards, 2008-05-26 + */ + CHKiRet(gtlsChkPeerAuth(pThis)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->bHaveSess) { + gnutls_deinit(pThis->sess); + pThis->bHaveSess = 0; + } + } + + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nsd_gtls) +CODESTARTobjQueryInterface(nsd_gtls) + if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = (rsRetVal(*)(nsd_t**)) nsd_gtlsConstruct; + pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_gtlsDestruct; + pIf->Abort = Abort; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->Connect = Connect; + pIf->SetSock = SetSock; + pIf->SetMode = SetMode; + pIf->SetAuthMode = SetAuthMode; + pIf->SetPermPeers =SetPermPeers; + pIf->CheckConnection = CheckConnection; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; + pIf->GetRemAddr = GetRemAddr; + pIf->EnableKeepAlive = EnableKeepAlive; +finalize_it: +ENDobjQueryInterface(nsd_gtls) + + +/* exit our class + */ +BEGINObjClassExit(nsd_gtls, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsd_gtls) + gtlsGlblExit(); /* shut down GnuTLS */ + + /* release objects we no longer need */ + objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME); + objRelease(net, LM_NET_FILENAME); + objRelease(glbl, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(nsd_gtls) + + +/* Initialize the nsd_gtls class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsd_gtls, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME)); + + /* now do global TLS init stuff */ + CHKiRet(gtlsGlblInit()); +ENDObjClassInit(nsd_gtls) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + nsdsel_gtlsClassExit(); + nsd_gtlsClassExit(); + pthread_mutex_destroy(&mutGtlsStrerror); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(nsd_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + CHKiRet(nsdsel_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + + pthread_mutex_init(&mutGtlsStrerror, NULL); +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/nsd_gtls.h b/runtime/nsd_gtls.h new file mode 100644 index 00000000..eb92ff2a --- /dev/null +++ b/runtime/nsd_gtls.h @@ -0,0 +1,90 @@ +/* An implementation of the nsd interface for GnuTLS. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDED_NSD_GTLS_H +#define INCLUDED_NSD_GTLS_H + +#include "nsd.h" + +#define NSD_GTLS_MAX_RCVBUF 8 * 1024 /* max size of buffer for message reception */ + +typedef enum { + gtlsRtry_None = 0, /**< no call needs to be retried */ + gtlsRtry_handshake = 1, + gtlsRtry_recv = 2 +} gtlsRtryCall_t; /**< IDs of calls that needs to be retried */ + +typedef nsd_if_t nsd_gtls_if_t; /* we just *implement* this interface */ + +/* the nsd_gtls object */ +struct nsd_gtls_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pTcp; /**< our aggregated nsd_ptcp data */ + uchar *pszConnectHost; /**< hostname used for connect - may be used to authenticate peer if no other name given */ + int iMode; /* 0 - plain tcp, 1 - TLS */ + int bAbortConn; /* if set, abort conncection (fatal error had happened) */ + enum { + GTLS_AUTH_CERTNAME = 0, + GTLS_AUTH_CERTFINGERPRINT = 1, + GTLS_AUTH_CERTVALID = 2, + GTLS_AUTH_CERTANON = 3 + } authMode; + gtlsRtryCall_t rtryCall;/**< what must we retry? */ + int bIsInitiator; /**< 0 if socket is the server end (listener), 1 if it is the initiator */ + gnutls_session sess; + int bHaveSess; /* as we don't know exactly which gnutls_session values are invalid, we use this one + to flag whether or not we are in a session (same as -1 for a socket meaning no sess) */ + int bReportAuthErr; /* only the first auth error is to be reported, this var triggers it. Initially, it is + * set to 1 and changed to 0 after the first report. It is changed back to 1 after + * one successful authentication. */ + permittedPeers_t *pPermPeers; /* permitted peers */ + gnutls_x509_crt ourCert; /**< our certificate, if in client mode (unused in server mode) */ + gnutls_x509_privkey ourKey; /**< our private key, if in client mode (unused in server mode) */ + short bOurCertIsInit; /**< 1 if our certificate is initialized and must be deinit on destruction */ + short bOurKeyIsInit; /**< 1 if our private key is initialized and must be deinit on destruction */ + char *pszRcvBuf; + int lenRcvBuf; /**< -1: empty, 0: connection closed, 1..NSD_GTLS_MAX_RCVBUF-1: data of that size present */ + int ptrRcvBuf; /**< offset for next recv operation if 0 < lenRcvBuf < NSD_GTLS_MAX_RCVBUF */ +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsd_gtlsCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsd_gtls); +/* some prototypes for things used by our nsdsel_gtls helper class */ +uchar *gtlsStrerror(int error); +rsRetVal gtlsChkPeerAuth(nsd_gtls_t *pThis); +rsRetVal gtlsRecordRecv(nsd_gtls_t *pThis); +static inline rsRetVal gtlsHasRcvInBuffer(nsd_gtls_t *pThis) { + /* we have a valid receive buffer one such is allocated and + * NOT exhausted! + */ + dbgprintf("hasRcvInBuffer on nsd %p: pszRcvBuf %p, lenRcvBuf %d\n", pThis, + pThis->pszRcvBuf, pThis->lenRcvBuf); + return(pThis->pszRcvBuf != NULL && pThis->lenRcvBuf != -1); + } + + +/* the name of our library binary */ +#define LM_NSD_GTLS_FILENAME "lmnsd_gtls" + +#endif /* #ifndef INCLUDED_NSD_GTLS_H */ diff --git a/runtime/nsd_ptcp.c b/runtime/nsd_ptcp.c new file mode 100644 index 00000000..f889a00e --- /dev/null +++ b/runtime/nsd_ptcp.c @@ -0,0 +1,823 @@ +/* nsd_ptcp.c + * + * An implementation of the nsd interface for plain tcp sockets. + * + * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <ctype.h> +#include <netdb.h> +#include <fnmatch.h> +#include <fcntl.h> +#include <unistd.h> + +#include "syslogd-types.h" +#include "module-template.h" +#include "parse.h" +#include "srUtils.h" +#include "obj.h" +#include "errmsg.h" +#include "net.h" +#include "netstrms.h" +#include "netstrm.h" +#include "nsdsel_ptcp.h" +#include "nsdpoll_ptcp.h" +#include "nsd_ptcp.h" +#include "prop.h" +#include "dnscache.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrms) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(prop) + + +/* a few deinit helpers */ + +/* close socket if open (may always be called) */ +static void +sockClose(int *pSock) +{ + if(*pSock >= 0) { + close(*pSock); + *pSock = -1; + } +} + +/* Standard-Constructor + */ +BEGINobjConstruct(nsd_ptcp) /* be sure to specify the object type also in END macro! */ + pThis->sock = -1; +ENDobjConstruct(nsd_ptcp) + + +/* destructor for the nsd_ptcp object */ +BEGINobjDestruct(nsd_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsd_ptcp) + sockClose(&pThis->sock); + if(pThis->remoteIP != NULL) + prop.Destruct(&pThis->remoteIP); + free(pThis->pRemHostName); +ENDobjDestruct(nsd_ptcp) + + +/* Provide access to the sockaddr_storage of the remote peer. This + * is needed by the legacy ACL system. --- gerhards, 2008-12-01 + */ +static rsRetVal +GetRemAddr(nsd_t *pNsd, struct sockaddr_storage **ppAddr) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + assert(ppAddr != NULL); + + *ppAddr = &(pThis->remAddr); + + RETiRet; +} + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_gtls) who utilize ourselfs + * for some of their functionality. -- rgerhards, 2008-04-18 + */ +static rsRetVal +GetSock(nsd_t *pNsd, int *pSock) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + assert(pSock != NULL); + + *pSock = pThis->sock; + + RETiRet; +} + + +/* Set the driver mode. We support no different modes, but allow mode + * 0 to be set to be compatible with config file defaults and the other + * drivers. + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetMode(nsd_t __attribute__((unused)) *pNsd, int mode) +{ + DEFiRet; + if(mode != 0) { + errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by " + "ptcp netstream driver", mode); + ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE); + } +finalize_it: + RETiRet; +} + + +/* Set the authentication mode. For us, the following is supported: + * anon - no certificate checks whatsoever (discouraged, but supported) + * mode == NULL is valid and defaults to anon + * Actually, we do not even record the mode right now, because we can + * always work in anon mode, only. So there is no point in recording + * something if that's the only choice. What the function does is + * return an error if something is requested that we can not support. + * rgerhards, 2008-05-17 + */ +static rsRetVal +SetAuthMode(nsd_t __attribute__((unused)) *pNsd, uchar *mode) +{ + DEFiRet; + if(mode != NULL && strcasecmp((char*)mode, "anon")) { + errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by " + "ptcp netstream driver", mode); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } + +finalize_it: + RETiRet; +} + + +/* Set the permitted peers. This is a dummy, always returning an + * error because we do not support fingerprint authentication. + * rgerhards, 2008-05-17 + */ +static rsRetVal +SetPermPeers(nsd_t __attribute__((unused)) *pNsd, permittedPeers_t __attribute__((unused)) *pPermPeers) +{ + DEFiRet; + + if(pPermPeers != NULL) { + errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by ptcp netstream driver"); + ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE); + } + +finalize_it: + RETiRet; +} + + + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_gtls) who utilize ourselfs + * for some of their functionality. + * This function sets the socket -- rgerhards, 2008-04-25 + */ +static rsRetVal +SetSock(nsd_t *pNsd, int sock) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + assert(sock >= 0); + + pThis->sock = sock; + + RETiRet; +} + + +/* abort a connection. This is meant to be called immediately + * before the Destruct call. -- rgerhards, 2008-03-24 + */ +static rsRetVal +Abort(nsd_t *pNsd) +{ + struct linger ling; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + + DEFiRet; + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + + if((pThis)->sock != -1) { + ling.l_onoff = 1; + ling.l_linger = 0; + if(setsockopt((pThis)->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0 ) { + dbgprintf("could not set SO_LINGER, errno %d\n", errno); + } + } + + RETiRet; +} + + +/* Set pRemHost based on the address provided. This is to be called upon accept()ing + * a connection request. It must be provided by the socket we received the + * message on as well as a NI_MAXHOST size large character buffer for the FQDN. + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. If we detect a malicious + * hostname, we return RS_RET_MALICIOUS_HNAME and let the caller decide + * on how to deal with that. + * rgerhards, 2008-03-31 + */ +static rsRetVal +FillRemHost(nsd_ptcp_t *pThis, struct sockaddr_storage *pAddr) +{ + prop_t *fqdn; + + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(pAddr != NULL); + + CHKiRet(dnscacheLookup(pAddr, &fqdn, NULL, NULL, &pThis->remoteIP)); + + /* We now have the names, so now let's allocate memory and store them permanently. + * (side note: we may hold on to these values for quite a while, thus we trim their + * memory consumption) + */ + if((pThis->pRemHostName = MALLOC(prop.GetStringLen(fqdn)+1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pThis->pRemHostName, propGetSzStr(fqdn), prop.GetStringLen(fqdn)+1); + prop.Destruct(&fqdn); + +finalize_it: + RETiRet; +} + + +/* accept an incoming connection request + * rgerhards, 2008-04-22 + */ +static rsRetVal +AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew) +{ + int sockflags; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + nsd_ptcp_t *pNew = NULL; + int iNewSock = -1; + + DEFiRet; + assert(ppNew != NULL); + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + iNewSock = accept(pThis->sock, (struct sockaddr*) &addr, &addrlen); + if(iNewSock < 0) { + if(Debug) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("nds_ptcp: error accepting connection on socket %d, errno %d: %s\n", + pThis->sock, errno, errStr); + } + ABORT_FINALIZE(RS_RET_ACCEPT_ERR); + } + + /* construct our object so that we can use it... */ + CHKiRet(nsd_ptcpConstruct(&pNew)); + + /* for the legacy ACL code, we need to preserve addr. While this is far from + * begin perfect (from an abstract design perspective), we need this to prevent + * breaking everything. TODO: we need to implement a new ACL module to get rid + * of this function. -- rgerhards, 2008-12-01 + */ + memcpy(&pNew->remAddr, &addr, sizeof(struct sockaddr_storage)); + CHKiRet(FillRemHost(pNew, &addr)); + + /* set the new socket to non-blocking IO -TODO:do we really need to do this here? Do we always want it? */ + if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(iNewSock, F_SETFL, sockflags); + } + if(sockflags == -1) { + dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket %d", errno, iNewSock); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + pNew->sock = iNewSock; + *ppNew = (nsd_t*) pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + nsd_ptcpDestruct(&pNew); + /* the close may be redundant, but that doesn't hurt... */ + sockClose(&iNewSock); + } + + RETiRet; +} + + +/* initialize tcp sockets for a listner. The initialized sockets are passed to the + * app-level caller via a callback. + * pLstnPort must point to a port name or number. NULL is NOT permitted. pLstnIP + * points to the port to listen to (NULL means "all"), iMaxSess has the maximum + * number of sessions permitted. + * rgerhards, 2008-04-22 + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax) +{ + DEFiRet; + netstrm_t *pNewStrm = NULL; + nsd_t *pNewNsd = NULL; + int error, maxs, on = 1; + int sock = -1; + int numSocks; + int sockflags; + struct addrinfo hints, *res = NULL, *r; + + ISOBJ_TYPE_assert(pNS, netstrms); + assert(fAddLstn != NULL); + assert(pLstnPort != NULL); + assert(iSessMax >= 0); + + dbgprintf("creating tcp listen socket on port %s\n", pLstnPort); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo((char*)pLstnIP, (char*) pLstnPort, &hints, &res); + if(error) { + dbgprintf("error %d querying port '%s'\n", error, pLstnPort); + ABORT_FINALIZE(RS_RET_INVALID_PORT); + } + + /* Count max number of sockets we may open */ + for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + + numSocks = 0; /* num of sockets counter at start of array */ + for(r = res; r != NULL ; r = r->ai_next) { + sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if(sock < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + dbgprintf("error %d creating tcp listen socket", errno); + /* it is debatable if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +#ifdef IPV6_V6ONLY + if(r->ai_family == AF_INET6) { + int iOn = 1; + if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&iOn, sizeof (iOn)) < 0) { + close(sock); + sock = -1; + continue; + } + } +#endif + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) { + dbgprintf("error %d setting tcp socket option\n", errno); + close(sock); + sock = -1; + continue; + } + + /* We use non-blocking IO! */ + if((sockflags = fcntl(sock, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(sock, F_SETFL, sockflags); + } + if(sockflags == -1) { + dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket", errno); + close(sock); + sock = -1; + continue; + } + + + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#ifndef BSD + if(net.should_use_so_bsdcompat()) { + if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)"); + close(sock); + sock = -1; + continue; + } + } +#endif + + if( (bind(sock, r->ai_addr, r->ai_addrlen) < 0) +#ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) +#endif + ) { + /* TODO: check if *we* bound the socket - else we *have* an error! */ + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("error %d while binding tcp socket: %s\n", errno, errStr); + close(sock); + sock = -1; + continue; + } + + if(listen(sock, iSessMax / 10 + 5) < 0) { + /* If the listen fails, it most probably fails because we ask + * for a too-large backlog. So in this case we first set back + * to a fixed, reasonable, limit that should work. Only if + * that fails, too, we give up. + */ + dbgprintf("listen with a backlog of %d failed - retrying with default of 32.\n", + iSessMax / 10 + 5); + if(listen(sock, 32) < 0) { + dbgprintf("tcp listen error %d, suspending\n", errno); + close(sock); + sock = -1; + continue; + } + } + + /* if we reach this point, we were able to obtain a valid socket, so we can + * construct a new netstrm obj and hand it over to the upper layers for inclusion + * into their socket array. -- rgerhards, 2008-04-23 + */ + CHKiRet(pNS->Drvr.Construct(&pNewNsd)); + CHKiRet(pNS->Drvr.SetSock(pNewNsd, sock)); + sock = -1; + CHKiRet(pNS->Drvr.SetMode(pNewNsd, netstrms.GetDrvrMode(pNS))); + CHKiRet(pNS->Drvr.SetAuthMode(pNewNsd, netstrms.GetDrvrAuthMode(pNS))); + CHKiRet(pNS->Drvr.SetPermPeers(pNewNsd, netstrms.GetDrvrPermPeers(pNS))); + CHKiRet(netstrms.CreateStrm(pNS, &pNewStrm)); + pNewStrm->pDrvrData = (nsd_t*) pNewNsd; + pNewNsd = NULL; + CHKiRet(fAddLstn(pUsr, pNewStrm)); + pNewStrm = NULL; + ++numSocks; + } + + if(numSocks != maxs) + dbgprintf("We could initialize %d TCP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", numSocks, maxs); + + if(numSocks == 0) { + dbgprintf("No TCP listen sockets could successfully be initialized\n"); + ABORT_FINALIZE(RS_RET_COULD_NOT_BIND); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + if(sock != -1) + close(sock); + if(pNewStrm != NULL) + netstrm.Destruct(&pNewStrm); + if(pNewNsd != NULL) + pNS->Drvr.Destruct(&pNewNsd); + } + + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read (or -1 in case of error) on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. If *pLenBuf is -1, an error occured and + * errno holds the exact error cause. + * rgerhards, 2008-03-17 + */ +static rsRetVal +Rcv(nsd_t *pNsd, uchar *pRcvBuf, ssize_t *pLenBuf) +{ + char errStr[1024]; + DEFiRet; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + *pLenBuf = recv(pThis->sock, pRcvBuf, *pLenBuf, MSG_DONTWAIT); + + if(*pLenBuf == 0) { + ABORT_FINALIZE(RS_RET_CLOSED); + } else if (*pLenBuf < 0) { + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("error during recv on NSD %p: %s\n", pNsd, errStr); + ABORT_FINALIZE(RS_RET_RCV_ERR); + } + +finalize_it: + RETiRet; +} + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ssize_t written; + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + written = send(pThis->sock, pBuf, *pLenBuf, 0); + + if(written == -1) { + switch(errno) { + case EAGAIN: + case EINTR: + /* this is fine, just retry... */ + written = 0; + break; + default: + ABORT_FINALIZE(RS_RET_IO_ERROR); + break; + } + } + + *pLenBuf = written; +finalize_it: + RETiRet; +} + + +/* Enable KEEPALIVE handling on the socket. + * rgerhards, 2009-06-02 + */ +static rsRetVal +EnableKeepAlive(nsd_t *pNsd) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + int ret; + int optval; + socklen_t optlen; + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + optval = 1; + optlen = sizeof(optval); + ret = setsockopt(pThis->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); + if(ret < 0) { + dbgprintf("EnableKeepAlive socket call returns error %d\n", ret); + ABORT_FINALIZE(RS_RET_ERR); + } + + dbgprintf("KEEPALIVE enabled for nsd %p\n", pThis); + +finalize_it: + RETiRet; +} + + +/* open a connection to a remote host (server). + * rgerhards, 2008-03-19 + */ +static rsRetVal +Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + struct addrinfo *res = NULL; + struct addrinfo hints; + + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(port != NULL); + assert(host != NULL); + assert(pThis->sock == -1); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if(getaddrinfo((char*)host, (char*)port, &hints, &res) != 0) { + dbgprintf("error %d in getaddrinfo\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if((pThis->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if(connect(pThis->sock, res->ai_addr, res->ai_addrlen) != 0) { + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + sockClose(&pThis->sock); + } + + RETiRet; +} + + +/* get the remote hostname. The returned hostname must be freed by the + * caller. + * rgerhards, 2008-04-24 + */ +static rsRetVal +GetRemoteHName(nsd_t *pNsd, uchar **ppszHName) +{ + DEFiRet; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(ppszHName != NULL); + + // TODO: how can the RemHost be empty? + CHKmalloc(*ppszHName = (uchar*)strdup(pThis->pRemHostName == NULL ? "" : (char*) pThis->pRemHostName)); + +finalize_it: + RETiRet; +} + + +/* This function checks if the connection is still alive - well, kind of... It + * is primarily being used for plain TCP syslog and it is quite a hack. However, + * as it seems to work, it is worth supporting it. The bottom line is that it + * should not be called by anything else but a plain tcp syslog sender. + * In order for it to work, it must be called *immediately* *before* the send() + * call. For details about what is done, see here: + * http://blog.gerhards.net/2008/06/getting-bit-more-reliability-from-plain.html + * rgerhards, 2008-06-09 + */ +static rsRetVal +CheckConnection(nsd_t *pNsd) +{ + DEFiRet; + int rc; + char msgbuf[1]; /* dummy */ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + rc = recv(pThis->sock, msgbuf, 1, MSG_DONTWAIT | MSG_PEEK); + if(rc == 0) { + dbgprintf("CheckConnection detected broken connection - closing it\n"); + /* in this case, the remote peer had shut down the connection and we + * need to close our side, too. + */ + sockClose(&pThis->sock); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } +finalize_it: + RETiRet; +} + + +/* get the remote host's IP address. Caller must Destruct the object. + */ +static rsRetVal +GetRemoteIP(nsd_t *pNsd, prop_t **ip) +{ + DEFiRet; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + prop.AddRef(pThis->remoteIP); + *ip = pThis->remoteIP; + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nsd_ptcp) +CODESTARTobjQueryInterface(nsd_ptcp) + if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = (rsRetVal(*)(nsd_t**)) nsd_ptcpConstruct; + pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_ptcpDestruct; + pIf->Abort = Abort; + pIf->GetRemAddr = GetRemAddr; + pIf->GetSock = GetSock; + pIf->SetSock = SetSock; + pIf->SetMode = SetMode; + pIf->SetAuthMode = SetAuthMode; + pIf->SetPermPeers = SetPermPeers; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->Connect = Connect; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; + pIf->CheckConnection = CheckConnection; + pIf->EnableKeepAlive = EnableKeepAlive; +finalize_it: +ENDobjQueryInterface(nsd_ptcp) + + +/* exit our class + */ +BEGINObjClassExit(nsd_ptcp, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsd_ptcp) + /* release objects we no longer need */ + objRelease(net, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrm, DONT_LOAD_LIB); + objRelease(netstrms, LM_NETSTRMS_FILENAME); +ENDObjClassExit(nsd_ptcp) + + +/* Initialize the nsd_ptcp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsd_ptcp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(net, CORE_COMPONENT)); + CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(netstrm, DONT_LOAD_LIB)); + + /* set our own handlers */ +ENDObjClassInit(nsd_ptcp) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit +# ifdef HAVE_EPOLL_CREATE /* module only available if epoll() is supported! */ + nsdpoll_ptcpClassExit(); +# endif + nsdsel_ptcpClassExit(); + nsd_ptcpClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(nsd_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + CHKiRet(nsdsel_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +# ifdef HAVE_EPOLL_CREATE /* module only available if epoll() is supported! */ + CHKiRet(nsdpoll_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +# endif +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/nsd_ptcp.h b/runtime/nsd_ptcp.h new file mode 100644 index 00000000..ed6b8565 --- /dev/null +++ b/runtime/nsd_ptcp.h @@ -0,0 +1,48 @@ +/* An implementation of the nsd interface for plain tcp sockets. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDED_NSD_PTCP_H +#define INCLUDED_NSD_PTCP_H + +#include <sys/socket.h> + +#include "nsd.h" +typedef nsd_if_t nsd_ptcp_if_t; /* we just *implement* this interface */ + +/* the nsd_ptcp object */ +struct nsd_ptcp_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + prop_t *remoteIP; /**< IP address of remote peer (currently used in server mode, only) */ + uchar *pRemHostName; /**< host name of remote peer (currently used in server mode, only) */ + struct sockaddr_storage remAddr; /**< remote addr as sockaddr - used for legacy ACL code */ + int sock; /**< the socket we use for regular, single-socket, operations */ +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsd_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsd_ptcp); + +/* the name of our library binary */ +#define LM_NSD_PTCP_FILENAME "lmnsd_ptcp" + +#endif /* #ifndef INCLUDED_NSD_PTCP_H */ diff --git a/runtime/nsdpoll_ptcp.c b/runtime/nsdpoll_ptcp.c new file mode 100644 index 00000000..8d95811a --- /dev/null +++ b/runtime/nsdpoll_ptcp.c @@ -0,0 +1,316 @@ +/* nsdpoll_ptcp.c + * + * An implementation of the nsd epoll() interface for plain tcp sockets. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#ifdef HAVE_EPOLL_CREATE /* this module requires epoll! */ + +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#if HAVE_SYS_EPOLL_H +# include <sys/epoll.h> +#endif + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "srUtils.h" +#include "nspoll.h" +#include "nsd_ptcp.h" +#include "nsdpoll_ptcp.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + + +/* -START------------------------- helpers for event list ------------------------------------ */ + +/* add new entry to list. We assume that the fd is not already present and DO NOT check this! + * Returns newly created entry in pEvtLst. + * Note that we currently need to use level-triggered mode, because the upper layers do not work + * in parallel. As such, in edge-triggered mode we may not get notified, because new data comes + * in after we have read everything that was present. To use ET mode, we need to change the upper + * peers so that they immediately start a new wait before processing the data read. That obviously + * requires more elaborate redesign and we postpone this until the current more simplictic mode has + * been proven OK in practice. + * rgerhards, 2009-11-18 + */ +static inline rsRetVal +addEvent(nsdpoll_ptcp_t *pThis, int id, void *pUsr, int mode, nsd_ptcp_t *pSock, nsdpoll_epollevt_lst_t **pEvtLst) { + nsdpoll_epollevt_lst_t *pNew; + DEFiRet; + + CHKmalloc(pNew = (nsdpoll_epollevt_lst_t*) calloc(1, sizeof(nsdpoll_epollevt_lst_t))); + pNew->id = id; + pNew->pUsr = pUsr; + pNew->pSock = pSock; + pNew->event.events = 0; /* TODO: at some time we should be able to use EPOLLET */ + //pNew->event.events = EPOLLET; + if(mode & NSDPOLL_IN) + pNew->event.events |= EPOLLIN; + if(mode & NSDPOLL_OUT) + pNew->event.events |= EPOLLOUT; + pNew->event.data.ptr = pNew; + pthread_mutex_lock(&pThis->mutEvtLst); + pNew->pNext = pThis->pRoot; + pThis->pRoot = pNew; + pthread_mutex_unlock(&pThis->mutEvtLst); + *pEvtLst = pNew; + +finalize_it: + RETiRet; +} + + +/* find and unlink the entry identified by id/pUsr from the list. + * rgerhards, 2009-11-23 + */ +static inline rsRetVal +unlinkEvent(nsdpoll_ptcp_t *pThis, int id, void *pUsr, nsdpoll_epollevt_lst_t **ppEvtLst) { + nsdpoll_epollevt_lst_t *pEvtLst; + nsdpoll_epollevt_lst_t *pPrev = NULL; + DEFiRet; + + pthread_mutex_lock(&pThis->mutEvtLst); + pEvtLst = pThis->pRoot; + while(pEvtLst != NULL && !(pEvtLst->id == id && pEvtLst->pUsr == pUsr)) { + pPrev = pEvtLst; + pEvtLst = pEvtLst->pNext; + } + if(pEvtLst == NULL) + ABORT_FINALIZE(RS_RET_NOT_FOUND); + + *ppEvtLst = pEvtLst; + + /* unlink */ + if(pPrev == NULL) + pThis->pRoot = pEvtLst->pNext; + else + pPrev->pNext = pEvtLst->pNext; + +finalize_it: + pthread_mutex_unlock(&pThis->mutEvtLst); + RETiRet; +} + + +/* destruct the provided element. It must already be unlinked from the list. + * rgerhards, 2009-11-23 + */ +static inline rsRetVal +delEvent(nsdpoll_epollevt_lst_t **ppEvtLst) { + DEFiRet; + free(*ppEvtLst); + *ppEvtLst = NULL; + RETiRet; +} + + +/* -END--------------------------- helpers for event list ------------------------------------ */ + + +/* Standard-Constructor + */ +BEGINobjConstruct(nsdpoll_ptcp) /* be sure to specify the object type also in END macro! */ +#if defined(EPOLL_CLOEXEC) && defined(HAVE_EPOLL_CREATE1) + DBGPRINTF("nsdpoll_ptcp uses epoll_create1()\n"); + pThis->efd = epoll_create1(EPOLL_CLOEXEC); + if(pThis->efd < 0 && errno == ENOSYS) +#endif + { + DBGPRINTF("nsdpoll_ptcp uses epoll_create()\n"); + pThis->efd = epoll_create(100); /* size is ignored in newer kernels, but 100 is not bad... */ + } + + if(pThis->efd < 0) { + DBGPRINTF("epoll_create1() could not create fd\n"); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + pthread_mutex_init(&pThis->mutEvtLst, NULL); +finalize_it: +ENDobjConstruct(nsdpoll_ptcp) + + +/* destructor for the nsdpoll_ptcp object */ +BEGINobjDestruct(nsdpoll_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */ + nsdpoll_epollevt_lst_t *node; + nsdpoll_epollevt_lst_t *nextnode; +CODESTARTobjDestruct(nsdpoll_ptcp) + /* we check if the epoll list still holds entries. This may happen, but + * is a bit unusual. + */ + if(pThis->pRoot != NULL) { + for(node = pThis->pRoot ; node != NULL ; node = nextnode) { + nextnode = node->pNext; + dbgprintf("nsdpoll_ptcp destruct, need to destruct node %p\n", node); + delEvent(&node); + } + } + pthread_mutex_destroy(&pThis->mutEvtLst); +ENDobjDestruct(nsdpoll_ptcp) + + +/* Modify socket set */ +static rsRetVal +Ctl(nsdpoll_t *pNsdpoll, nsd_t *pNsd, int id, void *pUsr, int mode, int op) { + nsdpoll_ptcp_t *pThis = (nsdpoll_ptcp_t*) pNsdpoll; + nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd; + nsdpoll_epollevt_lst_t *pEventLst; + int errSave; + char errStr[512]; + DEFiRet; + + if(op == NSDPOLL_ADD) { + dbgprintf("adding nsdpoll entry %d/%p, sock %d\n", id, pUsr, pSock->sock); + CHKiRet(addEvent(pThis, id, pUsr, mode, pSock, &pEventLst)); + if(epoll_ctl(pThis->efd, EPOLL_CTL_ADD, pSock->sock, &pEventLst->event) < 0) { + errSave = errno; + rs_strerror_r(errSave, errStr, sizeof(errStr)); + errmsg.LogError(errSave, RS_RET_ERR_EPOLL_CTL, + "epoll_ctl failed on fd %d, id %d/%p, op %d with %s\n", + pSock->sock, id, pUsr, mode, errStr); + } + } else if(op == NSDPOLL_DEL) { + dbgprintf("removing nsdpoll entry %d/%p, sock %d\n", id, pUsr, pSock->sock); + CHKiRet(unlinkEvent(pThis, id, pUsr, &pEventLst)); + if(epoll_ctl(pThis->efd, EPOLL_CTL_DEL, pSock->sock, &pEventLst->event) < 0) { + errSave = errno; + rs_strerror_r(errSave, errStr, sizeof(errStr)); + errmsg.LogError(errSave, RS_RET_ERR_EPOLL_CTL, + "epoll_ctl failed on fd %d, id %d/%p, op %d with %s\n", + pSock->sock, id, pUsr, mode, errStr); + ABORT_FINALIZE(RS_RET_ERR_EPOLL_CTL); + } + CHKiRet(delEvent(&pEventLst)); + } else { + dbgprintf("program error: invalid NSDPOLL_mode %d - ignoring request\n", op); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + RETiRet; +} + + +/* Wait for io to become ready. After the successful call, idRdy contains the + * id set by the caller for that i/o event, ppUsr is a pointer to a location + * where the user pointer shall be stored. + * numEntries contains the maximum number of entries on entry and the actual + * number of entries actually read on exit. + * rgerhards, 2009-11-18 + */ +static rsRetVal +Wait(nsdpoll_t *pNsdpoll, int timeout, int *numEntries, nsd_epworkset_t workset[]) { + nsdpoll_ptcp_t *pThis = (nsdpoll_ptcp_t*) pNsdpoll; + nsdpoll_epollevt_lst_t *pOurEvt; + struct epoll_event event[128]; + int nfds; + int i; + DEFiRet; + + assert(workset != NULL); + + if(*numEntries > 128) + *numEntries = 128; + DBGPRINTF("doing epoll_wait for max %d events\n", *numEntries); + nfds = epoll_wait(pThis->efd, event, *numEntries, timeout); + if(nfds == -1) { + if(errno == EINTR) { + ABORT_FINALIZE(RS_RET_EINTR); + } else { + DBGPRINTF("epoll() returned with error code %d\n", errno); + ABORT_FINALIZE(RS_RET_ERR_EPOLL); + } + } else if(nfds == 0) { + ABORT_FINALIZE(RS_RET_TIMEOUT); + } + + /* we got valid events, so tell the caller... */ +dbgprintf("epoll returned %d entries\n", nfds); + for(i = 0 ; i < nfds ; ++i) { + pOurEvt = (nsdpoll_epollevt_lst_t*) event[i].data.ptr; + workset[i].id = pOurEvt->id; + workset[i].pUsr = pOurEvt->pUsr; +dbgprintf("epoll push ppusr[%d]: %p\n", i, pOurEvt->pUsr); + } + *numEntries = nfds; + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end support for the epoll() interface ------------------------------ */ + + +/* queryInterface function */ +BEGINobjQueryInterface(nsdpoll_ptcp) +CODESTARTobjQueryInterface(nsdpoll_ptcp) + if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = (rsRetVal(*)(nsdpoll_t**)) nsdpoll_ptcpConstruct; + pIf->Destruct = (rsRetVal(*)(nsdpoll_t**)) nsdpoll_ptcpDestruct; + pIf->Ctl = Ctl; + pIf->Wait = Wait; +finalize_it: +ENDobjQueryInterface(nsdpoll_ptcp) + + +/* exit our class + */ +BEGINObjClassExit(nsdpoll_ptcp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdpoll_ptcp) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(nsdpoll_ptcp) + + +/* Initialize the nsdpoll_ptcp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsdpoll_ptcp, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(nsdpoll_ptcp) +#endif /* #ifdef HAVE_EPOLL_CREATE this module requires epoll! */ + +/* vi:set ai: + */ diff --git a/runtime/nsdpoll_ptcp.h b/runtime/nsdpoll_ptcp.h new file mode 100644 index 00000000..dfefad1b --- /dev/null +++ b/runtime/nsdpoll_ptcp.h @@ -0,0 +1,61 @@ +/* An implementation of the nsd poll interface for plain tcp sockets. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSDPOLL_PTCP_H +#define INCLUDED_NSDPOLL_PTCP_H + +#include "nsd.h" +#if HAVE_SYS_EPOLL_H +# include <sys/epoll.h> +#endif +typedef nsdpoll_if_t nsdpoll_ptcp_if_t; /* we just *implement* this interface */ +/* a helper object to keep track of the epoll event records + * Note that we need to keep track of that list because we need to + * free the events when they are no longer needed. + */ +typedef struct nsdpoll_epollevt_lst_s nsdpoll_epollevt_lst_t; +struct nsdpoll_epollevt_lst_s { +#if HAVE_SYS_EPOLL_H + epoll_event_t event; +#endif + int id; + void *pUsr; + nsd_ptcp_t *pSock; /* our associated netstream driver data */ + nsdpoll_epollevt_lst_t *pNext; +}; + +/* the nsdpoll_ptcp object */ +struct nsdpoll_ptcp_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + int efd; /* file descriptor used by epoll */ + nsdpoll_epollevt_lst_t *pRoot; /* Root of the epoll event list */ + pthread_mutex_t mutEvtLst; +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsdpoll_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsdpoll_ptcp); + +#endif /* #ifndef INCLUDED_NSDPOLL_PTCP_H */ diff --git a/runtime/nsdsel_gtls.c b/runtime/nsdsel_gtls.c new file mode 100644 index 00000000..b086add8 --- /dev/null +++ b/runtime/nsdsel_gtls.c @@ -0,0 +1,276 @@ +/* nsdsel_gtls.c + * + * An implementation of the nsd select() interface for GnuTLS. + * + * Copyright (C) 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> +#include <gnutls/gnutls.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "nsd.h" +#include "nsd_gtls.h" +#include "nsd_ptcp.h" +#include "nsdsel_ptcp.h" +#include "nsdsel_gtls.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(nsdsel_ptcp) + + +/* Standard-Constructor + */ +BEGINobjConstruct(nsdsel_gtls) /* be sure to specify the object type also in END macro! */ + iRet = nsdsel_ptcp.Construct(&pThis->pTcp); +ENDobjConstruct(nsdsel_gtls) + + +/* destructor for the nsdsel_gtls object */ +BEGINobjDestruct(nsdsel_gtls) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsdsel_gtls) + if(pThis->pTcp != NULL) + nsdsel_ptcp.Destruct(&pThis->pTcp); +ENDobjDestruct(nsdsel_gtls) + + +/* Add a socket to the select set */ +static rsRetVal +Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp) +{ + DEFiRet; + nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel; + nsd_gtls_t *pNsdGTLS = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert(pThis, nsdsel_gtls); + ISOBJ_TYPE_assert(pNsdGTLS, nsd_gtls); + if(pNsdGTLS->iMode == 1) { + if(waitOp == NSDSEL_RD && gtlsHasRcvInBuffer(pNsdGTLS)) { + ++pThis->iBufferRcvReady; + dbgprintf("nsdsel_gtls: data already present in buffer, initiating " + "dummy select %p->iBufferRcvReady=%d\n", + pThis, pThis->iBufferRcvReady); + FINALIZE; + } + if(pNsdGTLS->rtryCall != gtlsRtry_None) { + if(gnutls_record_get_direction(pNsdGTLS->sess) == 0) { + CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, NSDSEL_RD)); + } else { + CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, NSDSEL_WR)); + } + FINALIZE; + } + } + + /* if we reach this point, we need no special handling */ + CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, waitOp)); + +finalize_it: + RETiRet; +} + + +/* perform the select() piNumReady returns how many descriptors are ready for IO + * TODO: add timeout! + */ +static rsRetVal +Select(nsdsel_t *pNsdsel, int *piNumReady) +{ + DEFiRet; + nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel; + + ISOBJ_TYPE_assert(pThis, nsdsel_gtls); + if(pThis->iBufferRcvReady > 0) { + /* we still have data ready! */ + *piNumReady = pThis->iBufferRcvReady; + dbgprintf("nsdsel_gtls: doing dummy select, data present\n"); + } else { + iRet = nsdsel_ptcp.Select(pThis->pTcp, piNumReady); + } + + RETiRet; +} + + +/* retry an interrupted GTLS operation + * rgerhards, 2008-04-30 + */ +static rsRetVal +doRetry(nsd_gtls_t *pNsd) +{ + DEFiRet; + int gnuRet; + + dbgprintf("GnuTLS requested retry of %d operation - executing\n", pNsd->rtryCall); + + /* We follow a common scheme here: first, we do the systen call and + * then we check the result. So far, the result is checked after the + * switch, because the result check is the same for all calls. Note that + * this may change once we deal with the read and write calls (but + * probably this becomes an issue only when we begin to work on TLS + * for relp). -- rgerhards, 2008-04-30 + */ + switch(pNsd->rtryCall) { + case gtlsRtry_handshake: + gnuRet = gnutls_handshake(pNsd->sess); + if(gnuRet == 0) { + pNsd->rtryCall = gtlsRtry_None; /* we are done */ + /* we got a handshake, now check authorization */ + CHKiRet(gtlsChkPeerAuth(pNsd)); + } + break; + case gtlsRtry_recv: + dbgprintf("retrying gtls recv, nsd: %p\n", pNsd); + CHKiRet(gtlsRecordRecv(pNsd)); + pNsd->rtryCall = gtlsRtry_None; /* we are done */ + gnuRet = 0; + break; + default: + assert(0); /* this shall not happen! */ + dbgprintf("ERROR: pNsd->rtryCall invalid in nsdsel_gtls.c:%d\n", __LINE__); + gnuRet = 0; /* if it happens, we have at least a defined behaviour... ;) */ + break; + } + + if(gnuRet == 0) { + pNsd->rtryCall = gtlsRtry_None; /* we are done */ + } else if(gnuRet != GNUTLS_E_AGAIN && gnuRet != GNUTLS_E_INTERRUPTED) { + uchar *pErr = gtlsStrerror(gnuRet); + dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); + free(pErr); + pNsd->rtryCall = gtlsRtry_None; /* we are also done... ;) */ + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); + } + /* if we are interrupted once again (else case), we do not need to + * change our status because we are already setup for retries. + */ + +finalize_it: + if(iRet != RS_RET_OK && iRet != RS_RET_CLOSED && iRet != RS_RET_RETRY) + pNsd->bAbortConn = 1; /* request abort */ +dbgprintf("XXXXXX: doRetry: iRet %d, pNsd->bAbortConn %d\n", iRet, pNsd->bAbortConn); + RETiRet; +} + + +/* check if a socket is ready for IO */ +static rsRetVal +IsReady(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady) +{ + DEFiRet; + nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel; + nsd_gtls_t *pNsdGTLS = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert(pThis, nsdsel_gtls); + ISOBJ_TYPE_assert(pNsdGTLS, nsd_gtls); + if(pNsdGTLS->iMode == 1) { + if(waitOp == NSDSEL_RD && gtlsHasRcvInBuffer(pNsdGTLS)) { + *pbIsReady = 1; + --pThis->iBufferRcvReady; /* one "pseudo-read" less */ + dbgprintf("nsdl_gtls: dummy read, decermenting %p->iBufRcvReady, now %d\n", + pThis, pThis->iBufferRcvReady); + FINALIZE; + } + if(pNsdGTLS->rtryCall != gtlsRtry_None) { + CHKiRet(doRetry(pNsdGTLS)); + /* we used this up for our own internal processing, so the socket + * is not ready from the upper layer point of view. + */ + *pbIsReady = 0; + FINALIZE; + } + /* now we must ensure that we do not fall back to PTCP if we have + * done a "dummy" select. In that case, we know when the predicate + * is not matched here, we do not have data available for this + * socket. -- rgerhards, 2010-11-20 + */ + if(pThis->iBufferRcvReady) { + dbgprintf("nsd_gtls: dummy read, buffer not available for this FD\n"); + *pbIsReady = 0; + FINALIZE; + } + } + + CHKiRet(nsdsel_ptcp.IsReady(pThis->pTcp, pNsdGTLS->pTcp, waitOp, pbIsReady)); + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end support for the select() interface ------------------------------ */ + + +/* queryInterface function */ +BEGINobjQueryInterface(nsdsel_gtls) +CODESTARTobjQueryInterface(nsdsel_gtls) + if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = (rsRetVal(*)(nsdsel_t**)) nsdsel_gtlsConstruct; + pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_gtlsDestruct; + pIf->Add = Add; + pIf->Select = Select; + pIf->IsReady = IsReady; +finalize_it: +ENDobjQueryInterface(nsdsel_gtls) + + +/* exit our class + */ +BEGINObjClassExit(nsdsel_gtls, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdsel_gtls) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(nsdsel_ptcp, LM_NSD_PTCP_FILENAME); +ENDObjClassExit(nsdsel_gtls) + + +/* Initialize the nsdsel_gtls class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsdsel_gtls, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(nsdsel_ptcp, LM_NSD_PTCP_FILENAME)); + + /* set our own handlers */ +ENDObjClassInit(nsdsel_gtls) +/* vi:set ai: + */ diff --git a/runtime/nsdsel_gtls.h b/runtime/nsdsel_gtls.h new file mode 100644 index 00000000..eb96f6eb --- /dev/null +++ b/runtime/nsdsel_gtls.h @@ -0,0 +1,41 @@ +/* An implementation of the nsd select interface for GnuTLS. + * + * Copyright (C) 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDED_NSDSEL_GTLS_H +#define INCLUDED_NSDSEL_GTLS_H + +#include "nsd.h" +typedef nsdsel_if_t nsdsel_gtls_if_t; /* we just *implement* this interface */ + +/* the nsdsel_gtls object */ +struct nsdsel_gtls_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsdsel_t *pTcp; /* our aggregated ptcp sel handler (which does almost everything) */ + int iBufferRcvReady; /* number of descriptiors where no RD select is needed because we have data in buf */ +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsdsel_gtlsCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsdsel_gtls); + +#endif /* #ifndef INCLUDED_NSDSEL_GTLS_H */ diff --git a/runtime/nsdsel_ptcp.c b/runtime/nsdsel_ptcp.c new file mode 100644 index 00000000..e2cfca7c --- /dev/null +++ b/runtime/nsdsel_ptcp.c @@ -0,0 +1,227 @@ +/* nsdsel_ptcp.c + * + * An implementation of the nsd select() interface for plain tcp sockets. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "nsd_ptcp.h" +#include "nsdsel_ptcp.h" +#include "unlimited_select.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + + +/* Standard-Constructor + */ +BEGINobjConstruct(nsdsel_ptcp) /* be sure to specify the object type also in END macro! */ + pThis->maxfds = 0; +#ifdef USE_UNLIMITED_SELECT + pThis->pReadfds = calloc(1, glbl.GetFdSetSize()); + pThis->pWritefds = calloc(1, glbl.GetFdSetSize()); +#else + FD_ZERO(&pThis->readfds); + FD_ZERO(&pThis->writefds); +#endif +ENDobjConstruct(nsdsel_ptcp) + + +/* destructor for the nsdsel_ptcp object */ +BEGINobjDestruct(nsdsel_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsdsel_ptcp) +#ifdef USE_UNLIMITED_SELECT + freeFdSet(pThis->pReadfds); + freeFdSet(pThis->pWritefds); +#endif +ENDobjDestruct(nsdsel_ptcp) + + +/* Add a socket to the select set */ +static rsRetVal +Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp) +{ + DEFiRet; + nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel; + nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd; +#ifdef USE_UNLIMITED_SELECT + fd_set *pReadfds = pThis->pReadfds; + fd_set *pWritefds = pThis->pWritefds; +#else + fd_set *pReadfds = &pThis->readfds; + fd_set *pWritefds = &pThis->writefds; +#endif + + ISOBJ_TYPE_assert(pSock, nsd_ptcp); + ISOBJ_TYPE_assert(pThis, nsdsel_ptcp); + + switch(waitOp) { + case NSDSEL_RD: + FD_SET(pSock->sock, pReadfds); + break; + case NSDSEL_WR: + FD_SET(pSock->sock, pWritefds); + break; + case NSDSEL_RDWR: + FD_SET(pSock->sock, pReadfds); + FD_SET(pSock->sock, pWritefds); + break; + } + + if(pSock->sock > pThis->maxfds) + pThis->maxfds = pSock->sock; + + RETiRet; +} + + +/* perform the select() piNumReady returns how many descriptors are ready for IO + * TODO: add timeout! + */ +static rsRetVal +Select(nsdsel_t *pNsdsel, int *piNumReady) +{ + DEFiRet; + int i; + nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel; +#ifdef USE_UNLIMITED_SELECT + fd_set *pReadfds = pThis->pReadfds; + fd_set *pWritefds = pThis->pWritefds; +#else + fd_set *pReadfds = &pThis->readfds; + fd_set *pWritefds = &pThis->writefds; +#endif + + ISOBJ_TYPE_assert(pThis, nsdsel_ptcp); + assert(piNumReady != NULL); + + if(Debug) { // TODO: debug setting! + // TODO: name in dbgprintf! + dbgprintf("--------<NSDSEL_PTCP> calling select, active fds (max %d): ", pThis->maxfds); + for(i = 0; i <= pThis->maxfds; ++i) + if(FD_ISSET(i, pReadfds) || FD_ISSET(i, pWritefds)) + dbgprintf("%d ", i); + dbgprintf("\n"); + } + + /* now do the select */ + *piNumReady = select(pThis->maxfds+1, pReadfds, pWritefds, NULL, NULL); + + RETiRet; +} + + +/* check if a socket is ready for IO */ +static rsRetVal +IsReady(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady) +{ + DEFiRet; + nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel; + nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd; +#ifdef USE_UNLIMITED_SELECT + fd_set *pReadfds = pThis->pReadfds; + fd_set *pWritefds = pThis->pWritefds; +#else + fd_set *pReadfds = &pThis->readfds; + fd_set *pWritefds = &pThis->writefds; +#endif + + ISOBJ_TYPE_assert(pThis, nsdsel_ptcp); + ISOBJ_TYPE_assert(pSock, nsd_ptcp); + assert(pbIsReady != NULL); + + switch(waitOp) { + case NSDSEL_RD: + *pbIsReady = FD_ISSET(pSock->sock, pReadfds); + break; + case NSDSEL_WR: + *pbIsReady = FD_ISSET(pSock->sock, pWritefds); + break; + case NSDSEL_RDWR: + *pbIsReady = FD_ISSET(pSock->sock, pReadfds) + | FD_ISSET(pSock->sock, pWritefds); + break; + } + + RETiRet; +} + + +/* ------------------------------ end support for the select() interface ------------------------------ */ + + +/* queryInterface function */ +BEGINobjQueryInterface(nsdsel_ptcp) +CODESTARTobjQueryInterface(nsdsel_ptcp) + if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = (rsRetVal(*)(nsdsel_t**)) nsdsel_ptcpConstruct; + pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_ptcpDestruct; + pIf->Add = Add; + pIf->Select = Select; + pIf->IsReady = IsReady; +finalize_it: +ENDobjQueryInterface(nsdsel_ptcp) + + +/* exit our class + */ +BEGINObjClassExit(nsdsel_ptcp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdsel_ptcp) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(nsdsel_ptcp) + + +/* Initialize the nsdsel_ptcp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsdsel_ptcp, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(nsdsel_ptcp) +/* vi:set ai: + */ diff --git a/runtime/nsdsel_ptcp.h b/runtime/nsdsel_ptcp.h new file mode 100644 index 00000000..f9ec8210 --- /dev/null +++ b/runtime/nsdsel_ptcp.h @@ -0,0 +1,49 @@ +/* An implementation of the nsd select interface for plain tcp sockets. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSDSEL_PTCP_H +#define INCLUDED_NSDSEL_PTCP_H + +#include "nsd.h" +typedef nsdsel_if_t nsdsel_ptcp_if_t; /* we just *implement* this interface */ + +/* the nsdsel_ptcp object */ +struct nsdsel_ptcp_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + int maxfds; +#ifdef USE_UNLIMITED_SELECT + fd_set *pReadfds; + fd_set *pWritefds; +#else + fd_set readfds; + fd_set writefds; +#endif +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsdsel_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsdsel_ptcp); + +#endif /* #ifndef INCLUDED_NSDSEL_PTCP_H */ diff --git a/runtime/nspoll.c b/runtime/nspoll.c new file mode 100644 index 00000000..a936b255 --- /dev/null +++ b/runtime/nspoll.c @@ -0,0 +1,195 @@ +/* nspoll.c + * + * This is an io waiter interface utilizing the much-more-efficient poll/epoll API. + * Note that it may not always be available for a given driver. If so, that is reported + * back to the upper peer which then should consult a nssel-based io waiter. + * + * Work on this module begun 2009-11-18 by Rainer Gerhards. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include "rsyslog.h" +#include "obj.h" +#include "module-template.h" +#include "netstrm.h" +#include "nspoll.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) + + +/* load our low-level driver. This must be done before any + * driver-specific functions (allmost all...) can be carried + * out. Note that the driver's .ifIsLoaded is correctly + * initialized by calloc() and we depend on that. Please note that + * we do some name-mangeling. We know that each nsd driver also needs + * a nspoll driver. So we simply append "sel" to the nsd driver name: This, + * of course, means that the driver name must match these rules, but that + * shouldn't be a real problem. + * WARNING: this code is mostly identical to similar code in + * netstrms.c - TODO: abstract it and move it to some common place. + * rgerhards, 2008-04-28 + */ +static rsRetVal +loadDrvr(nspoll_t *pThis) +{ + DEFiRet; + uchar *pBaseDrvrName; + uchar szDrvrName[48]; /* 48 shall be large enough */ + + pBaseDrvrName = pThis->pBaseDrvrName; + if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */ + pBaseDrvrName = glbl.GetDfltNetstrmDrvr(); + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsdpoll_%s", pBaseDrvrName) == sizeof(szDrvrName)) + ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG); + CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName)); + + pThis->Drvr.ifVersion = nsdCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean + * enough. -- rgerhards, 2008-04-18 + */ + CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pDrvrName != NULL) + free(pThis->pDrvrName); + pThis->pDrvrName = NULL; + } + RETiRet; +} + + +/* Standard-Constructor */ +BEGINobjConstruct(nspoll) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(nspoll) + + +/* destructor for the nspoll object */ +BEGINobjDestruct(nspoll) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nspoll) + if(pThis->pDrvrData != NULL) + pThis->Drvr.Destruct(&pThis->pDrvrData); + + /* and now we must release our driver, if we got one. We use the presence of + * a driver name string as load indicator (because we also need that string + * to release the driver + */ + if(pThis->pDrvrName != NULL) { + obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr); + free(pThis->pDrvrName); + } +ENDobjDestruct(nspoll) + + +/* ConstructionFinalizer */ +static rsRetVal +ConstructFinalize(nspoll_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nspoll); + CHKiRet(loadDrvr(pThis)); + CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData)); +finalize_it: + RETiRet; +} + + +/* Carries out the actual wait (all done in lower layers) + */ +static rsRetVal +Wait(nspoll_t *pThis, int timeout, int *numEntries, nsd_epworkset_t workset[]) { + DEFiRet; + ISOBJ_TYPE_assert(pThis, nspoll); + assert(workset != NULL); + iRet = pThis->Drvr.Wait(pThis->pDrvrData, timeout, numEntries, workset); + RETiRet; +} + + +/* semantics like the epoll_ctl() function, does the same thing. + * rgerhards, 2009-11-18 + */ +static rsRetVal +Ctl(nspoll_t *pThis, netstrm_t *pStrm, int id, void *pUsr, int mode, int op) { + DEFiRet; + ISOBJ_TYPE_assert(pThis, nspoll); + iRet = pThis->Drvr.Ctl(pThis->pDrvrData, pStrm->pDrvrData, id, pUsr, mode, op); + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nspoll) +CODESTARTobjQueryInterface(nspoll) + if(pIf->ifVersion != nspollCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = nspollConstruct; + pIf->ConstructFinalize = ConstructFinalize; + pIf->Destruct = nspollDestruct; + pIf->Wait = Wait; + pIf->Ctl = Ctl; +finalize_it: +ENDobjQueryInterface(nspoll) + + +/* exit our class + */ +BEGINObjClassExit(nspoll, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nspoll) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); +ENDObjClassExit(nspoll) + + +/* Initialize the nspoll class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nspoll, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + DBGPRINTF("doing nspollClassInit\n"); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(nspoll) +/* vi:set ai: + */ diff --git a/runtime/nspoll.h b/runtime/nspoll.h new file mode 100644 index 00000000..037f6c38 --- /dev/null +++ b/runtime/nspoll.h @@ -0,0 +1,66 @@ +/* Definitions for the nspoll io activity waiter + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSPOLL_H +#define INCLUDED_NSPOLL_H + +#include "netstrms.h" + +/* some operations to be portable when we do not have epoll() available */ +#define NSDPOLL_ADD 1 +#define NSDPOLL_DEL 2 + +/* and some mode specifiers for waiting on input/output */ +#define NSDPOLL_IN 1 /* EPOLLIN */ +#define NSDPOLL_OUT 2 /* EPOLLOUT */ +/* next is 4, 8, 16, ... - must be bit values, as they are ored! */ + +/* the nspoll object */ +struct nspoll_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pDrvrData; /**< the driver's data elements */ + uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */ + uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */ + nsdpoll_if_t Drvr; /**< our stream driver */ +}; + + +/* interface */ +BEGINinterface(nspoll) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nspoll_t **ppThis); + rsRetVal (*ConstructFinalize)(nspoll_t *pThis); + rsRetVal (*Destruct)(nspoll_t **ppThis); + rsRetVal (*Wait)(nspoll_t *pNsdpoll, int timeout, int *numEntries, nsd_epworkset_t workset[]); + rsRetVal (*Ctl)(nspoll_t *pNsdpoll, netstrm_t *pStrm, int id, void *pUsr, int mode, int op); + rsRetVal (*IsEPollSupported)(void); /* static method */ +ENDinterface(nspoll) +#define nspollCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ +/* interface change in v2 is that wait supports multiple return objects */ + +/* prototypes */ +PROTOTYPEObj(nspoll); + +/* the name of our library binary */ +#define LM_NSPOLL_FILENAME LM_NETSTRMS_FILENAME + +#endif /* #ifndef INCLUDED_NSPOLL_H */ diff --git a/runtime/nssel.c b/runtime/nssel.c new file mode 100644 index 00000000..751dae9b --- /dev/null +++ b/runtime/nssel.c @@ -0,0 +1,226 @@ +/* nssel.c + * + * The io waiter is a helper object enabling us to wait on a set of streams to become + * ready for IO - this is modelled after select(). We need this, because + * stream drivers may have different concepts. Consequently, + * the structure must contain nsd_t's from the same stream driver type + * only. This is implemented as a singly-linked list where every + * new element is added at the top of the list. + * + * Work on this module begun 2008-04-22 by Rainer Gerhards. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include "rsyslog.h" +#include "obj.h" +#include "module-template.h" +#include "netstrm.h" +#include "nssel.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) + + +/* load our low-level driver. This must be done before any + * driver-specific functions (allmost all...) can be carried + * out. Note that the driver's .ifIsLoaded is correctly + * initialized by calloc() and we depend on that. Please note that + * we do some name-mangeling. We know that each nsd driver also needs + * a nssel driver. So we simply append "sel" to the nsd driver name: This, + * of course, means that the driver name must match these rules, but that + * shouldn't be a real problem. + * WARNING: this code is mostly identical to similar code in + * netstrms.c - TODO: abstract it and move it to some common place. + * rgerhards, 2008-04-28 + */ +static rsRetVal +loadDrvr(nssel_t *pThis) +{ + DEFiRet; + uchar *pBaseDrvrName; + uchar szDrvrName[48]; /* 48 shall be large enough */ + + pBaseDrvrName = pThis->pBaseDrvrName; + if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */ + pBaseDrvrName = glbl.GetDfltNetstrmDrvr(); + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsdsel_%s", pBaseDrvrName) == sizeof(szDrvrName)) + ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG); + CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName)); + + pThis->Drvr.ifVersion = nsdCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean + * enough. -- rgerhards, 2008-04-18 + */ + CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pDrvrName != NULL) + free(pThis->pDrvrName); + pThis->pDrvrName = NULL; + } + RETiRet; +} + + +/* Standard-Constructor */ +BEGINobjConstruct(nssel) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(nssel) + + +/* destructor for the nssel object */ +BEGINobjDestruct(nssel) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nssel) + if(pThis->pDrvrData != NULL) + pThis->Drvr.Destruct(&pThis->pDrvrData); + + /* and now we must release our driver, if we got one. We use the presence of + * a driver name string as load indicator (because we also need that string + * to release the driver + */ + if(pThis->pDrvrName != NULL) { + obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr); + free(pThis->pDrvrName); + } +ENDobjDestruct(nssel) + + +/* ConstructionFinalizer */ +static rsRetVal +ConstructFinalize(nssel_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nssel); + CHKiRet(loadDrvr(pThis)); + CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData)); +finalize_it: + RETiRet; +} + + +/* Add a stream object to the current select() set. + * Note that a single stream may have multiple "sockets" if + * it is a listener. If so, all of them are begin added. + */ +static rsRetVal +Add(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nssel); + ISOBJ_TYPE_assert(pStrm, netstrm); + + CHKiRet(pThis->Drvr.Add(pThis->pDrvrData, pStrm->pDrvrData, waitOp)); + +finalize_it: + RETiRet; +} + + +/* wait for IO to happen on one of our netstreams. iNumReady has + * the number of ready "sockets" after the call. This function blocks + * until some are ready. EAGAIN is retried. + */ +static rsRetVal +Wait(nssel_t *pThis, int *piNumReady) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nssel); + assert(piNumReady != NULL); + iRet = pThis->Drvr.Select(pThis->pDrvrData, piNumReady); + RETiRet; +} + + +/* Check if a stream is ready for IO. *piNumReady contains the remaining number + * of ready streams. Note that this function may say the stream is not ready + * but still decrement *piNumReady. This can happen when (e.g. with TLS) the low + * level driver requires some IO which is hidden from the upper layer point of view. + * rgerhards, 2008-04-23 + */ +static rsRetVal +IsReady(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp, int *pbIsReady, int *piNumReady) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nssel); + ISOBJ_TYPE_assert(pStrm, netstrm); + assert(pbIsReady != NULL); + assert(piNumReady != NULL); + iRet = pThis->Drvr.IsReady(pThis->pDrvrData, pStrm->pDrvrData, waitOp, pbIsReady); + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nssel) +CODESTARTobjQueryInterface(nssel) + if(pIf->ifVersion != nsselCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = nsselConstruct; + pIf->ConstructFinalize = ConstructFinalize; + pIf->Destruct = nsselDestruct; + pIf->Add = Add; + pIf->Wait = Wait; + pIf->IsReady = IsReady; +finalize_it: +ENDobjQueryInterface(nssel) + + +/* exit our class + */ +BEGINObjClassExit(nssel, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nssel) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); +ENDObjClassExit(nssel) + + +/* Initialize the nssel class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nssel, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + DBGPRINTF("doing nsselClassInit\n"); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(nssel) +/* vi:set ai: + */ diff --git a/runtime/nssel.h b/runtime/nssel.h new file mode 100644 index 00000000..d7f4fcd3 --- /dev/null +++ b/runtime/nssel.h @@ -0,0 +1,54 @@ +/* Definitions for the nssel IO waiter. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDED_NSSEL_H +#define INCLUDED_NSSEL_H + +#include "netstrms.h" + +/* the nssel object */ +struct nssel_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pDrvrData; /**< the driver's data elements */ + uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */ + uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */ + nsdsel_if_t Drvr; /**< our stream driver */ +}; + + +/* interface */ +BEGINinterface(nssel) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nssel_t **ppThis); + rsRetVal (*ConstructFinalize)(nssel_t *pThis); + rsRetVal (*Destruct)(nssel_t **ppThis); + rsRetVal (*Add)(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp); + rsRetVal (*Wait)(nssel_t *pThis, int *pNumReady); + rsRetVal (*IsReady)(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp, int *pbIsReady, int *piNumReady); +ENDinterface(nssel) +#define nsselCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(nssel); + +/* the name of our library binary */ +#define LM_NSSEL_FILENAME LM_NETSTRMS_FILENAME + +#endif /* #ifndef INCLUDED_NSSEL_H */ diff --git a/runtime/obj-types.h b/runtime/obj-types.h new file mode 100644 index 00000000..30a6a2c0 --- /dev/null +++ b/runtime/obj-types.h @@ -0,0 +1,417 @@ +/* Some type definitions and macros for the obj object. + * I needed to move them out of the main obj.h, because obj.h's + * prototypes use other data types. However, their .h's rely + * on some of the obj.h data types and macros. So I needed to break + * that loop somehow and I've done that by moving the typedefs + * into this file here. + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBJ_TYPES_H_INCLUDED +#define OBJ_TYPES_H_INCLUDED + +#include "stringbuf.h" +#include "syslogd-types.h" + +/* property types for obj[De]Serialize() */ +typedef enum { + PROPTYPE_NONE = 0, /* currently no value set */ + PROPTYPE_PSZ = 1, + PROPTYPE_SHORT = 2, + PROPTYPE_INT = 3, + PROPTYPE_LONG = 4, + PROPTYPE_INT64 = 5, + PROPTYPE_CSTR = 6, + PROPTYPE_SYSLOGTIME = 7 +} propType_t; + +typedef unsigned objID_t; + +typedef enum { /* IDs of base methods supported by all objects - used for jump table, so + * they must start at zero and be incremented. -- rgerhards, 2008-01-04 + */ + objMethod_CONSTRUCT = 0, + objMethod_DESTRUCT = 1, + objMethod_SERIALIZE = 2, + objMethod_DESERIALIZE = 3, + objMethod_SETPROPERTY = 4, + objMethod_CONSTRUCTION_FINALIZER = 5, + objMethod_GETSEVERITY = 6, + objMethod_DEBUGPRINT = 7 +} objMethod_t; +#define OBJ_NUM_METHODS 8 /* must be updated to contain the max number of methods supported */ + + +/* the base data type for interfaces + * This MUST be in sync with the ifBEGIN macro + */ +struct interface_s { + int ifVersion; /* must be set to version requested */ + int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes, 2-load failed; if not 1, functions can NOT be called! */ +}; + + +struct objInfo_s { + uchar *pszID; /* the object ID as a string */ + size_t lenID; /* length of the ID string */ + int iObjVers; + uchar *pszName; + rsRetVal (*objMethods[OBJ_NUM_METHODS])(); + rsRetVal (*QueryIF)(interface_t*); + struct modInfo_s *pModInfo; +}; + + +struct obj_s { /* the dummy struct that each derived class can be casted to */ + objInfo_t *pObjInfo; +#ifndef NDEBUG /* this means if debug... */ + unsigned int iObjCooCKiE; /* must always be 0xBADEFEE for a valid object */ +#endif + uchar *pszName; /* the name of *this* specific object instance */ +}; + + +/* macros which must be gloablly-visible (because they are used during definition of + * other objects. + */ +#ifndef NDEBUG /* this means if debug... */ +#include <string.h> +# define BEGINobjInstance \ + obj_t objData +# define ISOBJ_assert(pObj) \ + do { \ + ASSERT((pObj) != NULL); \ + ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + } while(0); +# define ISOBJ_TYPE_assert(pObj, objType) \ + do { \ + ASSERT(pObj != NULL); \ + if(strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)) { \ + dbgprintf("%s:%d ISOBJ assert failure: invalid object type, expected '%s' " \ + "actual '%s', cookie: %X\n", __FILE__, __LINE__, #objType, \ + (((obj_t*)pObj)->pObjInfo->pszID), ((obj_t*)(pObj))->iObjCooCKiE); \ + assert(0); /* trigger assertion, messge we already have */ \ + } \ + ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + } while(0) +#else /* non-debug mode, no checks but much faster */ +# define BEGINobjInstance obj_t objData +# define ISOBJ_TYPE_assert(pObj, objType) +# define ISOBJ_assert(pObj) +#endif + +/* a set method for *very simple* object accesses. Note that this does + * NOT conform to the standard calling conventions and should be + * used only if actually nothing can go wrong! -- rgerhards, 2008-04-17 + */ +#define DEFpropGetMeth(obj, prop, dataType)\ + dataType obj##Get##prop(void)\ + { \ + return pThis->prop = pVal; \ + } + +#define DEFpropSetMethPTR(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType *pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMethPTR(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType*) +#define DEFpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define DEFpropSetMethFP(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMethFP(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType) +#define DEFpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal) +#define INTERFACEpropSetMeth(obj, prop, dataType)\ + rsRetVal (*Set##prop)(obj##_t *pThis, dataType) +/* class initializer */ +#define PROTOTYPEObjClassInit(objName) rsRetVal objName##ClassInit(struct modInfo_s*) +/* below: objName must be the object name (e.g. vm, strm, ...) and ISCORE must be + * 1 if the module is a statically linked core module and 0 if it is a + * dynamically loaded one. -- rgerhards, 2008-02-29 + */ +#define OBJ_IS_CORE_MODULE 1 /* This should better be renamed to something like "OBJ_IS_NOT_LIBHEAD" or so... ;) */ +#define OBJ_IS_LOADABLE_MODULE 0 +#define BEGINObjClassInit(objName, objVers, objType) \ +rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ +{ \ + DEFiRet; \ + if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ + } \ + CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ + (rsRetVal (*)(void*))objName##Construct,\ + (rsRetVal (*)(void*))objName##Destruct,\ + (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); \ + +#define ENDObjClassInit(objName) \ + iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ +finalize_it: \ + RETiRet; \ +} + +/* ... and now the same for abstract classes. + * TODO: consolidate the two -- rgerhards, 2008-02-29 + */ +#define BEGINAbstractObjClassInit(objName, objVers, objType) \ +rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ +{ \ + DEFiRet; \ + if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ + } \ + CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ + NULL,\ + NULL,\ + (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); + +#define ENDObjClassInit(objName) \ + iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ +finalize_it: \ + RETiRet; \ +} + + +/* now come the class exit. This is to be called immediately before the class is + * unloaded (actual unload for plugins, program termination for core modules) + * gerhards, 2008-03-10 + */ +#define PROTOTYPEObjClassExit(objName) rsRetVal objName##ClassExit(void) +#define BEGINObjClassExit(objName, objType) \ +rsRetVal objName##ClassExit(void) \ +{ \ + DEFiRet; + +#define CODESTARTObjClassExit(objName) + +#define ENDObjClassExit(objName) \ + iRet = obj.UnregisterObj((uchar*)#objName); \ + RETiRet; \ +} + +/* this defines both the constructor and initializer + * rgerhards, 2008-01-10 + */ +#define BEGINobjConstruct(obj) \ + rsRetVal obj##Initialize(obj##_t __attribute__((unused)) *pThis) \ + { \ + DEFiRet; + +#define ENDobjConstruct(obj) \ + /* use finalize_it: before calling the macro (if you need it)! */ \ + RETiRet; \ + } \ + rsRetVal obj##Construct(obj##_t **ppThis) \ + { \ + DEFiRet; \ + obj##_t *pThis; \ + \ + ASSERT(ppThis != NULL); \ + \ + if((pThis = (obj##_t *)calloc(1, sizeof(obj##_t))) == NULL) { \ + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); \ + } \ + objConstructSetObjInfo(pThis); \ + \ + obj##Initialize(pThis); \ + \ + finalize_it: \ + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ + RETiRet; \ + } + + +/* this defines the destructor. The important point is that the base object + * destructor is called. The upper-level class shall destruct all of its + * properties, but not the instance itself. This is freed here by the + * framework (we need an intact pointer because we need to free the + * obj_t structures inside it). A pointer to the object pointer must be + * parse, because it is re-set to NULL (this, for example, is important in + * cancellation handlers). The object pointer is always named pThis. + * The object is always freed, even if there is some error while + * Cancellation is blocked during destructors, as this could have fatal + * side-effects. However, this also means the upper-level object should + * not perform any lenghty processing. + * IMPORTANT: if the upper level object requires some situations where the + * object shall not be destructed (e.g. via reference counting), then + * it shall set pThis to NULL, which prevents destruction of the + * object. + * processing. + * rgerhards, 2008-01-30 + */ +#define BEGINobjDestruct(OBJ) \ + rsRetVal OBJ##Destruct(OBJ##_t __attribute__((unused)) **ppThis) \ + { \ + DEFiRet; \ + OBJ##_t *pThis; + +#define CODESTARTobjDestruct(OBJ) \ + ASSERT(ppThis != NULL); \ + pThis = *ppThis; \ + ISOBJ_TYPE_assert(pThis, OBJ); + +/* note: there was a long-time bug in the macro below that lead to *ppThis = NULL + * only when the object was actually destructed. I discovered this issue during + * introduction of the pRcvFrom property in msg_t, but it potentially had other + * effects, too. I am not sure if some experienced instability resulted from this + * bug OR if its fix will cause harm to so-far "correctly" running code. The later + * may very well be. Thus I will change it only for the current branch and also + * the beta, but not in all old builds. Let's see how things evolve. + * rgerhards, 2009-06-30 + */ +#define ENDobjDestruct(OBJ) \ + goto finalize_it; /* prevent compiler warning ;) */ \ + /* no more code here! */ \ + finalize_it: \ + if(pThis != NULL) { \ + obj.DestructObjSelf((obj_t*) pThis); \ + free(pThis); \ + } \ + *ppThis = NULL; \ + RETiRet; \ + } + + +/* this defines the debug print entry point. DebugPrint is optional. If + * it is provided, the object should output some meaningful information + * via the debug system. + * rgerhards, 2008-02-20 + */ +#define PROTOTYPEObjDebugPrint(obj) rsRetVal obj##DebugPrint(obj##_t *pThis) +#define INTERFACEObjDebugPrint(obj) rsRetVal (*DebugPrint)(obj##_t *pThis) +#define BEGINobjDebugPrint(obj) \ + rsRetVal obj##DebugPrint(obj##_t __attribute__((unused)) *pThis) \ + { \ + DEFiRet; \ + +#define CODESTARTobjDebugPrint(obj) \ + ASSERT(pThis != NULL); \ + ISOBJ_TYPE_assert(pThis, obj); \ + +#define ENDobjDebugPrint(obj) \ + RETiRet; \ + } + +/* ------------------------------ object loader system ------------------------------ * + * The following code builds a dynamic object loader system. The + * root idea is that all objects are dynamically loadable, + * which is necessary to get a clean plug-in interface where every plugin can access + * rsyslog's rich object model via simple and quite portable methods. + * + * To do so, each object defines one or more interfaces. They are essentially structures + * with function (method) pointers. Anyone interested in calling an object must first + * obtain the interface and can then call through it. + * + * The interface data type must always be called <obj>_if_t, as this is expected + * by the macros. Having consitent naming is also easier for the programmer. By default, + * macros create a static variable named like the object in each calling objects + * static data block. + * + * rgerhards, 2008-02-21 (initial implementation), 2008-04-17 (update of this note) + */ + +/* this defines the QueryInterface print entry point. Over time, it should be + * present in all objects. + */ +#define BEGINobjQueryInterface(obj) \ + rsRetVal obj##QueryInterface(obj##_if_t *pIf) \ + { \ + DEFiRet; \ + +#define CODESTARTobjQueryInterface(obj) \ + ASSERT(pIf != NULL); + +#define ENDobjQueryInterface(obj) \ + RETiRet; \ + } + + +/* the following macros should be used to define interfaces inside the + * header files. + */ +#define BEGINinterface(obj) \ + typedef struct obj##_if_s {\ + ifBEGIN /* This MUST always be the first interface member */ +#define ENDinterface(obj) \ + } obj##_if_t; + +/* the following macro is used to get access to an object (not an instance, + * just the class itself!). It must be called before any of the object's + * methods can be accessed. The MYLIB part is the name of my library, or NULL if + * the caller is a core module. Using the right value here is important to get + * the reference counting correct (object accesses from the same library must + * not be counted because that would cause a library plugin to never unload, as + * its ClassExit() entry points are only called if no object is referenced, which + * would never happen as the library references itself. + * rgerhards, 2008-03-11 + */ +#define CORE_COMPONENT NULL /* use this to indicate this is a core component */ +#define DONT_LOAD_LIB NULL /* do not load a library to obtain object interface (currently same as CORE_COMPONENT) */ +#define objUse(objName, FILENAME) \ + obj.UseObj(__FILE__, (uchar*)#objName, (uchar*)FILENAME, (void*) &objName) +#define objRelease(objName, FILENAME) \ + obj.ReleaseObj(__FILE__, (uchar*)#objName, (uchar*) FILENAME, (void*) &objName) + +/* defines data that must always be present at the very begin of the interface structure */ +#define ifBEGIN \ + int ifVersion; /* must be set to version requested */ \ + int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes; if no, functions can NOT be called! */ + + +/* use the following define some place in your static data (suggested right at + * the beginning + */ +#define DEFobjCurrIf(obj) \ + static obj##_if_t obj = { .ifVersion = obj##CURR_IF_VERSION, .ifIsLoaded = 0 }; + +/* define the prototypes for a class - when we use interfaces, we just have few + * functions that actually need to be non-static. + */ +#define PROTOTYPEObj(obj) \ + PROTOTYPEObjClassInit(obj); \ + PROTOTYPEObjClassExit(obj) + +/* ------------------------------ end object loader system ------------------------------ */ + + +#include "modules.h" +#endif /* #ifndef OBJ_TYPES_H_INCLUDED */ diff --git a/runtime/obj.c b/runtime/obj.c new file mode 100644 index 00000000..63f1f38c --- /dev/null +++ b/runtime/obj.c @@ -0,0 +1,1481 @@ +/* obj.c + * + * This file implements a generic object "class". All other classes can + * use the service of this base class here to include auto-destruction and + * other capabilities in a generic manner. + * + * As of 2008-02-29, I (rgerhards) am adding support for dynamically loadable + * objects. In essence, each object will soon be available via its interface, + * only. Before any object's code is accessed (including global static methods), + * the caller needs to obtain an object interface. To do so, it needs to provide + * the object name and the file where the object is expected to reside in. A + * file may not be given, in which case the object is expected to reside in + * the rsyslog core. The caller than receives an interface pointer which can + * be utilized to access all the object's methods. This method enables rsyslog + * to load library modules on demand. In order to keep overhead low, callers + * should request object interface only once in the object Init function and + * free them when they exit. The only exception is when a caller needs to + * access an object only conditional, in which case a pointer to its interface + * shall be aquired as need first arises but still be released only on exit + * or when there definitely is no further need. The whole idea is to limit + * the very performance-intense act of dynamically loading an objects library. + * Of course, it is possible to violate this suggestion, but than you should + * have very good reasoning to do so. + * + * Please note that there is one trick we need to do. Each object queries + * the object interfaces and it does so via objUse(). objUse, however, is + * part of the obj object's interface (implemented via the file you are + * just reading). So in order to obtain a pointer to objUse, we need to + * call it - obviously not possible. One solution would be that objUse is + * hardcoded into all callers. That, however, would bring us into slight + * trouble with actually dynamically loaded modules, as we should NOT + * rely on the OS loader to resolve symbols back to the caller (this + * is a feature not universally available and highly importable). Of course, + * we can solve this with a pHostQueryEtryPoint() call. It still sounds + * somewhat unnatural to call a regular interface function via a special + * method. So what we do instead is define a special function called + * objGetObjInterface() which delivers our own interface. That function + * than will be defined global and be queriable via pHostQueryEtryPoint(). + * I agree, technically this is much the same, but from an architecture + * point of view it looks cleaner (at least to me). + * + * Please note that there is another egg-hen problem: we use a linked list, + * which is provided by the linkedList object. However, we need to + * initialize the linked list before we can provide the UseObj() + * functionality. That, in turn, would probably be required by the + * linkedList object. So the solution is to use a backdoor just to + * init the linked list and from then on use the usual interfaces. + * + * File begun on 2008-01-04 by RGerhards + * + * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <pthread.h> + +/* how many objects are supported by rsyslogd? */ +#define OBJ_NUM_IDS 100 /* TODO change to a linked list? info: 16 were currently in use 2008-02-29 */ + +#include "rsyslog.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "obj.h" +#include "stream.h" +#include "modules.h" +#include "errmsg.h" +#include "cfsysline.h" +#include "unicode-helper.h" +#include "datetime.h" + +/* static data */ +DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */ +DEFobjCurrIf(var) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(strm) +static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */ +pthread_mutex_t mutObjGlobalOp; /* mutex to guard global operations of the object system */ + + +/* cookies for serialized lines */ +#define COOKIE_OBJLINE '<' +#define COOKIE_PROPLINE '+' +#define COOKIE_ENDLINE '>' +#define COOKIE_BLANKLINE '.' + +/* forward definitions */ +static rsRetVal FindObjInfo(cstr_t *pszObjName, objInfo_t **ppInfo); + +/* methods */ + +/* This is a dummy method to be used when a standard method has not been + * implemented by an object. Having it allows us to simply call via the + * jump table without any NULL pointer checks - which gains quite + * some performance. -- rgerhards, 2008-01-04 + */ +static rsRetVal objInfoNotImplementedDummy(void __attribute__((unused)) *pThis) +{ + return RS_RET_NOT_IMPLEMENTED; +} + +/* and now the macro to check if something is not implemented + * must be provided an objInfo_t pointer. + */ +#define objInfoIsImplemented(pThis, method) \ + (pThis->objMethods[method] != objInfoNotImplementedDummy) + +/* construct an object Info object. Each class shall do this on init. The + * resulting object shall be cached during the lifetime of the class and each + * object shall receive a reference. A constructor and destructor MUST be provided for all + * objects, thus they are in the parameter list. + * pszID is the identifying object name and must point to constant pool memory. It is never freed. + */ +static rsRetVal +InfoConstruct(objInfo_t **ppThis, uchar *pszID, int iObjVers, + rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), + rsRetVal (*pQueryIF)(interface_t*), modInfo_t *pModInfo) +{ + DEFiRet; + int i; + objInfo_t *pThis; + + assert(ppThis != NULL); + + if((pThis = calloc(1, sizeof(objInfo_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pThis->pszID = pszID; + pThis->lenID = ustrlen(pszID); + pThis->pszName = ustrdup(pszID); /* it's OK if we have NULL ptr, GetName() will deal with that! */ + pThis->iObjVers = iObjVers; + pThis->QueryIF = pQueryIF; + pThis->pModInfo = pModInfo; + + pThis->objMethods[0] = pConstruct; + pThis->objMethods[1] = pDestruct; + for(i = 2 ; i < OBJ_NUM_METHODS ; ++i) { + pThis->objMethods[i] = objInfoNotImplementedDummy; + } + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* destruct the objInfo object - must be done only when no more instances exist. + * rgerhards, 2008-03-10 + */ +static rsRetVal +InfoDestruct(objInfo_t **ppThis) +{ + DEFiRet; + objInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + free(pThis->pszName); + free(pThis); + *ppThis = NULL; + + RETiRet; +} + + +/* set a method handler */ +static rsRetVal +InfoSetMethod(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)) +{ + assert(pThis != NULL); + assert(objMethod > 0 && objMethod < OBJ_NUM_METHODS); + pThis->objMethods[objMethod] = pHandler; + + return RS_RET_OK; +} + +/* destruct the base object properties. + * rgerhards, 2008-01-29 + */ +static rsRetVal +DestructObjSelf(obj_t *pThis) +{ + DEFiRet; + + ISOBJ_assert(pThis); + free(pThis->pszName); + + RETiRet; +} + + +/* --------------- object serializiation / deserialization support --------------- */ + + +/* serialize the header of an object + * pszRecType must be either "Obj" (Object) or "OPB" (Object Property Bag) + */ +static rsRetVal objSerializeHeader(strm_t *pStrm, obj_t *pObj, uchar *pszRecType) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); + + /* object cookie and serializer version (so far always 1) */ + CHKiRet(strm.WriteChar(pStrm, COOKIE_OBJLINE)); + CHKiRet(strm.Write(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */ + CHKiRet(strm.WriteChar(pStrm, ':')); + CHKiRet(strm.WriteChar(pStrm, '1')); + + /* object type, version and string length */ + CHKiRet(strm.WriteChar(pStrm, ':')); + CHKiRet(strm.Write(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)); + CHKiRet(strm.WriteChar(pStrm, ':')); + CHKiRet(strm.WriteLong(pStrm, objGetVersion(pObj))); + + /* record trailer */ + CHKiRet(strm.WriteChar(pStrm, ':')); + CHKiRet(strm.WriteChar(pStrm, '\n')); + +finalize_it: + RETiRet; +} + + +/* begin serialization of an object + * rgerhards, 2008-01-06 + */ +static rsRetVal +BeginSerialize(strm_t *pStrm, obj_t *pObj) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + + CHKiRet(strm.RecordBegin(pStrm)); + CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "Obj")); + +finalize_it: + RETiRet; +} + + +/* begin serialization of an object's property bag + * Note: a property bag is used to serialize some of an objects + * properties, but not necessarily all. A good example is the queue + * object, which at some stage needs to serialize a number of its + * properties, but not the queue data itself. From the object point + * of view, a property bag can not be used to re-instantiate an object. + * Otherwise, the serialization is exactly the same. + * rgerhards, 2008-01-11 + */ +static rsRetVal +BeginSerializePropBag(strm_t *pStrm, obj_t *pObj) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + + CHKiRet(strm.RecordBegin(pStrm)); + CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "OPB")); + +finalize_it: + RETiRet; +} + + +/* append a property + */ +static rsRetVal +SerializeProp(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr) +{ + DEFiRet; + uchar *pszBuf = NULL; + size_t lenBuf = 0; + uchar szBuf[64]; + varType_t vType = VARTYPE_NONE; + + ISOBJ_TYPE_assert(pStrm, strm); + assert(pszPropName != NULL); + + /*dbgprintf("objSerializeProp: strm %p, propName '%s', type %d, pUsr %p\n", pStrm, pszPropName, propType, pUsr);*/ + /* if we have no user pointer, there is no need to write this property. + * TODO: think if that's the righ point of view + * rgerhards, 2008-01-06 + */ + if(pUsr == NULL) { + ABORT_FINALIZE(RS_RET_OK); + } + + /* TODO: use the stream functions for data conversion here - should be quicker */ + + switch(propType) { + case PROPTYPE_PSZ: + pszBuf = (uchar*) pUsr; + lenBuf = ustrlen(pszBuf); + vType = VARTYPE_STR; + break; + case PROPTYPE_SHORT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr))); + pszBuf = szBuf; + lenBuf = ustrlen(szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr))); + pszBuf = szBuf; + lenBuf = ustrlen(szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_LONG: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr))); + pszBuf = szBuf; + lenBuf = ustrlen(szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT64: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr))); + pszBuf = szBuf; + lenBuf = ustrlen(szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_CSTR: + pszBuf = rsCStrGetSzStrNoNULL((cstr_t *) pUsr); + lenBuf = rsCStrLen((cstr_t*) pUsr); + vType = VARTYPE_STR; + break; + case PROPTYPE_SYSLOGTIME: + lenBuf = snprintf((char*) szBuf, sizeof(szBuf), "%d:%d:%d:%d:%d:%d:%d:%d:%d:%c:%d:%d", + ((syslogTime_t*)pUsr)->timeType, + ((syslogTime_t*)pUsr)->year, + ((syslogTime_t*)pUsr)->month, + ((syslogTime_t*)pUsr)->day, + ((syslogTime_t*)pUsr)->hour, + ((syslogTime_t*)pUsr)->minute, + ((syslogTime_t*)pUsr)->second, + ((syslogTime_t*)pUsr)->secfrac, + ((syslogTime_t*)pUsr)->secfracPrecision, + ((syslogTime_t*)pUsr)->OffsetMode, + ((syslogTime_t*)pUsr)->OffsetHour, + ((syslogTime_t*)pUsr)->OffsetMinute); + if(lenBuf > sizeof(szBuf) - 1) + ABORT_FINALIZE(RS_RET_PROVIDED_BUFFER_TOO_SMALL); + vType = VARTYPE_SYSLOGTIME; + pszBuf = szBuf; + break; + default: + dbgprintf("invalid PROPTYPE %d\n", propType); + break; + } + + /* cookie */ + CHKiRet(strm.WriteChar(pStrm, COOKIE_PROPLINE)); + /* name */ + CHKiRet(strm.Write(pStrm, pszPropName, ustrlen(pszPropName))); + CHKiRet(strm.WriteChar(pStrm, ':')); + /* type */ + CHKiRet(strm.WriteLong(pStrm, (int) vType)); + CHKiRet(strm.WriteChar(pStrm, ':')); + /* length */ + CHKiRet(strm.WriteLong(pStrm, lenBuf)); + CHKiRet(strm.WriteChar(pStrm, ':')); + + /* data */ + CHKiRet(strm.Write(pStrm, (uchar*) pszBuf, lenBuf)); + + /* trailer */ + CHKiRet(strm.WriteChar(pStrm, ':')); + CHKiRet(strm.WriteChar(pStrm, '\n')); + +finalize_it: + RETiRet; +} + + +/* end serialization of an object. The caller receives a + * standard C string, which he must free when no longer needed. + */ +static rsRetVal +EndSerialize(strm_t *pStrm) +{ + DEFiRet; + + assert(pStrm != NULL); + + CHKiRet(strm.WriteChar(pStrm, COOKIE_ENDLINE)); + CHKiRet(strm.Write(pStrm, (uchar*) "End\n", sizeof("END\n") - 1)); + CHKiRet(strm.WriteChar(pStrm, COOKIE_BLANKLINE)); + CHKiRet(strm.WriteChar(pStrm, '\n')); + + CHKiRet(strm.RecordEnd(pStrm)); + +finalize_it: + RETiRet; +} + + +/* define a helper to make code below a bit cleaner (and quicker to write) */ +#define NEXTC CHKiRet(strm.ReadChar(pStrm, &c))/*;dbgprintf("c: %c\n", c)*/ + + +/* de-serialize an embedded, non-octect-counted string. This is useful + * for deserializing the object name inside the header. The string is + * terminated by the first occurence of the ':' character. + * rgerhards, 2008-02-29 + */ +static rsRetVal +objDeserializeEmbedStr(cstr_t **ppStr, strm_t *pStrm) +{ + DEFiRet; + uchar c; + cstr_t *pStr = NULL; + + assert(ppStr != NULL); + + CHKiRet(cstrConstruct(&pStr)); + + NEXTC; + while(c != ':') { + CHKiRet(cstrAppendChar(pStr, c)); + NEXTC; + } + CHKiRet(cstrFinalize(pStr)); + + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK && pStr != NULL) + cstrDestruct(&pStr); + + RETiRet; +} + + +/* de-serialize a number */ +static rsRetVal objDeserializeNumber(number_t *pNum, strm_t *pStrm) +{ + DEFiRet; + number_t i; + int bIsNegative; + uchar c; + + assert(pNum != NULL); + + NEXTC; + if(c == '-') { + bIsNegative = 1; + NEXTC; + } else { + bIsNegative = 0; + } + + /* we check this so that we get more meaningful error codes */ + if(!isdigit(c)) ABORT_FINALIZE(RS_RET_INVALID_NUMBER); + + i = 0; + while(isdigit(c)) { + i = i * 10 + c - '0'; + NEXTC; + } + + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + if(bIsNegative) + i *= -1; + + *pNum = i; +finalize_it: + RETiRet; +} + + +/* de-serialize a string, length must be provided but may be 0 */ +static rsRetVal objDeserializeStr(cstr_t **ppCStr, int iLen, strm_t *pStrm) +{ + DEFiRet; + int i; + uchar c; + cstr_t *pCStr = NULL; + + assert(ppCStr != NULL); + assert(iLen >= 0); + + CHKiRet(cstrConstruct(&pCStr)); + + NEXTC; + for(i = 0 ; i < iLen ; ++i) { + CHKiRet(cstrAppendChar(pCStr, c)); + NEXTC; + } + CHKiRet(cstrFinalize(pCStr)); + + /* check terminator */ + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + *ppCStr = pCStr; + +finalize_it: + if(iRet != RS_RET_OK && pCStr != NULL) + cstrDestruct(&pCStr); + + RETiRet; +} + + +/* de-serialize a syslogTime -- rgerhards,2008-01-08 */ +#define GETVAL(var) \ + CHKiRet(objDeserializeNumber(&l, pStrm)); \ + pTime->var = l; +static rsRetVal objDeserializeSyslogTime(syslogTime_t *pTime, strm_t *pStrm) +{ + DEFiRet; + number_t l; + uchar c; + + assert(pTime != NULL); + + GETVAL(timeType); + GETVAL(year); + GETVAL(month); + GETVAL(day); + GETVAL(hour); + GETVAL(minute); + GETVAL(second); + GETVAL(secfrac); + GETVAL(secfracPrecision); + /* OffsetMode is a single character! */ + NEXTC; pTime->OffsetMode = c; + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + GETVAL(OffsetHour); + GETVAL(OffsetMinute); + +finalize_it: + RETiRet; +} +#undef GETVAL + +/* de-serialize an object header + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeHeader(uchar *pszRecType, cstr_t **ppstrID, int* poVers, strm_t *pStrm) +{ + DEFiRet; + number_t oVers; + uchar c; + + assert(ppstrID != NULL); + assert(poVers != NULL); + assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); + + /* check header cookie */ + NEXTC; if(c != COOKIE_OBJLINE) ABORT_FINALIZE(RS_RET_INVALID_HEADER); + NEXTC; if(c != pszRecType[0]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != pszRecType[1]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != pszRecType[2]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER); + NEXTC; if(c != '1') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); + + /* object type and version */ + CHKiRet(objDeserializeEmbedStr(ppstrID, pStrm)); + CHKiRet(objDeserializeNumber(&oVers, pStrm)); + + /* and now we skip over the rest until the delemiting \n */ + NEXTC; + while(c != '\n') { + NEXTC; + } + + *poVers = oVers; + +finalize_it: + RETiRet; +} + + +/* Deserialize a single property. Pointer must be positioned at begin of line. Whole line + * up until the \n is read. + */ +rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm) +{ + DEFiRet; + number_t i; + number_t iLen; + uchar c; + int step = 0; /* which step was successful? */ + int64 offs; + + assert(pProp != NULL); + + /* check cookie */ + NEXTC; + if(c != COOKIE_PROPLINE) { + /* oops, we've read one char that does not belong to use - unget it first */ + CHKiRet(strm.UnreadChar(pStrm, c)); + ABORT_FINALIZE(RS_RET_NO_PROPLINE); + } + + /* get the property name first */ + CHKiRet(cstrConstruct(&pProp->pcsName)); + + NEXTC; + while(c != ':') { + CHKiRet(cstrAppendChar(pProp->pcsName, c)); + NEXTC; + } + CHKiRet(cstrFinalize(pProp->pcsName)); + step = 1; + + /* property type */ + CHKiRet(objDeserializeNumber(&i, pStrm)); + pProp->varType = i; + step = 2; + + /* size (needed for strings) */ + CHKiRet(objDeserializeNumber(&iLen, pStrm)); + step = 3; + + /* we now need to deserialize the value */ + switch(pProp->varType) { + case VARTYPE_STR: + CHKiRet(objDeserializeStr(&pProp->val.pStr, iLen, pStrm)); + break; + case VARTYPE_NUMBER: + CHKiRet(objDeserializeNumber(&pProp->val.num, pStrm)); + break; + case VARTYPE_SYSLOGTIME: + CHKiRet(objDeserializeSyslogTime(&pProp->val.vSyslogTime, pStrm)); + break; + default: + dbgprintf("invalid VARTYPE %d\n", pProp->varType); + break; + } + step = 4; + + /* we should now be at the end of the line. So the next char must be \n */ + NEXTC; + if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_PROPFRAME); + +finalize_it: + if(Debug && iRet != RS_RET_OK && iRet != RS_RET_NO_PROPLINE) { + strm.GetCurrOffset(pStrm, &offs); + dbgprintf("error %d deserializing property name, offset %lld, step %d\n", + iRet, offs, step); + if(step >= 1) { + dbgprintf("error property name: '%s'\n", rsCStrGetSzStrNoNULL(pProp->pcsName)); + } + if(step >= 2) { + dbgprintf("error var type: '%d'\n", pProp->varType); + } + if(step >= 3) { + dbgprintf("error len: '%d'\n", (int) iLen); + } + if(step >= 4) { + switch(pProp->varType) { + case VARTYPE_STR: + dbgprintf("error data string: '%s'\n", + rsCStrGetSzStrNoNULL(pProp->val.pStr)); + break; + case VARTYPE_NUMBER: + dbgprintf("error number: %d\n", (int) pProp->val.num); + break; + case VARTYPE_SYSLOGTIME: + dbgprintf("syslog time was successfully parsed (but " + "is not displayed\n"); + break; + default: + break; + } + } + } + RETiRet; +} + + +/* de-serialize an object trailer. This does not get any data but checks if the + * format is ok. + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeTrailer(strm_t *pStrm) +{ + DEFiRet; + uchar c; + + /* check header cookie */ + NEXTC; if(c != COOKIE_ENDLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'E') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'd') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != COOKIE_BLANKLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + +finalize_it: + RETiRet; +} + + + +/* This method tries to recover a serial store if it got out of sync. + * To do so, it scans the line beginning cookies and waits for the object + * cookie. If that is found, control is returned. If the store is exhausted, + * we will receive an RS_RET_EOF error as part of NEXTC, which will also + * terminate this function. So we may either return with somehting that + * looks like a valid object or end of store. + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeTryRecover(strm_t *pStrm) +{ + DEFiRet; + uchar c; + int bWasNL; + int bRun; + + assert(pStrm != NULL); + bRun = 1; + bWasNL = 0; + + while(bRun) { + NEXTC; + if(c == '\n') + bWasNL = 1; + else { + if(bWasNL == 1 && c == COOKIE_OBJLINE) + bRun = 0; /* we found it! */ + else + bWasNL = 0; + } + } + + CHKiRet(strm.UnreadChar(pStrm, c)); + +finalize_it: + dbgprintf("deserializer has possibly been able to re-sync and recover, state %d\n", iRet); + RETiRet; +} + + +/* De-serialize the properties of an object. This includes processing + * of the trailer. Header must already have been processed. + * rgerhards, 2008-01-11 + */ +static rsRetVal objDeserializeProperties(obj_t *pObj, rsRetVal (*objSetProperty)(), strm_t *pStrm) +{ + DEFiRet; + var_t *pVar = NULL; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + iRet = objDeserializeProperty(pVar, pStrm); + while(iRet == RS_RET_OK) { + CHKiRet(objSetProperty(pObj, pVar)); + /* re-init var object - TODO: method of var! */ + rsCStrDestruct(&pVar->pcsName); /* no longer needed */ + if(pVar->varType == VARTYPE_STR) { + if(pVar->val.pStr != NULL) + rsCStrDestruct(&pVar->val.pStr); + } + iRet = objDeserializeProperty(pVar, pStrm); + } + + if(iRet != RS_RET_NO_PROPLINE) + FINALIZE; + + CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */ +finalize_it: + if(pVar != NULL) + var.Destruct(&pVar); + + RETiRet; +} + + +/* De-Serialize an object. + * Params: Pointer to object Pointer (pObj) (like a obj_t**, but can not do that due to compiler warning) + * expected object ID (to check against), a fixup function that can modify the object before it is finalized + * and a user pointer that is to be passed to that function in addition to the object. The fixup function + * pointer may be NULL, in which case none is called. + * The caller must destruct the created object. + * rgerhards, 2008-01-07 + */ +static rsRetVal +Deserialize(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr) +{ + DEFiRet; + rsRetVal iRetLocal; + obj_t *pObj = NULL; + int oVers = 0; /* keep compiler happy, but it is totally useless but takes up some execution time... */ + cstr_t *pstrID = NULL; + objInfo_t *pObjInfo; + + assert(ppObj != NULL); + assert(pszTypeExpected != NULL); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state, + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserialize error %d during header processing - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pszTypeExpected, ustrlen(pszTypeExpected))) /* TODO: optimize strlen() - caller shall provide */ + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCT](&pObj)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo->objMethods[objMethod_SETPROPERTY], pStrm)); + + /* check if we need to call a fixup function that modifies the object + * before it is finalized. -- rgerhards, 2008-01-13 + */ + if(fFixup != NULL) + CHKiRet(fFixup(pObj, pUsr)); + + /* we have a valid object, let's finalize our work and return */ + if(objInfoIsImplemented(pObjInfo, objMethod_CONSTRUCTION_FINALIZER)) + CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCTION_FINALIZER](pObj)); + + *((obj_t**) ppObj) = pObj; + +finalize_it: + if(iRet != RS_RET_OK && pObj != NULL) + free(pObj); /* TODO: check if we can call destructor 2008-01-13 rger */ + + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + + +/* De-Serialize an object, with known constructur and destructor. Params like Deserialize(). + * rgerhards, 2012-11-03 + */ +rsRetVal +objDeserializeWithMethods(void *ppObj, uchar *pszTypeExpected, int lenTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr, rsRetVal (*objConstruct)(), rsRetVal (*objConstructFinalize)(), rsRetVal (*objDeserialize)()) +{ + DEFiRet; + rsRetVal iRetLocal; + obj_t *pObj = NULL; + int oVers = 0; /* keep compiler happy, but it is totally useless but takes up some execution time... */ + cstr_t *pstrID = NULL; + + assert(ppObj != NULL); + assert(pszTypeExpected != NULL); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state, + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserialize error %d during header processing - " + "trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pszTypeExpected, lenTypeExpected)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(objConstruct(&pObj)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserialize(pObj, pStrm)); + CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */ + + /* check if we need to call a fixup function that modifies the object + * before it is finalized. -- rgerhards, 2008-01-13 + */ + if(fFixup != NULL) + CHKiRet(fFixup(pObj, pUsr)); + + /* we have a valid object, let's finalize our work and return */ + if(objConstructFinalize != NULL) { + CHKiRet(objConstructFinalize(pObj)); + } + + *((obj_t**) ppObj) = pObj; + +finalize_it: + if(iRet != RS_RET_OK && pObj != NULL) + free(pObj); /* TODO: check if we can call destructor 2008-01-13 rger */ + + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + +/* This is a dummy deserializer, to be used for the delete queue reader + * specifically. This is kind of a hack, but also to be replace (hopefully) soon + * by totally different code. So let's make it as simple as possible... + * rgerhards, 2012-11-06 + */ +rsRetVal +objDeserializeDummy(obj_t __attribute__((unused)) *pObj, strm_t *pStrm) +{ + DEFiRet; + var_t *pVar = NULL; + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + iRet = objDeserializeProperty(pVar, pStrm); + while(iRet == RS_RET_OK) { + /* this loop does actually NOGHTING but read the file... */ + /* re-init var object - TODO: method of var! */ + rsCStrDestruct(&pVar->pcsName); /* no longer needed */ + if(pVar->varType == VARTYPE_STR) { + if(pVar->val.pStr != NULL) + rsCStrDestruct(&pVar->val.pStr); + } + iRet = objDeserializeProperty(pVar, pStrm); + } +finalize_it: + if(iRet == RS_RET_NO_PROPLINE) + iRet = RS_RET_OK; /* NO_PROPLINE is OK and a kind of EOF! */ + if(pVar != NULL) + var.Destruct(&pVar); + RETiRet; +} + + +/* De-Serialize an object, but treat it as property bag. + * rgerhards, 2008-01-11 + */ +rsRetVal +objDeserializeObjAsPropBag(obj_t *pObj, strm_t *pStrm) +{ + DEFiRet; + rsRetVal iRetLocal; + cstr_t *pstrID = NULL; + int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ + objInfo_t *pObjInfo; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserializeObjAsPropBag error %d during header - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo->objMethods[objMethod_SETPROPERTY], pStrm)); + +finalize_it: + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + + + +/* De-Serialize an object property bag. As a property bag contains only partial properties, + * it is not instanciable. Thus, the caller must provide a pointer of an already-instanciated + * object of the correct type. + * Params: Pointer to object (pObj) + * Pointer to be passed to the function + * The caller must destruct the created object. + * rgerhards, 2008-01-07 + */ +static rsRetVal +DeserializePropBag(obj_t *pObj, strm_t *pStrm) +{ + DEFiRet; + rsRetVal iRetLocal; + cstr_t *pstrID = NULL; + int oVers; + objInfo_t *pObjInfo; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "OPB", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserializePropBag error %d during header - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo->objMethods[objMethod_SETPROPERTY], pStrm)); + +finalize_it: + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + +#undef NEXTC /* undef helper macro */ + + +/* --------------- end object serializiation / deserialization support --------------- */ + + +/* set the object (instance) name + * rgerhards, 2008-01-29 + * TODO: change the naming to a rsCStr obj! (faster) + */ +static rsRetVal +SetName(obj_t *pThis, uchar *pszName) +{ + DEFiRet; + + free(pThis->pszName); + CHKmalloc(pThis->pszName = ustrdup(pszName)); + +finalize_it: + RETiRet; +} + + +/* get the object (instance) name + * Note that we use a non-standard calling convention. Thus function must never + * fail, else we run into real big problems. So it must make sure that at least someting + * is returned. + * rgerhards, 2008-01-30 + */ +static uchar * +GetName(obj_t *pThis) +{ + uchar *ret; + uchar szName[128]; + + BEGINfunc + ISOBJ_assert(pThis); + + if(pThis->pszName == NULL) { + snprintf((char*)szName, sizeof(szName)/sizeof(uchar), "%s %p", objGetClassName(pThis), pThis); + SetName(pThis, szName); + /* looks strange, but we NEED to re-check because if there was an + * error in objSetName(), the pointer may still be NULL + */ + if(pThis->pszName == NULL) { + ret = objGetClassName(pThis); + } else { + ret = pThis->pszName; + } + } else { + ret = pThis->pszName; + } + + ENDfunc + return ret; +} + + +/* Find the objInfo object for the current object + * rgerhards, 2008-02-29 + */ +static rsRetVal +FindObjInfo(cstr_t *pstrOID, objInfo_t **ppInfo) +{ + DEFiRet; + int bFound; + int i; + + assert(pstrOID != NULL); + assert(ppInfo != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS) { + if(arrObjInfo[i] != NULL && !rsCStrSzStrCmp(pstrOID, arrObjInfo[i]->pszID, arrObjInfo[i]->lenID)) { + bFound = 1; + break; + } + ++i; + } + + if(!bFound) + ABORT_FINALIZE(RS_RET_NOT_FOUND); + + *ppInfo = arrObjInfo[i]; + +finalize_it: + if(iRet == RS_RET_OK) { + /* DEV DEBUG ONLY dbgprintf("caller requested object '%s', found at index %d\n", (*ppInfo)->pszID, i);*/ + /*EMPTY BY INTENSION*/; + } else { + dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet); + } + + RETiRet; +} + + +/* register a classes' info pointer, so that we can reference it later, if needed to + * (e.g. for de-serialization support). + * rgerhards, 2008-01-07 + * In this function, we look for a free space in the object table. While we do so, we + * also detect if the same object has already been registered, which is not valid. + * rgerhards, 2008-02-29 + */ +static rsRetVal +RegisterObj(uchar *pszObjName, objInfo_t *pInfo) +{ + DEFiRet; + int bFound; + int i; + + assert(pszObjName != NULL); + assert(pInfo != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS && arrObjInfo[i] != NULL) { + if( arrObjInfo[i] != NULL + && !ustrcmp(arrObjInfo[i]->pszID, pszObjName)) { + bFound = 1; + break; + } + ++i; + } + + if(bFound) ABORT_FINALIZE(RS_RET_OBJ_ALREADY_REGISTERED); + if(i >= OBJ_NUM_IDS) ABORT_FINALIZE(RS_RET_OBJ_REGISTRY_OUT_OF_SPACE); + + arrObjInfo[i] = pInfo; + /* DEV debug only: dbgprintf("object '%s' successfully registered with index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF); */ + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "registering object '%s' failed with error code %d", pszObjName, iRet); + } + + RETiRet; +} + + +/* deregister a classes' info pointer, usually called because the class is unloaded. + * After deregistration, the class can no longer be accessed, except if it is reloaded. + * rgerhards, 2008-03-10 + */ +static rsRetVal +UnregisterObj(uchar *pszObjName) +{ + DEFiRet; + int bFound; + int i; + + assert(pszObjName != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS) { + if( arrObjInfo[i] != NULL + && !ustrcmp(arrObjInfo[i]->pszID, pszObjName)) { + bFound = 1; + break; + } + ++i; + } + + if(!bFound) + ABORT_FINALIZE(RS_RET_OBJ_NOT_REGISTERED); + + InfoDestruct(&arrObjInfo[i]); + /* DEV debug only: dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i); */ + +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("unregistering object '%s' failed with error code %d\n", pszObjName, iRet); + } + + RETiRet; +} + + +/* This function shall be called by anyone who would like to use an object. It will + * try to locate the object, load it into memory if not already present and return + * a pointer to the objects interface. + * rgerhards, 2008-02-29 + */ +static rsRetVal +UseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) +{ + DEFiRet; + cstr_t *pStr = NULL; + objInfo_t *pObjInfo; + + + /* DEV debug only: dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */ + pthread_mutex_lock(&mutObjGlobalOp); + + if(pIf->ifIsLoaded == 1) { + ABORT_FINALIZE(RS_RET_OK); /* we are already set */ + } + if(pIf->ifIsLoaded == 2) { + ABORT_FINALIZE(RS_RET_LOAD_ERROR); /* we had a load error and can not continue */ + } + + /* we must be careful that we do not enter in infinite loop if an error occurs during + * loading a module. ModLoad emits an error message in such cases and that potentially + * can trigger the same code here. So we initially set the module state to "load error" + * and set it to "fully initialized" when the load succeeded. It's a bit hackish, but + * looks like a good solution. -- rgerhards, 2008-03-07 + */ + pIf->ifIsLoaded = 2; + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + iRet = FindObjInfo(pStr, &pObjInfo); + if(iRet == RS_RET_NOT_FOUND) { + /* in this case, we need to see if we can dynamically load the object */ + if(pObjFile == NULL) { + FINALIZE; /* no chance, we have lost... */ + } else { + CHKiRet(module.Load(pObjFile, 0, NULL)); + /* NOW, we must find it or we have a problem... */ + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + } + } else if(iRet != RS_RET_OK) { + FINALIZE; /* give up */ + } + + /* if we reach this point, we have a valid pObjInfo */ + if(pObjFile != NULL) { /* NULL means core module */ + module.Use(srcFile, pObjInfo->pModInfo); /* increase refcount */ + } + + CHKiRet(pObjInfo->QueryIF(pIf)); + pIf->ifIsLoaded = 1; /* we are happy */ + +finalize_it: + pthread_mutex_unlock(&mutObjGlobalOp); + + if(pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* This function shall be called when a caller is done with an object. Its primary + * purpose is to keep the reference count correct, which is highly important for + * modules residing in loadable modules. + * rgerhards, 2008-03-10 + */ +static rsRetVal +ReleaseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) +{ + DEFiRet; + cstr_t *pStr = NULL; + objInfo_t *pObjInfo; + + + /* dev debug only dbgprintf("source file %s releasing object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */ + pthread_mutex_lock(&mutObjGlobalOp); + + if(pObjFile == NULL) + FINALIZE; /* if it is not a lodable module, we do not need to do anything... */ + + if(pIf->ifIsLoaded == 0) { + FINALIZE; /* we are not loaded - this is perfectly OK... */ + } else if(pIf->ifIsLoaded == 2) { + pIf->ifIsLoaded = 0; /* clean up */ + FINALIZE; /* we had a load error and can not/must not continue */ + } + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + + /* if we reach this point, we have a valid pObjInfo */ + module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */ + + pIf->ifIsLoaded = 0; /* indicated "no longer valid" */ + +finalize_it: + pthread_mutex_unlock(&mutObjGlobalOp); + + if(pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(obj) +CODESTARTobjQueryInterface(obj) + if(pIf->ifVersion != objCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->UseObj = UseObj; + pIf->ReleaseObj = ReleaseObj; + pIf->InfoConstruct = InfoConstruct; + pIf->DestructObjSelf = DestructObjSelf; + pIf->BeginSerializePropBag = BeginSerializePropBag; + pIf->InfoSetMethod = InfoSetMethod; + pIf->BeginSerialize = BeginSerialize; + pIf->SerializeProp = SerializeProp; + pIf->EndSerialize = EndSerialize; + pIf->RegisterObj = RegisterObj; + pIf->UnregisterObj = UnregisterObj; + pIf->Deserialize = Deserialize; + pIf->DeserializePropBag = DeserializePropBag; + pIf->SetName = SetName; + pIf->GetName = GetName; +finalize_it: +ENDobjQueryInterface(obj) + + +/* This function returns a pointer to our own interface. It is used as the + * hook that every object (including dynamically loaded ones) can use to + * obtain a pointer to our interface which than can be used to obtain + * pointers to any other interface in the system. This function must be + * externally visible because of its special nature. + * rgerhards, 2008-02-29 [nice - will have that date the next time in 4 years ;)] + */ +rsRetVal +objGetObjInterface(obj_if_t *pIf) +{ + DEFiRet; + assert(pIf != NULL); + objQueryInterface(pIf); + RETiRet; +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +rsRetVal +objClassExit(void) +{ + DEFiRet; + /* release objects we no longer need */ + objRelease(strm, CORE_COMPONENT); + objRelease(var, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + + /* TODO: implement the class exits! */ +#if 0 + cfsyslineExit(pModInfo); + varClassExit(pModInfo); +#endif + errmsgClassExit(); + moduleClassExit(); + RETiRet; +} + + +/* initialize our own class + * Please note that this also initializes those classes that we rely on. + * Though this is a bit dirty, we need to do it - otherwise we can't get + * around that bootstrap problem. We need to face the fact the the obj + * class is a little different from the rest of the system, as it provides + * the core class loader functionality. + * rgerhards, 2008-02-29 + */ +rsRetVal +objClassInit(modInfo_t *pModInfo) +{ + pthread_mutexattr_t mutAttr; + int i; + DEFiRet; + + /* first, initialize the object system itself. This must be done + * before any other object is created. + */ + for(i = 0 ; i < OBJ_NUM_IDS ; ++i) { + arrObjInfo[i] = NULL; + } + + /* the mutex must be recursive, because objects may call into other + * object identifiers recursively. + */ + pthread_mutexattr_init(&mutAttr); + pthread_mutexattr_settype(&mutAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutObjGlobalOp, &mutAttr); + + /* request objects we use */ + CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */ + + /* init classes we use (limit to as few as possible!) */ + CHKiRet(errmsgClassInit(pModInfo)); + CHKiRet(datetimeClassInit(pModInfo)); + CHKiRet(cfsyslineInit()); + CHKiRet(varClassInit(pModInfo)); + CHKiRet(moduleClassInit(pModInfo)); + CHKiRet(strmClassInit(pModInfo)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(strm, CORE_COMPONENT)); + +finalize_it: + RETiRet; +} + +/* vi:set ai: + */ diff --git a/runtime/obj.h b/runtime/obj.h new file mode 100644 index 00000000..27d32b7a --- /dev/null +++ b/runtime/obj.h @@ -0,0 +1,129 @@ +/* Definition of the generic obj class module. + * + * This module relies heavily on preprocessor macros in order to + * provide fast execution time AND ease of use. + * + * Each object that uses this base class MUST provide a constructor with + * the following interface: + * + * Destruct(pThis); + * + * A constructor is not necessary (except for some features, e.g. de-serialization). + * If it is provided, it is a three-part constructor (to handle all cases with a + * generic interface): + * + * Construct(&pThis); + * SetProperty(pThis, property_t *); + * ConstructFinalize(pThis); + * + * SetProperty() and ConstructFinalize() may also be called on an object + * instance which has been Construct()'ed outside of this module. + * + * pThis always references to a pointer of the object. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBJ_H_INCLUDED +#define OBJ_H_INCLUDED + +#include "obj-types.h" +#include "var.h" +#include "stream.h" + +/* macros */ +/* the following one is a helper that prevents us from writing the + * ever-same code at the end of Construct() + */ +#define OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ + if(iRet == RS_RET_OK) { \ + *ppThis = pThis; \ + } else { \ + if(pThis != NULL) \ + free(pThis); \ + } + +#define objSerializeSCALAR_VAR(strm, propName, propType, var) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &var)); +#define objSerializeSCALAR(strm, propName, propType) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &pThis->propName)); +#define objSerializePTR(strm, propName, propType) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) pThis->propName)); +#define DEFobjStaticHelpers \ + static objInfo_t __attribute__((unused)) *pObjInfoOBJ = NULL; \ + DEFobjCurrIf(obj) + + +#define objGetClassName(pThis) (((obj_t*) (pThis))->pObjInfo->pszID) +#define objGetVersion(pThis) (((obj_t*) (pThis))->pObjInfo->iObjVers) +/* the next macro MUST be called in Constructors: */ +#ifndef NDEBUG /* this means if debug... */ +# define objConstructSetObjInfo(pThis) \ + ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \ + ((obj_t*) (pThis))->pszName = NULL; \ + ((obj_t*) (pThis))->iObjCooCKiE = 0xBADEFEE +#else +# define objConstructSetObjInfo(pThis) \ + ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \ + ((obj_t*) (pThis))->pszName = NULL +#endif +#define objSerialize(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_SERIALIZE]) + +#define OBJSetMethodHandler(methodID, pHdlr) \ + CHKiRet(obj.InfoSetMethod(pObjInfoOBJ, methodID, (rsRetVal (*)(void*)) pHdlr)) + +/* interfaces */ +BEGINinterface(obj) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*UseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); + rsRetVal (*ReleaseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); + rsRetVal (*InfoConstruct)(objInfo_t **ppThis, uchar *pszID, int iObjVers, + rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), + rsRetVal (*pQueryIF)(interface_t*), modInfo_t*); + rsRetVal (*DestructObjSelf)(obj_t *pThis); + rsRetVal (*BeginSerializePropBag)(strm_t *pStrm, obj_t *pObj); + rsRetVal (*InfoSetMethod)(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)); + rsRetVal (*BeginSerialize)(strm_t *pStrm, obj_t *pObj); + rsRetVal (*SerializeProp)(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr); + rsRetVal (*EndSerialize)(strm_t *pStrm); + rsRetVal (*RegisterObj)(uchar *pszObjName, objInfo_t *pInfo); + rsRetVal (*UnregisterObj)(uchar *pszObjName); + rsRetVal (*Deserialize)(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr); + rsRetVal (*DeserializePropBag)(obj_t *pObj, strm_t *pStrm); + rsRetVal (*SetName)(obj_t *pThis, uchar *pszName); + uchar * (*GetName)(obj_t *pThis); +ENDinterface(obj) +#define objCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +/* the following define *is* necessary, because it provides the root way of obtaining + * interfaces (at some place we need to start our query... + */ +rsRetVal objGetObjInterface(obj_if_t *pIf); +PROTOTYPEObjClassInit(obj); +PROTOTYPEObjClassExit(obj); +rsRetVal objDeserializeWithMethods(void *ppObj, uchar *pszTypeExpected, int lenTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr, rsRetVal (*objConstruct)(), rsRetVal (*objConstructFinalize)(), rsRetVal (*objDeserialize)()); +rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm); +rsRetVal objDeserializeDummy(obj_t *pObj, strm_t *pStrm); + + +/* the following definition is only for "friends" */ +extern pthread_mutex_t mutObjGlobalOp; /* mutex to guard global operations of the object system */ + +#endif /* #ifndef OBJ_H_INCLUDED */ diff --git a/runtime/objomsr.c b/runtime/objomsr.c new file mode 100644 index 00000000..e63eb681 --- /dev/null +++ b/runtime/objomsr.c @@ -0,0 +1,156 @@ +/* objomsr.c + * Implementation of the omsr (omodStringRequest) object. + * + * File begun on 2007-07-27 by RGerhards + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "rsyslog.h" +#include "objomsr.h" + + +/* destructor + */ +rsRetVal OMSRdestruct(omodStringRequest_t *pThis) +{ + int i; + + assert(pThis != NULL); + /* free the strings */ + if(pThis->ppTplName != NULL) { + for(i = 0 ; i < pThis->iNumEntries ; ++i) { + free(pThis->ppTplName[i]); + } + free(pThis->ppTplName); + } + if(pThis->piTplOpts != NULL) + free(pThis->piTplOpts); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor + */ +rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries) +{ + omodStringRequest_t *pThis = NULL; + DEFiRet; + + assert(ppThis != NULL); + assert(iNumEntries >= 0); + if(iNumEntries > CONF_OMOD_NUMSTRINGS_MAXSIZE) { + ABORT_FINALIZE(RS_RET_MAX_OMSR_REACHED); + } + CHKmalloc(pThis = calloc(1, sizeof(omodStringRequest_t))); + + /* got the structure, so fill it */ + pThis->iNumEntries = iNumEntries; + /* allocate string for template name array. The individual strings will be + * allocated as the code progresses (we do not yet know the string sizes) + */ + CHKmalloc(pThis->ppTplName = calloc(iNumEntries, sizeof(uchar*))); + + /* allocate the template options array. */ + CHKmalloc(pThis->piTplOpts = calloc(iNumEntries, sizeof(int))); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis != NULL) { + OMSRdestruct(pThis); + pThis = NULL; + } + } + *ppThis = pThis; + RETiRet; +} + +/* set a template name and option to the object. Index must be given. The pTplName must be + * pointing to memory that can be freed. If in doubt, the caller must strdup() the value. + */ +rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts) +{ + assert(pThis != NULL); + assert(iEntry < pThis->iNumEntries); + + if(pThis->ppTplName[iEntry] != NULL) + free(pThis->ppTplName[iEntry]); + pThis->ppTplName[iEntry] = pTplName; + pThis->piTplOpts[iEntry] = iTplOpts; + + return RS_RET_OK; +} + + +/* get number of entries for this object + */ +int OMSRgetEntryCount(omodStringRequest_t *pThis) +{ + assert(pThis != NULL); + return pThis->iNumEntries; +} + + +/* return data for a specific entry. All data returned is + * read-only and lasts only as long as the object lives. If the caller + * needs it for an extended period of time, the caller must copy the + * strings. Please note that the string pointer may be NULL, which is the + * case when it was never set. + */ +int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts) +{ + assert(pThis != NULL); + assert(ppTplName != NULL); + assert(piTplOpts != NULL); + assert(iEntry < pThis->iNumEntries); + + *ppTplName = pThis->ppTplName[iEntry]; + *piTplOpts = pThis->piTplOpts[iEntry]; + + return RS_RET_OK; +} + + +/* return the full set of template options that are supported by this version of + * OMSR. They are returned in an unsigned long value. The caller can mask that + * value to check on the option he is interested in. + * Note that this interface was added in 4.1.6, so a plugin must obtain a pointer + * to this interface via queryHostEtryPt(). + * rgerhards, 2009-04-03 + */ +rsRetVal +OMSRgetSupportedTplOpts(unsigned long *pOpts) +{ + DEFiRet; + assert(pOpts != NULL); + *pOpts = OMSR_RQD_TPL_OPT_SQL | OMSR_TPL_AS_ARRAY | OMSR_TPL_AS_MSG + | OMSR_TPL_AS_JSON; + RETiRet; +} + +/* vim:set ai: + */ diff --git a/runtime/objomsr.h b/runtime/objomsr.h new file mode 100644 index 00000000..3baccaa3 --- /dev/null +++ b/runtime/objomsr.h @@ -0,0 +1,51 @@ +/* Definition of the omsr (omodStringRequest) object. + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBJOMSR_H_INCLUDED +#define OBJOMSR_H_INCLUDED + +/* define flags for required template options */ +#define OMSR_NO_RQD_TPL_OPTS 0 +#define OMSR_RQD_TPL_OPT_SQL 1 +/* only one of OMSR_TPL_AS_ARRAY, _AS_MSG, or _AS_JSON must be specified, + * if all are given results are unpredictable. + */ +#define OMSR_TPL_AS_ARRAY 2 /* introduced in 4.1.6, 2009-04-03 */ +#define OMSR_TPL_AS_MSG 4 /* introduced in 5.3.4, 2009-11-02 */ +#define OMSR_TPL_AS_JSON 8 /* introduced in 6.5.1, 2012-09-02 */ +/* next option is 16, 32, 64, ... */ + +struct omodStringRequest_s { /* strings requested by output module for doAction() */ + int iNumEntries; /* number of array entries for data elements below */ + uchar **ppTplName; /* pointer to array of template names */ + int *piTplOpts;/* pointer to array of check-options when pulling template */ +}; +typedef struct omodStringRequest_s omodStringRequest_t; + +/* prototypes */ +rsRetVal OMSRdestruct(omodStringRequest_t *pThis); +rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries); +rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts); +rsRetVal OMSRgetSupportedTplOpts(unsigned long *pOpts); +int OMSRgetEntryCount(omodStringRequest_t *pThis); +int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts); + +#endif /* #ifndef OBJOMSR_H_INCLUDED */ diff --git a/runtime/parser.c b/runtime/parser.c new file mode 100644 index 00000000..74b28f4c --- /dev/null +++ b/runtime/parser.c @@ -0,0 +1,728 @@ +/* parser.c + * This module contains functions for message parsers. It still needs to be + * converted into an object (and much extended). + * + * Module begun 2008-10-09 by Rainer Gerhards (based on previous code from syslogd.c) + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <assert.h> +#ifdef USE_NETZIP +#include <zlib.h> +#endif + +#include "rsyslog.h" +#include "dirty.h" +#include "msg.h" +#include "obj.h" +#include "datetime.h" +#include "errmsg.h" +#include "parser.h" +#include "ruleset.h" +#include "unicode-helper.h" +#include "dirty.h" +#include "cfsysline.h" + +/* some defines */ +#define DEFUPRI (LOG_USER|LOG_NOTICE) + +/* definitions for objects we access */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(datetime) +DEFobjCurrIf(ruleset) + +/* static data */ + +/* config data */ +static uchar cCCEscapeChar = '#';/* character to be used to start an escape sequence for control chars */ +static int bEscapeCCOnRcv = 1; /* escape control characters on reception: 0 - no, 1 - yes */ +static int bSpaceLFOnRcv = 0; /* replace newlines with spaces on reception: 0 - no, 1 - yes */ +static int bEscape8BitChars = 0; /* escape characters > 127 on reception: 0 - no, 1 - yes */ +static int bEscapeTab = 1; /* escape tab control character when doing CC escapes: 0 - no, 1 - yes */ +static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */ + +/* This is the list of all parsers known to us. + * This is also used to unload all modules on shutdown. + */ +parserList_t *pParsLstRoot = NULL; + +/* this is the list of the default parsers, to be used if no others + * are specified. + */ +parserList_t *pDfltParsLst = NULL; + + +/* intialize (but NOT allocate) a parser list. Primarily meant as a hook + * which can be used to extend the list in the future. So far, just sets + * it to NULL. + */ +static rsRetVal +InitParserList(parserList_t **pListRoot) +{ + *pListRoot = NULL; + return RS_RET_OK; +} + + +/* destruct a parser list. The list elements are destroyed, but the parser objects + * themselves are not modified. (That is done at a late stage during rsyslogd + * shutdown and need not be considered here.) + */ +static rsRetVal +DestructParserList(parserList_t **ppListRoot) +{ + parserList_t *pParsLst; + parserList_t *pParsLstDel; + + pParsLst = *ppListRoot; + while(pParsLst != NULL) { + pParsLstDel = pParsLst; + pParsLst = pParsLst->pNext; + free(pParsLstDel); + } + *ppListRoot = NULL; + return RS_RET_OK; +} + + +/* Add a parser to the list. We use a VERY simple and ineffcient algorithm, + * but it is employed only for a few milliseconds during config processing. So + * I prefer to keep it very simple and with simple data structures. Unfortunately, + * we need to preserve the order, but I don't like to add a tail pointer as that + * would require a container object. So I do the extra work to skip to the tail + * when adding elements... + * rgerhards, 2009-11-03 + */ +static rsRetVal +AddParserToList(parserList_t **ppListRoot, parser_t *pParser) +{ + parserList_t *pThis; + parserList_t *pTail; + DEFiRet; + + CHKmalloc(pThis = MALLOC(sizeof(parserList_t))); + pThis->pParser = pParser; + pThis->pNext = NULL; + + if(*ppListRoot == NULL) { + pThis->pNext = *ppListRoot; + *ppListRoot = pThis; + } else { + /* find tail first */ + for(pTail = *ppListRoot ; pTail->pNext != NULL ; pTail = pTail->pNext) + /* just search, do nothing else */; + /* add at tail */ + pTail->pNext = pThis; + } + +finalize_it: + RETiRet; +} + +void +printParserList(parserList_t *pList) +{ + while(pList != NULL) { + dbgprintf("parser: %s\n", pList->pParser->pName); + pList = pList->pNext; + } +} + +/* find a parser based on the provided name */ +static rsRetVal +FindParser(parser_t **ppParser, uchar *pName) +{ + parserList_t *pThis; + DEFiRet; + + for(pThis = pParsLstRoot ; pThis != NULL ; pThis = pThis->pNext) { + if(ustrcmp(pThis->pParser->pName, pName) == 0) { + *ppParser = pThis->pParser; + FINALIZE; /* found it, iRet still eq. OK! */ + } + } + + iRet = RS_RET_PARSER_NOT_FOUND; + +finalize_it: + RETiRet; +} + + +/* --- END helper functions for parser list handling --- */ + +/* Add a an already existing parser to the default list. As usual, order + * of calls is important (most importantly, that means the legacy parser, + * which can process everything, MUST be added last!). + * rgerhards, 2009-11-04 + */ +static rsRetVal +AddDfltParser(uchar *pName) +{ + parser_t *pParser; + DEFiRet; + + CHKiRet(FindParser(&pParser, pName)); + CHKiRet(AddParserToList(&pDfltParsLst, pParser)); + DBGPRINTF("Parser '%s' added to default parser set.\n", pName); + +finalize_it: + RETiRet; +} + + + +BEGINobjConstruct(parser) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(parser) + +/* ConstructionFinalizer. The most important chore is to add the parser object + * to our global list of available parsers. + * rgerhards, 2009-11-03 + */ +rsRetVal parserConstructFinalize(parser_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, parser); + CHKiRet(AddParserToList(&pParsLstRoot, pThis)); + DBGPRINTF("Parser '%s' added to list of available parsers.\n", pThis->pName); + +finalize_it: + RETiRet; +} + +BEGINobjDestruct(parser) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(parser) + DBGPRINTF("destructing parser '%s'\n", pThis->pName); + free(pThis->pName); +ENDobjDestruct(parser) + + +/* uncompress a received message if it is compressed. + * pMsg->pszRawMsg buffer is updated. + * rgerhards, 2008-10-09 + */ +static inline rsRetVal uncompressMessage(msg_t *pMsg) +{ + DEFiRet; +# ifdef USE_NETZIP + uchar *deflateBuf = NULL; + uLongf iLenDefBuf; + uchar *pszMsg; + size_t lenMsg; + + assert(pMsg != NULL); + pszMsg = pMsg->pszRawMsg; + lenMsg = pMsg->iLenRawMsg; + + /* we first need to check if we have a compressed record. If so, + * we must decompress it. + */ + if(lenMsg > 0 && *pszMsg == 'z') { /* compressed data present? (do NOT change order if conditions!) */ + /* we have compressed data, so let's deflate it. We support a maximum + * message size of iMaxLine. If it is larger, an error message is logged + * and the message is dropped. We do NOT try to decompress larger messages + * as such might be used for denial of service. It might happen to later + * builds that such functionality be added as an optional, operator-configurable + * feature. + */ + int ret; + iLenDefBuf = glbl.GetMaxLine(); + CHKmalloc(deflateBuf = MALLOC(sizeof(uchar) * (iLenDefBuf + 1))); + ret = uncompress((uchar *) deflateBuf, &iLenDefBuf, (uchar *) pszMsg+1, lenMsg-1); + DBGPRINTF("Compressed message uncompressed with status %d, length: new %ld, old %d.\n", + ret, (long) iLenDefBuf, (int) (lenMsg-1)); + /* Now check if the uncompression worked. If not, there is not much we can do. In + * that case, we log an error message but ignore the message itself. Storing the + * compressed text is dangerous, as it contains control characters. So we do + * not do this. If someone would like to have a copy, this code here could be + * modified to do a hex-dump of the buffer in question. We do not include + * this functionality right now. + * rgerhards, 2006-12-07 + */ + if(ret != Z_OK) { + errmsg.LogError(0, NO_ERRCODE, "Uncompression of a message failed with return code %d " + "- enable debug logging if you need further information. " + "Message ignored.", ret); + FINALIZE; /* unconditional exit, nothing left to do... */ + } + MsgSetRawMsg(pMsg, (char*)deflateBuf, iLenDefBuf); + } +finalize_it: + if(deflateBuf != NULL) + free(deflateBuf); + +# else /* ifdef USE_NETZIP */ + + /* in this case, we still need to check if the message is compressed. If so, we must + * tell the user we can not accept it. + */ + if(pMsg->iLenRawMsg > 0 && *pMsg->pszRawMsg == 'z') { + errmsg.LogError(0, NO_ERRCODE, "Received a compressed message, but rsyslogd does not have compression " + "support enabled. The message will be ignored."); + ABORT_FINALIZE(RS_RET_NO_ZIP); + } + +finalize_it: +# endif /* ifdef USE_NETZIP */ + + RETiRet; +} + + +/* sanitize a received message + * if a message gets to large during sanitization, it is truncated. This is + * as specified in the upcoming syslog RFC series. + * rgerhards, 2008-10-09 + * We check if we have a NUL character at the very end of the + * message. This seems to be a frequent problem with a number of senders. + * So I have now decided to drop these NULs. However, if they are intentional, + * that may cause us some problems, e.g. with syslog-sign. On the other hand, + * current code always has problems with intentional NULs (as it needs to escape + * them to prevent problems with the C string libraries), so that does not + * really matter. Just to be on the save side, we'll log destruction of such + * NULs in the debug log. + * rgerhards, 2007-09-14 + */ +static inline rsRetVal +SanitizeMsg(msg_t *pMsg) +{ + DEFiRet; + uchar *pszMsg; + uchar *pDst; /* destination for copy job */ + size_t lenMsg; + size_t iSrc; + size_t iDst; + size_t iMaxLine; + size_t maxDest; + sbool bUpdatedLen = RSFALSE; + uchar szSanBuf[32*1024]; /* buffer used for sanitizing a string */ + + assert(pMsg != NULL); + assert(pMsg->iLenRawMsg > 0); + + pszMsg = pMsg->pszRawMsg; + lenMsg = pMsg->iLenRawMsg; + + /* remove NUL character at end of message (see comment in function header) + * Note that we do not need to add a NUL character in this case, because it + * is already present ;) + */ + if(pszMsg[lenMsg-1] == '\0') { + DBGPRINTF("dropped NUL at very end of message\n"); + bUpdatedLen = RSTRUE; + lenMsg--; + } + + /* then we check if we need to drop trailing LFs, which often make + * their way into syslog messages unintentionally. In order to remain + * compatible to recent IETF developments, we allow the user to + * turn on/off this handling. rgerhards, 2007-07-23 + */ + if(bDropTrailingLF && pszMsg[lenMsg-1] == '\n') { + DBGPRINTF("dropped LF at very end of message (DropTrailingLF is set)\n"); + lenMsg--; + pszMsg[lenMsg] = '\0'; + bUpdatedLen = RSTRUE; + } + + /* it is much quicker to sweep over the message and see if it actually + * needs sanitation than to do the sanitation in any case. So we first do + * this and terminate when it is not needed - which is expectedly the case + * for the vast majority of messages. -- rgerhards, 2009-06-15 + * Note that we do NOT check here if tab characters are to be escaped or + * not. I expect this functionality to be seldomly used and thus I do not + * like to pay the performance penalty. So the penalty is only with those + * that actually use it, because we may call the sanitizer without actual + * need below (but it then still will work perfectly well!). -- rgerhards, 2009-11-27 + */ + int bNeedSanitize = 0; + for(iSrc = 0 ; iSrc < lenMsg ; iSrc++) { + if(pszMsg[iSrc] < 32) { + if(bSpaceLFOnRcv && pszMsg[iSrc] == '\n') + pszMsg[iSrc] = ' '; + else if(pszMsg[iSrc] == '\0' || bEscapeCCOnRcv) { + bNeedSanitize = 1; + if (!bSpaceLFOnRcv) + break; + } + } else if(pszMsg[iSrc] > 127 && bEscape8BitChars) { + bNeedSanitize = 1; + break; + } + } + + if(!bNeedSanitize) { + if(bUpdatedLen == RSTRUE) + MsgSetRawMsgSize(pMsg, lenMsg); + FINALIZE; + } + + /* now copy over the message and sanitize it. Note that up to iSrc-1 there was + * obviously no need to sanitize, so we can go over that quickly... + */ + iMaxLine = glbl.GetMaxLine(); + maxDest = lenMsg * 4; /* message can grow at most four-fold */ + if(maxDest > iMaxLine) + maxDest = iMaxLine; /* but not more than the max size! */ + if(maxDest < sizeof(szSanBuf)) + pDst = szSanBuf; + else + CHKmalloc(pDst = MALLOC(sizeof(uchar) * (iMaxLine + 1))); + if(iSrc > 0) { + iSrc--; /* go back to where everything is OK */ + memcpy(pDst, pszMsg, iSrc); /* fast copy known good */ + } + iDst = iSrc; + while(iSrc < lenMsg && iDst < maxDest - 3) { /* leave some space if last char must be escaped */ + if((pszMsg[iSrc] < 32) && (pszMsg[iSrc] != '\t' || bEscapeTab)) { + /* note: \0 must always be escaped, the rest of the code currently + * can not handle it! -- rgerhards, 2009-08-26 + */ + if(pszMsg[iSrc] == '\0' || bEscapeCCOnRcv) { + /* we are configured to escape control characters. Please note + * that this most probably break non-western character sets like + * Japanese, Korean or Chinese. rgerhards, 2007-07-17 + */ + pDst[iDst++] = cCCEscapeChar; + pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0300) >> 6); + pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0070) >> 3); + pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0007)); + } + } else if(pszMsg[iSrc] > 127 && bEscape8BitChars) { + /* In this case, we also do the conversion. Note that this most + * probably breaks European languages. -- rgerhards, 2010-01-27 + */ + pDst[iDst++] = cCCEscapeChar; + pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0300) >> 6); + pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0070) >> 3); + pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0007)); + } else { + pDst[iDst++] = pszMsg[iSrc]; + } + ++iSrc; + } + pDst[iDst] = '\0'; + + MsgSetRawMsg(pMsg, (char*)pDst, iDst); /* save sanitized string */ + + if(pDst != szSanBuf) + free(pDst); + +finalize_it: + RETiRet; +} + +/* A standard parser to parse out the PRI. This is made available in + * this module as it is expected that allmost all parsers will need + * that functionality and so they do not need to implement it themsleves. + */ +static inline rsRetVal +ParsePRI(msg_t *pMsg) +{ + int pri; + uchar *msg; + int lenMsg; + DEFiRet; + + /* pull PRI */ + lenMsg = pMsg->iLenRawMsg; + msg = pMsg->pszRawMsg; + pri = DEFUPRI; + if(pMsg->msgFlags & NO_PRI_IN_RAW) { + /* In this case, simply do so as if the pri would be right at top */ + MsgSetAfterPRIOffs(pMsg, 0); + } else { + if(*msg == '<') { + /* while we process the PRI, we also fill the PRI textual representation + * inside the msg object. This may not be ideal from an OOP point of view, + * but it offers us performance... + */ + pri = 0; + while(--lenMsg > 0 && isdigit((int) *++msg)) { + pri = 10 * pri + (*msg - '0'); + } + if(*msg == '>') + ++msg; + if(pri & ~(LOG_FACMASK|LOG_PRIMASK)) + pri = DEFUPRI; + } + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + MsgSetAfterPRIOffs(pMsg, msg - pMsg->pszRawMsg); + } + RETiRet; +} + + +/* Parse a received message. The object's rawmsg property is taken and + * parsed according to the relevant standards. This can later be + * extended to support configured parsers. + * rgerhards, 2008-10-09 + */ +static rsRetVal +ParseMsg(msg_t *pMsg) +{ + rsRetVal localRet = RS_RET_ERR; + parserList_t *pParserList; + parser_t *pParser; + sbool bIsSanitized; + sbool bPRIisParsed; + static int iErrMsgRateLimiter = 0; + DEFiRet; + + if(pMsg->iLenRawMsg == 0) + ABORT_FINALIZE(RS_RET_EMPTY_MSG); + +# ifdef USE_NETZIP + CHKiRet(uncompressMessage(pMsg)); +# endif + + /* we take the risk to print a non-sanitized string, because this is the best we can get + * (and that functionality is too important for debugging to drop it...). + */ + DBGPRINTF("msg parser: flags %x, from '%s', msg '%.60s'\n", pMsg->msgFlags, + (pMsg->msgFlags & NEEDS_DNSRESOL) ? UCHAR_CONSTANT("~NOTRESOLVED~") : getRcvFrom(pMsg), + pMsg->pszRawMsg); + + /* we now need to go through our list of parsers and see which one is capable of + * parsing the message. Note that the first parser that requires message sanitization + * will cause it to happen. After that, access to the unsanitized message is no + * loger possible. + */ + pParserList = ruleset.GetParserList(ourConf, pMsg); + if(pParserList == NULL) { + pParserList = pDfltParsLst; + } + DBGPRINTF("parse using parser list %p%s.\n", pParserList, + (pParserList == pDfltParsLst) ? " (the default list)" : ""); + + bIsSanitized = RSFALSE; + bPRIisParsed = RSFALSE; + while(pParserList != NULL) { + pParser = pParserList->pParser; + if(pParser->bDoSanitazion && bIsSanitized == RSFALSE) { + CHKiRet(SanitizeMsg(pMsg)); + if(pParser->bDoPRIParsing && bPRIisParsed == RSFALSE) { + CHKiRet(ParsePRI(pMsg)); + bPRIisParsed = RSTRUE; + } + bIsSanitized = RSTRUE; + } + localRet = pParser->pModule->mod.pm.parse(pMsg); + DBGPRINTF("Parser '%s' returned %d\n", pParser->pName, localRet); + if(localRet != RS_RET_COULD_NOT_PARSE) + break; + pParserList = pParserList->pNext; + } + + /* We need to log a warning message and drop the message if we did not find a parser. + * Note that we log at most the first 1000 message, as this may very well be a problem + * that causes a message generation loop. We do not synchronize that counter, it doesn't + * matter if we log a handful messages more than we should... + */ + if(localRet != RS_RET_OK) { + if(++iErrMsgRateLimiter > 1000) { + errmsg.LogError(0, localRet, "Error: one message could not be processed by " + "any parser, message is being discarded (start of raw msg: '%.50s')", + pMsg->pszRawMsg); + } + DBGPRINTF("No parser could process the message (state %d), we need to discard it.\n", localRet); + ABORT_FINALIZE(localRet); + } + + /* "finalize" message object */ + pMsg->msgFlags &= ~NEEDS_PARSING; /* this message is now parsed */ + +finalize_it: + RETiRet; +} + +/* set the parser name - string is copied over, call can continue to use it, + * but must free it if desired. + */ +static rsRetVal +SetName(parser_t *pThis, uchar *name) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, parser); + assert(name != NULL); + + if(pThis->pName != NULL) { + free(pThis->pName); + pThis->pName = NULL; + } + + CHKmalloc(pThis->pName = ustrdup(name)); + +finalize_it: + RETiRet; +} + + +/* set a pointer to "our" module. Note that no module + * pointer must already be set. + */ +static rsRetVal +SetModPtr(parser_t *pThis, modInfo_t *pMod) +{ + ISOBJ_TYPE_assert(pThis, parser); + assert(pMod != NULL); + assert(pThis->pModule == NULL); + pThis->pModule = pMod; + return RS_RET_OK; +} + + +/* Specify if we should do standard message sanitazion before we pass the data + * down to the parser. + */ +static rsRetVal +SetDoSanitazion(parser_t *pThis, int bDoIt) +{ + ISOBJ_TYPE_assert(pThis, parser); + pThis->bDoSanitazion = bDoIt; + return RS_RET_OK; +} + + +/* Specify if we should do standard PRI parsing before we pass the data + * down to the parser module. + */ +static rsRetVal +SetDoPRIParsing(parser_t *pThis, int bDoIt) +{ + ISOBJ_TYPE_assert(pThis, parser); + pThis->bDoPRIParsing = bDoIt; + return RS_RET_OK; +} + + +/* queryInterface function-- rgerhards, 2009-11-03 + */ +BEGINobjQueryInterface(parser) +CODESTARTobjQueryInterface(parser) + if(pIf->ifVersion != parserCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = parserConstruct; + pIf->ConstructFinalize = parserConstructFinalize; + pIf->Destruct = parserDestruct; + pIf->SetName = SetName; + pIf->SetModPtr = SetModPtr; + pIf->SetDoSanitazion = SetDoSanitazion; + pIf->SetDoPRIParsing = SetDoPRIParsing; + pIf->ParseMsg = ParseMsg; + pIf->SanitizeMsg = SanitizeMsg; + pIf->InitParserList = InitParserList; + pIf->DestructParserList = DestructParserList; + pIf->AddParserToList = AddParserToList; + pIf->AddDfltParser = AddDfltParser; + pIf->FindParser = FindParser; +finalize_it: +ENDobjQueryInterface(parser) + + + +/* Reset config variables to default values. + * rgerhards, 2007-07-17 + */ +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cCCEscapeChar = '#'; + bEscapeCCOnRcv = 1; /* default is to escape control characters */ + bSpaceLFOnRcv = 0; + bEscape8BitChars = 0; /* default is to escape control characters */ + bEscapeTab = 1; /* default is to escape control characters */ + bDropTrailingLF = 1; /* default is to drop trailing LF's on reception */ + + return RS_RET_OK; +} + +/* This destroys the master parserlist and all of its parser entries. MUST only be + * done when the module is shut down. Parser modules are NOT unloaded, rsyslog + * does that at a later stage for all dynamically loaded modules. + */ +static void +destroyMasterParserList(void) +{ + parserList_t *pParsLst; + parserList_t *pParsLstDel; + + pParsLst = pParsLstRoot; + while(pParsLst != NULL) { + parserDestruct(&pParsLst->pParser); + pParsLstDel = pParsLst; + pParsLst = pParsLst->pNext; + free(pParsLstDel); + } +} + +/* Exit our class. + * rgerhards, 2009-11-04 + */ +BEGINObjClassExit(parser, OBJ_IS_CORE_MODULE) /* class, version */ + DestructParserList(&pDfltParsLst); + destroyMasterParserList(); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDObjClassExit(parser) + + +/* Initialize the parser class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2009-11-02 + */ +BEGINObjClassInit(parser, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + + CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar, NULL, &cCCEscapeChar, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscapeCCOnRcv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"spacelfonreceive", 0, eCmdHdlrBinary, NULL, &bSpaceLFOnRcv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"escape8bitcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscape8BitChars, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactertab", 0, eCmdHdlrBinary, NULL, &bEscapeTab, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); + + InitParserList(&pParsLstRoot); + InitParserList(&pDfltParsLst); +ENDObjClassInit(parser) + diff --git a/runtime/parser.h b/runtime/parser.h new file mode 100644 index 00000000..87a6269e --- /dev/null +++ b/runtime/parser.h @@ -0,0 +1,71 @@ +/* header for parser.c + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_PARSER_H +#define INCLUDED_PARSER_H + + +/* we create a small helper object, a list of parsers, that we can use to + * build a chain of them whereever this is needed (initially thought to be + * used in ruleset.c as well as ourselvs). + */ +struct parserList_s { + parser_t *pParser; + parserList_t *pNext; +}; + + +/* the parser object, a dummy because we have only static methods */ +struct parser_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pName; /* name of this parser */ + modInfo_t *pModule; /* pointer to parser's module */ + sbool bDoSanitazion; /* do standard message sanitazion before calling parser? */ + sbool bDoPRIParsing; /* do standard PRI parsing before calling parser? */ +}; + +/* interfaces */ +BEGINinterface(parser) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(var); + rsRetVal (*Construct)(parser_t **ppThis); + rsRetVal (*ConstructFinalize)(parser_t *pThis); + rsRetVal (*Destruct)(parser_t **ppThis); + rsRetVal (*SetName)(parser_t *pThis, uchar *name); + rsRetVal (*SetModPtr)(parser_t *pThis, modInfo_t *pMod); + rsRetVal (*SetDoSanitazion)(parser_t *pThis, int); + rsRetVal (*SetDoPRIParsing)(parser_t *pThis, int); + rsRetVal (*FindParser)(parser_t **ppThis, uchar*name); + rsRetVal (*InitParserList)(parserList_t **pListRoot); + rsRetVal (*DestructParserList)(parserList_t **pListRoot); + rsRetVal (*AddParserToList)(parserList_t **pListRoot, parser_t *pParser); + /* static functions */ + rsRetVal (*ParseMsg)(msg_t *pMsg); + rsRetVal (*SanitizeMsg)(msg_t *pMsg); + rsRetVal (*AddDfltParser)(uchar *); +ENDinterface(parser) +#define parserCURR_IF_VERSION 1 /* increment whenever you change the interface above! */ + +void printParserList(parserList_t *pList); + +/* prototypes */ +PROTOTYPEObj(parser); + + +#endif /* #ifndef INCLUDED_PARSER_H */ diff --git a/runtime/prop.c b/runtime/prop.c new file mode 100644 index 00000000..cb89fac0 --- /dev/null +++ b/runtime/prop.c @@ -0,0 +1,247 @@ +/* prop.c - rsyslog's prop object + * + * This object is meant to support message properties that are stored + * seperately from the message. The main intent is to support properties + * that are "constant" during a period of time, so that many messages may + * contain a reference to the same property. It is important, though, that + * properties are destroyed when they are no longer needed. + * + * Please note that this is a performance-critical part of the software and + * as such we may use some methods in here which do not look elegant, but + * which are fast... + * + * Module begun 2009-06-17 by Rainer Gerhards + * + * Copyright 2009-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "rsyslog.h" +#include "obj.h" +#include "obj-types.h" +#include "unicode-helper.h" +#include "atomic.h" +#include "prop.h" + +/* static data */ +DEFobjStaticHelpers + + +/* Standard-Constructor + */ +BEGINobjConstruct(prop) /* be sure to specify the object type also in END macro! */ + pThis->iRefCount = 1; + INIT_ATOMIC_HELPER_MUT(pThis->mutRefCount); +ENDobjConstruct(prop) + + +/* destructor for the prop object */ +BEGINobjDestruct(prop) /* be sure to specify the object type also in END and CODESTART macros! */ + int currRefCount; +CODESTARTobjDestruct(prop) + currRefCount = ATOMIC_DEC_AND_FETCH(&pThis->iRefCount, &pThis->mutRefCount); + if(currRefCount == 0) { + /* (only) in this case we need to actually destruct the object */ + if(pThis->len >= CONF_PROP_BUFSIZE) + free(pThis->szVal.psz); + DESTROY_ATOMIC_HELPER_MUT(pThis->mutRefCount); + } else { + pThis = NULL; /* tell framework NOT to destructing the object! */ + } +ENDobjDestruct(prop) + +/* set string, we make our own private copy! This MUST only be called BEFORE + * ConstructFinalize()! + */ +static rsRetVal SetString(prop_t *pThis, uchar *psz, int len) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, prop); + if(pThis->len >= CONF_PROP_BUFSIZE) + free(pThis->szVal.psz); + pThis->len = len; + if(len < CONF_PROP_BUFSIZE) { + memcpy(pThis->szVal.sz, psz, len + 1); + } else { + CHKmalloc(pThis->szVal.psz = MALLOC(len + 1)); + memcpy(pThis->szVal.psz, psz, len + 1); + } + +finalize_it: + RETiRet; +} + + +/* get string length */ +static int GetStringLen(prop_t *pThis) +{ + return pThis->len; +} + + +/* get string */ +rsRetVal GetString(prop_t *pThis, uchar **ppsz, int *plen) +{ + BEGINfunc + ISOBJ_TYPE_assert(pThis, prop); + if(pThis->len < CONF_PROP_BUFSIZE) { + *ppsz = pThis->szVal.sz; + } else { + *ppsz = pThis->szVal.psz; + } + *plen = pThis->len; + ENDfunc + return RS_RET_OK; +} + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +propConstructFinalize(prop_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, prop); + RETiRet; +} + + +/* add a new reference. It is VERY IMPORTANT to call this function whenever + * the property is handed over to some entitiy that later call Destruct() on it. + */ +static rsRetVal AddRef(prop_t *pThis) +{ + ATOMIC_INC(&pThis->iRefCount, &pThis->mutRefCount); + return RS_RET_OK; +} + + +/* this is a "do it all in one shot" function that creates a new property, + * assigns the provided string to it and finalizes the property. Among the + * convenience, it is alos (very, very) slightly faster. + * rgerhards, 2009-07-01 + */ +static rsRetVal CreateStringProp(prop_t **ppThis, uchar* psz, int len) +{ + DEFiRet; + propConstruct(ppThis); + SetString(*ppThis, psz, len); + propConstructFinalize(*ppThis); + RETiRet; +} + +/* another one-stop function, quite useful: it takes a property pointer and + * a string. If the string is already contained in the property, nothing happens. + * If the string is different (or the pointer NULL), the current property + * is destructed and a new one created. This can be used to get a specific + * name in those cases where there is a good chance that the property + * immediatly previously processed already contained the value we need - in + * which case we save us all the creation overhead by just reusing the already + * existing property). + * rgerhards, 2009-07-01 + */ +rsRetVal CreateOrReuseStringProp(prop_t **ppThis, uchar *psz, int len) +{ + uchar *pszPrev; + int lenPrev; + DEFiRet; + assert(ppThis != NULL); + + if(*ppThis == NULL) { + /* we need to create a property */ + CHKiRet(CreateStringProp(ppThis, psz, len)); + } else { + /* already exists, check if we can re-use it */ + GetString(*ppThis, &pszPrev, &lenPrev); + if(len != lenPrev || ustrcmp(psz, pszPrev)) { + /* different, need to discard old & create new one */ + propDestruct(ppThis); + CHKiRet(CreateStringProp(ppThis, psz, len)); + } /* else we can re-use the existing one! */ + } + +finalize_it: + RETiRet; +} + + +/* debugprint for the prop object */ +BEGINobjDebugPrint(prop) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(prop) + dbgprintf("prop object %p - no further debug info implemented\n", pThis); +ENDobjDebugPrint(prop) + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(prop) +CODESTARTobjQueryInterface(prop) + if(pIf->ifVersion != propCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = propConstruct; + pIf->ConstructFinalize = propConstructFinalize; + pIf->Destruct = propDestruct; + pIf->DebugPrint = propDebugPrint; + pIf->SetString = SetString; + pIf->GetString = GetString; + pIf->GetStringLen = GetStringLen; + pIf->AddRef = AddRef; + pIf->CreateStringProp = CreateStringProp; + pIf->CreateOrReuseStringProp = CreateOrReuseStringProp; + +finalize_it: +ENDobjQueryInterface(prop) + + +/* Exit the prop class. + * rgerhards, 2009-04-06 + */ +BEGINObjClassExit(prop, OBJ_IS_CORE_MODULE) /* class, version */ +// objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(prop) + + +/* Initialize the prop class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(prop, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ +// CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, propDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, propConstructFinalize); +ENDObjClassInit(prop) + +/* vi:set ai: + */ diff --git a/runtime/prop.h b/runtime/prop.h new file mode 100644 index 00000000..c7564e6b --- /dev/null +++ b/runtime/prop.h @@ -0,0 +1,65 @@ +/* The prop object. + * + * This implements props within rsyslog. + * + * Copyright 2009-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_PROP_H +#define INCLUDED_PROP_H +#include "atomic.h" + +/* the prop object */ +struct prop_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + int iRefCount; /* reference counter */ + union { + uchar *psz; /* stored string */ + uchar sz[CONF_PROP_BUFSIZE]; + } szVal; + int len; /* we use int intentionally, otherwise we may get some troubles... */ + DEF_ATOMIC_HELPER_MUT(mutRefCount); +}; + +/* interfaces */ +BEGINinterface(prop) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(prop); + rsRetVal (*Construct)(prop_t **ppThis); + rsRetVal (*ConstructFinalize)(prop_t *pThis); + rsRetVal (*Destruct)(prop_t **ppThis); + rsRetVal (*SetString)(prop_t *pThis, uchar* psz, int len); + rsRetVal (*GetString)(prop_t *pThis, uchar** ppsz, int *plen); + int (*GetStringLen)(prop_t *pThis); + rsRetVal (*AddRef)(prop_t *pThis); + rsRetVal (*CreateStringProp)(prop_t **ppThis, uchar* psz, int len); + rsRetVal (*CreateOrReuseStringProp)(prop_t **ppThis, uchar *psz, int len); +ENDinterface(prop) +#define propCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* get classic c-style string */ +static inline uchar * +propGetSzStr(prop_t *pThis) +{ + return(pThis->len < CONF_PROP_BUFSIZE) ? pThis->szVal.sz : pThis->szVal.psz; +} + +/* prototypes */ +PROTOTYPEObj(prop); + +#endif /* #ifndef INCLUDED_PROP_H */ diff --git a/runtime/queue.c b/runtime/queue.c new file mode 100644 index 00000000..699e2a66 --- /dev/null +++ b/runtime/queue.c @@ -0,0 +1,2938 @@ +/* queue.c + * + * This file implements the queue object and its several queueing methods. + * + * File begun on 2008-01-03 by RGerhards + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * NOTE: as of 2009-04-22, I have begin to remove the qqueue* prefix from static + * function names - this makes it really hard to read and does not provide much + * benefit, at least I (now) think so... + * + * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> /* required for HP UX */ +#include <time.h> +#include <errno.h> + +#include "rsyslog.h" +#include "queue.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "wtp.h" +#include "wti.h" +#include "msg.h" +#include "atomic.h" +#include "errmsg.h" +#include "datetime.h" +#include "unicode-helper.h" +#include "statsobj.h" + +#ifdef OS_SOLARIS +# include <sched.h> +#endif + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(strm) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(datetime) +DEFobjCurrIf(statsobj) + +/* forward-definitions */ +static inline rsRetVal doEnqSingleObj(qqueue_t *pThis, flowControl_t flowCtlType, msg_t *pMsg); +static rsRetVal qqueueChkPersist(qqueue_t *pThis, int nUpdates); +static rsRetVal RateLimiter(qqueue_t *pThis); +static int qqueueChkStopWrkrDA(qqueue_t *pThis); +static rsRetVal GetDeqBatchSize(qqueue_t *pThis, int *pVal); +static rsRetVal ConsumerDA(qqueue_t *pThis, wti_t *pWti); +static rsRetVal batchProcessed(qqueue_t *pThis, wti_t *pWti); +static rsRetVal qqueueMultiEnqObjNonDirect(qqueue_t *pThis, multi_submit_t *pMultiSub); +static rsRetVal qqueueMultiEnqObjDirect(qqueue_t *pThis, multi_submit_t *pMultiSub); +static rsRetVal qAddDirect(qqueue_t *pThis, msg_t *pMsg); +static rsRetVal qDestructDirect(qqueue_t __attribute__((unused)) *pThis); +static rsRetVal qConstructDirect(qqueue_t __attribute__((unused)) *pThis); +static rsRetVal qDelDirect(qqueue_t __attribute__((unused)) *pThis); +static rsRetVal qDestructDisk(qqueue_t *pThis); + +/* some constants for queuePersist () */ +#define QUEUE_CHECKPOINT 1 +#define QUEUE_NO_CHECKPOINT 0 + +/* tables for interfacing with the v6 config system */ +static struct cnfparamdescr cnfpdescr[] = { + { "queue.filename", eCmdHdlrGetWord, 0 }, + { "queue.size", eCmdHdlrSize, 0 }, + { "queue.dequeuebatchsize", eCmdHdlrInt, 0 }, + { "queue.maxdiskspace", eCmdHdlrSize, 0 }, + { "queue.highwatermark", eCmdHdlrInt, 0 }, + { "queue.lowwatermark", eCmdHdlrInt, 0 }, + { "queue.fulldelaymark", eCmdHdlrInt, 0 }, + { "queue.lightdelaymark", eCmdHdlrInt, 0 }, + { "queue.discardmark", eCmdHdlrInt, 0 }, + { "queue.discardseverity", eCmdHdlrFacility, 0 }, + { "queue.checkpointinterval", eCmdHdlrInt, 0 }, + { "queue.syncqueuefiles", eCmdHdlrBinary, 0 }, + { "queue.type", eCmdHdlrQueueType, 0 }, + { "queue.workerthreads", eCmdHdlrInt, 0 }, + { "queue.timeoutshutdown", eCmdHdlrInt, 0 }, + { "queue.timeoutactioncompletion", eCmdHdlrInt, 0 }, + { "queue.timeoutenqueue", eCmdHdlrInt, 0 }, + { "queue.timeoutworkerthreadshutdown", eCmdHdlrInt, 0 }, + { "queue.workerthreadminimummessages", eCmdHdlrInt, 0 }, + { "queue.maxfilesize", eCmdHdlrSize, 0 }, + { "queue.saveonshutdown", eCmdHdlrBinary, 0 }, + { "queue.dequeueslowdown", eCmdHdlrInt, 0 }, + { "queue.dequeuetimebegin", eCmdHdlrInt, 0 }, + { "queue.dequeuetimeend", eCmdHdlrInt, 0 }, + { "queue.cry.provider", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk pblk = + { CNFPARAMBLK_VERSION, + sizeof(cnfpdescr)/sizeof(struct cnfparamdescr), + cnfpdescr + }; + +/* debug aid */ +static inline void displayBatchState(batch_t *pBatch) +{ + int i; + for(i = 0 ; i < pBatch->nElem ; ++i) { + DBGPRINTF("displayBatchState %p[%d]: %d\n", pBatch, i, pBatch->eltState[i]); + } +} + +/*********************************************************************** + * we need a private data structure, the "to-delete" list. As C does + * not provide any partly private data structures, we implement this + * structure right here inside the module. + * Note that this list must always be kept sorted based on a unique + * dequeue ID (which is monotonically increasing). + * rgerhards, 2009-05-18 + ***********************************************************************/ + +/* generate next uniqueue dequeue ID. Note that uniqueness is only required + * on a per-queue basis and while this instance runs. So a stricly monotonically + * increasing counter is sufficient (if enough bits are used). + */ +static inline qDeqID getNextDeqID(qqueue_t *pQueue) +{ + ISOBJ_TYPE_assert(pQueue, qqueue); + return pQueue->deqIDAdd++; +} + + +/* return the top element of the to-delete list or NULL, if the + * list is empty. + */ +static inline toDeleteLst_t *tdlPeek(qqueue_t *pQueue) +{ + ISOBJ_TYPE_assert(pQueue, qqueue); + return pQueue->toDeleteLst; +} + + +/* remove the top element of the to-delete list. Nothing but the + * element itself is destroyed. Must not be called when the list + * is empty. + */ +static inline rsRetVal tdlPop(qqueue_t *pQueue) +{ + toDeleteLst_t *pRemove; + DEFiRet; + + ISOBJ_TYPE_assert(pQueue, qqueue); + assert(pQueue->toDeleteLst != NULL); + + pRemove = pQueue->toDeleteLst; + pQueue->toDeleteLst = pQueue->toDeleteLst->pNext; + free(pRemove); + + RETiRet; +} + + +/* Add a new to-delete list entry. The function allocates the data + * structure, populates it with the values provided and links the new + * element into the correct place inside the list. + */ +static inline rsRetVal tdlAdd(qqueue_t *pQueue, qDeqID deqID, int nElemDeq) +{ + toDeleteLst_t *pNew; + toDeleteLst_t *pPrev; + DEFiRet; + + ISOBJ_TYPE_assert(pQueue, qqueue); + assert(pQueue->toDeleteLst != NULL); + + CHKmalloc(pNew = MALLOC(sizeof(toDeleteLst_t))); + pNew->deqID = deqID; + pNew->nElemDeq = nElemDeq; + + /* now find right spot */ + for( pPrev = pQueue->toDeleteLst + ; pPrev != NULL && deqID > pPrev->deqID + ; pPrev = pPrev->pNext) { + /*JUST SEARCH*/; + } + + if(pPrev == NULL) { + pNew->pNext = pQueue->toDeleteLst; + pQueue->toDeleteLst = pNew; + } else { + pNew->pNext = pPrev->pNext; + pPrev->pNext = pNew; + } + +finalize_it: + RETiRet; +} + + +/* methods */ + +static inline char * +getQueueTypeName(queueType_t t) +{ + char *r; + + switch(t) { + case QUEUETYPE_FIXED_ARRAY: + r = "FixedArray"; + break; + case QUEUETYPE_LINKEDLIST: + r = "LinkedList"; + break; + case QUEUETYPE_DISK: + r = "Disk"; + break; + case QUEUETYPE_DIRECT: + r = "Direct"; + break; + default: + r = "invalid/unknown queue mode"; + break; + } + return r; +} + +void +qqueueDbgPrint(qqueue_t *pThis) +{ + dbgoprint((obj_t*) pThis, "parameter dump:\n"); + dbgoprint((obj_t*) pThis, "queue.filename '%s'\n", + (pThis->pszFilePrefix == NULL) ? "[NONE]" : (char*)pThis->pszFilePrefix); + dbgoprint((obj_t*) pThis, "queue.size: %d\n", pThis->iMaxQueueSize); + dbgoprint((obj_t*) pThis, "queue.dequeuebatchsize: %d\n", pThis->iDeqBatchSize); + dbgoprint((obj_t*) pThis, "queue.maxdiskspace: %lld\n", pThis->iMaxFileSize); + dbgoprint((obj_t*) pThis, "queue.highwatermark: %d\n", pThis->iHighWtrMrk); + dbgoprint((obj_t*) pThis, "queue.lowwatermark: %d\n", pThis->iLowWtrMrk); + dbgoprint((obj_t*) pThis, "queue.fulldelaymark: %d\n", pThis->iFullDlyMrk); + dbgoprint((obj_t*) pThis, "queue.lightdelaymark: %d\n", pThis->iLightDlyMrk); + dbgoprint((obj_t*) pThis, "queue.discardmark: %d\n", pThis->iDiscardMrk); + dbgoprint((obj_t*) pThis, "queue.discardseverity: %d\n", pThis->iDiscardSeverity); + dbgoprint((obj_t*) pThis, "queue.checkpointinterval: %d\n", pThis->iPersistUpdCnt); + dbgoprint((obj_t*) pThis, "queue.syncqueuefiles: %d\n", pThis->bSyncQueueFiles); + dbgoprint((obj_t*) pThis, "queue.type: %d [%s]\n", pThis->qType, getQueueTypeName(pThis->qType)); + dbgoprint((obj_t*) pThis, "queue.workerthreads: %d\n", pThis->iNumWorkerThreads); + dbgoprint((obj_t*) pThis, "queue.timeoutshutdown: %d\n", pThis->toQShutdown); + dbgoprint((obj_t*) pThis, "queue.timeoutactioncompletion: %d\n", pThis->toActShutdown); + dbgoprint((obj_t*) pThis, "queue.timeoutenqueue: %d\n", pThis->toEnq); + dbgoprint((obj_t*) pThis, "queue.timeoutworkerthreadshutdown: %d\n", pThis->toWrkShutdown); + dbgoprint((obj_t*) pThis, "queue.workerthreadminimummessages: %d\n", pThis->iMinMsgsPerWrkr); + dbgoprint((obj_t*) pThis, "queue.maxfilesize: %lld\n", pThis->iMaxFileSize); + dbgoprint((obj_t*) pThis, "queue.saveonshutdown: %d\n", pThis->bSaveOnShutdown); + dbgoprint((obj_t*) pThis, "queue.dequeueslowdown: %d\n", pThis->iDeqSlowdown); + dbgoprint((obj_t*) pThis, "queue.dequeuetimebegin: %d\n", pThis->iDeqtWinFromHr); + dbgoprint((obj_t*) pThis, "queuedequeuetimend.: %d\n", pThis->iDeqtWinToHr); +} + + +/* get the physical queue size. Must only be called + * while mutex is locked! + * rgerhards, 2008-01-29 + */ +static inline int +getPhysicalQueueSize(qqueue_t *pThis) +{ + return pThis->iQueueSize; +} + + +/* get the logical queue size (that is store size minus logically dequeued elements). + * Must only be called while mutex is locked! + * rgerhards, 2009-05-19 + */ +static inline int +getLogicalQueueSize(qqueue_t *pThis) +{ + return pThis->iQueueSize - pThis->nLogDeq; +} + + + +/* This function drains the queue in cases where this needs to be done. The most probable + * reason is a HUP which needs to discard data (because the queue is configured to be lossy). + * During a shutdown, this is typically not needed, as the OS frees up ressources and does + * this much quicker than when we clean up ourselvs. -- rgerhards, 2008-10-21 + * This function returns void, as it makes no sense to communicate an error back, even if + * it happens. + * This functions works "around" the regular deque mechanism, because it is only used to + * clean up (in cases where message loss is acceptable). + */ +static inline void queueDrain(qqueue_t *pThis) +{ + msg_t *pMsg; + ASSERT(pThis != NULL); + + BEGINfunc + DBGOPRINT((obj_t*) pThis, "queue (type %d) will lose %d messages, destroying...\n", pThis->qType, pThis->iQueueSize); + /* iQueueSize is not decremented by qDel(), so we need to do it ourselves */ + while(ATOMIC_DEC_AND_FETCH(&pThis->iQueueSize, &pThis->mutQueueSize) > 0) { + pThis->qDeq(pThis, &pMsg); + if(pMsg != NULL) { + msgDestruct(&pMsg); + } + pThis->qDel(pThis); + } + ENDfunc +} + + +/* --------------- code for disk-assisted (DA) queue modes -------------------- */ + + +/* returns the number of workers that should be advised at + * this point in time. The mutex must be locked when + * ths function is called. -- rgerhards, 2008-01-25 + */ +static inline rsRetVal +qqueueAdviseMaxWorkers(qqueue_t *pThis) +{ + DEFiRet; + int iMaxWorkers; + + ISOBJ_TYPE_assert(pThis, qqueue); + + if(!pThis->bEnqOnly) { + if(pThis->bIsDA && getLogicalQueueSize(pThis) >= pThis->iHighWtrMrk) { + DBGOPRINT((obj_t*) pThis, "(re)activating DA worker\n"); + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* disk queues have always one worker */ + } else { + if(getLogicalQueueSize(pThis) == 0) { + iMaxWorkers = 0; + } else if(pThis->qType == QUEUETYPE_DISK || pThis->iMinMsgsPerWrkr == 0) { + iMaxWorkers = 1; + } else { + iMaxWorkers = getLogicalQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1; + } + wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers); + } + } + + RETiRet; +} + + +/* check if we run in disk-assisted mode and record that + * setting for easy (and quick!) access in the future. This + * function must only be called from constructors and only + * from those that support disk-assisted modes (aka memory- + * based queue drivers). + * rgerhards, 2008-01-14 + */ +static rsRetVal +qqueueChkIsDA(qqueue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + if(pThis->pszFilePrefix != NULL) { + pThis->bIsDA = 1; + DBGOPRINT((obj_t*) pThis, "is disk-assisted, disk will be used on demand\n"); + } else { + DBGOPRINT((obj_t*) pThis, "is NOT disk-assisted\n"); + } + + RETiRet; +} + + +/* Start disk-assisted queue mode. + * rgerhards, 2008-01-15 + */ +static rsRetVal +StartDA(qqueue_t *pThis) +{ + DEFiRet; + uchar pszDAQName[128]; + + ISOBJ_TYPE_assert(pThis, qqueue); + + /* create message queue */ + CHKiRet(qqueueConstruct(&pThis->pqDA, QUEUETYPE_DISK , 1, 0, pThis->pConsumer)); + + /* give it a name */ + snprintf((char*) pszDAQName, sizeof(pszDAQName)/sizeof(uchar), "%s[DA]", obj.GetName((obj_t*) pThis)); + obj.SetName((obj_t*) pThis->pqDA, pszDAQName); + + /* as the created queue is the same object class, we take the + * liberty to access its properties directly. + */ + pThis->pqDA->pqParent = pThis; + + CHKiRet(qqueueSetpAction(pThis->pqDA, pThis->pAction)); + CHKiRet(qqueueSetsizeOnDiskMax(pThis->pqDA, pThis->sizeOnDiskMax)); + CHKiRet(qqueueSetiDeqSlowdown(pThis->pqDA, pThis->iDeqSlowdown)); + CHKiRet(qqueueSetMaxFileSize(pThis->pqDA, pThis->iMaxFileSize)); + CHKiRet(qqueueSetFilePrefix(pThis->pqDA, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(qqueueSetiPersistUpdCnt(pThis->pqDA, pThis->iPersistUpdCnt)); + CHKiRet(qqueueSetbSyncQueueFiles(pThis->pqDA, pThis->bSyncQueueFiles)); + CHKiRet(qqueueSettoActShutdown(pThis->pqDA, pThis->toActShutdown)); + CHKiRet(qqueueSettoEnq(pThis->pqDA, pThis->toEnq)); + CHKiRet(qqueueSetiDeqtWinFromHr(pThis->pqDA, pThis->iDeqtWinFromHr)); + CHKiRet(qqueueSetiDeqtWinToHr(pThis->pqDA, pThis->iDeqtWinToHr)); + CHKiRet(qqueueSettoQShutdown(pThis->pqDA, pThis->toQShutdown)); + CHKiRet(qqueueSetiHighWtrMrk(pThis->pqDA, 0)); + CHKiRet(qqueueSetiDiscardMrk(pThis->pqDA, 0)); + + iRet = qqueueStart(pThis->pqDA); + /* file not found is expected, that means it is no previous QIF available */ + if(iRet != RS_RET_OK && iRet != RS_RET_FILE_NOT_FOUND) { + errno = 0; /* else an errno is shown in errmsg! */ + errmsg.LogError(errno, iRet, "error starting up disk queue, using pure in-memory mode"); + pThis->bIsDA = 0; /* disable memory mode */ + FINALIZE; /* something is wrong */ + } + + DBGOPRINT((obj_t*) pThis, "DA queue initialized, disk queue 0x%lx\n", + qqueueGetID(pThis->pqDA)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pqDA != NULL) { + qqueueDestruct(&pThis->pqDA); + } + DBGOPRINT((obj_t*) pThis, "error %d creating disk queue - giving up.\n", iRet); + pThis->bIsDA = 0; + } + + RETiRet; +} + + +/* initiate DA mode + * param bEnqOnly tells if the disk queue is to be run in enqueue-only mode. This may + * be needed during shutdown of memory queues which need to be persisted to disk. + * If this function fails (should not happen), DA mode is not turned on. + * rgerhards, 2008-01-16 + */ +static rsRetVal +InitDA(qqueue_t *pThis, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + uchar pszBuf[64]; + size_t lenBuf; + + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + /* check if we already have a DA worker pool. If not, initiate one. Please note that the + * pool is created on first need but never again destructed (until the queue is). This + * is intentional. We assume that when we need it once, we may also need it on another + * occasion. Ressources used are quite minimal when no worker is running. + * rgerhards, 2008-01-24 + * NOTE: this is the DA worker *pool*, not the DA queue! + */ + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:DAwpool", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpDA)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpDA, pszBuf, lenBuf)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) qqueueChkStopWrkrDA)); + CHKiRet(wtpSetpfGetDeqBatchSize (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int*)) GetDeqBatchSize)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void *pWti)) ConsumerDA)); + CHKiRet(wtpSetpfObjProcessed (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, wti_t *pWti)) batchProcessed)); + CHKiRet(wtpSetpmutUsr (pThis->pWtpDA, pThis->mut)); + CHKiRet(wtpSetpcondBusy (pThis->pWtpDA, &pThis->notEmpty)); + CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpDA, 1)); + CHKiRet(wtpSettoWrkShutdown (pThis->pWtpDA, pThis->toWrkShutdown)); + CHKiRet(wtpSetpUsr (pThis->pWtpDA, pThis)); + CHKiRet(wtpConstructFinalize (pThis->pWtpDA)); + /* if we reach this point, we have a "good" DA worker pool */ + + /* now construct the actual queue (if it does not already exist) */ + if(pThis->pqDA == NULL) { + CHKiRet(StartDA(pThis)); + } + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + RETiRet; +} + + +/* --------------- end code for disk-assisted queue modes -------------------- */ + + +/* Now, we define type-specific handlers. The provide a generic functionality, + * but for this specific type of queue. The mapping to these handlers happens during + * queue construction. Later on, handlers are called by pointers present in the + * queue instance object. + */ + +/* -------------------- fixed array -------------------- */ +static rsRetVal qConstructFixedArray(qqueue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->iMaxQueueSize == 0) + ABORT_FINALIZE(RS_RET_QSIZE_ZERO); + + if((pThis->tVars.farray.pBuf = MALLOC(sizeof(void *) * pThis->iMaxQueueSize)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->tVars.farray.deqhead = 0; + pThis->tVars.farray.head = 0; + pThis->tVars.farray.tail = 0; + + qqueueChkIsDA(pThis); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructFixedArray(qqueue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + queueDrain(pThis); /* discard any remaining queue entries */ + free(pThis->tVars.farray.pBuf); + + RETiRet; +} + + +static rsRetVal qAddFixedArray(qqueue_t *pThis, msg_t* in) +{ + DEFiRet; + + ASSERT(pThis != NULL); + pThis->tVars.farray.pBuf[pThis->tVars.farray.tail] = in; + pThis->tVars.farray.tail++; + if (pThis->tVars.farray.tail == pThis->iMaxQueueSize) + pThis->tVars.farray.tail = 0; + + RETiRet; +} + + +static rsRetVal qDeqFixedArray(qqueue_t *pThis, msg_t **out) +{ + DEFiRet; + + ASSERT(pThis != NULL); + *out = (void*) pThis->tVars.farray.pBuf[pThis->tVars.farray.deqhead]; + + pThis->tVars.farray.deqhead++; + if (pThis->tVars.farray.deqhead == pThis->iMaxQueueSize) + pThis->tVars.farray.deqhead = 0; + + RETiRet; +} + + +static rsRetVal qDelFixedArray(qqueue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + pThis->tVars.farray.head++; + if (pThis->tVars.farray.head == pThis->iMaxQueueSize) + pThis->tVars.farray.head = 0; + + RETiRet; +} + + +/* -------------------- linked list -------------------- */ + + +static rsRetVal qConstructLinkedList(qqueue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + pThis->tVars.linklist.pDeqRoot = NULL; + pThis->tVars.linklist.pDelRoot = NULL; + pThis->tVars.linklist.pLast = NULL; + + qqueueChkIsDA(pThis); + + RETiRet; +} + + +static rsRetVal qDestructLinkedList(qqueue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + queueDrain(pThis); /* discard any remaining queue entries */ + + /* with the linked list type, there is nothing left to do here. The + * reason is that there are no dynamic elements for the list itself. + */ + + RETiRet; +} + +static rsRetVal qAddLinkedList(qqueue_t *pThis, msg_t* pMsg) +{ + qLinkedList_t *pEntry; + DEFiRet; + + CHKmalloc((pEntry = (qLinkedList_t*) MALLOC(sizeof(qLinkedList_t)))); + + pEntry->pNext = NULL; + pEntry->pMsg = pMsg; + + if(pThis->tVars.linklist.pDelRoot == NULL) { + pThis->tVars.linklist.pDelRoot = pThis->tVars.linklist.pDeqRoot = pThis->tVars.linklist.pLast = pEntry; + } else { + pThis->tVars.linklist.pLast->pNext = pEntry; + pThis->tVars.linklist.pLast = pEntry; + } + + if(pThis->tVars.linklist.pDeqRoot == NULL) { + pThis->tVars.linklist.pDeqRoot = pEntry; + } + +finalize_it: + RETiRet; +} + + +static rsRetVal qDeqLinkedList(qqueue_t *pThis, msg_t **ppMsg) +{ + qLinkedList_t *pEntry; + DEFiRet; + + pEntry = pThis->tVars.linklist.pDeqRoot; + *ppMsg = pEntry->pMsg; + pThis->tVars.linklist.pDeqRoot = pEntry->pNext; + + RETiRet; +} + + +static rsRetVal qDelLinkedList(qqueue_t *pThis) +{ + qLinkedList_t *pEntry; + DEFiRet; + + pEntry = pThis->tVars.linklist.pDelRoot; + + if(pThis->tVars.linklist.pDelRoot == pThis->tVars.linklist.pLast) { + pThis->tVars.linklist.pDelRoot = pThis->tVars.linklist.pDeqRoot = pThis->tVars.linklist.pLast = NULL; + } else { + pThis->tVars.linklist.pDelRoot = pEntry->pNext; + } + + free(pEntry); + + RETiRet; +} + + +/* -------------------- disk -------------------- */ + + +/* The following function is used to "save" ourself from being killed by + * a fatally failed disk queue. A fatal failure is, for example, if no + * data can be read or written. In that case, the disk support is disabled, + * with all on-disk structures kept as-is as much as possible. Instead, the + * queue is switched to direct mode, so that at least + * some processing can happen. Of course, this may still have lots of + * undesired side-effects, but is probably better than aborting the + * syslogd. Note that this function *must* succeed in one way or another, as + * we can not recover from failure here. But it may emit different return + * states, which can trigger different processing in the higher layers. + * rgerhards, 2011-05-03 + */ +static inline rsRetVal +queueSwitchToEmergencyMode(qqueue_t *pThis, rsRetVal initiatingError) +{ + pThis->iQueueSize = 0; + pThis->nLogDeq = 0; + qDestructDisk(pThis); /* free disk structures */ + + pThis->qType = QUEUETYPE_DIRECT; + pThis->qConstruct = qConstructDirect; + pThis->qDestruct = qDestructDirect; + pThis->qAdd = qAddDirect; + pThis->qDel = qDelDirect; + pThis->MultiEnq = qqueueMultiEnqObjDirect; + if(pThis->pqParent != NULL) { + DBGOPRINT((obj_t*) pThis, "DA queue is in emergency mode, disabling DA in parent\n"); + pThis->pqParent->bIsDA = 0; + pThis->pqParent->pqDA = NULL; + /* This may have undesired side effects, not sure if I really evaluated + * all. So you know where to look at if you come to this point during + * troubleshooting ;) -- rgerhards, 2011-05-03 + */ + } + + errmsg.LogError(0, initiatingError, "fatal error on disk queue '%s', emergency switch to direct mode", + obj.GetName((obj_t*) pThis)); + return RS_RET_ERR_QUEUE_EMERGENCY; +} + + +static rsRetVal +qqueueLoadPersStrmInfoFixup(strm_t *pStrm, qqueue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_TYPE_assert(pThis, qqueue); + CHKiRet(strm.SetDir(pStrm, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); +finalize_it: + RETiRet; +} + + +/* The method loads the persistent queue information. + * rgerhards, 2008-01-11 + */ +static rsRetVal +qqueueTryLoadPersistedInfo(qqueue_t *pThis) +{ + DEFiRet; + strm_t *psQIF = NULL; + struct stat stat_buf; + + ISOBJ_TYPE_assert(pThis, qqueue); + + /* check if the file exists */ + if(stat((char*) pThis->pszQIFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + DBGOPRINT((obj_t*) pThis, "clean startup, no .qi file found\n"); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + DBGOPRINT((obj_t*) pThis, "error %d trying to access .qi file\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + + /* If we reach this point, we have a .qi file */ + + CHKiRet(strm.Construct(&psQIF)); + CHKiRet(strm.SettOperationsMode(psQIF, STREAMMODE_READ)); + CHKiRet(strm.SetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strm.SetFName(psQIF, pThis->pszQIFNam, pThis->lenQIFNam)); + CHKiRet(strm.ConstructFinalize(psQIF)); + + /* first, we try to read the property bag for ourselfs */ + CHKiRet(obj.DeserializePropBag((obj_t*) pThis, psQIF)); + + /* then the stream objects (same order as when persisted!) */ + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pWrite, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))qqueueLoadPersStrmInfoFixup, pThis)); + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pReadDel, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))qqueueLoadPersStrmInfoFixup, pThis)); + /* create a duplicate for the read "pointer". */ + CHKiRet(strm.Dup(pThis->tVars.disk.pReadDel, &pThis->tVars.disk.pReadDeq)); + CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDeq, 0)); /* deq must NOT delete the files! */ + CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pReadDeq)); + /* if we use a crypto provider, we need to amend the objects with it's info */ + if(pThis->useCryprov) { + CHKiRet(strm.Setcryprov(pThis->tVars.disk.pWrite, &pThis->cryprov)); + CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pWrite, pThis->cryprovData)); + CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDeq, &pThis->cryprov)); + CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDeq, pThis->cryprovData)); + CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDel, &pThis->cryprov)); + CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDel, pThis->cryprovData)); + } + + CHKiRet(strm.SeekCurrOffs(pThis->tVars.disk.pWrite)); + CHKiRet(strm.SeekCurrOffs(pThis->tVars.disk.pReadDel)); + CHKiRet(strm.SeekCurrOffs(pThis->tVars.disk.pReadDeq)); + + /* OK, we could successfully read the file, so we now can request that it be + * deleted when we are done with the persisted information. + */ + pThis->bNeedDelQIF = 1; + +finalize_it: + if(psQIF != NULL) + strm.Destruct(&psQIF); + + if(iRet != RS_RET_OK) { + DBGOPRINT((obj_t*) pThis, "state %d reading .qi file - can not read persisted info (if any)\n", + iRet); + } + + RETiRet; +} + + +/* disk queue constructor. + * Note that we use a file limit of 10,000,000 files. That number should never pose a + * problem. If so, I guess the user has a design issue... But of course, the code can + * always be changed (though it would probably be more appropriate to increase the + * allowed file size at this point - that should be a config setting... + * rgerhards, 2008-01-10 + */ +static rsRetVal qConstructDisk(qqueue_t *pThis) +{ + DEFiRet; + int bRestarted = 0; + + ASSERT(pThis != NULL); + + /* and now check if there is some persistent information that needs to be read in */ + iRet = qqueueTryLoadPersistedInfo(pThis); + if(iRet == RS_RET_OK) + bRestarted = 1; + else if(iRet != RS_RET_FILE_NOT_FOUND) + FINALIZE; + + if(bRestarted == 1) { + ; + } else { + CHKiRet(strm.Construct(&pThis->tVars.disk.pWrite)); + CHKiRet(strm.SetbSync(pThis->tVars.disk.pWrite, pThis->bSyncQueueFiles)); + CHKiRet(strm.SetDir(pThis->tVars.disk.pWrite, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); + CHKiRet(strm.SetiMaxFiles(pThis->tVars.disk.pWrite, 10000000)); + CHKiRet(strm.SettOperationsMode(pThis->tVars.disk.pWrite, STREAMMODE_WRITE)); + CHKiRet(strm.SetsType(pThis->tVars.disk.pWrite, STREAMTYPE_FILE_CIRCULAR)); + if(pThis->useCryprov) { + CHKiRet(strm.Setcryprov(pThis->tVars.disk.pWrite, &pThis->cryprov)); + CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pWrite, pThis->cryprovData)); + } + CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pWrite)); + + CHKiRet(strm.Construct(&pThis->tVars.disk.pReadDeq)); + CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDeq, 0)); + CHKiRet(strm.SetDir(pThis->tVars.disk.pReadDeq, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); + CHKiRet(strm.SetiMaxFiles(pThis->tVars.disk.pReadDeq, 10000000)); + CHKiRet(strm.SettOperationsMode(pThis->tVars.disk.pReadDeq, STREAMMODE_READ)); + CHKiRet(strm.SetsType(pThis->tVars.disk.pReadDeq, STREAMTYPE_FILE_CIRCULAR)); + if(pThis->useCryprov) { + CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDeq, &pThis->cryprov)); + CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDeq, pThis->cryprovData)); + } + CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pReadDeq)); + + CHKiRet(strm.Construct(&pThis->tVars.disk.pReadDel)); + CHKiRet(strm.SetbSync(pThis->tVars.disk.pReadDel, pThis->bSyncQueueFiles)); + CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDel, 1)); + CHKiRet(strm.SetDir(pThis->tVars.disk.pReadDel, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); + CHKiRet(strm.SetiMaxFiles(pThis->tVars.disk.pReadDel, 10000000)); + CHKiRet(strm.SettOperationsMode(pThis->tVars.disk.pReadDel, STREAMMODE_READ)); + CHKiRet(strm.SetsType(pThis->tVars.disk.pReadDel, STREAMTYPE_FILE_CIRCULAR)); + if(pThis->useCryprov) { + CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDel, &pThis->cryprov)); + CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDel, pThis->cryprovData)); + } + CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pReadDel)); + + CHKiRet(strm.SetFName(pThis->tVars.disk.pWrite, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(strm.SetFName(pThis->tVars.disk.pReadDeq, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(strm.SetFName(pThis->tVars.disk.pReadDel, pThis->pszFilePrefix, pThis->lenFilePrefix)); + } + + /* now we set (and overwrite in case of a persisted restart) some parameters which + * should always reflect the current configuration variables. Be careful by doing so, + * for example file name generation must not be changed as that would break the + * ability to read existing queue files. -- rgerhards, 2008-01-12 + */ + CHKiRet(strm.SetiMaxFileSize(pThis->tVars.disk.pWrite, pThis->iMaxFileSize)); + CHKiRet(strm.SetiMaxFileSize(pThis->tVars.disk.pReadDeq, pThis->iMaxFileSize)); + CHKiRet(strm.SetiMaxFileSize(pThis->tVars.disk.pReadDel, pThis->iMaxFileSize)); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructDisk(qqueue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->tVars.disk.pWrite != NULL) + strm.Destruct(&pThis->tVars.disk.pWrite); + if(pThis->tVars.disk.pReadDeq != NULL) + strm.Destruct(&pThis->tVars.disk.pReadDeq); + if(pThis->tVars.disk.pReadDel != NULL) + strm.Destruct(&pThis->tVars.disk.pReadDel); + + RETiRet; +} + +static rsRetVal qAddDisk(qqueue_t *pThis, msg_t* pMsg) +{ + DEFiRet; + number_t nWriteCount; + + ASSERT(pThis != NULL); + + CHKiRet(strm.SetWCntr(pThis->tVars.disk.pWrite, &nWriteCount)); + CHKiRet((objSerialize(pMsg))(pMsg, pThis->tVars.disk.pWrite)); + CHKiRet(strm.Flush(pThis->tVars.disk.pWrite)); + CHKiRet(strm.SetWCntr(pThis->tVars.disk.pWrite, NULL)); /* no more counting for now... */ + + pThis->tVars.disk.sizeOnDisk += nWriteCount; + + /* we have enqueued the user element to disk. So we now need to destruct + * the in-memory representation. The instance will be re-created upon + * dequeue. -- rgerhards, 2008-07-09 + */ + msgDestruct(&pMsg); + + DBGOPRINT((obj_t*) pThis, "write wrote %lld octets to disk, queue disk size now %lld octets, EnqOnly:%d\n", + nWriteCount, pThis->tVars.disk.sizeOnDisk, pThis->bEnqOnly); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDeqDisk(qqueue_t *pThis, msg_t **ppMsg) +{ + DEFiRet; + iRet = objDeserializeWithMethods(ppMsg, (uchar*) "msg", 3, pThis->tVars.disk.pReadDeq, NULL, + NULL, msgConstructForDeserializer, NULL, MsgDeserialize); + RETiRet; +} + + +/* -------------------- direct (no queueing) -------------------- */ +static rsRetVal qConstructDirect(qqueue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + + +static rsRetVal qDestructDirect(qqueue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + +static rsRetVal qAddDirect(qqueue_t *pThis, msg_t* pMsg) +{ + batch_t singleBatch; + batch_obj_t batchObj; + batch_state_t batchState = BATCH_STATE_RDY; + sbool active = 1; + int i; + DEFiRet; + + //TODO: init batchObj (states _OK and new fields -- CHECK) + ASSERT(pThis != NULL); + + /* calling the consumer is quite different here than it is from a worker thread */ + /* we need to provide the consumer's return value back to the caller because in direct + * mode the consumer probably has a lot to convey (which get's lost in the other modes + * because they are asynchronous. But direct mode is deliberately synchronous. + * rgerhards, 2008-02-12 + * We use our knowledge about the batch_t structure below, but without that, we + * pay a too-large performance toll... -- rgerhards, 2009-04-22 + */ + memset(&batchObj, 0, sizeof(batch_obj_t)); + memset(&singleBatch, 0, sizeof(batch_t)); + batchObj.pMsg = pMsg; + singleBatch.nElem = 1; /* there always is only one in direct mode */ + singleBatch.pElem = &batchObj; + singleBatch.eltState = &batchState; + singleBatch.active = &active; + iRet = pThis->pConsumer(pThis->pAction, &singleBatch, &pThis->bShutdownImmediate); + /* delete the batch string params: TODO: create its own "class" for this */ + for(i = 0 ; i < CONF_OMOD_NUMSTRINGS_MAXSIZE ; ++i) { + free(batchObj.staticActStrings[i]); + } + msgDestruct(&pMsg); + + RETiRet; +} + +/* "enqueue" a batch in direct mode. This is a shortcut which saves all the overhead + * otherwise incured. -- rgerhards, ~2010-06-23 + */ +rsRetVal qqueueEnqObjDirectBatch(qqueue_t *pThis, batch_t *pBatch) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* calling the consumer is quite different here than it is from a worker thread */ + /* we need to provide the consumer's return value back to the caller because in direct + * mode the consumer probably has a lot to convey (which get's lost in the other modes + * because they are asynchronous. But direct mode is deliberately synchronous. + * rgerhards, 2008-02-12 + * We use our knowledge about the batch_t structure below, but without that, we + * pay a too-large performance toll... -- rgerhards, 2009-04-22 + */ + iRet = pThis->pConsumer(pThis->pAction, pBatch, NULL); + + RETiRet; +} + + +static rsRetVal qDelDirect(qqueue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + + +/* --------------- end type-specific handlers -------------------- */ + + +/* generic code to add a queue entry + * We use some specific code to most efficiently support direct mode + * queues. This is justified in spite of the gain and the need to do some + * things truely different. -- rgerhards, 2008-02-12 + */ +static rsRetVal +qqueueAdd(qqueue_t *pThis, msg_t *pMsg) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + CHKiRet(pThis->qAdd(pThis, pMsg)); + + if(pThis->qType != QUEUETYPE_DIRECT) { + ATOMIC_INC(&pThis->iQueueSize, &pThis->mutQueueSize); + DBGOPRINT((obj_t*) pThis, "entry added, size now log %d, phys %d entries\n", + getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis)); + } + +finalize_it: + RETiRet; +} + + +/* generic code to dequeue a queue entry + */ +static rsRetVal +qqueueDeq(qqueue_t *pThis, msg_t **ppMsg) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* we do NOT abort if we encounter an error, because otherwise the queue + * will not be decremented, what will most probably result in an endless loop. + * If we decrement, however, we may lose a message. But that is better than + * losing the whole process because it loops... -- rgerhards, 2008-01-03 + */ + iRet = pThis->qDeq(pThis, ppMsg); + ATOMIC_INC(&pThis->nLogDeq, &pThis->mutLogDeq); + +// DBGOPRINT((obj_t*) pThis, "entry deleted, size now log %d, phys %d entries\n", +// getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis)); + + RETiRet; +} + + +/* Try to shut down regular and DA queue workers, within the queue timeout + * period. That means processing continues as usual. This is the expected + * usual case, where during shutdown those messages remaining are being + * processed. At this point, it is acceptable that the queue can not be + * fully depleted, that case is handled in the next step. During this phase, + * we first shut down the main queue DA worker to prevent new data to arrive + * at the DA queue, and then we ask the regular workers of both the Regular + * and DA queue to try complete processing. + * rgerhards, 2009-10-14 + */ +static inline rsRetVal +tryShutdownWorkersWithinQueueTimeout(qqueue_t *pThis) +{ + struct timespec tTimeout; + rsRetVal iRetLocal; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ + + if(pThis->bIsDA) { + /* We need to lock the mutex, as otherwise we may have a race that prevents + * us from awaking the DA worker. */ + d_pthread_mutex_lock(pThis->mut); + + /* tell regular queue DA worker to stop shuffling messages to DA queue... */ + DBGOPRINT((obj_t*) pThis, "setting EnqOnly mode for DA worker\n"); + pThis->pqDA->bEnqOnly = 1; + wtpSetState(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE); + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); + DBGOPRINT((obj_t*) pThis, "awoke DA worker, told it to shut down.\n"); + + /* also tell the DA queue worker to shut down, so that it already knows... */ + wtpSetState(pThis->pqDA->pWtpReg, wtpState_SHUTDOWN); + wtpAdviseMaxWorkers(pThis->pqDA->pWtpReg, 1); /* awake its lone worker */ + DBGOPRINT((obj_t*) pThis, "awoke DA queue regular worker, told it to shut down when done.\n"); + + d_pthread_mutex_unlock(pThis->mut); + } + + + /* first calculate absolute timeout - we need the absolute value here, because we need to coordinate + * shutdown of both the regular and DA queue on *the same* timeout. + */ + timeoutComp(&tTimeout, pThis->toQShutdown); + DBGOPRINT((obj_t*) pThis, "trying shutdown of regular workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + DBGOPRINT((obj_t*) pThis, "regular shutdown timed out on primary queue (this is OK)\n"); + } else { + DBGOPRINT((obj_t*) pThis, "regular queue workers shut down.\n"); + } + + /* OK, the worker for the regular queue is processed, on the the DA queue regular worker. */ + if(pThis->pqDA != NULL) { + DBGOPRINT((obj_t*) pThis, "we have a DA queue (0x%lx), requesting its shutdown.\n", + qqueueGetID(pThis->pqDA)); + /* we use the same absolute timeout as above, so we do not use more than the configured + * timeout interval! + */ + DBGOPRINT((obj_t*) pThis, "trying shutdown of regular worker of DA queue\n"); + iRetLocal = wtpShutdownAll(pThis->pqDA->pWtpReg, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + DBGOPRINT((obj_t*) pThis, "shutdown timed out on DA queue worker (this is OK)\n"); + } else { + DBGOPRINT((obj_t*) pThis, "DA queue worker shut down.\n"); + } + } + + RETiRet; +} + + +/* Try to shut down regular and DA queue workers, within the action timeout + * period. This aborts processing, but at the end of the current action, in + * a well-defined manner. During this phase, we terminate all three worker + * pools, including the regular queue DA worker if it not yet has terminated. + * Not finishing processing all messages is OK (and expected) at this stage + * (they may be preserved later, depending * on bSaveOnShutdown setting). + * rgerhards, 2009-10-14 + */ +static rsRetVal +tryShutdownWorkersWithinActionTimeout(qqueue_t *pThis) +{ + struct timespec tTimeout; + rsRetVal iRetLocal; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ + + /* instruct workers to finish ASAP, even if still work exists */ + DBGOPRINT((obj_t*) pThis, "trying to shutdown workers within Action Timeout"); + DBGOPRINT((obj_t*) pThis, "setting EnqOnly mode\n"); + pThis->bEnqOnly = 1; + pThis->bShutdownImmediate = 1; + /* now DA queue */ + if(pThis->bIsDA) { + pThis->pqDA->bEnqOnly = 1; + pThis->pqDA->bShutdownImmediate = 1; + } + +// TODO: make sure we have at minimum a 10ms timeout - workers deserve a chance... + /* now give the queue workers a last chance to gracefully shut down (based on action timeout setting) */ + timeoutComp(&tTimeout, pThis->toActShutdown); + DBGOPRINT((obj_t*) pThis, "trying immediate shutdown of regular workers (if any)\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + DBGOPRINT((obj_t*) pThis, "immediate shutdown timed out on primary queue (this is acceptable and " + "triggers cancellation)\n"); + } else if(iRetLocal != RS_RET_OK) { + DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the primary queue " + "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); + } + + if(pThis->pqDA != NULL) { + /* and now the same for the DA queue */ + DBGOPRINT((obj_t*) pThis, "trying immediate shutdown of DA queue workers\n"); + iRetLocal = wtpShutdownAll(pThis->pqDA->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + DBGOPRINT((obj_t*) pThis, "immediate shutdown timed out on DA queue (this is acceptable " + "and triggers cancellation)\n"); + } else if(iRetLocal != RS_RET_OK) { + DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the DA " + "queue in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); + } + + /* and now we need to terminate the DA worker itself. We always grant it a 100ms timeout, + * which should be sufficient and usually not be required (it is expected to have finished + * long before while we were processing the queue timeout in shutdown phase 1). + * rgerhards, 2009-10-14 + */ + timeoutComp(&tTimeout, 100); + DBGOPRINT((obj_t*) pThis, "trying regular shutdown of main queue DA worker pool\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + DBGOPRINT((obj_t*) pThis, "shutdown timed out on main queue DA worker pool " + "(this is not good, but probably OK)\n"); + } else { + DBGOPRINT((obj_t*) pThis, "main queue DA worker pool shut down.\n"); + } + } + + RETiRet; +} + + +/* This function cancels all remaining regular workers for both the main and the DA + * queue. + * rgerhards, 2009-05-29 + */ +static rsRetVal +cancelWorkers(qqueue_t *pThis) +{ + rsRetVal iRetLocal; + DEFiRet; + + /* Now queue workers should have terminated. If not, we need to cancel them as we have applied + * all timeout setting. If any worker in any queue still executes, its consumer is possibly + * long-running and cancelling is the only way to get rid of it. + */ + DBGOPRINT((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the primary queue\n"); + iRetLocal = wtpCancelAll(pThis->pWtpReg); /* returns immediately if all threads already have terminated */ + if(iRetLocal != RS_RET_OK) { + DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d trying to cancel primary queue worker " + "threads, continuing, but results are unpredictable\n", iRetLocal); + } + + /* ... and now the DA queue, if it exists (should always be after the primary one) */ + if(pThis->pqDA != NULL) { + DBGOPRINT((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the DA queue\n"); + iRetLocal = wtpCancelAll(pThis->pqDA->pWtpReg); /* returns immediately if all threads already have terminated */ + if(iRetLocal != RS_RET_OK) { + DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d trying to cancel DA queue worker " + "threads, continuing, but results are unpredictable\n", iRetLocal); + } + + /* finally, we cancel the main queue's DA worker pool, if it still is running. It may be + * restarted later to persist the queue. But we stop it, because otherwise we get into + * big trouble when resetting the logical dequeue pointer. This operation can only be + * done when *no* worker is running. So time for a shutdown... -- rgerhards, 2009-05-28 + */ + DBGOPRINT((obj_t*) pThis, "checking to see if main queue DA worker pool needs to be cancelled\n"); + wtpCancelAll(pThis->pWtpDA); /* returns immediately if all threads already have terminated */ + } + + RETiRet; +} + + +/* This function shuts down all worker threads and waits until they + * have terminated. If they timeout, they are cancelled. + * rgerhards, 2008-01-24 + * Please note that this function shuts down BOTH the parent AND the child queue + * in DA case. This is necessary because their timeouts are tightly coupled. Most + * importantly, the timeouts would be applied twice (or logic be extremely + * complex) if each would have its own shutdown. The function does not self check + * this condition - the caller must make sure it is not called with a parent. + * rgerhards, 2009-05-26: we do NO longer persist the queue here if bSaveOnShutdown + * is set. This must be handled by the caller. Not doing that cleans up the queue + * shutdown considerably. Also, older engines had a potential hang condition when + * the DA queue was already started and the DA worker configured for infinite + * retries and the action was during retry processing. This was a design issue, + * which is solved as of now. Note that the shutdown now may take a little bit + * longer, because we no longer can persist the queue in parallel to waiting + * on worker timeouts. + */ +static rsRetVal +ShutdownWorkers(qqueue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ + + DBGOPRINT((obj_t*) pThis, "initiating worker thread shutdown sequence\n"); + + CHKiRet(tryShutdownWorkersWithinQueueTimeout(pThis)); + + if(getPhysicalQueueSize(pThis) > 0) { + CHKiRet(tryShutdownWorkersWithinActionTimeout(pThis)); + } + + CHKiRet(cancelWorkers(pThis)); + + /* ... finally ... all worker threads have terminated :-) + * Well, more precisely, they *are in termination*. Some cancel cleanup handlers + * may still be running. Note that the main queue's DA worker may still be running. + */ + DBGOPRINT((obj_t*) pThis, "worker threads terminated, remaining queue size log %d, phys %d.\n", + getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis)); + +finalize_it: + RETiRet; +} + +/* Constructor for the queue object + * This constructs the data structure, but does not yet start the queue. That + * is done by queueStart(). The reason is that we want to give the caller a chance + * to modify some parameters before the queue is actually started. + */ +rsRetVal qqueueConstruct(qqueue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*, batch_t*,int*)) +{ + DEFiRet; + qqueue_t *pThis; + + ASSERT(ppThis != NULL); + ASSERT(pConsumer != NULL); + ASSERT(iWorkerThreads >= 0); + + CHKmalloc(pThis = (qqueue_t *)calloc(1, sizeof(qqueue_t))); + + /* we have an object, so let's fill the properties */ + objConstructSetObjInfo(pThis); + if((pThis->pszSpoolDir = (uchar*) strdup((char*)glbl.GetWorkDir())) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* set some water marks so that we have useful defaults if none are set specifically */ + pThis->iFullDlyMrk = -1; + pThis->iLightDlyMrk = -1; + pThis->lenSpoolDir = ustrlen(pThis->pszSpoolDir); + pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */ + pThis->iQueueSize = 0; + pThis->nLogDeq = 0; + pThis->useCryprov = 0; + pThis->iMaxQueueSize = iMaxQueueSize; + pThis->pConsumer = pConsumer; + pThis->iNumWorkerThreads = iWorkerThreads; + pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ + pThis->iDeqBatchSize = 8; /* conservative default, should still provide good performance */ + + pThis->pszFilePrefix = NULL; + pThis->qType = qType; + + + INIT_ATOMIC_HELPER_MUT(pThis->mutQueueSize); + INIT_ATOMIC_HELPER_MUT(pThis->mutLogDeq); + +finalize_it: + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP + RETiRet; +} + + +/* set default inside queue object suitable for action queues. + * This shall be called directly after queue construction. This functions has + * been added in support of the new v6 config system. It expect properly pre-initialized + * objects, but we need to differentiate between ruleset main and action queues. + * In order to avoid unnecessary complexity, we provide the necessary defaults + * via specific function calls. + */ +void +qqueueSetDefaultsActionQueue(qqueue_t *pThis) +{ + pThis->qType = QUEUETYPE_DIRECT; /* type of the main message queue above */ + pThis->iMaxQueueSize = 1000; /* size of the main message queue above */ + pThis->iDeqBatchSize = 128; /* default batch size */ + pThis->iHighWtrMrk = 800; /* high water mark for disk-assisted queues */ + pThis->iLowWtrMrk = 200; /* low water mark for disk-assisted queues */ + pThis->iDiscardMrk = 980; /* begin to discard messages */ + pThis->iDiscardSeverity = 8; /* turn off */ + pThis->iNumWorkerThreads = 1; /* number of worker threads for the mm queue above */ + pThis->iMaxFileSize = 1024*1024; + pThis->iPersistUpdCnt = 0; /* persist queue info every n updates */ + pThis->bSyncQueueFiles = 0; + pThis->toQShutdown = 0; /* queue shutdown */ + pThis->toActShutdown = 1000; /* action shutdown (in phase 2) */ + pThis->toEnq = 2000; /* timeout for queue enque */ + pThis->toWrkShutdown = 60000; /* timeout for worker thread shutdown */ + pThis->iMinMsgsPerWrkr = 100; /* minimum messages per worker needed to start a new one */ + pThis->bSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ + pThis->sizeOnDiskMax = 0; /* unlimited */ + pThis->iDeqSlowdown = 0; + pThis->iDeqtWinFromHr = 0; + pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ +} + + +/* set defaults inside queue object suitable for main/ruleset queues. + * See queueSetDefaultsActionQueue() for more details and background. + */ +void +qqueueSetDefaultsRulesetQueue(qqueue_t *pThis) +{ + pThis->qType = QUEUETYPE_FIXED_ARRAY; /* type of the main message queue above */ + pThis->iMaxQueueSize = 50000; /* size of the main message queue above */ + pThis->iDeqBatchSize = 1024; /* default batch size */ + pThis->iHighWtrMrk = 45000; /* high water mark for disk-assisted queues */ + pThis->iLowWtrMrk = 20000; /* low water mark for disk-assisted queues */ + pThis->iDiscardMrk = 49500; /* begin to discard messages */ + pThis->iDiscardSeverity = 8; /* turn off */ + pThis->iNumWorkerThreads = 1; /* number of worker threads for the mm queue above */ + pThis->iMaxFileSize = 16*1024*1024; + pThis->iPersistUpdCnt = 0; /* persist queue info every n updates */ + pThis->bSyncQueueFiles = 0; + pThis->toQShutdown = 1500; /* queue shutdown */ + pThis->toActShutdown = 1000; /* action shutdown (in phase 2) */ + pThis->toEnq = 2000; /* timeout for queue enque */ + pThis->toWrkShutdown = 60000; /* timeout for worker thread shutdown */ + pThis->iMinMsgsPerWrkr = 1000; /* minimum messages per worker needed to start a new one */ + pThis->bSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ + pThis->sizeOnDiskMax = 0; /* unlimited */ + pThis->iDeqSlowdown = 0; + pThis->iDeqtWinFromHr = 0; + pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ +} + + +/* This function checks if the provided message shall be discarded and does so, if needed. + * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to + * provide real-time creation of spool files. + * Note: cached copies of iQueueSize is provided so that no mutex locks are required. + * The caller must have obtained them while the mutex was locked. Of course, these values may no + * longer be current, but that is OK for the discard check. At worst, the message is either processed + * or discarded when it should not have been. As discarding is in itself somewhat racy and erratic, + * that is no problems for us. This function MUST NOT lock the queue mutex, it could result in + * deadlocks! + * If the message is discarded, it can no longer be processed by the caller. So be sure to check + * the return state! + * rgerhards, 2008-01-24 + */ +static int qqueueChkDiscardMsg(qqueue_t *pThis, int iQueueSize, msg_t *pMsg) +{ + DEFiRet; + rsRetVal iRetLocal; + int iSeverity; + + ISOBJ_TYPE_assert(pThis, qqueue); + + if(pThis->iDiscardMrk > 0 && iQueueSize >= pThis->iDiscardMrk) { + iRetLocal = MsgGetSeverity(pMsg, &iSeverity); + if(iRetLocal == RS_RET_OK && iSeverity >= pThis->iDiscardSeverity) { + DBGOPRINT((obj_t*) pThis, "queue nearly full (%d entries), discarded severity %d message\n", + iQueueSize, iSeverity); + STATSCOUNTER_INC(pThis->ctrNFDscrd, pThis->mutCtrNFDscrd); + msgDestruct(&pMsg); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } else { + DBGOPRINT((obj_t*) pThis, "queue nearly full (%d entries), but could not drop msg " + "(iRet: %d, severity %d)\n", iQueueSize, iRetLocal, iSeverity); + } + } + +finalize_it: + RETiRet; +} + + +/* Finally remove n elements from the queue store. + */ +static inline rsRetVal +DoDeleteBatchFromQStore(qqueue_t *pThis, int nElem) +{ + int i; + off64_t bytesDel; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + + /* now send delete request to storage driver */ + if(pThis->qType == QUEUETYPE_DISK) { + strmMultiFileSeek(pThis->tVars.disk.pReadDel, pThis->tVars.disk.deqFileNumOut, + pThis->tVars.disk.deqOffs, &bytesDel); + /* We need to correct the on-disk file size. This time it is a bit tricky: + * we free disk space only upon file deletion. So we need to keep track of what we + * have read until we get an out-offset that is lower than the in-offset (which + * indicates file change). Then, we can subtract the whole thing from the on-disk + * size. -- rgerhards, 2008-01-30 + */ + if(bytesDel != 0) { + pThis->tVars.disk.sizeOnDisk -= bytesDel; + DBGOPRINT((obj_t*) pThis, "a %lld octet file has been deleted, now %lld octets disk " + "space used\n", bytesDel, pThis->tVars.disk.sizeOnDisk); + /* awake possibly waiting enq process */ + pthread_cond_signal(&pThis->notFull); /* we hold the mutex while we are in here! */ + } + } else { /* memory queue */ + for(i = 0 ; i < nElem ; ++i) { + pThis->qDel(pThis); + } + } + + /* iQueueSize is not decremented by qDel(), so we need to do it ourselves */ + ATOMIC_SUB(&pThis->iQueueSize, nElem, &pThis->mutQueueSize); + ATOMIC_SUB(&pThis->nLogDeq, nElem, &pThis->mutLogDeq); + DBGPRINTF("delete batch from store, new sizes: log %d, phys %d\n", + getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis)); + ++pThis->deqIDDel; /* one more batch dequeued */ + + RETiRet; +} + + +/* remove messages from the physical queue store that are fully processed. This is + * controlled via the to-delete list. + */ +static inline rsRetVal +DeleteBatchFromQStore(qqueue_t *pThis, batch_t *pBatch) +{ + toDeleteLst_t *pTdl; + qDeqID deqIDDel; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + assert(pBatch != NULL); + + pTdl = tdlPeek(pThis); /* get current head element */ + if(pTdl == NULL) { /* to-delete list empty */ + DoDeleteBatchFromQStore(pThis, pBatch->nElem); + } else if(pBatch->deqID == pThis->deqIDDel) { + deqIDDel = pThis->deqIDDel; + pTdl = tdlPeek(pThis); + while(pTdl != NULL && deqIDDel == pTdl->deqID) { + DoDeleteBatchFromQStore(pThis, pTdl->nElemDeq); + tdlPop(pThis); + ++deqIDDel; + pTdl = tdlPeek(pThis); + } + /* old entries deleted, now delete current ones... */ + DoDeleteBatchFromQStore(pThis, pBatch->nElem); + } else { + /* can not delete, insert into to-delete list */ + DBGPRINTF("not at head of to-delete list, enqueue %d\n", (int) pBatch->deqID); + CHKiRet(tdlAdd(pThis, pBatch->deqID, pBatch->nElem)); + } + +finalize_it: + RETiRet; +} + + +/* Delete a batch of processed user objects from the queue, which includes + * destructing the objects themself. Any entries not marked as finally + * processed are enqueued again. The new enqueue is necessary because we have a + * rgerhards, 2009-05-13 + */ +static inline rsRetVal +DeleteProcessedBatch(qqueue_t *pThis, batch_t *pBatch) +{ + int i; + msg_t *pMsg; + int nEnqueued = 0; + rsRetVal localRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + assert(pBatch != NULL); + + for(i = 0 ; i < pBatch->nElem ; ++i) { + pMsg = pBatch->pElem[i].pMsg; + if( pBatch->eltState[i] == BATCH_STATE_RDY + || pBatch->eltState[i] == BATCH_STATE_SUB) { + localRet = doEnqSingleObj(pThis, eFLOWCTL_NO_DELAY, MsgAddRef(pMsg)); + ++nEnqueued; + if(localRet != RS_RET_OK) { + DBGPRINTF("error %d re-enqueuing unprocessed data element - discarded\n", localRet); + } + } + msgDestruct(&pMsg); + } + + DBGPRINTF("we deleted %d objects and enqueued %d objects\n", i-nEnqueued, nEnqueued); + + if(nEnqueued > 0) + qqueueChkPersist(pThis, nEnqueued); + + iRet = DeleteBatchFromQStore(pThis, pBatch); + + pBatch->nElem = pBatch->nElemDeq = 0; /* reset batch */ // TODO: more fine init, new fields! 2010-06-14 + + RETiRet; +} + + +/* dequeue as many user pointers as are available, until we hit the configured + * upper limit of pointers. Note that this function also deletes all processed + * objects from the previous batch. However, it is perfectly valid that the + * previous batch contained NO objects at all. For example, this happens + * immediately after system startup or when a queue was exhausted and the queue + * worker needed to wait for new data. + * This must only be called when the queue mutex is LOOKED, otherwise serious + * malfunction will happen. + */ +static inline rsRetVal +DequeueConsumableElements(qqueue_t *pThis, wti_t *pWti, int *piRemainingQueueSize) +{ + int nDequeued; + int nDiscarded; + int nDeleted; + int iQueueSize; + msg_t *pMsg; + rsRetVal localRet; + DEFiRet; + + nDeleted = pWti->batch.nElemDeq; + DeleteProcessedBatch(pThis, &pWti->batch); + + nDequeued = nDiscarded = 0; + if(pThis->qType == QUEUETYPE_DISK) { + pThis->tVars.disk.deqFileNumIn = strmGetCurrFileNum(pThis->tVars.disk.pReadDeq); + } + while((iQueueSize = getLogicalQueueSize(pThis)) > 0 && nDequeued < pThis->iDeqBatchSize) { + CHKiRet(qqueueDeq(pThis, &pMsg)); + + /* check if we should discard this element */ + localRet = qqueueChkDiscardMsg(pThis, pThis->iQueueSize, pMsg); + if(localRet == RS_RET_QUEUE_FULL) { + ++nDiscarded; + continue; + } else if(localRet != RS_RET_OK) { + ABORT_FINALIZE(localRet); + } + + /* all well, use this element */ + pWti->batch.pElem[nDequeued].pMsg = pMsg; + pWti->batch.eltState[nDequeued] = BATCH_STATE_RDY; + ++nDequeued; + } + + if(pThis->qType == QUEUETYPE_DISK) { + strm.GetCurrOffset(pThis->tVars.disk.pReadDeq, &pThis->tVars.disk.deqOffs); + pThis->tVars.disk.deqFileNumOut = strmGetCurrFileNum(pThis->tVars.disk.pReadDeq); + } + + /* it is sufficient to persist only when the bulk of work is done */ + qqueueChkPersist(pThis, nDequeued+nDiscarded+nDeleted); + + pWti->batch.nElem = nDequeued; + pWti->batch.nElemDeq = nDequeued + nDiscarded; + pWti->batch.deqID = getNextDeqID(pThis); + *piRemainingQueueSize = iQueueSize; +finalize_it: + RETiRet; +} + + +/* dequeue the queued object for the queue consumers. + * rgerhards, 2008-10-21 + * I made a radical change - we now dequeue multiple elements, and store these objects in + * an array of user pointers. We expect that this increases performance. + * rgerhards, 2009-04-22 + */ +static rsRetVal +DequeueConsumable(qqueue_t *pThis, wti_t *pWti) +{ + DEFiRet; + int iQueueSize = 0; /* keep the compiler happy... */ + + /* dequeue element batch (still protected from mutex) */ + iRet = DequeueConsumableElements(pThis, pWti, &iQueueSize); + + /* awake some flow-controlled sources if we can do this right now */ + /* TODO: this could be done better from a performance point of view -- do it only if + * we have someone waiting for the condition (or only when we hit the watermark right + * on the nail [exact value]) -- rgerhards, 2008-03-14 + * now that we dequeue batches of pointers, this is much less an issue... + * rgerhards, 2009-04-22 + */ + if(iQueueSize < pThis->iFullDlyMrk / 2 || glbl.GetGlobalInputTermState() == 1) { + pthread_cond_broadcast(&pThis->belowFullDlyWtrMrk); + } + + if(iQueueSize < pThis->iLightDlyMrk / 2) { + pthread_cond_broadcast(&pThis->belowLightDlyWtrMrk); + } + + pthread_cond_signal(&pThis->notFull); + /* WE ARE NO LONGER PROTECTED BY THE MUTEX */ + + if(iRet != RS_RET_OK && iRet != RS_RET_DISCARDMSG) { + DBGOPRINT((obj_t*) pThis, "error %d dequeueing element - ignoring, but strange things " + "may happen\n", iRet); + } + + RETiRet; +} + + +/* The rate limiter + * + * Here we may wait if a dequeue time window is defined or if we are + * rate-limited. TODO: If we do so, we should also look into the + * way new worker threads are spawned. Obviously, it doesn't make much + * sense to spawn additional worker threads when none of them can do any + * processing. However, it is deemed acceptable to allow this for an initial + * implementation of the timeframe/rate limiting feature. + * Please also note that these feature could also be implemented at the action + * level. However, that would limit them to be used together with actions. We have + * taken the broader approach, moving it right into the queue. This is even + * necessary if we want to prevent spawning of multiple unnecessary worker + * threads as described above. -- rgerhards, 2008-04-02 + * + * + * time window: tCurr is current time; tFrom is start time, tTo is end time (in mil 24h format). + * We may have tFrom = 4, tTo = 10 --> run from 4 to 10 hrs. nice and happy + * we may also have tFrom= 22, tTo = 4 -> run from 10pm to 4am, which is actually two + * windows: 0-4; 22-23:59 + * so when to run? Let's assume we have 3am + * + * if(tTo < tFrom) { + * if(tCurr < tTo [3 < 4] || tCurr > tFrom [3 > 22]) + * do work + * else + * sleep for tFrom - tCurr "hours" [22 - 5 --> 17] + * } else { + * if(tCurr >= tFrom [3 >= 4] && tCurr < tTo [3 < 10]) + * do work + * else + * sleep for tTo - tCurr "hours" [4 - 3 --> 1] + * } + * + * Bottom line: we need to check which type of window we have and need to adjust our + * logic accordingly. Of course, sleep calculations need to be done up to the minute, + * but you get the idea from the code above. + */ +static rsRetVal +RateLimiter(qqueue_t *pThis) +{ + DEFiRet; + int iDelay; + int iHrCurr; + time_t tCurr; + struct tm m; + + ISOBJ_TYPE_assert(pThis, qqueue); + + iDelay = 0; + if(pThis->iDeqtWinToHr != 25) { /* 25 means disabled */ + /* time calls are expensive, so only do them when needed */ + datetime.GetTime(&tCurr); + localtime_r(&tCurr, &m); + iHrCurr = m.tm_hour; + + if(pThis->iDeqtWinToHr < pThis->iDeqtWinFromHr) { + if(iHrCurr < pThis->iDeqtWinToHr || iHrCurr > pThis->iDeqtWinFromHr) { + ; /* do not delay */ + } else { + iDelay = (pThis->iDeqtWinFromHr - iHrCurr) * 3600; + /* this time, we are already into the next hour, so we need + * to subtract our current minute and seconds. + */ + iDelay -= m.tm_min * 60; + iDelay -= m.tm_sec; + } + } else { + if(iHrCurr >= pThis->iDeqtWinFromHr && iHrCurr < pThis->iDeqtWinToHr) { + ; /* do not delay */ + } else { + if(iHrCurr < pThis->iDeqtWinFromHr) { + iDelay = (pThis->iDeqtWinFromHr - iHrCurr - 1) * 3600; /* -1 as we are already in the hour */ + iDelay += (60 - m.tm_min) * 60; + iDelay += 60 - m.tm_sec; + } else { + iDelay = (24 - iHrCurr + pThis->iDeqtWinFromHr) * 3600; + /* this time, we are already into the next hour, so we need + * to subtract our current minute and seconds. + */ + iDelay -= m.tm_min * 60; + iDelay -= m.tm_sec; + } + } + } + } + + if(iDelay > 0) { + DBGOPRINT((obj_t*) pThis, "outside dequeue time window, delaying %d seconds\n", iDelay); + srSleep(iDelay, 0); + } + + RETiRet; +} + + +/* This dequeues the next batch. Note that this function must not be + * cancelled, else it will leave back an inconsistent state. + * rgerhards, 2009-05-20 + */ +static inline rsRetVal +DequeueForConsumer(qqueue_t *pThis, wti_t *pWti) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(DequeueConsumable(pThis, pWti)); + + if(pWti->batch.nElem == 0) + ABORT_FINALIZE(RS_RET_IDLE); + + +finalize_it: + RETiRet; +} + + +/* This is called when a batch is processed and the worker does not + * ask for another batch (e.g. because it is to be terminated) + * Note that we must not be terminated while we delete a processed + * batch. Otherwise, we may not complete it, and then the cancel + * handler also tries to delete the batch. But then it finds some of + * the messages already destructed. This was a bug we have seen, especially + * with disk mode, where a delete takes rather long. Anyhow, the coneptual + * problem exists in all queue modes. + * rgerhards, 2009-05-27 + */ +static rsRetVal +batchProcessed(qqueue_t *pThis, wti_t *pWti) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ISOBJ_TYPE_assert(pWti, wti); + + int iCancelStateSave; + /* at this spot, we must not be cancelled */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + DeleteProcessedBatch(pThis, &pWti->batch); + qqueueChkPersist(pThis, pWti->batch.nElemDeq); + pthread_setcancelstate(iCancelStateSave, NULL); + + RETiRet; +} + + +/* This is the queue consumer in the regular (non-DA) case. It is + * protected by the queue mutex, but MUST release it as soon as possible. + * rgerhards, 2008-01-21 + */ +static rsRetVal +ConsumerReg(qqueue_t *pThis, wti_t *pWti) +{ + int iCancelStateSave; + int bNeedReLock = 0; /**< do we need to lock the mutex again? */ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ISOBJ_TYPE_assert(pWti, wti); + + iRet = DequeueForConsumer(pThis, pWti); + if(iRet == RS_RET_FILE_NOT_FOUND) { + /* This is a fatal condition and means the queue is almost unusable */ + d_pthread_mutex_unlock(pThis->mut); + DBGOPRINT((obj_t*) pThis, "got 'file not found' error %d, queue defunct\n", iRet); + iRet = queueSwitchToEmergencyMode(pThis, iRet); + // TODO: think about what to return as iRet -- keep RS_RET_FILE_NOT_FOUND? + d_pthread_mutex_lock(pThis->mut); + } + if (iRet != RS_RET_OK) { + FINALIZE; + } + + /* we now have a non-idle batch of work, so we can release the queue mutex and process it */ + d_pthread_mutex_unlock(pThis->mut); + bNeedReLock = 1; + + /* at this spot, we may be cancelled */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &iCancelStateSave); + + + CHKiRet(pThis->pConsumer(pThis->pAction, &pWti->batch, &pThis->bShutdownImmediate)); + + /* we now need to check if we should deliberately delay processing a bit + * and, if so, do that. -- rgerhards, 2008-01-30 + */ +//TODO: MULTIQUEUE: the following setting is no longer correct - need to think about how to do that... + if(pThis->iDeqSlowdown) { + DBGOPRINT((obj_t*) pThis, "sleeping %d microseconds as requested by config params\n", + pThis->iDeqSlowdown); + srSleep(pThis->iDeqSlowdown / 1000000, pThis->iDeqSlowdown % 1000000); + } + + /* but now cancellation is no longer permitted */ + pthread_setcancelstate(iCancelStateSave, NULL); + +finalize_it: + DBGPRINTF("regular consumer finished, iret=%d, szlog %d sz phys %d\n", iRet, + getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis)); + + /* now we are done, but potentially need to re-aquire the mutex */ + if(bNeedReLock) + d_pthread_mutex_lock(pThis->mut); + + RETiRet; +} + + +/* This is a special consumer to feed the disk-queue in disk-assisted mode. + * When active, our own queue more or less acts as a memory buffer to the disk. + * So this consumer just needs to drain the memory queue and submit entries + * to the disk queue. The disk queue will then call the actual consumer from + * the app point of view (we chain two queues here). + * When this method is entered, the mutex is always locked and needs to be unlocked + * as part of the processing. + * rgerhards, 2008-01-14 + */ +static rsRetVal +ConsumerDA(qqueue_t *pThis, wti_t *pWti) +{ + int i; + int iCancelStateSave; + int bNeedReLock = 0; /**< do we need to lock the mutex again? */ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(DequeueForConsumer(pThis, pWti)); + + /* we now have a non-idle batch of work, so we can release the queue mutex and process it */ + d_pthread_mutex_unlock(pThis->mut); + bNeedReLock = 1; + + /* at this spot, we may be cancelled */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &iCancelStateSave); + + /* iterate over returned results and enqueue them in DA queue */ + for(i = 0 ; i < pWti->batch.nElem && !pThis->bShutdownImmediate ; i++) { + CHKiRet(qqueueEnqMsg(pThis->pqDA, eFLOWCTL_NO_DELAY, + MsgAddRef(pWti->batch.pElem[i].pMsg))); + pWti->batch.eltState[i] = BATCH_STATE_COMM; /* commited to other queue! */ + } + + /* but now cancellation is no longer permitted */ + pthread_setcancelstate(iCancelStateSave, NULL); + +finalize_it: + /* now we are done, but potentially need to re-aquire the mutex */ + if(bNeedReLock) + d_pthread_mutex_lock(pThis->mut); + DBGOPRINT((obj_t*) pThis, "DAConsumer returns with iRet %d\n", iRet); + RETiRet; +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! + */ +static rsRetVal +qqueueChkStopWrkrDA(qqueue_t *pThis) +{ + DEFiRet; + + /*DBGPRINTF("XXXX: chkStopWrkrDA called, low watermark %d, log Size %d, phys Size %d, bEnqOnly %d\n", + pThis->iLowWtrMrk, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis), pThis->bEnqOnly);*/ + if(pThis->bEnqOnly) { + iRet = RS_RET_TERMINATE_WHEN_IDLE; + } + if(getPhysicalQueueSize(pThis) <= pThis->iLowWtrMrk) { + iRet = RS_RET_TERMINATE_NOW; + } + + RETiRet; +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! + * If we are a child, we have done our duty when the queue is empty. In that case, + * we can terminate. Version for the regular worker thread. + */ +static rsRetVal +ChkStopWrkrReg(qqueue_t *pThis) +{ + DEFiRet; + /*DBGPRINTF("XXXX: chkStopWrkrReg called, low watermark %d, log Size %d, phys Size %d, bEnqOnly %d\n", + pThis->iLowWtrMrk, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis), pThis->bEnqOnly);*/ + if(pThis->bEnqOnly) { + iRet = RS_RET_TERMINATE_NOW; + } else if(pThis->pqParent != NULL) { + iRet = RS_RET_TERMINATE_WHEN_IDLE; + } + + RETiRet; +} + + +/* return the configured "deq max at once" interval + * rgerhards, 2009-04-22 + */ +static rsRetVal +GetDeqBatchSize(qqueue_t *pThis, int *pVal) +{ + DEFiRet; + assert(pVal != NULL); + *pVal = pThis->iDeqBatchSize; +if(pThis->pqParent != NULL) // TODO: check why we actually do this! + *pVal = 16; + RETiRet; +} + + +/* start up the queue - it must have been constructed and parameters defined + * before. + */ +rsRetVal +qqueueStart(qqueue_t *pThis) /* this is the ConstructionFinalizer */ +{ + DEFiRet; + uchar pszBuf[64]; + uchar pszQIFNam[MAXFNAME]; + int wrk; + uchar *qName; + size_t lenBuf; + + ASSERT(pThis != NULL); + + /* set type-specific handlers and other very type-specific things + * (we can not totally hide it...) + */ + switch(pThis->qType) { + case QUEUETYPE_FIXED_ARRAY: + pThis->qConstruct = qConstructFixedArray; + pThis->qDestruct = qDestructFixedArray; + pThis->qAdd = qAddFixedArray; + pThis->qDeq = qDeqFixedArray; + pThis->qDel = qDelFixedArray; + pThis->MultiEnq = qqueueMultiEnqObjNonDirect; + break; + case QUEUETYPE_LINKEDLIST: + pThis->qConstruct = qConstructLinkedList; + pThis->qDestruct = qDestructLinkedList; + pThis->qAdd = qAddLinkedList; + pThis->qDeq = qDeqLinkedList; + pThis->qDel = qDelLinkedList; + pThis->MultiEnq = qqueueMultiEnqObjNonDirect; + break; + case QUEUETYPE_DISK: + pThis->qConstruct = qConstructDisk; + pThis->qDestruct = qDestructDisk; + pThis->qAdd = qAddDisk; + pThis->qDeq = qDeqDisk; + pThis->qDel = NULL; /* delete for disk handled via special code! */ + pThis->MultiEnq = qqueueMultiEnqObjNonDirect; + /* special handling */ + pThis->iNumWorkerThreads = 1; /* we need exactly one worker */ + /* pre-construct file name for .qi file */ + pThis->lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), + "%s/%s.qi", (char*) glbl.GetWorkDir(), (char*)pThis->pszFilePrefix); + pThis->pszQIFNam = ustrdup(pszQIFNam); + DBGOPRINT((obj_t*) pThis, ".qi file name is '%s', len %d\n", pThis->pszQIFNam, + (int) pThis->lenQIFNam); + break; + case QUEUETYPE_DIRECT: + pThis->qConstruct = qConstructDirect; + pThis->qDestruct = qDestructDirect; + pThis->qAdd = qAddDirect; + pThis->qDel = qDelDirect; + pThis->MultiEnq = qqueueMultiEnqObjDirect; + break; + } + + if(pThis->iFullDlyMrk == -1) + pThis->iFullDlyMrk = pThis->iMaxQueueSize + - (pThis->iMaxQueueSize / 100) * 3; /* default 97% */ + if(pThis->iLightDlyMrk == -1) + pThis->iLightDlyMrk = pThis->iMaxQueueSize + - (pThis->iMaxQueueSize / 100) * 30; /* default 70% */ + + /* we need to do a quick check if our water marks are set plausible. If not, + * we correct the most important shortcomings. TODO: do that!!!! -- rgerhards, 2008-03-14 + */ + + /* finalize some initializations that could not yet be done because it is + * influenced by properties which might have been set after queueConstruct () + */ + if(pThis->pqParent == NULL) { + pThis->mut = (pthread_mutex_t *) MALLOC (sizeof (pthread_mutex_t)); + pthread_mutex_init(pThis->mut, NULL); + } else { + /* child queue, we need to use parent's mutex */ + DBGOPRINT((obj_t*) pThis, "I am a child\n"); + pThis->mut = pThis->pqParent->mut; + } + + pthread_mutex_init(&pThis->mutThrdMgmt, NULL); + pthread_cond_init (&pThis->notFull, NULL); + pthread_cond_init (&pThis->notEmpty, NULL); + pthread_cond_init (&pThis->belowFullDlyWtrMrk, NULL); + pthread_cond_init (&pThis->belowLightDlyWtrMrk, NULL); + + /* call type-specific constructor */ + CHKiRet(pThis->qConstruct(pThis)); /* this also sets bIsDA */ + + /* re-adjust some params if required */ + if(pThis->bIsDA) { + /* if we are in DA mode, we must make sure full delayable messages do not + * initiate going to disk! + */ + wrk = pThis->iHighWtrMrk - (pThis->iHighWtrMrk / 100) * 50; /* 50% of high water mark */ + if(wrk < pThis->iFullDlyMrk) + pThis->iFullDlyMrk = wrk; + } + + DBGOPRINT((obj_t*) pThis, "type %d, enq-only %d, disk assisted %d, maxFileSz %lld, lqsize %d, pqsize %d, child %d, " + "full delay %d, light delay %d, deq batch size %d starting\n", + pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->iMaxFileSize, + getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis), + pThis->pqParent == NULL ? 0 : 1, pThis->iFullDlyMrk, pThis->iLightDlyMrk, + pThis->iDeqBatchSize); + + pThis->bQueueStarted = 1; + if(pThis->qType == QUEUETYPE_DIRECT) + FINALIZE; /* with direct queues, we are already finished... */ + + /* create worker thread pools for regular and DA operation. + */ + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:Reg", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpReg)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpReg, pszBuf, lenBuf)); + CHKiRet(wtpSetpfRateLimiter (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) RateLimiter)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) ChkStopWrkrReg)); + CHKiRet(wtpSetpfGetDeqBatchSize (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int*)) GetDeqBatchSize)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void *pWti)) ConsumerReg)); + CHKiRet(wtpSetpfObjProcessed (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, wti_t *pWti)) batchProcessed)); + CHKiRet(wtpSetpmutUsr (pThis->pWtpReg, pThis->mut)); + CHKiRet(wtpSetpcondBusy (pThis->pWtpReg, &pThis->notEmpty)); + CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpReg, pThis->iNumWorkerThreads)); + CHKiRet(wtpSettoWrkShutdown (pThis->pWtpReg, pThis->toWrkShutdown)); + CHKiRet(wtpSetpUsr (pThis->pWtpReg, pThis)); + CHKiRet(wtpConstructFinalize (pThis->pWtpReg)); + + /* set up DA system if we have a disk-assisted queue */ + if(pThis->bIsDA) + InitDA(pThis, LOCK_MUTEX); /* initiate DA mode */ + + DBGOPRINT((obj_t*) pThis, "queue finished initialization\n"); + + /* if the queue already contains data, we need to start the correct number of worker threads. This can be + * the case when a disk queue has been loaded. If we did not start it here, it would never start. + */ + qqueueAdviseMaxWorkers(pThis); + + /* support statistics gathering */ + qName = obj.GetName((obj_t*)pThis); + CHKiRet(statsobj.Construct(&pThis->statsobj)); + CHKiRet(statsobj.SetName(pThis->statsobj, qName)); + /* we need to save the queue size, as the stats module initializes it to 0! */ + /* iQueueSize is a dual-use counter: no init, no mutex! */ + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("size"), + ctrType_Int, &pThis->iQueueSize)); + + STATSCOUNTER_INIT(pThis->ctrEnqueued, pThis->mutCtrEnqueued); + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("enqueued"), + ctrType_IntCtr, &pThis->ctrEnqueued)); + + STATSCOUNTER_INIT(pThis->ctrFull, pThis->mutCtrFull); + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("full"), + ctrType_IntCtr, &pThis->ctrFull)); + + STATSCOUNTER_INIT(pThis->ctrFDscrd, pThis->mutCtrFDscrd); + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("discarded.full"), + ctrType_IntCtr, &pThis->ctrFDscrd)); + STATSCOUNTER_INIT(pThis->ctrNFDscrd, pThis->mutCtrNFDscrd); + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("discarded.nf"), + ctrType_IntCtr, &pThis->ctrNFDscrd)); + + pThis->ctrMaxqsize = 0; /* no mutex needed, thus no init call */ + CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("maxqsize"), + ctrType_Int, &pThis->ctrMaxqsize)); + + CHKiRet(statsobj.ConstructFinalize(pThis->statsobj)); + +finalize_it: + RETiRet; +} + + +/* persist the queue to disk (write the .qi file). If we have something to persist, we first + * save the information on the queue properties itself and then we call + * the queue-type specific drivers. + * Variable bIsCheckpoint is set to 1 if the persist is for a checkpoint, + * and 0 otherwise. + * rgerhards, 2008-01-10 + */ +static rsRetVal qqueuePersist(qqueue_t *pThis, int bIsCheckpoint) +{ + DEFiRet; + strm_t *psQIF = NULL; /* Queue Info File */ + + ASSERT(pThis != NULL); + + if(pThis->qType != QUEUETYPE_DISK) { + if(getPhysicalQueueSize(pThis) > 0) { + /* This error code is OK, but we will probably not implement this any time + * The reason is that persistence happens via DA queues. But I would like to + * leave the code as is, as we so have a hook in case we need one. + * -- rgerhards, 2008-01-28 + */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + } else + FINALIZE; /* if the queue is empty, we are happy and done... */ + } + + DBGOPRINT((obj_t*) pThis, "persisting queue to disk, %d entries...\n", getPhysicalQueueSize(pThis)); + + if((bIsCheckpoint != QUEUE_CHECKPOINT) && (getPhysicalQueueSize(pThis) == 0)) { + if(pThis->bNeedDelQIF) { + unlink((char*)pThis->pszQIFNam); + pThis->bNeedDelQIF = 0; + } + /* indicate spool file needs to be deleted */ + if(pThis->tVars.disk.pReadDel != NULL) /* may be NULL if we had a startup failure! */ + CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDel, 1)); + FINALIZE; /* nothing left to do, so be happy */ + } + + CHKiRet(strm.Construct(&psQIF)); + CHKiRet(strm.SettOperationsMode(psQIF, STREAMMODE_WRITE_TRUNC)); + CHKiRet(strm.SetbSync(psQIF, pThis->bSyncQueueFiles)); + CHKiRet(strm.SetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strm.SetFName(psQIF, pThis->pszQIFNam, pThis->lenQIFNam)); + CHKiRet(strm.ConstructFinalize(psQIF)); + + /* first, write the property bag for ourselfs + * And, surprisingly enough, we currently need to persist only the size of the + * queue. All the rest is re-created with then-current config parameters when the + * queue is re-created. Well, we'll also save the current queue type, just so that + * we know when somebody has changed the queue type... -- rgerhards, 2008-01-11 + */ + CHKiRet(obj.BeginSerializePropBag(psQIF, (obj_t*) pThis)); + objSerializeSCALAR(psQIF, iQueueSize, INT); + objSerializeSCALAR(psQIF, tVars.disk.sizeOnDisk, INT64); + CHKiRet(obj.EndSerialize(psQIF)); + + /* now persist the stream info */ + if(pThis->tVars.disk.pWrite != NULL) + CHKiRet(strm.Serialize(pThis->tVars.disk.pWrite, psQIF)); + if(pThis->tVars.disk.pReadDel != NULL) + CHKiRet(strm.Serialize(pThis->tVars.disk.pReadDel, psQIF)); + + /* tell the input file object that it must not delete the file on close if the queue + * is non-empty - but only if we are not during a simple checkpoint + */ + if(bIsCheckpoint != QUEUE_CHECKPOINT + && pThis->tVars.disk.pReadDel != NULL) { + CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDel, 0)); + } + + /* we have persisted the queue object. So whenever it comes to an empty queue, + * we need to delete the QIF. Thus, we indicte that need. + */ + pThis->bNeedDelQIF = 1; + +finalize_it: + if(psQIF != NULL) + strm.Destruct(&psQIF); + + RETiRet; +} + + +/* check if we need to persist the current queue info. If an + * error occurs, this should be ignored by caller (but we still + * abide to our regular call interface)... + * rgerhards, 2008-01-13 + * nUpdates is the number of updates since the last call to this function. + * It may be > 1 due to batches. -- rgerhards, 2009-05-12 + */ +static rsRetVal qqueueChkPersist(qqueue_t *pThis, int nUpdates) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, qqueue); + assert(nUpdates >= 0); + + if(nUpdates == 0) + FINALIZE; + + pThis->iUpdsSincePersist += nUpdates; + if(pThis->iPersistUpdCnt && pThis->iUpdsSincePersist >= pThis->iPersistUpdCnt) { + qqueuePersist(pThis, QUEUE_CHECKPOINT); + pThis->iUpdsSincePersist = 0; + } + +finalize_it: + RETiRet; +} + + +/* persist a queue with all data elements to disk - this is used to handle + * bSaveOnShutdown. We utilize the DA worker to do this. This must only + * be called after all workers have been shut down and if bSaveOnShutdown + * is actually set. Note that this function may potentially run long, + * depending on the queue configuration (e.g. store on remote machine). + * rgerhards, 2009-05-26 + */ +static inline rsRetVal +DoSaveOnShutdown(qqueue_t *pThis) +{ + struct timespec tTimeout; + rsRetVal iRetLocal; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + + /* we reduce the low water mark, otherwise the DA worker would terminate when + * it is reached. + */ + DBGOPRINT((obj_t*) pThis, "bSaveOnShutdown set, restarting DA worker...\n"); + pThis->bShutdownImmediate = 0; /* would termiante the DA worker! */ + pThis->iLowWtrMrk = 0; + wtpSetState(pThis->pWtpDA, wtpState_SHUTDOWN); /* shutdown worker (only) when done (was _IMMEDIATE!) */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* restart DA worker */ + + DBGOPRINT((obj_t*) pThis, "waiting for DA worker to terminate...\n"); + timeoutComp(&tTimeout, QUEUE_TIMEOUT_ETERNAL); + /* and run the primary queue's DA worker to drain the queue */ + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); + DBGOPRINT((obj_t*) pThis, "end queue persistence run, iRet %d, queue size log %d, phys %d\n", + iRetLocal, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis)); + if(iRetLocal != RS_RET_OK) { + DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d after trying to shut down primary queue in disk save mode, " + "continuing, but results are unpredictable\n", iRetLocal); + } + + RETiRet; +} + + +/* destructor for the queue object */ +BEGINobjDestruct(qqueue) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(qqueue) + if(pThis->bQueueStarted) { + /* shut down all workers + * We do not need to shutdown workers when we are in enqueue-only mode or we are a + * direct queue - because in both cases we have none... ;) + * with a child! -- rgerhards, 2008-01-28 + */ + if(pThis->qType != QUEUETYPE_DIRECT && !pThis->bEnqOnly && pThis->pqParent == NULL + && pThis->pWtpReg != NULL) + ShutdownWorkers(pThis); + + if(pThis->bIsDA && getPhysicalQueueSize(pThis) > 0 && pThis->bSaveOnShutdown) { + CHKiRet(DoSaveOnShutdown(pThis)); + } + + /* finally destruct our (regular) worker thread pool + * Note: currently pWtpReg is never NULL, but if we optimize our logic, this may happen, + * e.g. when they are not created in enqueue-only mode. We already check the condition + * as this may otherwise be very hard to find once we optimize (and have long forgotten + * about this condition here ;) + * rgerhards, 2008-01-25 + */ + if(pThis->qType != QUEUETYPE_DIRECT && pThis->pWtpReg != NULL) { + wtpDestruct(&pThis->pWtpReg); + } + + /* Now check if we actually have a DA queue and, if so, destruct it. + * Note that the wtp must be destructed first, it may be in cancel cleanup handler + * *right now* and actually *need* to access the queue object to persist some final + * data (re-queueing case). So we need to destruct the wtp first, which will make + * sure all workers have terminated. Please note that this also generates a situation + * where it is possible that the DA queue has a parent pointer but the parent has + * no WtpDA associated with it - which is perfectly legal thanks to this code here. + */ + if(pThis->pWtpDA != NULL) { + wtpDestruct(&pThis->pWtpDA); + } + if(pThis->pqDA != NULL) { + qqueueDestruct(&pThis->pqDA); + } + + /* persist the queue (we always do that - queuePersits() does cleanup if the queue is empty) + * This handler is most important for disk queues, it will finally persist the necessary + * on-disk structures. In theory, other queueing modes may implement their other (non-DA) + * methods of persisting a queue between runs, but in practice all of this is done via + * disk queues and DA mode. Anyhow, it doesn't hurt to know that we could extend it here + * if need arises (what I doubt...) -- rgerhards, 2008-01-25 + */ + CHKiRet_Hdlr(qqueuePersist(pThis, QUEUE_NO_CHECKPOINT)) { + DBGOPRINT((obj_t*) pThis, "error %d persisting queue - data lost!\n", iRet); + } + + /* finally, clean up some simple things... */ + if(pThis->pqParent == NULL) { + /* if we are not a child, we allocated our own mutex, which we now need to destroy */ + pthread_mutex_destroy(pThis->mut); + free(pThis->mut); + } + pthread_mutex_destroy(&pThis->mutThrdMgmt); + pthread_cond_destroy(&pThis->notFull); + pthread_cond_destroy(&pThis->notEmpty); + pthread_cond_destroy(&pThis->belowFullDlyWtrMrk); + pthread_cond_destroy(&pThis->belowLightDlyWtrMrk); + + DESTROY_ATOMIC_HELPER_MUT(pThis->mutQueueSize); + DESTROY_ATOMIC_HELPER_MUT(pThis->mutLogDeq); + + /* type-specific destructor */ + iRet = pThis->qDestruct(pThis); + } + + free(pThis->pszFilePrefix); + free(pThis->pszSpoolDir); + if(pThis->useCryprov) { + pThis->cryprov.Destruct(&pThis->cryprovData); + obj.ReleaseObj(__FILE__, pThis->cryprovNameFull+2, pThis->cryprovNameFull, + (void*) &pThis->cryprov); + free(pThis->cryprovName); + free(pThis->cryprovNameFull); + } + + /* some queues do not provide stats and thus have no statsobj! */ + if(pThis->statsobj != NULL) + statsobj.Destruct(&pThis->statsobj); +ENDobjDestruct(qqueue) + + +/* set the queue's file prefix + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +rsRetVal +qqueueSetFilePrefix(qqueue_t *pThis, uchar *pszPrefix, size_t iLenPrefix) +{ + DEFiRet; + + free(pThis->pszFilePrefix); + pThis->pszFilePrefix = NULL; + + if(pszPrefix == NULL) /* just unset the prefix! */ + ABORT_FINALIZE(RS_RET_OK); + + if((pThis->pszFilePrefix = MALLOC(sizeof(uchar) * iLenPrefix + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pThis->pszFilePrefix, pszPrefix, iLenPrefix + 1); + pThis->lenFilePrefix = iLenPrefix; + +finalize_it: + RETiRet; +} + +/* set the queue's maximum file size + * rgerhards, 2008-01-09 + */ +rsRetVal +qqueueSetMaxFileSize(qqueue_t *pThis, size_t iMaxFileSize) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + + if(iMaxFileSize < 1024) { + ABORT_FINALIZE(RS_RET_VALUE_TOO_LOW); + } + + pThis->iMaxFileSize = iMaxFileSize; + +finalize_it: + RETiRet; +} + + +/* enqueue a single data object. + * Note that the queue mutex MUST already be locked when this function is called. + * rgerhards, 2009-06-16 + */ +static inline rsRetVal +doEnqSingleObj(qqueue_t *pThis, flowControl_t flowCtlType, msg_t *pMsg) +{ + DEFiRet; + int err; + struct timespec t; + + STATSCOUNTER_INC(pThis->ctrEnqueued, pThis->mutCtrEnqueued); + /* first check if we need to discard this message (which will cause CHKiRet() to exit) + */ + CHKiRet(qqueueChkDiscardMsg(pThis, pThis->iQueueSize, pMsg)); + + /* handle flow control + * There are two different flow control mechanisms: basic and advanced flow control. + * Basic flow control has always been implemented and protects the queue structures + * in that it makes sure no more data is enqueued than the queue is configured to + * support. Enhanced flow control is being added today. There are some sources which + * can easily be stopped, e.g. a file reader. This is the case because it is unlikely + * that blocking those sources will have negative effects (after all, the file is + * continued to be written). Other sources can somewhat be blocked (e.g. the kernel + * log reader or the local log stream reader): in general, nothing is lost if messages + * from these sources are not picked up immediately. HOWEVER, they can not block for + * an extended period of time, as this either causes message loss or - even worse - some + * other bad effects (e.g. unresponsive system in respect to the main system log socket). + * Finally, there are some (few) sources which can not be blocked at all. UDP syslog is + * a prime example. If a UDP message is not received, it is simply lost. So we can't + * do anything against UDP sockets that come in too fast. The core idea of advanced + * flow control is that we take into account the different natures of the sources and + * select flow control mechanisms that fit these needs. This also means, in the end + * result, that non-blockable sources like UDP syslog receive priority in the system. + * It's a side effect, but a good one ;) -- rgerhards, 2008-03-14 + */ + if(flowCtlType == eFLOWCTL_FULL_DELAY) { + while(pThis->iQueueSize >= pThis->iFullDlyMrk&& ! glbl.GetGlobalInputTermState()) { + /* We have a problem during shutdown if we block eternally. In that + * case, the the input thread cannot be terminated. So we wake up + * from time to time to check for termination. + * TODO/v6(at earliest): check if we could signal the condition during + * shutdown. However, this requires new queue registries and thus is + * far to much change for a stable version (and I am still not sure it + * is worth the effort, given how seldom this situation occurs and how + * few resources the wakeups need). -- rgerhards, 2012-05-03 + * In any case, this was the old code (if we do the TODO): + * pthread_cond_wait(&pThis->belowFullDlyWtrMrk, pThis->mut); + */ + DBGOPRINT((obj_t*) pThis, "enqueueMsg: FullDelay mark reached for full delayable message " + "- blocking, queue size is %d.\n", pThis->iQueueSize); + timeoutComp(&t, 1000); + err = pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t); + if(err != 0 && err != ETIMEDOUT) { + /* Something is really wrong now. Report to debug log and abort the + * wait. That keeps us running, even though we may lose messages. + */ + DBGOPRINT((obj_t*) pThis, "potential program bug: pthread_cond_timedwait()" + "/fulldelay returned %d\n", err); + break; + + } + DBGPRINTF("wti worker in full delay timed out, checking termination...\n"); + } + } else if(flowCtlType == eFLOWCTL_LIGHT_DELAY && !glbl.GetGlobalInputTermState()) { + if(pThis->iQueueSize >= pThis->iLightDlyMrk) { + DBGOPRINT((obj_t*) pThis, "enqueueMsg: LightDelay mark reached for light " + "delayable message - blocking a bit.\n"); + timeoutComp(&t, 1000); /* 1000 millisconds = 1 second TODO: make configurable */ + err = pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t); + if(err != 0 && err != ETIMEDOUT) { + /* Something is really wrong now. Report to debug log */ + DBGOPRINT((obj_t*) pThis, "potential program bug: pthread_cond_timedwait()" + "/lightdelay returned %d\n", err); + + } + } + } + + /* from our regular flow control settings, we are now ready to enqueue the object. + * However, we now need to do a check if the queue permits to add more data. If that + * is not the case, basic flow control enters the field, which means we wait for + * the queue to become ready or drop the new message. -- rgerhards, 2008-03-14 + */ + while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) + || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 + && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { + STATSCOUNTER_INC(pThis->ctrFull, pThis->mutCtrFull); + if(pThis->toEnq == 0 || pThis->bEnqOnly) { + DBGOPRINT((obj_t*) pThis, "enqueueMsg: queue FULL - configured for immediate discarding.\n"); + STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd); + msgDestruct(&pMsg); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } else { + DBGOPRINT((obj_t*) pThis, "enqueueMsg: queue FULL - waiting %dms to drain.\n", pThis->toEnq); + if(glbl.GetGlobalInputTermState()) { + DBGOPRINT((obj_t*) pThis, "enqueueMsg: queue FULL, discard due to FORCE_TERM.\n"); + ABORT_FINALIZE(RS_RET_FORCE_TERM); + } + timeoutComp(&t, pThis->toEnq); + if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { + DBGOPRINT((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); + STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd); + msgDestruct(&pMsg); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } + dbgoprint((obj_t*) pThis, "enqueueMsg: wait solved queue full condition, enqueing\n"); + } + } + + /* and finally enqueue the message */ + CHKiRet(qqueueAdd(pThis, pMsg)); + STATSCOUNTER_SETMAX_NOMUT(pThis->ctrMaxqsize, pThis->iQueueSize); + +finalize_it: + RETiRet; +} + +/* ------------------------------ multi-enqueue functions ------------------------------ */ +/* enqueue multiple user data elements at once. The aim is to provide a faster interface + * for object submission. Uses the multi_submit_t helper object. + * Please note that this function is not cancel-safe and consequently + * sets the calling thread's cancelibility state to PTHREAD_CANCEL_DISABLE + * during its execution. If that is not done, race conditions occur if the + * thread is canceled (most important use case is input module termination). + * rgerhards, 2009-06-16 + * Note: there now exists multiple different functions implementing specially + * optimized algorithms for different config cases. -- rgerhards, 2010-06-09 + */ +/* now the function for all modes but direct */ +static rsRetVal +qqueueMultiEnqObjNonDirect(qqueue_t *pThis, multi_submit_t *pMultiSub) +{ + int iCancelStateSave; + int i; + rsRetVal localRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + assert(pMultiSub != NULL); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(pThis->mut); + for(i = 0 ; i < pMultiSub->nElem ; ++i) { + localRet = doEnqSingleObj(pThis, pMultiSub->ppMsgs[i]->flowCtlType, (void*)pMultiSub->ppMsgs[i]); + if(localRet != RS_RET_OK && localRet != RS_RET_QUEUE_FULL) + ABORT_FINALIZE(localRet); + } + qqueueChkPersist(pThis, pMultiSub->nElem); + +finalize_it: + /* make sure at least one worker is running. */ + qqueueAdviseMaxWorkers(pThis); + /* and release the mutex */ + d_pthread_mutex_unlock(pThis->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + DBGOPRINT((obj_t*) pThis, "MultiEnqObj advised worker start\n"); + + RETiRet; +} + +/* now, the same function, but for direct mode */ +static rsRetVal +qqueueMultiEnqObjDirect(qqueue_t *pThis, multi_submit_t *pMultiSub) +{ + int i; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + assert(pMultiSub != NULL); + + for(i = 0 ; i < pMultiSub->nElem ; ++i) { + CHKiRet(qAddDirect(pThis, (void*)pMultiSub->ppMsgs[i])); + } + +finalize_it: + RETiRet; +} +/* ------------------------------ END multi-enqueue functions ------------------------------ */ + + +/* enqueue a new user data element in direct mode + * NOTE/TODO: This is a TESTER/EXPERIEMENTAL, to be changed to better + * code later on (like multi submit!) 2010-06-10 + * Enqueues the new element and awakes worker thread. + */ +rsRetVal +qqueueEnqMsgDirect(qqueue_t *pThis, msg_t *pMsg) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, qqueue); + iRet = qAddDirect(pThis, pMsg); + RETiRet; +} + + +/* enqueue a new user data element + * Enqueues the new element and awakes worker thread. + */ +rsRetVal +qqueueEnqMsg(qqueue_t *pThis, flowControl_t flowCtlType, msg_t *pMsg) +{ + DEFiRet; + int iCancelStateSave; + + ISOBJ_TYPE_assert(pThis, qqueue); + + if(pThis->qType != QUEUETYPE_DIRECT) { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(pThis->mut); + } + + CHKiRet(doEnqSingleObj(pThis, flowCtlType, pMsg)); + + qqueueChkPersist(pThis, 1); + +finalize_it: + if(pThis->qType != QUEUETYPE_DIRECT) { + /* make sure at least one worker is running. */ + qqueueAdviseMaxWorkers(pThis); + /* and release the mutex */ + d_pthread_mutex_unlock(pThis->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + DBGOPRINT((obj_t*) pThis, "EnqueueMsg advised worker start\n"); + } + + RETiRet; +} + + +/* are any queue params set at all? 1 - yes, 0 - no + * We need to evaluate the param block for this function, which is somewhat + * inefficient. HOWEVER, this is only done during config load, so we really + * don't care... -- rgerhards, 2013-05-10 + */ +int +queueCnfParamsSet(struct nvlst *lst) +{ + int r; + struct cnfparamvals *pvals; + + pvals = nvlstGetParams(lst, &pblk, NULL); + r = cnfparamvalsIsSet(&pblk, pvals); + cnfparamvalsDestruct(pvals, &pblk); + return r; +} + + +static inline rsRetVal +initCryprov(qqueue_t *pThis, struct nvlst *lst) +{ + uchar szDrvrName[1024]; + DEFiRet; + + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmcry_%s", pThis->cryprovName) + == sizeof(szDrvrName)) { + errmsg.LogError(0, RS_RET_ERR, "queue: crypto provider " + "name is too long: '%s' - encryption disabled", + pThis->cryprovName); + ABORT_FINALIZE(RS_RET_ERR); + } + pThis->cryprovNameFull = ustrdup(szDrvrName); + + pThis->cryprov.ifVersion = cryprovCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean enough. + */ + if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pThis->cryprov) + != RS_RET_OK) { + errmsg.LogError(0, RS_RET_LOAD_ERROR, "queue: could not load " + "crypto provider '%s' - encryption disabled", + szDrvrName); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } + + if(pThis->cryprov.Construct(&pThis->cryprovData) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_CRYPROV_ERR, "queue: error constructing " + "crypto provider %s dataset - encryption disabled", + szDrvrName); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } + CHKiRet(pThis->cryprov.SetCnfParam(pThis->cryprovData, lst, CRYPROV_PARAMTYPE_DISK)); + + dbgprintf("loaded crypto provider %s, data instance at %p\n", + szDrvrName, pThis->cryprovData); + pThis->useCryprov = 1; +finalize_it: + RETiRet; +} + +/* apply all params from param block to queue. Must be called before + * finalizing. This supports the v6 config system. Defaults were already + * set during queue creation. The pvals object is destructed by this + * function. + */ +rsRetVal +qqueueApplyCnfParam(qqueue_t *pThis, struct nvlst *lst) +{ + int i; + struct cnfparamvals *pvals; + + pvals = nvlstGetParams(lst, &pblk, NULL); + if(Debug) { + dbgprintf("queue param blk:\n"); + cnfparamsPrint(&pblk, pvals); + } + for(i = 0 ; i < pblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblk.descr[i].name, "queue.filename")) { + pThis->pszFilePrefix = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + pThis->lenFilePrefix = es_strlen(pvals[i].val.d.estr); + } else if(!strcmp(pblk.descr[i].name, "queue.cry.provider")) { + pThis->cryprovName = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblk.descr[i].name, "queue.size")) { + pThis->iMaxQueueSize = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.dequeuebatchsize")) { + pThis->iDeqBatchSize = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.maxdiskspace")) { + pThis->iMaxFileSize = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.highwatermark")) { + pThis->iHighWtrMrk = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.lowwatermark")) { + pThis->iLowWtrMrk = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.fulldelaymark")) { + pThis->iFullDlyMrk = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.lightdelaymark")) { + pThis->iLightDlyMrk = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.discardmark")) { + pThis->iDiscardMrk = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.discardseverity")) { + pThis->iDiscardSeverity = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.checkpointinterval")) { + pThis->iPersistUpdCnt = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.syncqueuefiles")) { + pThis->bSyncQueueFiles = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.type")) { + pThis->qType = (queueType_t) pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.workerthreads")) { + pThis->iNumWorkerThreads = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.timeoutshutdown")) { + pThis->toQShutdown = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.timeoutactioncompletion")) { + pThis->toActShutdown = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.timeoutenqueue")) { + pThis->toEnq = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.timeoutworkerthreadshutdown")) { + pThis->toWrkShutdown = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.workerthreadminimummessages")) { + pThis->iMinMsgsPerWrkr = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.maxfilesize")) { + pThis->iMaxFileSize = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.saveonshutdown")) { + pThis->bSaveOnShutdown = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.dequeueslowdown")) { + pThis->iDeqSlowdown = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queue.dequeuetimebegin")) { + pThis->iDeqtWinFromHr = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "queuedequeuetimend.")) { + pThis->iDeqtWinToHr = pvals[i].val.d.n; + } else { + DBGPRINTF("queue: program error, non-handled " + "param '%s'\n", pblk.descr[i].name); + } + } + if(pThis->qType == QUEUETYPE_DISK) { + if(pThis->pszFilePrefix == NULL) { + errmsg.LogError(0, RS_RET_QUEUE_DISK_NO_FN, "error on queue '%s', disk mode selected, but " + "no queue file name given; queue type changed to 'linkedList'", + obj.GetName((obj_t*) pThis)); + pThis->qType = QUEUETYPE_LINKEDLIST; + } + } + + if(pThis->pszFilePrefix == NULL && pThis->cryprovName != NULL) { + errmsg.LogError(0, RS_RET_QUEUE_CRY_DISK_ONLY, "error on queue '%s', crypto provider can " + "only be set for disk or disk assisted queue - ignored", + obj.GetName((obj_t*) pThis)); + free(pThis->cryprovName); + pThis->cryprovName = NULL; + } + + if(pThis->cryprovName != NULL) { + initCryprov(pThis, lst); + } + + cnfparamvalsDestruct(pvals, &pblk); + return RS_RET_OK; +} + + +/* some simple object access methods */ +DEFpropSetMeth(qqueue, bSyncQueueFiles, int) +DEFpropSetMeth(qqueue, iPersistUpdCnt, int) +DEFpropSetMeth(qqueue, iDeqtWinFromHr, int) +DEFpropSetMeth(qqueue, iDeqtWinToHr, int) +DEFpropSetMeth(qqueue, toQShutdown, long) +DEFpropSetMeth(qqueue, toActShutdown, long) +DEFpropSetMeth(qqueue, toWrkShutdown, long) +DEFpropSetMeth(qqueue, toEnq, long) +DEFpropSetMeth(qqueue, iHighWtrMrk, int) +DEFpropSetMeth(qqueue, iLowWtrMrk, int) +DEFpropSetMeth(qqueue, iDiscardMrk, int) +DEFpropSetMeth(qqueue, iFullDlyMrk, int) +DEFpropSetMeth(qqueue, iDiscardSeverity, int) +DEFpropSetMeth(qqueue, iLightDlyMrk, int) +DEFpropSetMeth(qqueue, bIsDA, int) +DEFpropSetMeth(qqueue, iMinMsgsPerWrkr, int) +DEFpropSetMeth(qqueue, bSaveOnShutdown, int) +DEFpropSetMeth(qqueue, pAction, action_t*) +DEFpropSetMeth(qqueue, iDeqSlowdown, int) +DEFpropSetMeth(qqueue, iDeqBatchSize, int) +DEFpropSetMeth(qqueue, sizeOnDiskMax, int64) + + +/* This function can be used as a generic way to set properties. Only the subset + * of properties required to read persisted property bags is supported. This + * functions shall only be called by the property bag reader, thus it is static. + * rgerhards, 2008-01-11 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +static rsRetVal qqueueSetProperty(qqueue_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, qqueue); + ASSERT(pProp != NULL); + + if(isProp("iQueueSize")) { + pThis->iQueueSize = pProp->val.num; + } else if(isProp("tVars.disk.sizeOnDisk")) { + pThis->tVars.disk.sizeOnDisk = pProp->val.num; + } else if(isProp("qType")) { + if(pThis->qType != pProp->val.num) + ABORT_FINALIZE(RS_RET_QTYPE_MISMATCH); + } + +finalize_it: + RETiRet; +} +#undef isProp + +/* dummy */ +rsRetVal qqueueQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(qqueue, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(strm, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_SETPROPERTY, qqueueSetProperty); +ENDObjClassInit(qqueue) + +/* vi:set ai: + */ diff --git a/runtime/queue.h b/runtime/queue.h new file mode 100644 index 00000000..844523ad --- /dev/null +++ b/runtime/queue.h @@ -0,0 +1,234 @@ +/* Definition of the queue support module. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef QUEUE_H_INCLUDED +#define QUEUE_H_INCLUDED + +#include <pthread.h> +#include "obj.h" +#include "wtp.h" +#include "batch.h" +#include "stream.h" +#include "statsobj.h" +#include "cryprov.h" + +/* support for the toDelete list */ +typedef struct toDeleteLst_s toDeleteLst_t; +struct toDeleteLst_s { + qDeqID deqID; + int nElemDeq; /* numbe of elements that were dequeued and as such must now be discarded */ + struct toDeleteLst_s *pNext; +}; + + +/* queue types */ +typedef enum { + QUEUETYPE_FIXED_ARRAY = 0,/* a simple queue made out of a fixed (initially malloced) array fast but memoryhog */ + QUEUETYPE_LINKEDLIST = 1, /* linked list used as buffer, lower fixed memory overhead but slower */ + QUEUETYPE_DISK = 2, /* disk files used as buffer */ + QUEUETYPE_DIRECT = 3 /* no queuing happens, consumer is directly called */ +} queueType_t; + +/* list member definition for linked list types of queues: */ +typedef struct qLinkedList_S { + struct qLinkedList_S *pNext; + msg_t *pMsg; +} qLinkedList_t; + + +/* the queue object */ +struct queue_s { + BEGINobjInstance; + queueType_t qType; + int nLogDeq; /* number of elements currently logically dequeued */ + int bShutdownImmediate; /* should all workers cease processing messages? */ + sbool bEnqOnly; /* does queue run in enqueue-only mode (1) or not (0)? */ + sbool bSaveOnShutdown;/* persists everthing on shutdown (if DA!)? 1-yes, 0-no */ + sbool bQueueStarted; /* has queueStart() been called on this queue? 1-yes, 0-no */ + int iQueueSize; /* Current number of elements in the queue */ + int iMaxQueueSize; /* how large can the queue grow? */ + int iNumWorkerThreads;/* number of worker threads to use */ + int iCurNumWrkThrd;/* current number of active worker threads */ + int iMinMsgsPerWrkr;/* minimum nbr of msgs per worker thread, if more, a new worker is started until max wrkrs */ + wtp_t *pWtpDA; + wtp_t *pWtpReg; + action_t *pAction; /* for action queues, ptr to action object; for main queues unused */ + int iUpdsSincePersist;/* nbr of queue updates since the last persist call */ + int iPersistUpdCnt; /* persits queue info after this nbr of updates - 0 -> persist only on shutdown */ + sbool bSyncQueueFiles;/* if working with files, sync them after each write? */ + int iHighWtrMrk; /* high water mark for disk-assisted memory queues */ + int iLowWtrMrk; /* low water mark for disk-assisted memory queues */ + int iDiscardMrk; /* if the queue is above this mark, low-severity messages are discarded */ + int iFullDlyMrk; /* if the queue is above this mark, FULL_DELAYable message are put on hold */ + int iLightDlyMrk; /* if the queue is above this mark, LIGHT_DELAYable message are put on hold */ + int iDiscardSeverity;/* messages of this severity above are discarded on too-full queue */ + sbool bNeedDelQIF; /* does the QIF file need to be deleted when queue becomes empty? */ + int toQShutdown; /* timeout for regular queue shutdown in ms */ + int toActShutdown; /* timeout for long-running action shutdown in ms */ + int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */ + toDeleteLst_t *toDeleteLst;/* this queue's to-delete list */ + int toEnq; /* enqueue timeout */ + int iDeqBatchSize; /* max number of elements that shall be dequeued at once */ + /* rate limiting settings (will be expanded) */ + int iDeqSlowdown; /* slow down dequeue by specified nbr of microseconds */ + /* end rate limiting */ + /* dequeue time window settings (may also be expanded) */ + int iDeqtWinFromHr; /* begin of dequeue time window (hour only) */ + int iDeqtWinToHr; /* end of dequeue time window (hour only), set to 25 to disable deq window! */ + /* note that begin and end have specific semantics. It is a big difference if we have + * begin 4, end 22 or begin 22, end 4. In the later case, dequeuing will run from 10p, + * throughout the night and stop at 4 in the morning. In the first case, it will start + * at 4am, run throughout the day, and stop at 10 in the evening! So far, not logic is + * applied to detect user configuration errors (and tell me how should we detect what + * the user really wanted...). -- rgerhards, 2008-04-02 + */ + /* end dequeue time window */ + rsRetVal (*pConsumer)(void *,batch_t*,int*); /* user-supplied consumer function for dequeued messages */ + /* calling interface for pConsumer: arg1 is the global user pointer from this structure, arg2 is the + * user pointer array that was dequeued (actual sample: for actions, arg1 is the pAction and arg2 + * is pointer to an array of message message pointers), arg3 is a pointer to an interger which is zero + * during normal operations and one if the consumer must urgently shut down. + */ + /* type-specific handlers (set during construction) */ + rsRetVal (*qConstruct)(struct queue_s *pThis); + rsRetVal (*qDestruct)(struct queue_s *pThis); + rsRetVal (*qAdd)(struct queue_s *pThis, msg_t *pMsg); + rsRetVal (*qDeq)(struct queue_s *pThis, msg_t **ppMsg); + rsRetVal (*qDel)(struct queue_s *pThis); + /* end type-specific handler */ + /* public entry points (set during construction, permit to set best algorithm for params selected) */ + rsRetVal (*MultiEnq)(qqueue_t *pThis, multi_submit_t *pMultiSub); + /* end public entry points */ + /* synchronization variables */ + pthread_mutex_t mutThrdMgmt; /* mutex for the queue's thread management */ + pthread_mutex_t *mut; /* mutex for enqueing and dequeueing messages */ + pthread_cond_t notFull, notEmpty; + pthread_cond_t belowFullDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ + pthread_cond_t belowLightDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ + int bThrdStateChanged; /* at least one thread state has changed if 1 */ + /* end sync variables */ + /* the following variables are always present, because they + * are not only used for the "disk" queueing mode but also for + * any other queueing mode if it is set to "disk assisted". + * rgerhards, 2008-01-09 + */ + uchar *pszSpoolDir; + size_t lenSpoolDir; + uchar *pszFilePrefix; + size_t lenFilePrefix; + uchar *pszQIFNam; /* full .qi file name, based on parts above */ + size_t lenQIFNam; + int iNumberFiles; /* how many files make up the queue? */ + int64 iMaxFileSize; /* max size for a single queue file */ + int64 sizeOnDiskMax; /* maximum size on disk allowed */ + qDeqID deqIDAdd; /* next dequeue ID to use during add to queue store */ + qDeqID deqIDDel; /* queue store delete position */ + int bIsDA; /* is this queue disk assisted? */ + struct queue_s *pqDA; /* queue for disk-assisted modes */ + struct queue_s *pqParent;/* pointer to the parent (if this is a child queue) */ + int bDAEnqOnly; /* EnqOnly setting for DA queue */ + /* now follow queueing mode specific data elements */ + //union { /* different data elements based on queue type (qType) */ + struct { /* different data elements based on queue type (qType) */ + struct { + long deqhead, head, tail; + void** pBuf; /* the queued user data structure */ + } farray; + struct { + qLinkedList_t *pDeqRoot; + qLinkedList_t *pDelRoot; + qLinkedList_t *pLast; + } linklist; + struct { + int64 sizeOnDisk; /* current amount of disk space used */ + int64 deqOffs; /* offset after dequeue batch - used for file deleter */ + int deqFileNumIn; /* same for the circular file numbers, mainly for */ + int deqFileNumOut;/* deleting finished files */ + strm_t *pWrite; /* current file to be written */ + strm_t *pReadDeq; /* current file for dequeueing */ + strm_t *pReadDel; /* current file for deleting */ + } disk; + } tVars; + sbool useCryprov; /* quicker than checkig ptr (1 vs 8 bytes!) */ + uchar *cryprovName; /* crypto provider to use */ + cryprov_if_t cryprov; /* ptr to crypto provider interface */ + void *cryprovData; /* opaque data ptr for provider use */ + uchar *cryprovNameFull;/* full internal crypto provider name */ + DEF_ATOMIC_HELPER_MUT(mutQueueSize); + DEF_ATOMIC_HELPER_MUT(mutLogDeq); + /* for statistics subsystem */ + statsobj_t *statsobj; + STATSCOUNTER_DEF(ctrEnqueued, mutCtrEnqueued); + STATSCOUNTER_DEF(ctrFull, mutCtrFull); + STATSCOUNTER_DEF(ctrFDscrd, mutCtrFDscrd); + STATSCOUNTER_DEF(ctrNFDscrd, mutCtrNFDscrd); + int ctrMaxqsize; /* NOT guarded by a mutex */ +}; + + +/* the define below is an "eternal" timeout for the timeout settings which require a value. + * It is one day, which is not really eternal, but comes close to it if we think about + * rsyslog (e.g.: do you want to wait on shutdown for more than a day? ;)) + * rgerhards, 2008-01-17 + */ +#define QUEUE_TIMEOUT_ETERNAL 24 * 60 * 60 * 1000 + +/* prototypes */ +rsRetVal qqueueDestruct(qqueue_t **ppThis); +rsRetVal qqueueEnqMsgDirect(qqueue_t *pThis, msg_t *pMsg); +rsRetVal qqueueEnqMsg(qqueue_t *pThis, flowControl_t flwCtlType, msg_t *pMsg); +rsRetVal qqueueStart(qqueue_t *pThis); +rsRetVal qqueueSetMaxFileSize(qqueue_t *pThis, size_t iMaxFileSize); +rsRetVal qqueueSetFilePrefix(qqueue_t *pThis, uchar *pszPrefix, size_t iLenPrefix); +rsRetVal qqueueConstruct(qqueue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,batch_t*, int*)); +rsRetVal qqueueEnqObjDirectBatch(qqueue_t *pThis, batch_t *pBatch); +int queueCnfParamsSet(struct nvlst *lst); +rsRetVal qqueueApplyCnfParam(qqueue_t *pThis, struct nvlst *lst); +void qqueueSetDefaultsRulesetQueue(qqueue_t *pThis); +void qqueueSetDefaultsActionQueue(qqueue_t *pThis); +void qqueueDbgPrint(qqueue_t *pThis); + +PROTOTYPEObjClassInit(qqueue); +PROTOTYPEpropSetMeth(qqueue, iPersistUpdCnt, int); +PROTOTYPEpropSetMeth(qqueue, bSyncQueueFiles, int); +PROTOTYPEpropSetMeth(qqueue, iDeqtWinFromHr, int); +PROTOTYPEpropSetMeth(qqueue, iDeqtWinToHr, int); +PROTOTYPEpropSetMeth(qqueue, toQShutdown, long); +PROTOTYPEpropSetMeth(qqueue, toActShutdown, long); +PROTOTYPEpropSetMeth(qqueue, toWrkShutdown, long); +PROTOTYPEpropSetMeth(qqueue, toEnq, long); +PROTOTYPEpropSetMeth(qqueue, iLightDlyMrk, int); +PROTOTYPEpropSetMeth(qqueue, iHighWtrMrk, int); +PROTOTYPEpropSetMeth(qqueue, iLowWtrMrk, int); +PROTOTYPEpropSetMeth(qqueue, iDiscardMrk, int); +PROTOTYPEpropSetMeth(qqueue, iDiscardSeverity, int); +PROTOTYPEpropSetMeth(qqueue, iMinMsgsPerWrkr, int); +PROTOTYPEpropSetMeth(qqueue, bSaveOnShutdown, int); +PROTOTYPEpropSetMeth(qqueue, pAction, action_t*); +PROTOTYPEpropSetMeth(qqueue, iDeqSlowdown, int); +PROTOTYPEpropSetMeth(qqueue, sizeOnDiskMax, int64); +PROTOTYPEpropSetMeth(qqueue, iDeqBatchSize, int); +#define qqueueGetID(pThis) ((unsigned long) pThis) + +#endif /* #ifndef QUEUE_H_INCLUDED */ diff --git a/runtime/ratelimit.c b/runtime/ratelimit.c new file mode 100644 index 00000000..a808e04a --- /dev/null +++ b/runtime/ratelimit.c @@ -0,0 +1,385 @@ +/* ratelimit.c + * support for rate-limiting sources, including "last message + * repeated n times" processing. + * + * Copyright 2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "rsyslog.h" +#include "errmsg.h" +#include "ratelimit.h" +#include "datetime.h" +#include "parser.h" +#include "unicode-helper.h" +#include "msg.h" +#include "rsconf.h" +#include "dirty.h" + +/* definitions for objects we access */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(datetime) +DEFobjCurrIf(parser) + +/* static data */ + +/* generate a "repeated n times" message */ +static inline msg_t * +ratelimitGenRepMsg(ratelimit_t *ratelimit) +{ + msg_t *repMsg; + size_t lenRepMsg; + uchar szRepMsg[1024]; + + if(ratelimit->nsupp == 1) { /* we simply use the original message! */ + repMsg = MsgAddRef(ratelimit->pMsg); + } else {/* we need to duplicate, original message may still be in use in other + * parts of the system! */ + if((repMsg = MsgDup(ratelimit->pMsg)) == NULL) { + DBGPRINTF("Message duplication failed, dropping repeat message.\n"); + goto done; + } + lenRepMsg = snprintf((char*)szRepMsg, sizeof(szRepMsg), + " message repeated %d times: [%.800s]", + ratelimit->nsupp, getMSG(ratelimit->pMsg)); + MsgReplaceMSG(repMsg, szRepMsg, lenRepMsg); + } + +done: return repMsg; +} + +static inline rsRetVal +doLastMessageRepeatedNTimes(ratelimit_t *ratelimit, msg_t *pMsg, msg_t **ppRepMsg) +{ + int bNeedUnlockMutex = 0; + rsRetVal localRet; + DEFiRet; + + if((pMsg->msgFlags & NEEDS_PARSING) != 0) { + if((localRet = parser.ParseMsg(pMsg)) != RS_RET_OK) { + DBGPRINTF("Message discarded, parsing error %d\n", localRet); + ABORT_FINALIZE(RS_RET_DISCARDMSG); + } + } + + if(ratelimit->bThreadSafe) { + pthread_mutex_lock(&ratelimit->mut); + bNeedUnlockMutex = 1; + } + + if( ratelimit->pMsg != NULL && + getMSGLen(pMsg) == getMSGLen(ratelimit->pMsg) && + !ustrcmp(getMSG(pMsg), getMSG(ratelimit->pMsg)) && + !strcmp(getHOSTNAME(pMsg), getHOSTNAME(ratelimit->pMsg)) && + !strcmp(getPROCID(pMsg, LOCK_MUTEX), getPROCID(ratelimit->pMsg, LOCK_MUTEX)) && + !strcmp(getAPPNAME(pMsg, LOCK_MUTEX), getAPPNAME(ratelimit->pMsg, LOCK_MUTEX))) { + ratelimit->nsupp++; + DBGPRINTF("msg repeated %d times\n", ratelimit->nsupp); + /* use current message, so we have the new timestamp + * (means we need to discard previous one) */ + msgDestruct(&ratelimit->pMsg); + ratelimit->pMsg = pMsg; + ABORT_FINALIZE(RS_RET_DISCARDMSG); + } else {/* new message, do "repeat processing" & save it */ + if(ratelimit->pMsg != NULL) { + if(ratelimit->nsupp > 0) { + *ppRepMsg = ratelimitGenRepMsg(ratelimit); + ratelimit->nsupp = 0; + } + msgDestruct(&ratelimit->pMsg); + } + ratelimit->pMsg = MsgAddRef(pMsg); + } + +finalize_it: + if(bNeedUnlockMutex) + pthread_mutex_unlock(&ratelimit->mut); + RETiRet; +} + + +/* helper: tell how many messages we lost due to linux-like ratelimiting */ +static inline void +tellLostCnt(ratelimit_t *ratelimit) +{ + uchar msgbuf[1024]; + if(ratelimit->missed) { + snprintf((char*)msgbuf, sizeof(msgbuf), + "%s: %u messages lost due to rate-limiting", + ratelimit->name, ratelimit->missed); + ratelimit->missed = 0; + logmsgInternal(RS_RET_RATE_LIMITED, LOG_SYSLOG|LOG_INFO, msgbuf, 0); + } +} + +/* Linux-like ratelimiting, modelled after the linux kernel + * returns 1 if message is within rate limit and shall be + * processed, 0 otherwise. + * This implementation is NOT THREAD-SAFE and must not + * be called concurrently. + */ +static inline int +withinRatelimit(ratelimit_t *ratelimit, time_t tt) +{ + int ret; + uchar msgbuf[1024]; + + if(ratelimit->interval == 0) { + ret = 1; + goto finalize_it; + } + + /* we primarily need "NoTimeCache" mode for imjournal, as it + * sets the message generation time to the journal timestamp. + * As such, we do not get a proper indication of the actual + * message rate. To prevent this, we need to query local + * system time ourselvs. + */ + if(ratelimit->bNoTimeCache) + tt = time(NULL); + + assert(ratelimit->burst != 0); + + if(ratelimit->begin == 0) + ratelimit->begin = tt; + + /* resume if we go out of time window */ + if(tt > ratelimit->begin + ratelimit->interval) { + ratelimit->begin = 0; + ratelimit->done = 0; + tellLostCnt(ratelimit); + } + + /* do actual limit check */ + if(ratelimit->burst > ratelimit->done) { + ratelimit->done++; + ret = 1; + } else { + ratelimit->missed++; + if(ratelimit->missed == 1) { + snprintf((char*)msgbuf, sizeof(msgbuf), + "%s: begin to drop messages due to rate-limiting", + ratelimit->name); + logmsgInternal(RS_RET_RATE_LIMITED, LOG_SYSLOG|LOG_INFO, msgbuf, 0); + } + ret = 0; + } + +finalize_it: + return ret; +} + + +/* ratelimit a message, that means: + * - handle "last message repeated n times" logic + * - handle actual (discarding) rate-limiting + * This function returns RS_RET_OK, if the caller shall process + * the message regularly and RS_RET_DISCARD if the caller must + * discard the message. The caller should also discard the message + * if another return status occurs. This places some burden on the + * caller logic, but provides best performance. Demanding this + * cooperative mode can enable a faulty caller to thrash up part + * of the system, but we accept that risk (a faulty caller can + * always do all sorts of evil, so...) + * If *ppRepMsg != NULL on return, the caller must enqueue that + * message before the original message. + */ +rsRetVal +ratelimitMsg(ratelimit_t *ratelimit, msg_t *pMsg, msg_t **ppRepMsg) +{ + DEFiRet; + + *ppRepMsg = NULL; + /* Only the messages having severity level at or below the + * treshold (the value is >=) are subject to ratelimiting. */ + if(ratelimit->interval && (pMsg->iSeverity >= ratelimit->severity)) { + if(withinRatelimit(ratelimit, pMsg->ttGenTime) == 0) { + msgDestruct(&pMsg); + ABORT_FINALIZE(RS_RET_DISCARDMSG); + } + } + if(ratelimit->bReduceRepeatMsgs) { + CHKiRet(doLastMessageRepeatedNTimes(ratelimit, pMsg, ppRepMsg)); + } +finalize_it: + RETiRet; +} + +/* returns 1, if the ratelimiter performs any checks and 0 otherwise */ +int +ratelimitChecked(ratelimit_t *ratelimit) +{ + return ratelimit->interval || ratelimit->bReduceRepeatMsgs; +} + + +/* add a message to a ratelimiter/multisubmit structure. + * ratelimiting is automatically handled according to the ratelimit + * settings. + * if pMultiSub == NULL, a single-message enqueue happens (under reconsideration) + */ +rsRetVal +ratelimitAddMsg(ratelimit_t *ratelimit, multi_submit_t *pMultiSub, msg_t *pMsg) +{ + rsRetVal localRet; + msg_t *repMsg; + DEFiRet; + + if(pMultiSub == NULL) { + localRet = ratelimitMsg(ratelimit, pMsg, &repMsg); + if(repMsg != NULL) + CHKiRet(submitMsg2(repMsg)); + if(localRet == RS_RET_OK) + CHKiRet(submitMsg2(pMsg)); + } else { + localRet = ratelimitMsg(ratelimit, pMsg, &repMsg); + if(repMsg != NULL) { + pMultiSub->ppMsgs[pMultiSub->nElem++] = repMsg; + if(pMultiSub->nElem == pMultiSub->maxElem) + CHKiRet(multiSubmitMsg2(pMultiSub)); + } + if(localRet == RS_RET_OK) { + pMultiSub->ppMsgs[pMultiSub->nElem++] = pMsg; + if(pMultiSub->nElem == pMultiSub->maxElem) + CHKiRet(multiSubmitMsg2(pMultiSub)); + } + } + +finalize_it: + RETiRet; +} + + +/* modname must be a static name (usually expected to be the module + * name and MUST be present. dynname may be NULL and can be used for + * dynamic information, e.g. PID or listener IP, ... + * Both values should be kept brief. + */ +rsRetVal +ratelimitNew(ratelimit_t **ppThis, char *modname, char *dynname) +{ + ratelimit_t *pThis; + char namebuf[256]; + DEFiRet; + + CHKmalloc(pThis = calloc(1, sizeof(ratelimit_t))); + if(modname == NULL) + modname ="*ERROR:MODULE NAME MISSING*"; + + if(dynname == NULL) { + pThis->name = strdup(modname); + } else { + snprintf(namebuf, sizeof(namebuf), "%s[%s]", + modname, dynname); + namebuf[sizeof(namebuf)-1] = '\0'; /* to be on safe side */ + pThis->name = strdup(namebuf); + } + /* pThis->severity == 0 - all messages are ratelimited */ + pThis->bReduceRepeatMsgs = loadConf->globals.bReduceRepeatMsgs; + *ppThis = pThis; +finalize_it: + RETiRet; +} + + +/* enable linux-like ratelimiting */ +void +ratelimitSetLinuxLike(ratelimit_t *ratelimit, unsigned short interval, unsigned short burst) +{ + ratelimit->interval = interval; + ratelimit->burst = burst; + ratelimit->done = 0; + ratelimit->missed = 0; + ratelimit->begin = 0; +} + + +/* enable thread-safe operations mode. This make sure that + * a single ratelimiter can be called from multiple threads. As + * this causes some overhead and is not always required, it needs + * to be explicitely enabled. This operation cannot be undone + * (think: why should one do that???) + */ +void +ratelimitSetThreadSafe(ratelimit_t *ratelimit) +{ + ratelimit->bThreadSafe = 1; + pthread_mutex_init(&ratelimit->mut, NULL); +} +void +ratelimitSetNoTimeCache(ratelimit_t *ratelimit) +{ + ratelimit->bNoTimeCache = 1; + pthread_mutex_init(&ratelimit->mut, NULL); +} + +/* Severity level determines which messages are subject to + * ratelimiting. Default (no value set) is all messages. + */ +void +ratelimitSetSeverity(ratelimit_t *ratelimit, intTiny severity) +{ + ratelimit->severity = severity; +} + +void +ratelimitDestruct(ratelimit_t *ratelimit) +{ + msg_t *pMsg; + if(ratelimit->pMsg != NULL) { + if(ratelimit->nsupp > 0) { + pMsg = ratelimitGenRepMsg(ratelimit); + if(pMsg != NULL) + submitMsg2(pMsg); + } + msgDestruct(&ratelimit->pMsg); + } + tellLostCnt(ratelimit); + if(ratelimit->bThreadSafe) + pthread_mutex_destroy(&ratelimit->mut); + free(ratelimit->name); + free(ratelimit); +} + +void +ratelimitModExit(void) +{ + objRelease(datetime, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); +} + +rsRetVal +ratelimitModInit(void) +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); +finalize_it: + RETiRet; +} diff --git a/runtime/ratelimit.h b/runtime/ratelimit.h new file mode 100644 index 00000000..563777fd --- /dev/null +++ b/runtime/ratelimit.h @@ -0,0 +1,55 @@ +/* header for ratelimit.c + * + * Copyright 2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_RATELIMIT_H +#define INCLUDED_RATELIMIT_H + +struct ratelimit_s { + char *name; /**< rate limiter name, e.g. for user messages */ + /* support for Linux kernel-type ratelimiting */ + unsigned short interval; + unsigned short burst; + intTiny severity; /**< ratelimit only equal or lower severity levels (eq or higher values) */ + unsigned done; + unsigned missed; + time_t begin; + /* support for "last message repeated n times */ + int bReduceRepeatMsgs; /**< shall we do "last message repeated n times" processing? */ + unsigned nsupp; /**< nbr of msgs suppressed */ + msg_t *pMsg; + sbool bThreadSafe; /**< do we need to operate in Thread-Safe mode? */ + sbool bNoTimeCache; /**< if we shall not used cached reception time */ + pthread_mutex_t mut; /**< mutex if thread-safe operation desired */ +}; + +/* prototypes */ +rsRetVal ratelimitNew(ratelimit_t **ppThis, char *modname, char *dynname); +void ratelimitSetThreadSafe(ratelimit_t *ratelimit); +void ratelimitSetLinuxLike(ratelimit_t *ratelimit, unsigned short interval, unsigned short burst); +void ratelimitSetNoTimeCache(ratelimit_t *ratelimit); +void ratelimitSetSeverity(ratelimit_t *ratelimit, intTiny severity); +rsRetVal ratelimitMsg(ratelimit_t *ratelimit, msg_t *pMsg, msg_t **ppRep); +rsRetVal ratelimitAddMsg(ratelimit_t *ratelimit, multi_submit_t *pMultiSub, msg_t *pMsg); +void ratelimitDestruct(ratelimit_t *pThis); +int ratelimitChecked(ratelimit_t *ratelimit); +rsRetVal ratelimitModInit(void); +void ratelimitModExit(void); + +#endif /* #ifndef INCLUDED_RATELIMIT_H */ diff --git a/runtime/regexp.c b/runtime/regexp.c new file mode 100644 index 00000000..912db9c9 --- /dev/null +++ b/runtime/regexp.c @@ -0,0 +1,101 @@ +/* The regexp object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <regex.h> +#include <string.h> +#include <assert.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "regexp.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(regexp) +CODESTARTobjQueryInterface(regexp) + if(pIf->ifVersion != regexpCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->regcomp = regcomp; + pIf->regexec = regexec; + pIf->regerror = regerror; + pIf->regfree = regfree; +finalize_it: +ENDobjQueryInterface(regexp) + + +/* Initialize the regexp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(regexp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ +ENDObjClassInit(regexp) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + CHKiRet(regexpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + /* Initialize all classes that are in our module - this includes ourselfs */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/regexp.h b/runtime/regexp.h new file mode 100644 index 00000000..16b0c4e6 --- /dev/null +++ b/runtime/regexp.h @@ -0,0 +1,44 @@ +/* The regexp object. It encapsulates the C regexp functionality. The primary + * purpose of this wrapper class is to enable rsyslogd core to be build without + * regexp libraries. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_REGEXP_H +#define INCLUDED_REGEXP_H + +#include <regex.h> + +/* interfaces */ +BEGINinterface(regexp) /* name must also be changed in ENDinterface macro! */ + int (*regcomp)(regex_t *preg, const char *regex, int cflags); + int (*regexec)(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); + size_t (*regerror)(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size); + void (*regfree)(regex_t *preg); +ENDinterface(regexp) +#define regexpCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(regexp); + +/* the name of our library binary */ +#define LM_REGEXP_FILENAME "lmregexp" + +#endif /* #ifndef INCLUDED_REGEXP_H */ diff --git a/runtime/rsconf.c b/runtime/rsconf.c new file mode 100644 index 00000000..d8b81f1b --- /dev/null +++ b/runtime/rsconf.c @@ -0,0 +1,1386 @@ +/* rsconf.c - the rsyslog configuration system. + * + * Module begun 2011-04-19 by Rainer Gerhards + * + * Copyright 2011-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <grp.h> +#include <stdarg.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "rsyslog.h" +#include "obj.h" +#include "srUtils.h" +#include "ruleset.h" +#include "modules.h" +#include "conf.h" +#include "queue.h" +#include "rsconf.h" +#include "cfsysline.h" +#include "errmsg.h" +#include "action.h" +#include "glbl.h" +#include "unicode-helper.h" +#include "omshell.h" +#include "omusrmsg.h" +#include "omfwd.h" +#include "omfile.h" +#include "ompipe.h" +#include "omdiscard.h" +#include "pmrfc5424.h" +#include "pmrfc3164.h" +#include "smfile.h" +#include "smtradfile.h" +#include "smfwd.h" +#include "smtradfwd.h" +#include "parser.h" +#include "outchannel.h" +#include "threads.h" +#include "datetime.h" +#include "parserif.h" +#include "modules.h" +#include "dirty.h" +#include "template.h" + +extern char* yytext; +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(ruleset) +DEFobjCurrIf(module) +DEFobjCurrIf(conf) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + +/* exported static data */ +rsconf_t *runConf = NULL;/* the currently running config */ +rsconf_t *loadConf = NULL;/* the config currently being loaded (no concurrent config load supported!) */ + +/* hardcoded standard templates (used for defaults) */ +static uchar template_DebugFormat[] = "\"Debug line with all properties:\nFROMHOST: '%FROMHOST%', fromhost-ip: '%fromhost-ip%', HOSTNAME: '%HOSTNAME%', PRI: %PRI%,\nsyslogtag '%syslogtag%', programname: '%programname%', APP-NAME: '%APP-NAME%', PROCID: '%PROCID%', MSGID: '%MSGID%',\nTIMESTAMP: '%TIMESTAMP%', STRUCTURED-DATA: '%STRUCTURED-DATA%',\nmsg: '%msg%'\nescaped msg: '%msg:::drop-cc%'\ninputname: %inputname% rawmsg: '%rawmsg%'\n\n\""; +static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\""; +static uchar template_TraditionalFileFormat[] = "=RSYSLOG_TraditionalFileFormat"; +static uchar template_FileFormat[] = "=RSYSLOG_FileFormat"; +static uchar template_ForwardFormat[] = "=RSYSLOG_ForwardFormat"; +static uchar template_TraditionalForwardFormat[] = "=RSYSLOG_TraditionalForwardFormat"; +static uchar template_WallFmt[] = "\"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r\""; +static uchar template_StdUsrMsgFmt[] = "\" %syslogtag%%msg%\n\r\""; +static uchar template_StdDBFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')\",SQL"; +static uchar template_StdPgSQLFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-pgsql%', '%timegenerated:::date-pgsql%', %iut%, '%syslogtag%')\",STDSQL"; +static uchar template_spoofadr[] = "\"%fromhost-ip%\""; +static uchar template_SysklogdFileFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%\n\""; +static uchar template_StdJSONFmt[] = "\"{\\\"message\\\":\\\"%msg:::json%\\\",\\\"fromhost\\\":\\\"%HOSTNAME:::json%\\\",\\\"facility\\\":\\\"%syslogfacility-text%\\\",\\\"priority\\\":\\\"%syslogpriority-text%\\\",\\\"timereported\\\":\\\"%timereported:::date-rfc3339%\\\",\\\"timegenerated\\\":\\\"%timegenerated:::date-rfc3339%\\\"}\""; +/* end templates */ + +/* tables for interfacing with the v6 config system (as far as we need to) */ +static struct cnfparamdescr inppdescr[] = { + { "type", eCmdHdlrString, CNFPARAM_REQUIRED } +}; +static struct cnfparamblk inppblk = + { CNFPARAMBLK_VERSION, + sizeof(inppdescr)/sizeof(struct cnfparamdescr), + inppdescr + }; + +/* forward-definitions */ +void cnfDoCfsysline(char *ln); + +/* Standard-Constructor + */ +BEGINobjConstruct(rsconf) /* be sure to specify the object type also in END macro! */ + pThis->globals.bDebugPrintTemplateList = 1; + pThis->globals.bDebugPrintModuleList = 0; + pThis->globals.bDebugPrintCfSysLineHandlerList = 0; + pThis->globals.bLogStatusMsgs = DFLT_bLogStatusMsgs; + pThis->globals.bErrMsgToStderr = 1; + pThis->globals.umask = -1; + pThis->templates.root = NULL; + pThis->templates.last = NULL; + pThis->templates.lastStatic = NULL; + pThis->actions.nbrActions = 0; + CHKiRet(llInit(&pThis->rulesets.llRulesets, rulesetDestructForLinkedList, + rulesetKeyDestruct, strcasecmp)); + /* queue params */ + pThis->globals.mainQ.iMainMsgQueueSize = 10000; + pThis->globals.mainQ.iMainMsgQHighWtrMark = 8000; + pThis->globals.mainQ.iMainMsgQLowWtrMark = 2000; + pThis->globals.mainQ.iMainMsgQDiscardMark = 9800; + pThis->globals.mainQ.iMainMsgQDiscardSeverity = 8; + pThis->globals.mainQ.iMainMsgQueueNumWorkers = 1; + pThis->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + pThis->globals.mainQ.pszMainMsgQFName = NULL; + pThis->globals.mainQ.iMainMsgQueMaxFileSize = 1024*1024; + pThis->globals.mainQ.iMainMsgQPersistUpdCnt = 0; + pThis->globals.mainQ.bMainMsgQSyncQeueFiles = 0; + pThis->globals.mainQ.iMainMsgQtoQShutdown = 1500; + pThis->globals.mainQ.iMainMsgQtoActShutdown = 1000; + pThis->globals.mainQ.iMainMsgQtoEnq = 2000; + pThis->globals.mainQ.iMainMsgQtoWrkShutdown = 60000; + pThis->globals.mainQ.iMainMsgQWrkMinMsgs = 100; + pThis->globals.mainQ.iMainMsgQDeqSlowdown = 0; + pThis->globals.mainQ.iMainMsgQueMaxDiskSpace = 0; + pThis->globals.mainQ.iMainMsgQueDeqBatchSize = 32; + pThis->globals.mainQ.bMainMsgQSaveOnShutdown = 1; + pThis->globals.mainQ.iMainMsgQueueDeqtWinFromHr = 0; + pThis->globals.mainQ.iMainMsgQueueDeqtWinToHr = 25; + /* end queue params */ +finalize_it: +ENDobjConstruct(rsconf) + + +/* ConstructionFinalizer + */ +rsRetVal rsconfConstructFinalize(rsconf_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, rsconf); + RETiRet; +} + + +/* call freeCnf() module entry points AND free the module entries themselfes. + */ +static inline void +freeCnf(rsconf_t *pThis) +{ + cfgmodules_etry_t *etry, *del; + etry = pThis->modules.root; + while(etry != NULL) { + if(etry->pMod->beginCnfLoad != NULL) { + dbgprintf("calling freeCnf(%p) for module '%s'\n", + etry->modCnf, (char*) module.GetName(etry->pMod)); + etry->pMod->freeCnf(etry->modCnf); + } + del = etry; + etry = etry->next; + free(del); + } +} + + +/* destructor for the rsconf object */ +BEGINobjDestruct(rsconf) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(rsconf) + freeCnf(pThis); + tplDeleteAll(pThis); + free(pThis->globals.mainQ.pszMainMsgQFName); + free(pThis->globals.pszConfDAGFile); + llDestroy(&(pThis->rulesets.llRulesets)); +ENDobjDestruct(rsconf) + + +/* DebugPrint support for the rsconf object */ +BEGINobjDebugPrint(rsconf) /* be sure to specify the object type also in END and CODESTART macros! */ + cfgmodules_etry_t *modNode; + + dbgprintf("configuration object %p\n", pThis); + dbgprintf("Global Settings:\n"); + dbgprintf(" bDebugPrintTemplateList.............: %d\n", + pThis->globals.bDebugPrintTemplateList); + dbgprintf(" bDebugPrintModuleList : %d\n", + pThis->globals.bDebugPrintModuleList); + dbgprintf(" bDebugPrintCfSysLineHandlerList.....: %d\n", + pThis->globals.bDebugPrintCfSysLineHandlerList); + dbgprintf(" bLogStatusMsgs : %d\n", + pThis->globals.bLogStatusMsgs); + dbgprintf(" bErrMsgToStderr.....................: %d\n", + pThis->globals.bErrMsgToStderr); + dbgprintf(" drop Msgs with malicious PTR Record : %d\n", + glbl.GetDropMalPTRMsgs()); + ruleset.DebugPrintAll(pThis); + dbgprintf("\n"); + if(pThis->globals.bDebugPrintTemplateList) + tplPrintList(pThis); + if(pThis->globals.bDebugPrintModuleList) + module.PrintList(); + if(pThis->globals.bDebugPrintCfSysLineHandlerList) + dbgPrintCfSysLineHandlers(); + // TODO: The following code needs to be "streamlined", so far just moved over... + dbgprintf("Main queue size %d messages.\n", pThis->globals.mainQ.iMainMsgQueueSize); + dbgprintf("Main queue worker threads: %d, wThread shutdown: %d, Perists every %d updates.\n", + pThis->globals.mainQ.iMainMsgQueueNumWorkers, + pThis->globals.mainQ.iMainMsgQtoWrkShutdown, pThis->globals.mainQ.iMainMsgQPersistUpdCnt); + dbgprintf("Main queue timeouts: shutdown: %d, action completion shutdown: %d, enq: %d\n", + pThis->globals.mainQ.iMainMsgQtoQShutdown, + pThis->globals.mainQ.iMainMsgQtoActShutdown, pThis->globals.mainQ.iMainMsgQtoEnq); + dbgprintf("Main queue watermarks: high: %d, low: %d, discard: %d, discard-severity: %d\n", + pThis->globals.mainQ.iMainMsgQHighWtrMark, pThis->globals.mainQ.iMainMsgQLowWtrMark, + pThis->globals.mainQ.iMainMsgQDiscardMark, pThis->globals.mainQ.iMainMsgQDiscardSeverity); + dbgprintf("Main queue save on shutdown %d, max disk space allowed %lld\n", + pThis->globals.mainQ.bMainMsgQSaveOnShutdown, pThis->globals.mainQ.iMainMsgQueMaxDiskSpace); + /* TODO: add + iActionRetryCount = 0; + iActionRetryInterval = 30000; + static int iMainMsgQtoWrkMinMsgs = 100; + static int iMainMsgQbSaveOnShutdown = 1; + iMainMsgQueMaxDiskSpace = 0; + setQPROP(qqueueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", 100); + setQPROP(qqueueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", 1); + */ + dbgprintf("Work Directory: '%s'.\n", glbl.GetWorkDir()); + ochPrintList(); + dbgprintf("Modules used in this configuration:\n"); + for(modNode = pThis->modules.root ; modNode != NULL ; modNode = modNode->next) { + dbgprintf(" %s\n", module.GetName(modNode->pMod)); + } +CODESTARTobjDebugPrint(rsconf) +ENDobjDebugPrint(rsconf) + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * TODO: this was taken from msg.c and we should consolidate it with the code + * there. This is especially important when we increase the number of system + * variables (what we definitely want to do). + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_MINUTE } eNOWType; +static rsRetVal +getNOW(eNOWType eNow, es_str_t **estr) +{ + DEFiRet; + uchar szBuf[16]; + struct syslogTime t; + es_size_t len; + + datetime.getCurrTime(&t, NULL); + switch(eNow) { + case NOW_NOW: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), + "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); + break; + case NOW_YEAR: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%4.4d", t.year); + break; + case NOW_MONTH: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.month); + break; + case NOW_DAY: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.day); + break; + case NOW_HOUR: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.hour); + break; + case NOW_MINUTE: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.minute); + break; + default: + len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "*invld eNow*"); + break; + } + + /* now create a string object out of it and hand that over to the var */ + *estr = es_newStrFromCStr((char*)szBuf, len); + + RETiRet; +} + + + +static inline es_str_t * +getSysVar(char *name) +{ + es_str_t *estr = NULL; + rsRetVal iRet = RS_RET_OK; + + if(!strcmp(name, "now")) { + CHKiRet(getNOW(NOW_NOW, &estr)); + } else if(!strcmp(name, "year")) { + CHKiRet(getNOW(NOW_YEAR, &estr)); + } else if(!strcmp(name, "month")) { + CHKiRet(getNOW(NOW_MONTH, &estr)); + } else if(!strcmp(name, "day")) { + CHKiRet(getNOW(NOW_DAY, &estr)); + } else if(!strcmp(name, "hour")) { + CHKiRet(getNOW(NOW_HOUR, &estr)); + } else if(!strcmp(name, "minute")) { + CHKiRet(getNOW(NOW_MINUTE, &estr)); + } else if(!strcmp(name, "myhostname")) { + char *hn = (char*)glbl.GetLocalHostName(); + estr = es_newStrFromCStr(hn, strlen(hn)); + } else { + ABORT_FINALIZE(RS_RET_SYSVAR_NOT_FOUND); + } +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("getSysVar error iRet %d\n", iRet); + if(estr == NULL) + estr = es_newStrFromCStr("*ERROR*", sizeof("*ERROR*") - 1); + } + return estr; +} + + +/* Process input() objects */ +rsRetVal +inputProcessCnf(struct cnfobj *o) +{ + struct cnfparamvals *pvals; + modInfo_t *pMod; + uchar *cnfModName = NULL; + int typeIdx; + DEFiRet; + + pvals = nvlstGetParams(o->nvlst, &inppblk, NULL); + if(pvals == NULL) { + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + DBGPRINTF("input param blk after inputProcessCnf:\n"); + cnfparamsPrint(&inppblk, pvals); + typeIdx = cnfparamGetIdx(&inppblk, "type"); + cnfModName = (uchar*)es_str2cstr(pvals[typeIdx].val.d.estr, NULL); + if((pMod = module.FindWithCnfName(loadConf, cnfModName, eMOD_IN)) == NULL) { + errmsg.LogError(0, RS_RET_MOD_UNKNOWN, "input module name '%s' is unknown", cnfModName); + ABORT_FINALIZE(RS_RET_MOD_UNKNOWN); + } + if(pMod->mod.im.newInpInst == NULL) { + errmsg.LogError(0, RS_RET_MOD_NO_INPUT_STMT, + "input module '%s' does not support input() statement", cnfModName); + ABORT_FINALIZE(RS_RET_MOD_NO_INPUT_STMT); + } + CHKiRet(pMod->mod.im.newInpInst(o->nvlst)); +finalize_it: + free(cnfModName); + cnfparamvalsDestruct(pvals, &inppblk); + RETiRet; +} + +/*------------------------------ interface to flex/bison parser ------------------------------*/ +extern int yylineno; + +void +parser_errmsg(char *fmt, ...) +{ + va_list ap; + char errBuf[1024]; + + va_start(ap, fmt); + if(vsnprintf(errBuf, sizeof(errBuf), fmt, ap) == sizeof(errBuf)) + errBuf[sizeof(errBuf)-1] = '\0'; + errmsg.LogError(0, RS_RET_CONF_PARSE_ERROR, + "error during parsing file %s, on or before line %d: %s", + cnfcurrfn, yylineno, errBuf); + va_end(ap); +} + +int +yyerror(char *s) +{ + parser_errmsg("%s on token '%s'", s, yytext); + return 0; +} +void cnfDoObj(struct cnfobj *o) +{ + int bChkUnuse = 1; + + dbgprintf("cnf:global:obj: "); + cnfobjPrint(o); + switch(o->objType) { + case CNFOBJ_GLOBAL: + glblProcessCnf(o); + break; + case CNFOBJ_MODULE: + modulesProcessCnf(o); + break; + case CNFOBJ_INPUT: + inputProcessCnf(o); + break; + case CNFOBJ_TPL: + if(tplProcessCnf(o) != RS_RET_OK) + parser_errmsg("error processing template object"); + break; + case CNFOBJ_RULESET: + rulesetProcessCnf(o); + break; + case CNFOBJ_PROPERTY: + case CNFOBJ_CONSTANT: + /* these types are processed at a later stage */ + bChkUnuse = 0; + break; + default: + dbgprintf("cnfDoObj program error: unexpected object type %u\n", + o->objType); + break; + } + if(bChkUnuse) + nvlstChkUnused(o->nvlst); + cnfobjDestruct(o); +} + +void cnfDoScript(struct cnfstmt *script) +{ + dbgprintf("cnf:global:script\n"); + ruleset.AddScript(ruleset.GetCurrent(loadConf), script); +} + +void cnfDoCfsysline(char *ln) +{ + DBGPRINTF("cnf:global:cfsysline: %s\n", ln); + /* the legacy system needs the "$" stripped */ + conf.cfsysline((uchar*) ln+1); + free(ln); +} + +void cnfDoBSDTag(char *ln) +{ + DBGPRINTF("cnf:global:BSD tag: %s\n", ln); + errmsg.LogError(0, RS_RET_BSD_BLOCKS_UNSUPPORTED, + "BSD-style blocks are no longer supported in rsyslog, " + "see http://www.rsyslog.com/g/BSD for details and a " + "solution (Block '%s')", ln); + free(ln); +} + +void cnfDoBSDHost(char *ln) +{ + DBGPRINTF("cnf:global:BSD host: %s\n", ln); + errmsg.LogError(0, RS_RET_BSD_BLOCKS_UNSUPPORTED, + "BSD-style blocks are no longer supported in rsyslog, " + "see http://www.rsyslog.com/g/BSD for details and a " + "solution (Block '%s')", ln); + free(ln); +} + +es_str_t* +cnfGetVar(char *name, void *usrptr) +{ + es_str_t *estr; + if(name[0] == '$') { + if(name[1] == '$') + estr = getSysVar(name+2); + else if(name[1] == '!') + estr = msgGetCEEVarNew((msg_t*) usrptr, name+2); + else + estr = msgGetMsgVarNew((msg_t*) usrptr, (uchar*)name+1); + } else { /* if this happens, we have a program logic error */ + estr = es_newStrFromCStr("err: var must start with $", + strlen("err: var must start with $")); + } + if(Debug) { + char *s; + s = es_str2cstr(estr, NULL); + dbgprintf("rainerscript: var '%s': '%s'\n", name, s); + free(s); + } + return estr; +} +/*------------------------------ end interface to flex/bison parser ------------------------------*/ + + + +/* drop to specified group + * if something goes wrong, the function never returns + * Note that such an abort can cause damage to on-disk structures, so we should + * re-design the "interface" in the long term. -- rgerhards, 2008-11-26 + */ +static void doDropPrivGid(int iGid) +{ + int res; + uchar szBuf[1024]; + + res = setgroups(0, NULL); /* remove all supplementary group IDs */ + if(res) { + perror("could not remove supplemental group IDs"); + exit(1); + } + DBGPRINTF("setgroups(0, NULL): %d\n", res); + res = setgid(iGid); + if(res) { + /* if we can not set the userid, this is fatal, so let's unconditionally abort */ + perror("could not set requested group id"); + exit(1); + } + DBGPRINTF("setgid(%d): %d\n", iGid, res); + snprintf((char*)szBuf, sizeof(szBuf)/sizeof(uchar), "rsyslogd's groupid changed to %d", iGid); + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, szBuf, 0); +} + + +/* drop to specified user + * if something goes wrong, the function never returns + * Note that such an abort can cause damage to on-disk structures, so we should + * re-design the "interface" in the long term. -- rgerhards, 2008-11-19 + */ +static void doDropPrivUid(int iUid) +{ + int res; + uchar szBuf[1024]; + + res = setuid(iUid); + if(res) { + /* if we can not set the userid, this is fatal, so let's unconditionally abort */ + perror("could not set requested userid"); + exit(1); + } + DBGPRINTF("setuid(%d): %d\n", iUid, res); + snprintf((char*)szBuf, sizeof(szBuf)/sizeof(uchar), "rsyslogd's userid changed to %d", iUid); + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, szBuf, 0); +} + + + +/* drop privileges. This will drop to the configured privileges, if + * set by the user. After this method has been executed, the previous + * privileges can no be re-gained. + */ +static inline rsRetVal +dropPrivileges(rsconf_t *cnf) +{ + DEFiRet; + + /* If instructed to do so, we now drop privileges. Note that this is not 100% secure, + * because outputs are already running at this time. However, we can implement + * dropping of privileges rather quickly and it will work in many cases. While it is not + * the ultimate solution, the current one is still much better than not being able to + * drop privileges at all. Doing it correctly, requires a change in architecture, which + * we should do over time. TODO -- rgerhards, 2008-11-19 + */ + if(cnf->globals.gidDropPriv != 0) { + doDropPrivGid(ourConf->globals.gidDropPriv); + DBGPRINTF("group privileges have been dropped to gid %u\n", (unsigned) + ourConf->globals.gidDropPriv); + } + + if(cnf->globals.uidDropPriv != 0) { + doDropPrivUid(ourConf->globals.uidDropPriv); + DBGPRINTF("user privileges have been dropped to uid %u\n", (unsigned) + ourConf->globals.uidDropPriv); + } + + RETiRet; +} + + +/* tell the rsysog core (including ourselfs) that the config load is done and + * we need to prepare to move over to activate mode. + */ +static inline void +tellCoreConfigLoadDone(void) +{ + glblDoneLoadCnf(); +} + + +/* Tell input modules that the config parsing stage is over. */ +static rsRetVal +tellModulesConfigLoadDone(void) +{ + cfgmodules_etry_t *node; + + BEGINfunc + DBGPRINTF("telling modules that config load for %p is done\n", loadConf); + node = module.GetNxtCnfType(loadConf, NULL, eMOD_ANY); + while(node != NULL) { + if(node->pMod->beginCnfLoad != NULL) + node->pMod->endCnfLoad(node->modCnf); + node = module.GetNxtCnfType(runConf, node, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* Tell input modules to verify config object */ +static rsRetVal +tellModulesCheckConfig(void) +{ + cfgmodules_etry_t *node; + rsRetVal localRet; + + BEGINfunc + DBGPRINTF("telling modules to check config %p\n", loadConf); + node = module.GetNxtCnfType(loadConf, NULL, eMOD_ANY); + while(node != NULL) { + if(node->pMod->beginCnfLoad != NULL) { + localRet = node->pMod->checkCnf(node->modCnf); + DBGPRINTF("module %s tells us config can %sbe activated\n", + node->pMod->pszName, (localRet == RS_RET_OK) ? "" : "NOT "); + if(localRet == RS_RET_OK) { + node->canActivate = 1; + } else { + node->canActivate = 0; + } + } + node = module.GetNxtCnfType(runConf, node, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* Tell modules to activate current running config (pre privilege drop) */ +static rsRetVal +tellModulesActivateConfigPrePrivDrop(void) +{ + cfgmodules_etry_t *node; + rsRetVal localRet; + + BEGINfunc + DBGPRINTF("telling modules to activate config (before dropping privs) %p\n", runConf); + node = module.GetNxtCnfType(runConf, NULL, eMOD_ANY); + while(node != NULL) { + if( node->pMod->beginCnfLoad != NULL + && node->pMod->activateCnfPrePrivDrop != NULL + && node->canActivate) { + DBGPRINTF("pre priv drop activating config %p for module %s\n", + runConf, node->pMod->pszName); + localRet = node->pMod->activateCnfPrePrivDrop(node->modCnf); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "activation of module %s failed", + node->pMod->pszName); + node->canActivate = 0; /* in a sense, could not activate... */ + } + } + node = module.GetNxtCnfType(runConf, node, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* Tell modules to activate current running config */ +static rsRetVal +tellModulesActivateConfig(void) +{ + cfgmodules_etry_t *node; + rsRetVal localRet; + + BEGINfunc + DBGPRINTF("telling modules to activate config %p\n", runConf); + node = module.GetNxtCnfType(runConf, NULL, eMOD_ANY); + while(node != NULL) { + if(node->pMod->beginCnfLoad != NULL && node->canActivate) { + DBGPRINTF("activating config %p for module %s\n", + runConf, node->pMod->pszName); + localRet = node->pMod->activateCnf(node->modCnf); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "activation of module %s failed", + node->pMod->pszName); + node->canActivate = 0; /* in a sense, could not activate... */ + } + } + node = module.GetNxtCnfType(runConf, node, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* Actually run the input modules. This happens after privileges are dropped, + * if that is requested. + */ +static rsRetVal +runInputModules(void) +{ + cfgmodules_etry_t *node; + int bNeedsCancel; + + BEGINfunc + node = module.GetNxtCnfType(runConf, NULL, eMOD_IN); + while(node != NULL) { + if(node->canRun) { + bNeedsCancel = (node->pMod->isCompatibleWithFeature(sFEATURENonCancelInputTermination) == RS_RET_OK) ? + 0 : 1; + DBGPRINTF("running module %s with config %p, term mode: %s\n", node->pMod->pszName, node, + bNeedsCancel ? "cancel" : "cooperative/SIGTTIN"); + thrdCreate(node->pMod->mod.im.runInput, node->pMod->mod.im.afterRun, bNeedsCancel, + (node->pMod->cnfName == NULL) ? node->pMod->pszName : node->pMod->cnfName); + } + node = module.GetNxtCnfType(runConf, node, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* Make the modules check if they are ready to start. + */ +static rsRetVal +startInputModules(void) +{ + DEFiRet; + cfgmodules_etry_t *node; + + node = module.GetNxtCnfType(runConf, NULL, eMOD_IN); + while(node != NULL) { + if(node->canActivate) { + iRet = node->pMod->mod.im.willRun(); + node->canRun = (iRet == RS_RET_OK); + if(!node->canRun) { + DBGPRINTF("module %s will not run, iRet %d\n", node->pMod->pszName, iRet); + } + } else { + node->canRun = 0; + } + node = module.GetNxtCnfType(runConf, node, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* activate the main queue */ +static inline rsRetVal +activateMainQueue() +{ + DEFiRet; + /* create message queue */ + CHKiRet_Hdlr(createMainQueue(&pMsgQueue, UCHAR_CONSTANT("main Q"), NULL)) { + /* no queue is fatal, we need to give up in that case... */ + fprintf(stderr, "fatal error %d: could not create message queue - rsyslogd can not run!\n", iRet); + FINALIZE; + } + + bHaveMainQueue = (ourConf->globals.mainQ.MainMsgQueType == QUEUETYPE_DIRECT) ? 0 : 1; + DBGPRINTF("Main processing queue is initialized and running\n"); +finalize_it: + RETiRet; +} + + +/* set the processes umask (upon configuration request) */ +static inline rsRetVal +setUmask(int iUmask) +{ + if(iUmask != -1) { + umask(iUmask); + DBGPRINTF("umask set to 0%3.3o.\n", iUmask); + } + + return RS_RET_OK; +} + + +/* Activate an already-loaded configuration. The configuration will become + * the new running conf (if successful). Note that in theory this method may + * be called when there already is a running conf. In practice, the current + * version of rsyslog does not support this. Future versions probably will. + * Begun 2011-04-20, rgerhards + */ +rsRetVal +activate(rsconf_t *cnf) +{ + DEFiRet; + + /* at this point, we "switch" over to the running conf */ + runConf = cnf; +# if 0 /* currently the DAG is not supported -- code missing! */ + /* TODO: re-enable this functionality some time later! */ + /* check if we need to generate a config DAG and, if so, do that */ + if(ourConf->globals.pszConfDAGFile != NULL) + generateConfigDAG(ourConf->globals.pszConfDAGFile); +# endif + setUmask(cnf->globals.umask); + + /* the output part and the queue is now ready to run. So it is a good time + * to initialize the inputs. Please note that the net code above should be + * shuffled to down here once we have everything in input modules. + * rgerhards, 2007-12-14 + * NOTE: as of 2009-06-29, the input modules are initialized, but not yet run. + * Keep in mind. though, that the outputs already run if the queue was + * persisted to disk. -- rgerhards + */ + tellModulesActivateConfigPrePrivDrop(); + + CHKiRet(dropPrivileges(cnf)); + + tellModulesActivateConfig(); + startInputModules(); + CHKiRet(activateActions()); + CHKiRet(activateMainQueue()); + /* finally let the inputs run... */ + runInputModules(); + + dbgprintf("configuration %p activated\n", cnf); + +finalize_it: + RETiRet; +} + + +/* -------------------- some legacy config handlers -------------------- + * TODO: move to conf.c? + */ + +/* legacy config system: set the action resume interval */ +static rsRetVal setActionResumeInterval(void __attribute__((unused)) *pVal, int iNewVal) +{ + return actionSetGlobalResumeInterval(iNewVal); +} + + +/* Switch the default ruleset (that, what servcies bind to if nothing specific + * is specified). + * rgerhards, 2009-06-12 + */ +static rsRetVal +setDefaultRuleset(void __attribute__((unused)) *pVal, uchar *pszName) +{ + DEFiRet; + + CHKiRet(ruleset.SetDefaultRuleset(ourConf, pszName)); + +finalize_it: + free(pszName); /* no longer needed */ + RETiRet; +} + + +/* Switch to either an already existing rule set or start a new one. The + * named rule set becomes the new "current" rule set (what means that new + * actions are added to it). + * rgerhards, 2009-06-12 + */ +static rsRetVal +setCurrRuleset(void __attribute__((unused)) *pVal, uchar *pszName) +{ + ruleset_t *pRuleset; + rsRetVal localRet; + DEFiRet; + + localRet = ruleset.SetCurrRuleset(ourConf, pszName); + + if(localRet == RS_RET_NOT_FOUND) { + DBGPRINTF("begin new current rule set '%s'\n", pszName); + CHKiRet(ruleset.Construct(&pRuleset)); + CHKiRet(ruleset.SetName(pRuleset, pszName)); + CHKiRet(ruleset.ConstructFinalize(ourConf, pRuleset)); + rulesetSetCurrRulesetPtr(pRuleset); + } else { + ABORT_FINALIZE(localRet); + } + +finalize_it: + free(pszName); /* no longer needed */ + RETiRet; +} + + +/* set the main message queue mode + * rgerhards, 2008-01-03 + */ +static rsRetVal setMainMsgQueType(void __attribute__((unused)) *pVal, uchar *pszType) +{ + DEFiRet; + + if (!strcasecmp((char *) pszType, "fixedarray")) { + loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + DBGPRINTF("main message queue type set to FIXED_ARRAY\n"); + } else if (!strcasecmp((char *) pszType, "linkedlist")) { + loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_LINKEDLIST; + DBGPRINTF("main message queue type set to LINKEDLIST\n"); + } else if (!strcasecmp((char *) pszType, "disk")) { + loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_DISK; + DBGPRINTF("main message queue type set to DISK\n"); + } else if (!strcasecmp((char *) pszType, "direct")) { + loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_DIRECT; + DBGPRINTF("main message queue type set to DIRECT (no queueing at all)\n"); + } else { + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "unknown mainmessagequeuetype parameter: %s", (char *) pszType); + iRet = RS_RET_INVALID_PARAMS; + } + free(pszType); /* no longer needed */ + + RETiRet; +} + + +/* -------------------- end legacy config handlers -------------------- */ + + +/* set the processes max number ob files (upon configuration request) + * 2009-04-14 rgerhards + */ +static rsRetVal setMaxFiles(void __attribute__((unused)) *pVal, int iFiles) +{ +// TODO this must use a local var, then carry out action during activate! + struct rlimit maxFiles; + char errStr[1024]; + DEFiRet; + + maxFiles.rlim_cur = iFiles; + maxFiles.rlim_max = iFiles; + + if(setrlimit(RLIMIT_NOFILE, &maxFiles) < 0) { + /* NOTE: under valgrind, we seem to be unable to extend the size! */ + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_ERR_RLIM_NOFILE, "could not set process file limit to %d: %s [kernel max %ld]", + iFiles, errStr, (long) maxFiles.rlim_max); + ABORT_FINALIZE(RS_RET_ERR_RLIM_NOFILE); + } +#ifdef USE_UNLIMITED_SELECT + glbl.SetFdSetSize(howmany(iFiles, __NFDBITS) * sizeof (fd_mask)); +#endif + DBGPRINTF("Max number of files set to %d [kernel max %ld].\n", iFiles, (long) maxFiles.rlim_max); + +finalize_it: + RETiRet; +} + + +/* legacy config system: reset config variables to default values. */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + loadConf->globals.bLogStatusMsgs = DFLT_bLogStatusMsgs; + loadConf->globals.bDebugPrintTemplateList = 1; + loadConf->globals.bDebugPrintCfSysLineHandlerList = 1; + loadConf->globals.bDebugPrintModuleList = 1; + loadConf->globals.bAbortOnUncleanConfig = 0; + loadConf->globals.bReduceRepeatMsgs = 0; + free(loadConf->globals.mainQ.pszMainMsgQFName); + loadConf->globals.mainQ.pszMainMsgQFName = NULL; + loadConf->globals.mainQ.iMainMsgQueueSize = 10000; + loadConf->globals.mainQ.iMainMsgQHighWtrMark = 8000; + loadConf->globals.mainQ.iMainMsgQLowWtrMark = 2000; + loadConf->globals.mainQ.iMainMsgQDiscardMark = 9800; + loadConf->globals.mainQ.iMainMsgQDiscardSeverity = 8; + loadConf->globals.mainQ.iMainMsgQueMaxFileSize = 1024 * 1024; + loadConf->globals.mainQ.iMainMsgQueueNumWorkers = 1; + loadConf->globals.mainQ.iMainMsgQPersistUpdCnt = 0; + loadConf->globals.mainQ.bMainMsgQSyncQeueFiles = 0; + loadConf->globals.mainQ.iMainMsgQtoQShutdown = 1500; + loadConf->globals.mainQ.iMainMsgQtoActShutdown = 1000; + loadConf->globals.mainQ.iMainMsgQtoEnq = 2000; + loadConf->globals.mainQ.iMainMsgQtoWrkShutdown = 60000; + loadConf->globals.mainQ.iMainMsgQWrkMinMsgs = 100; + loadConf->globals.mainQ.iMainMsgQDeqSlowdown = 0; + loadConf->globals.mainQ.bMainMsgQSaveOnShutdown = 1; + loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + loadConf->globals.mainQ.iMainMsgQueMaxDiskSpace = 0; + loadConf->globals.mainQ.iMainMsgQueDeqBatchSize = 32; + + return RS_RET_OK; +} + + +/* legacy config system: set the action resume interval */ +static rsRetVal +setModDir(void __attribute__((unused)) *pVal, uchar* pszNewVal) +{ + DEFiRet; + iRet = module.SetModDir(pszNewVal); + free(pszNewVal); + RETiRet; +} + + +/* "load" a build in module and register it for the current load config */ +static rsRetVal +regBuildInModule(rsRetVal (*modInit)(), uchar *name, void *pModHdlr) +{ + cfgmodules_etry_t *pNew; + cfgmodules_etry_t *pLast; + modInfo_t *pMod; + DEFiRet; + CHKiRet(module.doModInit(modInit, name, pModHdlr, &pMod)); + readyModForCnf(pMod, &pNew, &pLast); + addModToCnfList(pNew, pLast); +finalize_it: + RETiRet; +} + + +/* load build-in modules + * very first version begun on 2007-07-23 by rgerhards + */ +static rsRetVal +loadBuildInModules() +{ + DEFiRet; + + CHKiRet(regBuildInModule(modInitFile, UCHAR_CONSTANT("builtin:omfile"), NULL)); + CHKiRet(regBuildInModule(modInitPipe, UCHAR_CONSTANT("builtin:ompipe"), NULL)); + CHKiRet(regBuildInModule(modInitShell, UCHAR_CONSTANT("builtin-shell"), NULL)); + CHKiRet(regBuildInModule(modInitDiscard, UCHAR_CONSTANT("builtin:omdiscard"), NULL)); +# ifdef SYSLOG_INET + CHKiRet(regBuildInModule(modInitFwd, UCHAR_CONSTANT("builtin:omfwd"), NULL)); +# endif + + /* dirty, but this must be for the time being: the usrmsg module must always be + * loaded as last module. This is because it processes any type of action selector. + * If we load it before other modules, these others will never have a chance of + * working with the config file. We may change that implementation so that a user name + * must start with an alnum, that would definitely help (but would it break backwards + * compatibility?). * rgerhards, 2007-07-23 + * User names now must begin with: + * [a-zA-Z0-9_.] + */ + CHKiRet(regBuildInModule(modInitUsrMsg, (uchar*) "builtin:omusrmsg", NULL)); + + /* load build-in parser modules */ + CHKiRet(regBuildInModule(modInitpmrfc5424, UCHAR_CONSTANT("builtin:pmrfc5424"), NULL)); + CHKiRet(regBuildInModule(modInitpmrfc3164, UCHAR_CONSTANT("builtin:pmrfc3164"), NULL)); + + /* and set default parser modules. Order is *very* important, legacy + * (3164) parser needs to go last! */ + CHKiRet(parser.AddDfltParser(UCHAR_CONSTANT("rsyslog.rfc5424"))); + CHKiRet(parser.AddDfltParser(UCHAR_CONSTANT("rsyslog.rfc3164"))); + + /* load build-in strgen modules */ + CHKiRet(regBuildInModule(modInitsmfile, UCHAR_CONSTANT("builtin:smfile"), NULL)); + CHKiRet(regBuildInModule(modInitsmtradfile, UCHAR_CONSTANT("builtin:smtradfile"), NULL)); + CHKiRet(regBuildInModule(modInitsmfwd, UCHAR_CONSTANT("builtin:smfwd"), NULL)); + CHKiRet(regBuildInModule(modInitsmtradfwd, UCHAR_CONSTANT("builtin:smtradfwd"), NULL)); + +finalize_it: + if(iRet != RS_RET_OK) { + /* we need to do fprintf, as we do not yet have an error reporting system + * in place. + */ + fprintf(stderr, "fatal error: could not activate built-in modules. Error code %d.\n", + iRet); + } + RETiRet; +} + + +/* intialize the legacy config system */ +static inline rsRetVal +initLegacyConf(void) +{ + DEFiRet; + uchar *pTmp; + ruleset_t *pRuleset; + + DBGPRINTF("doing legacy config system init\n"); + /* construct the default ruleset */ + ruleset.Construct(&pRuleset); + ruleset.SetName(pRuleset, UCHAR_CONSTANT("RSYSLOG_DefaultRuleset")); + ruleset.ConstructFinalize(loadConf, pRuleset); + rulesetSetCurrRulesetPtr(pRuleset); + + /* now register config handlers */ + CHKiRet(regCfSysLineHdlr((uchar *)"sleep", 0, eCmdHdlrGoneAway, + NULL, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"logrsyslogstatusmessages", 0, eCmdHdlrBinary, + NULL, &loadConf->globals.bLogStatusMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"errormessagestostderr", 0, eCmdHdlrBinary, + NULL, &loadConf->globals.bErrMsgToStderr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"abortonuncleanconfig", 0, eCmdHdlrBinary, + NULL, &loadConf->globals.bAbortOnUncleanConfig, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary, + NULL, &loadConf->globals.bReduceRepeatMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debugprinttemplatelist", 0, eCmdHdlrBinary, + NULL, &(loadConf->globals.bDebugPrintTemplateList), NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debugprintmodulelist", 0, eCmdHdlrBinary, + NULL, &(loadConf->globals.bDebugPrintModuleList), NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debugprintcfsyslinehandlerlist", 0, eCmdHdlrBinary, + NULL, &(loadConf->globals.bDebugPrintCfSysLineHandlerList), NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"privdroptouser", 0, eCmdHdlrUID, + NULL, &loadConf->globals.uidDropPriv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"privdroptouserid", 0, eCmdHdlrInt, + NULL, &loadConf->globals.uidDropPriv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"privdroptogroup", 0, eCmdHdlrGID, + NULL, &loadConf->globals.gidDropPriv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"privdroptogroupid", 0, eCmdHdlrGID, + NULL, &loadConf->globals.gidDropPriv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"generateconfiggraph", 0, eCmdHdlrGetWord, + NULL, &loadConf->globals.pszConfDAGFile, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"umask", 0, eCmdHdlrFileCreateMode, + NULL, &loadConf->globals.umask, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"maxopenfiles", 0, eCmdHdlrInt, + setMaxFiles, NULL, NULL)); + + CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeinterval", 0, eCmdHdlrInt, + setActionResumeInterval, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, + conf.doModLoad, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultruleset", 0, eCmdHdlrGetWord, + setDefaultRuleset, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"ruleset", 0, eCmdHdlrGetWord, + setCurrRuleset, NULL, NULL)); + + /* handler for "larger" config statements (tie into legacy conf system) */ + CHKiRet(regCfSysLineHdlr((uchar *)"template", 0, eCmdHdlrCustomHandler, + conf.doNameLine, (void*)DIR_TEMPLATE, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"outchannel", 0, eCmdHdlrCustomHandler, + conf.doNameLine, (void*)DIR_OUTCHANNEL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"allowedsender", 0, eCmdHdlrCustomHandler, + conf.doNameLine, (void*)DIR_ALLOWEDSENDER, NULL)); + + /* the following are parameters for the main message queue. I have the + * strong feeling that this needs to go to a different space, but that + * feeling may be wrong - we'll see how things evolve. + * rgerhards, 2011-04-21 + */ + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuefilename", 0, eCmdHdlrGetWord, + NULL, &loadConf->globals.mainQ.pszMainMsgQFName, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesize", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQueueSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuehighwatermark", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQHighWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuelowwatermark", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQLowWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardmark", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQDiscardMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardseverity", 0, eCmdHdlrSeverity, + NULL, &loadConf->globals.mainQ.iMainMsgQDiscardSeverity, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuecheckpointinterval", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQPersistUpdCnt, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesyncqueuefiles", 0, eCmdHdlrBinary, + NULL, &loadConf->globals.mainQ.bMainMsgQSyncQeueFiles, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetype", 0, eCmdHdlrGetWord, + setMainMsgQueType, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreads", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQueueNumWorkers, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutshutdown", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQtoQShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutactioncompletion", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQtoActShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutenqueue", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQtoEnq, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkertimeoutthreadshutdown", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQtoWrkShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeueslowdown", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQDeqSlowdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreadminimummessages", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQWrkMinMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxfilesize", 0, eCmdHdlrSize, + NULL, &loadConf->globals.mainQ.iMainMsgQueMaxFileSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuebatchsize", 0, eCmdHdlrSize, + NULL, &loadConf->globals.mainQ.iMainMsgQueDeqBatchSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxdiskspace", 0, eCmdHdlrSize, + NULL, &loadConf->globals.mainQ.iMainMsgQueMaxDiskSpace, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesaveonshutdown", 0, eCmdHdlrBinary, + NULL, &loadConf->globals.mainQ.bMainMsgQSaveOnShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimebegin", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQueueDeqtWinFromHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimeend", 0, eCmdHdlrInt, + NULL, &loadConf->globals.mainQ.iMainMsgQueueDeqtWinToHr, NULL)); + /* moddir is a bit hard problem -- because it actually needs to + * modify a setting that is specific to module.c. The important point + * is that this action MUST actually be carried out during config load, + * because we must load modules in order to get their config extensions + * (no way around). + * TODO: think about a clean solution + */ + CHKiRet(regCfSysLineHdlr((uchar *)"moddir", 0, eCmdHdlrGetWord, + setModDir, NULL, NULL)); + + /* finally, the reset handler */ + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, NULL)); + + /* initialize the build-in templates */ + pTmp = template_DebugFormat; + tplAddLine(ourConf, "RSYSLOG_DebugFormat", &pTmp); + pTmp = template_SyslogProtocol23Format; + tplAddLine(ourConf, "RSYSLOG_SyslogProtocol23Format", &pTmp); + pTmp = template_FileFormat; /* new format for files with high-precision stamp */ + tplAddLine(ourConf, "RSYSLOG_FileFormat", &pTmp); + pTmp = template_TraditionalFileFormat; + tplAddLine(ourConf, "RSYSLOG_TraditionalFileFormat", &pTmp); + pTmp = template_WallFmt; + tplAddLine(ourConf, " WallFmt", &pTmp); + pTmp = template_ForwardFormat; + tplAddLine(ourConf, "RSYSLOG_ForwardFormat", &pTmp); + pTmp = template_TraditionalForwardFormat; + tplAddLine(ourConf, "RSYSLOG_TraditionalForwardFormat", &pTmp); + pTmp = template_StdUsrMsgFmt; + tplAddLine(ourConf, " StdUsrMsgFmt", &pTmp); + pTmp = template_StdDBFmt; + tplAddLine(ourConf, " StdDBFmt", &pTmp); + pTmp = template_SysklogdFileFormat; + tplAddLine(ourConf, "RSYSLOG_SysklogdFileFormat", &pTmp); + pTmp = template_StdPgSQLFmt; + tplAddLine(ourConf, " StdPgSQLFmt", &pTmp); + pTmp = template_StdJSONFmt; + tplAddLine(ourConf, " StdJSONFmt", &pTmp); + pTmp = template_spoofadr; + tplLastStaticInit(ourConf, tplAddLine(ourConf, "RSYSLOG_omudpspoofDfltSourceTpl", &pTmp)); + +finalize_it: + RETiRet; +} + + +/* validate the current configuration, generate error messages, do + * optimizations, etc, etc,... + */ +static inline rsRetVal +validateConf(void) +{ + DEFiRet; + + /* some checks */ + if(ourConf->globals.mainQ.iMainMsgQueueNumWorkers < 1) { + errmsg.LogError(0, NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n"); + ourConf->globals.mainQ.iMainMsgQueueNumWorkers = 1; + } + + if(ourConf->globals.mainQ.MainMsgQueType == QUEUETYPE_DISK) { + errno = 0; /* for logerror! */ + if(glbl.GetWorkDir() == NULL) { + errmsg.LogError(0, NO_ERRCODE, "No $WorkDirectory specified - can not run main message queue in 'disk' mode. " + "Using 'FixedArray' instead.\n"); + ourConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + } + if(ourConf->globals.mainQ.pszMainMsgQFName == NULL) { + errmsg.LogError(0, NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main message queue in " + "'disk' mode. Using 'FixedArray' instead.\n"); + ourConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + } + } + RETiRet; +} + + +/* Load a configuration. This will do all necessary steps to create + * the in-memory representation of the configuration, including support + * for multiple configuration languages. + * Note that to support the legacy language we must provide some global + * object that holds the currently-being-loaded config ptr. + * Begun 2011-04-20, rgerhards + */ +rsRetVal +load(rsconf_t **cnf, uchar *confFile) +{ + int iNbrActions; + int r; + DEFiRet; + + CHKiRet(rsconfConstruct(&loadConf)); +ourConf = loadConf; // TODO: remove, once ourConf is gone! + + CHKiRet(loadBuildInModules()); + CHKiRet(initLegacyConf()); + + /* open the configuration file */ + r = cnfSetLexFile((char*)confFile); + if(r == 0) { + r = yyparse(); + conf.GetNbrActActions(loadConf, &iNbrActions); + } + + if(r == 1) { + errmsg.LogError(0, RS_RET_CONF_PARSE_ERROR, + "CONFIG ERROR: could not interpret master " + "config file '%s'.", confFile); + ABORT_FINALIZE(RS_RET_CONF_PARSE_ERROR); + } else if(iNbrActions == 0) { + errmsg.LogError(0, RS_RET_NO_ACTIONS, "CONFIG ERROR: there are no " + "active actions configured. Inputs will " + "run, but no output whatsoever is created."); + ABORT_FINALIZE(RS_RET_NO_ACTIONS); + } + tellLexEndParsing(); + rulesetOptimizeAll(loadConf); + + tellCoreConfigLoadDone(); + tellModulesConfigLoadDone(); + + tellModulesCheckConfig(); + CHKiRet(validateConf()); + + /* we are done checking the config - now validate if we should actually run or not. + * If not, terminate. -- rgerhards, 2008-07-25 + * TODO: iConfigVerify -- should it be pulled from the config, or leave as is (option)? + */ + if(iConfigVerify) { + if(iRet == RS_RET_OK) + iRet = RS_RET_VALIDATION_RUN; + FINALIZE; + } + + /* all OK, pass loaded conf to caller */ + *cnf = loadConf; +// TODO: enable this once all config code is moved to here! loadConf = NULL; + + dbgprintf("rsyslog finished loading master config %p\n", loadConf); + rsconfDebugPrint(loadConf); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + */ +BEGINobjQueryInterface(rsconf) +CODESTARTobjQueryInterface(rsconf) + if(pIf->ifVersion != rsconfCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = rsconfConstruct; + pIf->ConstructFinalize = rsconfConstructFinalize; + pIf->Destruct = rsconfDestruct; + pIf->DebugPrint = rsconfDebugPrint; + pIf->Load = load; + pIf->Activate = activate; +finalize_it: +ENDobjQueryInterface(rsconf) + + +/* Initialize the rsconf class. Must be called as the very first method + * before anything else is called inside this class. + */ +BEGINObjClassInit(rsconf, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(conf, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, rsconfDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, rsconfConstructFinalize); +ENDObjClassInit(rsconf) + + +/* De-initialize the rsconf class. + */ +BEGINObjClassExit(rsconf, OBJ_IS_CORE_MODULE) /* class, version */ + objRelease(ruleset, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(conf, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); +ENDObjClassExit(rsconf) + +/* vi:set ai: + */ diff --git a/runtime/rsconf.h b/runtime/rsconf.h new file mode 100644 index 00000000..484fec8c --- /dev/null +++ b/runtime/rsconf.h @@ -0,0 +1,182 @@ +/* The rsconf object. It models a complete rsyslog configuration. + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_RSCONF_H +#define INCLUDED_RSCONF_H + +#include "linkedlist.h" +#include "queue.h" + +/* --- configuration objects (the plan is to have ALL upper layers in this file) --- */ + +/* queue config parameters. TODO: move to queue.c? */ +struct queuecnf_s { + int iMainMsgQueueSize; /* size of the main message queue above */ + int iMainMsgQHighWtrMark; /* high water mark for disk-assisted queues */ + int iMainMsgQLowWtrMark; /* low water mark for disk-assisted queues */ + int iMainMsgQDiscardMark; /* begin to discard messages */ + int iMainMsgQDiscardSeverity; /* by default, discard nothing to prevent unintentional loss */ + int iMainMsgQueueNumWorkers; /* number of worker threads for the mm queue above */ + queueType_t MainMsgQueType; /* type of the main message queue above */ + uchar *pszMainMsgQFName; /* prefix for the main message queue file */ + int64 iMainMsgQueMaxFileSize; + int iMainMsgQPersistUpdCnt; /* persist queue info every n updates */ + int bMainMsgQSyncQeueFiles; /* sync queue files on every write? */ + int iMainMsgQtoQShutdown; /* queue shutdown (ms) */ + int iMainMsgQtoActShutdown; /* action shutdown (in phase 2) */ + int iMainMsgQtoEnq; /* timeout for queue enque */ + int iMainMsgQtoWrkShutdown; /* timeout for worker thread shutdown */ + int iMainMsgQWrkMinMsgs; /* minimum messages per worker needed to start a new one */ + int iMainMsgQDeqSlowdown; /* dequeue slowdown (simple rate limiting) */ + int64 iMainMsgQueMaxDiskSpace; /* max disk space allocated 0 ==> unlimited */ + int64 iMainMsgQueDeqBatchSize; /* dequeue batch size */ + int bMainMsgQSaveOnShutdown; /* save queue on shutdown (when DA enabled)? */ + int iMainMsgQueueDeqtWinFromHr; /* hour begin of time frame when queue is to be dequeued */ + int iMainMsgQueueDeqtWinToHr; /* hour begin of time frame when queue is to be dequeued */ +}; + +/* globals are data items that are really global, and can be set only + * once (at least in theory, because the legacy system permits them to + * be re-set as often as the user likes). + */ +struct globals_s { + int bDebugPrintTemplateList; + int bDebugPrintModuleList; + int bDebugPrintCfSysLineHandlerList; + int bLogStatusMsgs; /* log rsyslog start/stop/HUP messages? */ + int bErrMsgToStderr; /* print error messages to stderr + (in addition to everything else)? */ + int bAbortOnUncleanConfig; /* abort run (rather than starting with partial + config) if there was any issue in conf */ + int uidDropPriv; /* user-id to which priveleges should be dropped to */ + int gidDropPriv; /* group-id to which priveleges should be dropped to */ + int umask; /* umask to use */ + uchar *pszConfDAGFile; /* name of config DAG file, non-NULL means generate one */ + + // TODO are the following ones defaults? + int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */ + + //TODO: other representation for main queue? Or just load it differently? + queuecnf_t mainQ; /* main queue paramters */ +}; + +/* (global) defaults are global in the sense that they are accessible + * to all code, but they can change value and other objects (like + * actions) actually copy the value a global had at the time the action + * was defined. In that sense, a global default is just that, a default, + * wich can (and will) be changed in the course of config file + * processing. Once the config file has been processed, defaults + * can be dropped. The current code does not do this for simplicity. + * That is not a problem, because the defaults do not take up much memory. + * At a later stage, we may think about dropping them. -- rgerhards, 2011-04-19 + */ +struct defaults_s { +}; + + +/* list of modules loaded in this configuration (config specific module list) */ +struct cfgmodules_etry_s { + cfgmodules_etry_t *next; + modInfo_t *pMod; + void *modCnf; /* pointer to the input module conf */ + /* the following data is input module specific */ + sbool canActivate; /* OK to activate this config? */ + sbool canRun; /* OK to run this config? */ +}; + +struct cfgmodules_s { + cfgmodules_etry_t *root; +}; + +/* outchannel-specific data */ +struct outchannels_s { + struct outchannel *ochRoot; /* the root of the outchannel list */ + struct outchannel *ochLast; /* points to the last element of the outchannel list */ +}; + +struct templates_s { + struct template *root; /* the root of the template list */ + struct template *last; /* points to the last element of the template list */ + struct template *lastStatic; /* last static element of the template list */ +}; + + +struct actions_s { + unsigned nbrActions; /* number of actions */ +}; + + +struct rulesets_s { + linkedList_t llRulesets; /* this is NOT a pointer - no typo here ;) */ + + /* support for legacy rsyslog.conf format */ + ruleset_t *pCurr; /* currently "active" ruleset */ + ruleset_t *pDflt; /* current default ruleset, e.g. for binding to actions which have no other */ +}; + + +/* --- end configuration objects --- */ + +/* the rsconf object */ +struct rsconf_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + cfgmodules_t modules; + globals_t globals; + defaults_t defaults; + templates_t templates; + outchannels_t och; + actions_t actions; + rulesets_t rulesets; + /* note: rulesets include the complete output part: + * - rules + * - filter (as part of the action) + * - actions + * Of course, we need to debate if we shall change that some time... + */ +}; + + +/* interfaces */ +BEGINinterface(rsconf) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(rsconf); + rsRetVal (*Construct)(rsconf_t **ppThis); + rsRetVal (*ConstructFinalize)(rsconf_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(rsconf_t **ppThis); + rsRetVal (*Load)(rsconf_t **ppThis, uchar *confFile); + rsRetVal (*Activate)(rsconf_t *ppThis); +ENDinterface(rsconf) +// TODO: switch version to 1 for first "complete" version!!!! 2011-04-20 +#define rsconfCURR_IF_VERSION 0 /* increment whenever you change the interface above! */ + + +/* prototypes */ +PROTOTYPEObj(rsconf); + +/* globally-visible external data */ +extern rsconf_t *runConf;/* the currently running config */ +extern rsconf_t *loadConf;/* the config currently being loaded (no concurrent config load supported!) */ + + +/* some defaults (to be removed?) */ +#define DFLT_bLogStatusMsgs 1 + +#endif /* #ifndef INCLUDED_RSCONF_H */ diff --git a/runtime/rsyslog.c b/runtime/rsyslog.c new file mode 100644 index 00000000..047dfa9b --- /dev/null +++ b/runtime/rsyslog.c @@ -0,0 +1,249 @@ +/* rsyslog.c - the main entry point into rsyslog's runtime library (RTL) + * + * This module contains all function which work on a RTL global level. It's + * name is abbreviated to "rsrt" (rsyslog runtime). + * + * Please note that the runtime library tends to be plugin-safe. That is, it must be + * initialized by calling a global initialization function. However, that + * function checks if the library is already initialized and, if so, does + * nothing except incrementing a refeence count. Similarly, the deinit + * function does nothing as long as there are still other users (which + * is tracked via the refcount). As such, it is safe to call init and + * exit multiple times, as long as this are always matching calls. This + * capability is needed for a plugin system, where one plugin never + * knows what the other did. HOWEVER, as of this writing, not all runtime + * library objects may work cleanly without static global data (the + * debug system is a very good example of this). So while we aim at the + * ability to work well in a plugin environment, things may not really work + * out. If you intend to use the rsyslog runtime library inside plugins, + * you should investigate the situation in detail. Please note that the + * rsyslog project itself does not yet need this functionality - thus you + * can safely assume it is totally untested ;). + * + * rgerhards, 2008-04-17: I have now once again checked on the plugin-safety. + * Unfortunately, there is currently no hook at all with which we could + * abstract a global data instance class. As such, we can NOT make the + * runtime plugin-safe in the above-described sense. As the rsyslog + * project itself does not need this functionality (and it is quesationable + * if someone else ever will), we do currently do not make an effort to + * support it. So if you intend to use rsyslog runtime inside a non-rsyslog + * plugin system, be careful! + * + * The rsyslog runtime library is in general reentrant and thread-safe. There + * are some intentional exceptions (e.g. inside the msg object). These are + * documented. Any other threading and reentrency issue can be considered a bug. + * + * Module begun 2008-04-16 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "stringbuf.h" +#include "wti.h" +#include "wtp.h" +#include "datetime.h" +#include "queue.h" +#include "conf.h" +#include "rsconf.h" +#include "glbl.h" +#include "errmsg.h" +#include "prop.h" +#include "ruleset.h" +#include "parser.h" +#include "strgen.h" +#include "statsobj.h" +#include "atomic.h" + +#ifdef HAVE_PTHREAD_SETSCHEDPARAM +struct sched_param default_sched_param; +pthread_attr_t default_thread_attr; +int default_thr_sched_policy; +#endif + +/* forward definitions */ +static rsRetVal dfltErrLogger(int, uchar *errMsg); + +/* globally visible static data - see comment in rsyslog.h for details */ +uchar *glblModPath; /* module load path */ +rsRetVal (*glblErrLogger)(int, uchar*) = dfltErrLogger; /* the error logger to use by the errmsg module */ + +/* static data */ +static int iRefCount = 0; /* our refcount - it MUST exist only once inside a process (not thread) + thus it is perfectly OK to use a static. MUST be initialized to 0! */ + +/* This is the default instance of the error logger. It simply writes the message + * to stderr. It is expected that this is replaced by the runtime user very early + * during startup (at least if the default is unsuitable). However, we provide a + * default so that we can log errors during the intial phase, most importantly + * during initialization. -- rgerhards. 2008-04-17 + */ +static rsRetVal dfltErrLogger(int iErr, uchar *errMsg) +{ + DEFiRet; + fprintf(stderr, "rsyslog runtime error(%d): %s\n", iErr, errMsg); + RETiRet; +} + + +/* set the error log function + * rgerhards, 2008-04-18 + */ +rsRetVal +rsrtSetErrLogger(rsRetVal (*errLogger)(int, uchar*)) +{ + DEFiRet; + assert(errLogger != NULL); + glblErrLogger = errLogger; + RETiRet; +} + + +/* globally initialze the runtime system + * NOTE: this is NOT thread safe and must not be called concurrently. If that + * ever poses a problem, we may use proper mutex calls - not considered needed yet. + * If ppErrObj is provided, it receives a char pointer to the name of the object that + * caused the problem (if one occured). The caller must never free this pointer. If + * ppErrObj is NULL, no such information will be provided. pObjIF is the pointer to + * the "obj" object interface, which may be used to query any other rsyslog objects. + * rgerhards, 2008-04-16 + */ +rsRetVal +rsrtInit(char **ppErrObj, obj_if_t *pObjIF) +{ + DEFiRet; + + if(iRefCount == 0) { + /* init runtime only if not yet done */ +#ifdef HAVE_PTHREAD_SETSCHEDPARAM + CHKiRet(pthread_getschedparam(pthread_self(), + &default_thr_sched_policy, + &default_sched_param)); + CHKiRet(pthread_attr_init(&default_thread_attr)); + CHKiRet(pthread_attr_setschedpolicy(&default_thread_attr, + default_thr_sched_policy)); + CHKiRet(pthread_attr_setschedparam(&default_thread_attr, + &default_sched_param)); + CHKiRet(pthread_attr_setinheritsched(&default_thread_attr, + PTHREAD_EXPLICIT_SCHED)); +#endif + if(ppErrObj != NULL) *ppErrObj = "obj"; + CHKiRet(objClassInit(NULL)); /* *THIS* *MUST* always be the first class initilizer being called! */ + CHKiRet(objGetObjInterface(pObjIF)); /* this provides the root pointer for all other queries */ + + /* initialize core classes. We must be very careful with the order of events. Some + * classes use others and if we do not initialize them in the right order, we may end + * up with an invalid call. The most important thing that can happen is that an error + * is detected and needs to be logged, wich in turn requires a broader number of classes + * to be available. The solution is that we take care in the order of calls AND use a + * class immediately after it is initialized. And, of course, we load those classes + * first that we use ourselfs... -- rgerhards, 2008-03-07 + */ + if(ppErrObj != NULL) *ppErrObj = "statsobj"; + CHKiRet(statsobjClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "prop"; + CHKiRet(propClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "glbl"; + CHKiRet(glblClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "msg"; + CHKiRet(msgClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "ruleset"; + CHKiRet(rulesetClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "wti"; + CHKiRet(wtiClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "wtp"; + CHKiRet(wtpClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "queue"; + CHKiRet(qqueueClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "conf"; + CHKiRet(confClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "parser"; + CHKiRet(parserClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "strgen"; + CHKiRet(strgenClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "rsconf"; + CHKiRet(rsconfClassInit(NULL)); + + /* dummy "classes" */ + if(ppErrObj != NULL) *ppErrObj = "str"; + CHKiRet(strInit()); + } + + ++iRefCount; + dbgprintf("rsyslog runtime initialized, version %s, current users %d\n", VERSION, iRefCount); + +finalize_it: + RETiRet; +} + + +/* globally de-initialze the runtime system + * NOTE: this is NOT thread safe and must not be called concurrently. If that + * ever poses a problem, we may use proper mutex calls - not considered needed yet. + * This function must be provided with the caller's obj object pointer. This is + * automatically deinitialized by the runtime system. + * rgerhards, 2008-04-16 + */ +rsRetVal +rsrtExit(void) +{ + DEFiRet; + + if(iRefCount == 1) { + /* do actual de-init only if we are the last runtime user */ + confClassExit(); + glblClassExit(); + rulesetClassExit(); + + objClassExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + } + + --iRefCount; + /* TODO we must deinit this pointer! pObjIF = NULL; / * no longer exists for this caller */ + + dbgprintf("rsyslog runtime de-initialized, current users %d\n", iRefCount); + + RETiRet; +} + + +/* returns 0 if the rsyslog runtime is not initialized and another value + * if it is. This function is primarily meant to be used by runtime functions + * itself. However, it is safe to call it before initializing the runtime. + * Plugins should NOT rely on this function. The reason is that another caller + * may have already initialized it but deinits it before this plugin is done. + * So for plugins and like architectures, the right course of action is to + * call rsrtInit() and rsrtExit(), which can be called by multiple callers. + * rgerhards, 2008-04-16 + */ +int rsrtIsInit(void) +{ + return iRefCount; +} + + +/* vim:set ai: + */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h new file mode 100644 index 00000000..179d93e6 --- /dev/null +++ b/runtime/rsyslog.h @@ -0,0 +1,559 @@ +/* This is the header file for the rsyslog runtime. It must be included + * if someone intends to use the runtime. + * + * Begun 2005-09-15 RGerhards + * + * Copyright (C) 2005-2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_RSYSLOG_H +#define INCLUDED_RSYSLOG_H +#include <pthread.h> +#include "typedefs.h" + +/* ############################################################# * + * # Some constant values # * + * ############################################################# */ +#define CONST_LEN_TIMESTAMP_3164 15 /* number of chars (excluding \0!) in a RFC3164 timestamp */ +#define CONST_LEN_TIMESTAMP_3339 32 /* number of chars (excluding \0!) in a RFC3339 timestamp */ + +/* ############################################################# * + * # Config Settings # * + * ############################################################# */ +#define RS_STRINGBUF_ALLOC_INCREMENT 128 +/* MAXSIZE are absolute maxima, while BUFSIZE are just values after which + * processing is more time-intense. The BUFSIZE params currently add their + * value to the fixed size of the message object. + */ +#define CONF_TAG_MAXSIZE 512 /* a value that is deemed far too large for any valid TAG */ +#define CONF_HOSTNAME_MAXSIZE 512 /* a value that is deemed far too large for any valid HOSTNAME */ +#define CONF_RAWMSG_BUFSIZE 101 +#define CONF_TAG_BUFSIZE 32 +#define CONF_PROGNAME_BUFSIZE 16 +#define CONF_HOSTNAME_BUFSIZE 32 +#define CONF_PROP_BUFSIZE 16 /* should be close to sizeof(ptr) or lighly above it */ +#define CONF_MIN_SIZE_FOR_COMPRESS 60 /* config param: minimum message size to try compression. The smaller + * the message, the less likely is any compression gain. We check for + * gain before we submit the message. But to do so we still need to + * do the (costly) compress() call. The following setting sets a size + * for which no call to compress() is done at all. This may result in + * a few more bytes being transmited but better overall performance. + * Note: I have not yet checked the minimum UDP packet size. It might be + * that we do not save anything by compressing very small messages, because + * UDP might need to pad ;) + * rgerhards, 2006-11-30 + */ + +#define CONF_OMOD_NUMSTRINGS_MAXSIZE 5 /* cache for pointers to output module buffer pointers. All + * rsyslog-provided plugins do NOT need more than five buffers. If + * more are needed (future developments, third-parties), rsyslog + * must be recompiled with a larger parameter. Hardcoding this + * saves us some overhead, both in runtime in code complexity. As + * it is doubtful if ever more than 3 parameters are needed, the + * approach taken here is considered appropriate. + * rgerhards, 2010-06-24 + */ +#define CONF_NUM_MULTISUB 1024 /* default number of messages per multisub structure */ + +/* ############################################################# * + * # End Config Settings # * + * ############################################################# */ + +/* portability: not all platforms have these defines, so we + * define them here if they are missing. -- rgerhards, 2008-03-04 + */ +#ifndef LOG_MAKEPRI +# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) +#endif +#ifndef LOG_PRI +# define LOG_PRI(p) ((p) & LOG_PRIMASK) +#endif +#ifndef LOG_FAC +# define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) +#endif + + +/* the rsyslog core provides information about present feature to plugins + * asking it. Below are feature-test macros which must be used to query + * features. Note that this must be powers of two, so that multiple queries + * can be combined. -- rgerhards, 2009-04-27 + */ +#define CORE_FEATURE_BATCHING 1 +/*#define CORE_FEATURE_whatever 2 ... and so on ... */ + +#ifndef _PATH_CONSOLE +#define _PATH_CONSOLE "/dev/console" +#endif + +/* properties are now encoded as (tiny) integers. I do not use an enum as I would like + * to keep the memory footprint small (and thus cache hits high). + * rgerhards, 2009-06-26 + */ +typedef uintTiny propid_t; +#define PROP_INVALID 0 +#define PROP_MSG 1 +#define PROP_TIMESTAMP 2 +#define PROP_HOSTNAME 3 +#define PROP_SYSLOGTAG 4 +#define PROP_RAWMSG 5 +#define PROP_INPUTNAME 6 +#define PROP_FROMHOST 7 +#define PROP_FROMHOST_IP 8 +#define PROP_PRI 9 +#define PROP_PRI_TEXT 10 +#define PROP_IUT 11 +#define PROP_SYSLOGFACILITY 12 +#define PROP_SYSLOGFACILITY_TEXT 13 +#define PROP_SYSLOGSEVERITY 14 +#define PROP_SYSLOGSEVERITY_TEXT 15 +#define PROP_TIMEGENERATED 16 +#define PROP_PROGRAMNAME 17 +#define PROP_PROTOCOL_VERSION 18 +#define PROP_STRUCTURED_DATA 19 +#define PROP_APP_NAME 20 +#define PROP_PROCID 21 +#define PROP_MSGID 22 +#define PROP_PARSESUCCESS 23 +#define PROP_SYS_NOW 150 +#define PROP_SYS_YEAR 151 +#define PROP_SYS_MONTH 152 +#define PROP_SYS_DAY 153 +#define PROP_SYS_HOUR 154 +#define PROP_SYS_HHOUR 155 +#define PROP_SYS_QHOUR 156 +#define PROP_SYS_MINUTE 157 +#define PROP_SYS_MYHOSTNAME 158 +#define PROP_CEE 200 +#define PROP_CEE_ALL_JSON 201 +#define PROP_SYS_BOM 159 +#define PROP_SYS_UPTIME 160 +#define PROP_UUID 161 + + +/* The error codes below are orginally "borrowed" from + * liblogging. As such, we reserve values up to -2999 + * just in case we need to borrow something more ;) +*/ +enum rsRetVal_ /** return value. All methods return this if not specified otherwise */ +{ + /* the first two define are for errmsg.logError(), so that we can use the rsRetVal + * as an rsyslog error code. -- rgerhards, 20080-06-27 + */ + RS_RET_NO_ERRCODE = -1, /**< RESERVED for NO_ERRCODE errmsg.logError status name */ + RS_RET_INCLUDE_ERRNO = 1073741824, /* 2**30 - do NOT use error codes above this! */ + /* begin regular error codes */ + RS_RET_NOT_IMPLEMENTED = -7, /**< implementation is missing (probably internal error or lazyness ;)) */ + RS_RET_OUT_OF_MEMORY = -6, /**< memory allocation failed */ + RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50,/**< the caller provided a buffer, but the called function sees the size of this buffer is too small - operation not carried out */ + RS_RET_TRUE = -3, /**< to indicate a true state (can be used as TRUE, legacy) */ + RS_RET_FALSE = -2, /**< to indicate a false state (can be used as FALSE, legacy) */ + RS_RET_NO_IRET = -8, /**< This is a trick for the debuging system - it means no iRet is provided */ + RS_RET_VALIDATION_RUN = -9, /**< indicates a (config) validation run, processing not carried out */ + RS_RET_ERR = -3000, /**< generic failure */ + RS_TRUNCAT_TOO_LARGE = -3001, /**< truncation operation where too many chars should be truncated */ + RS_RET_FOUND_AT_STRING_END = -3002, /**< some value found, but at the last pos of string */ + RS_RET_NOT_FOUND = -3003, /**< some requested value not found */ + RS_RET_MISSING_TRAIL_QUOTE = -3004, /**< an expected trailing quote is missing */ + RS_RET_NO_DIGIT = -3005, /**< an digit was expected, but none found (mostly parsing) */ + RS_RET_NO_MORE_DATA = -3006, /**< insufficient data, e.g. end of string during parsing */ + RS_RET_INVALID_IP = -3007, /**< invalid ip found where valid was expected */ + RS_RET_OBJ_CREATION_FAILED = - 3008, /**< the creation of an object failed (no details available) */ + RS_RET_PARAM_ERROR = -1000, /**< invalid parameter in call to function */ + RS_RET_MISSING_INTERFACE = -1001,/**< interface version mismatch, required missing */ + RS_RET_INVALID_CORE_INTERFACE = -1002,/**< interface provided by host invalid, can not be used */ + RS_RET_ENTRY_POINT_NOT_FOUND = -1003,/**< a requested entry point was not found */ + RS_RET_MODULE_ENTRY_POINT_NOT_FOUND = -1004,/**< a entry point requested from a module was not present in it */ + RS_RET_OBJ_NOT_AVAILABLE = -1005,/**< something could not be completed because the required object is not available*/ + RS_RET_LOAD_ERROR = -1006,/**< we had an error loading the object/interface and can not continue */ + RS_RET_MODULE_STILL_REFERENCED = -1007,/**< module could not be unloaded because it still is referenced by someone */ + RS_RET_OBJ_UNKNOWN = -1008,/**< object is unknown where required */ + RS_RET_OBJ_NOT_REGISTERED = -1009,/**< tried to unregister an object that is not registered */ + /* return states for config file processing */ + RS_RET_NONE = -2000, /**< some value is not available - not necessarily an error */ + RS_RET_CONFLINE_UNPROCESSED = -2001,/**< config line was not processed, pass to other module */ + RS_RET_DISCARDMSG = -2002, /**< discard message (no error state, processing request!) */ + RS_RET_INCOMPATIBLE = -2003, /**< function not compatible with requested feature */ + RS_RET_NOENTRY = -2004, /**< do not create an entry for (whatever) - not necessary an error */ + RS_RET_NO_SQL_STRING = -2005, /**< string is not suitable for use as SQL */ + RS_RET_DISABLE_ACTION = -2006, /**< action requests that it be disabled */ + RS_RET_SUSPENDED = -2007, /**< something was suspended, not neccesarily an error */ + RS_RET_RQD_TPLOPT_MISSING = -2008,/**< a required template option is missing */ + RS_RET_INVALID_VALUE = -2009,/**< some value is invalid (e.g. user-supplied data) */ + RS_RET_INVALID_INT = -2010,/**< invalid integer */ + RS_RET_INVALID_CMD = -2011,/**< invalid command */ + RS_RET_VAL_OUT_OF_RANGE = -2012, /**< value out of range */ + RS_RET_FOPEN_FAILURE = -2013, /**< failure during fopen, for example file not found - see errno */ + RS_RET_END_OF_LINKEDLIST = -2014, /**< end of linked list, not an error, but a status */ + RS_RET_CHAIN_NOT_PERMITTED = -2015, /**< chaining (e.g. of config command handlers) not permitted */ + RS_RET_INVALID_PARAMS = -2016,/**< supplied parameters are invalid */ + RS_RET_EMPTY_LIST = -2017, /**< linked list is empty */ + RS_RET_FINISHED = -2018, /**< some opertion is finished, not an error state */ + RS_RET_INVALID_SOURCE = -2019, /**< source (address) invalid for some reason */ + RS_RET_ADDRESS_UNKNOWN = -2020, /**< an address is unknown - not necessarily an error */ + RS_RET_MALICIOUS_ENTITY = -2021, /**< there is an malicious entity involved */ + RS_RET_NO_KERNEL_LOGSRC = -2022, /**< no source for kernel logs can be obtained */ + RS_RET_TCP_SEND_ERROR = -2023, /**< error during TCP send process */ + RS_RET_GSS_SEND_ERROR = -2024, /**< error during GSS (via TCP) send process */ + RS_RET_TCP_SOCKCREATE_ERR = -2025, /**< error during creation of TCP socket */ + RS_RET_GSS_SENDINIT_ERROR = -2024, /**< error during GSS (via TCP) send initialization process */ + RS_RET_EOF = -2026, /**< end of file reached, not necessarily an error */ + RS_RET_IO_ERROR = -2027, /**< some kind of IO error happened */ + RS_RET_INVALID_OID = -2028, /**< invalid object ID */ + RS_RET_INVALID_HEADER = -2029, /**< invalid header */ + RS_RET_INVALID_HEADER_VERS = -2030, /**< invalid header version */ + RS_RET_INVALID_DELIMITER = -2031, /**< invalid delimiter, e.g. between params */ + RS_RET_INVALID_PROPFRAME = -2032, /**< invalid framing in serialized property */ + RS_RET_NO_PROPLINE = -2033, /**< line is not a property line */ + RS_RET_INVALID_TRAILER = -2034, /**< invalid trailer */ + RS_RET_VALUE_TOO_LOW = -2035, /**< a provided value is too low */ + RS_RET_FILE_PREFIX_MISSING = -2036, /**< a required file prefix (parameter?) is missing */ + RS_RET_INVALID_HEADER_RECTYPE = -2037, /**< invalid record type in header or invalid header */ + RS_RET_QTYPE_MISMATCH = -2038, /**< different qType when reading back a property type */ + RS_RET_NO_FILE_ACCESS = -2039, /**< covers EACCES error on file open() */ + RS_RET_FILE_NOT_FOUND = -2040, /**< file not found */ + RS_RET_TIMED_OUT = -2041, /**< timeout occured (not necessarily an error) */ + RS_RET_QSIZE_ZERO = -2042, /**< queue size is zero where this is not supported */ + RS_RET_ALREADY_STARTING = -2043, /**< something (a thread?) is already starting - not necessarily an error */ + RS_RET_NO_MORE_THREADS = -2044, /**< no more threads available, not necessarily an error */ + RS_RET_NO_FILEPREFIX = -2045, /**< file prefix is not specified where one is needed */ + RS_RET_CONFIG_ERROR = -2046, /**< there is a problem with the user-provided config settigs */ + RS_RET_OUT_OF_DESRIPTORS = -2047, /**< a descriptor table's space has been exhausted */ + RS_RET_NO_DRIVERS = -2048, /**< a required drivers missing */ + RS_RET_NO_DRIVERNAME = -2049, /**< driver name missing where one was required */ + RS_RET_EOS = -2050, /**< end of stream (of whatever) */ + RS_RET_SYNTAX_ERROR = -2051, /**< syntax error, eg. during parsing */ + RS_RET_INVALID_OCTAL_DIGIT = -2052, /**< invalid octal digit during parsing */ + RS_RET_INVALID_HEX_DIGIT = -2053, /**< invalid hex digit during parsing */ + RS_RET_INTERFACE_NOT_SUPPORTED = -2054, /**< interface not supported */ + RS_RET_OUT_OF_STACKSPACE = -2055, /**< a stack data structure is exhausted and can not be grown */ + RS_RET_STACK_EMPTY = -2056, /**< a pop was requested on a stack, but the stack was already empty */ + RS_RET_INVALID_VMOP = -2057, /**< invalid virtual machine instruction */ + RS_RET_INVALID_VAR = -2058, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */ + RS_RET_INVALID_NUMBER = -2059, /**< number invalid during parsing */ + RS_RET_NOT_A_NUMBER = -2060, /**< e.g. conversion impossible because the string is not a number */ + RS_RET_OBJ_ALREADY_REGISTERED = -2061, /**< object (name) is already registered */ + RS_RET_OBJ_REGISTRY_OUT_OF_SPACE = -2062, /**< the object registry has run out of space */ + RS_RET_HOST_NOT_PERMITTED = -2063, /**< a host is not permitted to perform an action it requested */ + RS_RET_MODULE_LOAD_ERR = -2064, /**< module could not be loaded */ + RS_RET_MODULE_LOAD_ERR_PATHLEN = -2065, /**< module could not be loaded - path to long */ + RS_RET_MODULE_LOAD_ERR_DLOPEN = -2066, /**< module could not be loaded - problem in dlopen() */ + RS_RET_MODULE_LOAD_ERR_NO_INIT = -2067, /**< module could not be loaded - init() missing */ + RS_RET_MODULE_LOAD_ERR_INIT_FAILED = -2068, /**< module could not be loaded - init() failed */ + RS_RET_NO_SOCKET = -2069, /**< socket could not be obtained or was not provided */ + RS_RET_SMTP_ERROR = -2070, /**< error during SMTP transation */ + RS_RET_MAIL_NO_TO = -2071, /**< recipient for mail destination is missing */ + RS_RET_MAIL_NO_FROM = -2072, /**< sender for mail destination is missing */ + RS_RET_INVALID_PRI = -2073, /**< PRI value is invalid */ + RS_RET_MALICIOUS_HNAME = -2074, /**< remote peer is trying malicious things with its hostname */ + RS_RET_INVALID_HNAME = -2075, /**< remote peer's hostname invalid or unobtainable */ + RS_RET_INVALID_PORT = -2076, /**< invalid port value */ + RS_RET_COULD_NOT_BIND = -2077, /**< could not bind socket, defunct */ + RS_RET_GNUTLS_ERR = -2078, /**< (unexpected) error in GnuTLS call */ + RS_RET_MAX_SESS_REACHED = -2079, /**< max nbr of sessions reached, can not create more */ + RS_RET_MAX_LSTN_REACHED = -2080, /**< max nbr of listeners reached, can not create more */ + RS_RET_INVALID_DRVR_MODE = -2081, /**< tried to set mode not supported by driver */ + RS_RET_DRVRNAME_TOO_LONG = -2082, /**< driver name too long - should never happen */ + RS_RET_TLS_HANDSHAKE_ERR = -2083, /**< TLS handshake failed */ + RS_RET_TLS_CERT_ERR = -2084, /**< generic TLS certificate error */ + RS_RET_TLS_NO_CERT = -2085, /**< no TLS certificate available where one was expected */ + RS_RET_VALUE_NOT_SUPPORTED = -2086, /**< a provided value is not supported */ + RS_RET_VALUE_NOT_IN_THIS_MODE = -2087, /**< a provided value is invalid for the curret mode */ + RS_RET_INVALID_FINGERPRINT = -2088, /**< a fingerprint is not valid for this use case */ + RS_RET_CONNECTION_ABORTREQ = -2089, /**< connection was abort requested due to previous error */ + RS_RET_CERT_INVALID = -2090, /**< a x509 certificate failed validation */ + RS_RET_CERT_INVALID_DN = -2091, /**< distinguised name in x509 certificate is invalid (e.g. wrong escaping) */ + RS_RET_CERT_EXPIRED = -2092, /**< we are past a x.509 cert's expiration time */ + RS_RET_CERT_NOT_YET_ACTIVE = -2094, /**< x.509 cert's activation time not yet reached */ + RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */ + RS_RET_FILE_NO_STAT = -2096, /**< can not stat() a file */ + RS_RET_FILE_TOO_LARGE = -2097, /**< a file is larger than permitted */ + RS_RET_INVALID_WILDCARD = -2098, /**< a wildcard entry is invalid */ + RS_RET_CLOSED = -2099, /**< connection was closed */ + RS_RET_RETRY = -2100, /**< call should be retried (e.g. EGAIN on recv) */ + RS_RET_GSS_ERR = -2101, /**< generic error occured in GSSAPI subsystem */ + RS_RET_CERTLESS = -2102, /**< state: we run without machine cert (this may be OK) */ + RS_RET_NO_ACTIONS = -2103, /**< no active actions are configured (no output will be created) */ + RS_RET_CONF_FILE_NOT_FOUND = -2104, /**< config file or directory not found */ + RS_RET_QUEUE_FULL = -2105, /**< queue is full, operation could not be completed */ + RS_RET_ACCEPT_ERR = -2106, /**< error during accept() system call */ + RS_RET_INVLD_TIME = -2107, /**< invalid timestamp (e.g. could not be parsed) */ + RS_RET_NO_ZIP = -2108, /**< ZIP functionality is not present */ + RS_RET_CODE_ERR = -2109, /**< program code (internal) error */ + RS_RET_FUNC_NO_LPAREN = -2110, /**< left parenthesis missing after function call (rainerscript) */ + RS_RET_FUNC_MISSING_EXPR = -2111, /**< no expression after comma in function call (rainerscript) */ + RS_RET_INVLD_NBR_ARGUMENTS = -2112, /**< invalid number of arguments for function call (rainerscript) */ + RS_RET_INVLD_FUNC = -2113, /**< invalid function name for function call (rainerscript) */ + RS_RET_DUP_FUNC_NAME = -2114, /**< duplicate function name (rainerscript) */ + RS_RET_UNKNW_FUNC = -2115, /**< unkown function name (rainerscript) */ + RS_RET_ERR_RLIM_NOFILE = -2116, /**< error setting max. nbr open files process limit */ + RS_RET_ERR_CREAT_PIPE = -2117, /**< error during pipe creation */ + RS_RET_ERR_FORK = -2118, /**< error during fork() */ + RS_RET_ERR_WRITE_PIPE = -2119, /**< error writing to pipe */ + RS_RET_RSCORE_TOO_OLD = -2120, /**< rsyslog core is too old for ... (eg this plugin) */ + RS_RET_DEFER_COMMIT = -2121, /**< output plugin status: not yet committed (an OK state!) */ + RS_RET_PREVIOUS_COMMITTED = -2122, /**< output plugin status: previous record was committed (an OK state!) */ + RS_RET_ACTION_FAILED = -2123, /**< action failed and is now suspended */ + RS_RET_NONFATAL_CONFIG_ERR = -2124, /**< non-fatal error during config processing */ + RS_RET_NON_SIZELIMITCMD = -2125, /**< size limit for file defined, but no size limit command given */ + RS_RET_SIZELIMITCMD_DIDNT_RESOLVE = -2126, /**< size limit command did not resolve situation */ + RS_RET_STREAM_DISABLED = -2127, /**< a file has been disabled (e.g. by size limit restriction) */ + RS_RET_FILENAME_INVALID = -2140, /**< filename invalid, not found, no access, ... */ + RS_RET_ZLIB_ERR = -2141, /**< error during zlib call */ + RS_RET_VAR_NOT_FOUND = -2142, /**< variable not found */ + RS_RET_EMPTY_MSG = -2143, /**< provided (raw) MSG is empty */ + RS_RET_PEER_CLOSED_CONN = -2144, /**< remote peer closed connection (information, no error) */ + RS_RET_ERR_OPEN_KLOG = -2145, /**< error opening the kernel log socket (primarily solaris) */ + RS_RET_ERR_AQ_CONLOG = -2146, /**< error aquiring console log (on solaris) */ + RS_RET_ERR_DOOR = -2147, /**< some problems with handling the Solaris door functionality */ + RS_RET_NO_SRCNAME_TPL = -2150, /**< sourcename template was not specified where one was needed (omudpspoof spoof addr) */ + RS_RET_HOST_NOT_SPECIFIED = -2151, /**< (target) host was not specified where it was needed */ + RS_RET_ERR_LIBNET_INIT = -2152, /**< error initializing libnet, e.g. because not running as root */ + RS_RET_FORCE_TERM = -2153, /**< thread was forced to terminate by bShallShutdown, a state, not an error */ + RS_RET_RULES_QUEUE_EXISTS = -2154,/**< we were instructed to create a new ruleset queue, but one already exists */ + RS_RET_NO_CURR_RULESET = -2155,/**< no current ruleset exists (but one is required) */ + RS_RET_NO_MSG_PASSING = -2156,/**< output module interface parameter passing mode "MSG" is not available but required */ + RS_RET_RULESET_NOT_FOUND = -2157,/**< a required ruleset could not be found */ + RS_RET_NO_RULESET= -2158,/**< no ruleset name as specified where one was needed */ + RS_RET_PARSER_NOT_FOUND = -2159,/**< parser with the specified name was not found */ + RS_RET_COULD_NOT_PARSE = -2160,/**< (this) parser could not parse the message (no error, means try next one) */ + RS_RET_EINTR = -2161, /**< EINTR occured during a system call (not necessarily an error) */ + RS_RET_ERR_EPOLL = -2162, /**< epoll() returned with an unexpected error code */ + RS_RET_ERR_EPOLL_CTL = -2163, /**< epol_ctll() returned with an unexpected error code */ + RS_RET_TIMEOUT = -2164, /**< timeout occured during operation */ + RS_RET_RCV_ERR = -2165, /**< error occured during socket rcv operation */ + RS_RET_NO_SOCK_CONFIGURED = -2166, /**< no socket (name) was configured where one is required */ + RS_RET_CONF_NOT_GLBL = -2167, /**< $Begin not in global scope */ + RS_RET_CONF_IN_GLBL = -2168, /**< $End when in global scope */ + RS_RET_CONF_INVLD_END = -2169, /**< $End for wrong conf object (probably nesting error) */ + RS_RET_CONF_INVLD_SCOPE = -2170,/**< config statement not valid in current scope (e.g. global stmt in action block) */ + RS_RET_CONF_END_NO_ACT = -2171, /**< end of action block, but no actual action specified */ + RS_RET_NO_LSTN_DEFINED = -2172, /**< no listener defined (e.g. inside an input module) */ + RS_RET_EPOLL_CR_FAILED = -2173, /**< epoll_create() failed */ + RS_RET_EPOLL_CTL_FAILED = -2174, /**< epoll_ctl() failed */ + RS_RET_INTERNAL_ERROR = -2175, /**< rsyslogd internal error, unexpected code path reached */ + RS_RET_ERR_CRE_AFUX = -2176, /**< error creating AF_UNIX socket (and binding it) */ + RS_RET_RATE_LIMITED = -2177, /**< some messages discarded due to exceeding a rate limit */ + RS_RET_ERR_HDFS_WRITE = -2178, /**< error writing to HDFS */ + RS_RET_ERR_HDFS_OPEN = -2179, /**< error during hdfsOpen (e.g. file does not exist) */ + RS_RET_FILE_NOT_SPECIFIED = -2180, /**< file name not configured where this was required */ + RS_RET_ERR_WRKDIR = -2181, /**< problems with the rsyslog working directory */ + RS_RET_WRN_WRKDIR = -2182, /**< correctable problems with the rsyslog working directory */ + RS_RET_ERR_QUEUE_EMERGENCY = -2183, /**< some fatal error caused queue to switch to emergency mode */ + RS_RET_OUTDATED_STMT = -2184, /**< some outdated statement/functionality is being used in conf file */ + RS_RET_MISSING_WHITESPACE = -2185, /**< whitespace is missing in some config construct */ + RS_RET_OK_WARN = -2186, /**< config part: everything was OK, but a warning message was emitted */ + + RS_RET_INVLD_CONF_OBJ= -2200, /**< invalid config object (e.g. $Begin conf statement) */ + RS_RET_ERR_LIBEE_INIT = -2201, /**< cannot obtain libee ctx */ + RS_RET_ERR_LIBLOGNORM_INIT = -2202,/**< cannot obtain liblognorm ctx */ + RS_RET_ERR_LIBLOGNORM_SAMPDB_LOAD = -2203,/**< liblognorm sampledb load failed */ + RS_RET_CMD_GONE_AWAY = -2204,/**< config directive existed, but no longer supported */ + RS_RET_ERR_SCHED_PARAMS = -2205,/**< there is a problem with configured thread scheduling params */ + RS_RET_SOCKNAME_MISSING = -2206,/**< no socket name configured where one is required */ + RS_RET_CONF_PARSE_ERROR = -2207,/**< (fatal) error parsing config file */ + RS_RET_CONF_RQRD_PARAM_MISSING = -2208,/**< required parameter in config object is missing */ + RS_RET_MOD_UNKNOWN = -2209,/**< module (config name) is unknown */ + RS_RET_CONFOBJ_UNSUPPORTED = -2210,/**< config objects (v6 conf) are not supported here */ + RS_RET_MISSING_CNFPARAMS = -2211, /**< missing configuration parameters */ + RS_RET_NO_LISTNERS = -2212, /**< module loaded, but no listeners are defined */ + RS_RET_INVLD_PROTOCOL = -2213, /**< invalid protocol specified in config file */ + RS_RET_CNF_INVLD_FRAMING = -2214, /**< invalid framing specified in config file */ + RS_RET_LEGA_ACT_NOT_SUPPORTED = -2215, /**< the module (no longer) supports legacy action syntax */ + RS_RET_MAX_OMSR_REACHED = -2216, /**< max nbr of string requests reached, not supported by core */ + RS_RET_UID_MISSING = -2217, /**< a user id is missing (but e.g. a password provided) */ + RS_RET_DATAFAIL = -2218, /**< data passed to action caused failure */ + /* reserved for pre-v6.5 */ + RS_RET_DUP_PARAM = -2220, /**< config parameter is given more than once */ + RS_RET_MODULE_ALREADY_IN_CONF = -2221, /**< module already in current configuration */ + RS_RET_PARAM_NOT_PERMITTED = -2222, /**< legacy parameter no longer permitted (usally already set by v2) */ + RS_RET_NO_JSON_PASSING = -2223, /**< rsyslog core does not support JSON-passing plugin API */ + RS_RET_MOD_NO_INPUT_STMT = -2224, /**< (input) module does not support input() statement */ + RS_RET_NO_CEE_MSG = -2225, /**< the message being processed is NOT CEE-enhanced */ + + /**** up to 2290 is reserved for v6 use ****/ + RS_RET_RELP_ERR = -2291, /**<< error in RELP processing */ + /**** up to 3000 is reserved for c7 use ****/ + RS_RET_JNAME_NO_ROOT = -2301, /**< root element is missing in JSON path */ + RS_RET_JNAME_INVALID = -2302, /**< JSON path is invalid */ + RS_RET_JSON_PARSE_ERR = -2303, /**< we had a problem parsing JSON (or extra data) */ + RS_RET_BSD_BLOCKS_UNSUPPORTED = -2304, /**< BSD-style config blocks are no longer supported */ + RS_RET_JNAME_NOTFOUND = -2305, /**< JSON name not found (does not exist) */ + RS_RET_INVLD_SETOP = -2305, /**< invalid variable set operation, incompatible type */ + RS_RET_RULESET_EXISTS = -2306,/**< ruleset already exists */ + RS_RET_DEPRECATED = -2307,/**< deprecated functionality is used */ + RS_RET_DS_PROP_SEQ_ERR = -2308,/**< property sequence error deserializing object */ + RS_RET_TPL_INVLD_PROP = -2309,/**< property name error in template (unknown name) */ + RS_RET_NO_RULEBASE = -2310,/**< mmnormalize: rulebase can not be found or otherwise invalid */ + RS_RET_INVLD_MODE = -2311,/**< invalid mode specified in configuration */ + RS_RET_INVLD_ANON_BITS = -2312,/**< mmanon: invalid number of bits to anonymize specified */ + RS_RET_REPLCHAR_IGNORED = -2313,/**< mmanon: replacementChar parameter is ignored */ + RS_RET_SIGPROV_ERR = -2320,/**< error in signature provider */ + RS_RET_CRYPROV_ERR = -2321,/**< error in cryptography encryption provider */ + RS_RET_EI_OPN_ERR = -2322,/**< error opening an .encinfo file */ + RS_RET_EI_NO_EXISTS = -2323,/**< .encinfo file does not exist (status, not necessarily error!)*/ + RS_RET_EI_WR_ERR = -2324,/**< error writing an .encinfo file */ + RS_RET_EI_INVLD_FILE = -2325,/**< header indicates the file is no .encinfo file */ + RS_RET_CRY_INVLD_ALGO = -2326,/**< user specified invalid (unkonwn) crypto algorithm */ + RS_RET_CRY_INVLD_MODE = -2327,/**< user specified invalid (unkonwn) crypto mode */ + RS_RET_QUEUE_DISK_NO_FN = -2328,/**< disk queue configured, but filename not set */ + /* up to 2350 reserved for 7.4 */ + RS_RET_QUEUE_CRY_DISK_ONLY = -2351,/**< crypto provider only supported for disk-associated queues */ + RS_RET_NO_DATA = -2352,/**< file has no data; more a state than a real error */ + + /* RainerScript error messages (range 1000.. 1999) */ + RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ + RS_RET_FIELD_NOT_FOUND = 1002, /**< field() function did not find requested field */ + + /* some generic error/status codes */ + RS_RET_OK = 0, /**< operation successful */ + RS_RET_OK_DELETE_LISTENTRY = 1, /**< operation successful, but callee requested the deletion of an entry (special state) */ + RS_RET_TERMINATE_NOW = 2, /**< operation successful, function is requested to terminate (mostly used with threads) */ + RS_RET_NO_RUN = 3, /**< operation successful, but function does not like to be executed */ + RS_RET_IDLE = 4, /**< operation successful, but callee is idle (e.g. because queue is empty) */ + RS_RET_TERMINATE_WHEN_IDLE = 5 /**< operation successful, function is requested to terminate when idle */ +}; + +/* some helpful macros to work with srRetVals. + * Be sure to call the to-be-returned variable always "iRet" and + * the function finalizer always "finalize_it". + */ +#define CHKiRet(code) if((iRet = code) != RS_RET_OK) goto finalize_it +/* macro below is to be used if we need our own handling, eg for cleanup */ +#define CHKiRet_Hdlr(code) if((iRet = code) != RS_RET_OK) +/* macro below is to handle failing malloc/calloc/strdup... which we almost always handle in the same way... */ +#define CHKmalloc(operation) if((operation) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY) +/* macro below is used in conjunction with CHKiRet_Hdlr, else use ABORT_FINALIZE */ +#define FINALIZE goto finalize_it; +#define DEFiRet BEGINfunc rsRetVal iRet = RS_RET_OK +#define RETiRet do{ ENDfuncIRet return iRet; }while(0) + +#define ABORT_FINALIZE(errCode) \ + do { \ + iRet = errCode; \ + goto finalize_it; \ + } while (0) + +/** Object ID. These are for internal checking. Each + * object is assigned a specific ID. This is contained in + * all Object structs (just like C++ RTTI). We can use + * this field to see if we have been passed a correct ID. + * Other than that, there is currently no other use for + * the object id. + */ +enum rsObjectID +{ + OIDrsFreed = -1, /**< assigned, when an object is freed. If this + * is seen during a method call, this is an + * invalid object pointer! + */ + OIDrsInvalid = 0, /**< value created by calloc(), so do not use ;) */ + /* The 0x3412 is a debug aid. It helps us find object IDs in memory + * dumps (on X86, this is 1234 in the dump ;) + * If you are on an embedded device and you would like to save space + * make them 1 byte only. + */ + OIDrsCStr = 0x34120001, + OIDrsPars = 0x34120002 +}; +typedef enum rsObjectID rsObjID; + +/* support to set object types */ +#ifdef NDEBUG +#define rsSETOBJTYPE(pObj, type) +#define rsCHECKVALIDOBJECT(x, type) +#else +#define rsSETOBJTYPE(pObj, type) pObj->OID = type; +#define rsCHECKVALIDOBJECT(x, type) {assert(x != NULL); assert(x->OID == type);} +#endif + +/** + * This macro should be used to free objects. + * It aids in interpreting dumps during debugging. + */ +#ifdef NDEBUG +#define RSFREEOBJ(x) free(x) +#else +#define RSFREEOBJ(x) {(x)->OID = OIDrsFreed; free(x);} +#endif + +#ifdef HAVE_PTHREAD_SETSCHEDPARAM +extern struct sched_param default_sched_param; +extern pthread_attr_t default_thread_attr; +extern int default_thr_sched_policy; +#endif + + +/* for the time being, we do our own portability handling here. It + * looks like autotools either does not yet support checks for it, or + * I wasn't smart enough to find them ;) rgerhards, 2007-07-18 + */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +#ifndef O_CLOEXEC +/* of course, this limits the functionality... */ +# define O_CLOEXEC 0 +#endif + +/* some constants */ +#define MUTEX_ALREADY_LOCKED 0 +#define LOCK_MUTEX 1 + +/* The following prototype is convenient, even though it may not be the 100% correct place.. -- rgerhards 2008-01-07 */ +void dbgprintf(char *, ...) __attribute__((format(printf, 1, 2))); + + +#include "debug.h" +#include "obj.h" + +/* the variable below is a trick: before we can init the runtime, the caller + * may want to set a module load path. We can not do this via the glbl class + * because it needs an initialized runtime system (and may at some point in time + * even be loaded itself). So this is a no-go. What we do is use a single global + * variable which may be provided with a pointer by the caller. This variable + * resides in rsyslog.c, the main runtime file. We have not seen any realy valule + * in providing object access functions. If you don't like that, feel free to + * add them. -- rgerhards, 2008-04-17 + */ +extern uchar *glblModPath; /* module load path */ +extern rsRetVal (*glblErrLogger)(int, uchar*); + +/* some runtime prototypes */ +rsRetVal rsrtInit(char **ppErrObj, obj_if_t *pObjIF); +rsRetVal rsrtExit(void); +int rsrtIsInit(void); +rsRetVal rsrtSetErrLogger(rsRetVal (*errLogger)(int, uchar*)); + +/* this define below is (later) intended to be used to implement empty + * structs. TODO: check if compilers supports this and, if not, define + * a dummy variable. This requires review of where in code empty structs + * are already defined. -- rgerhards, 2010-07-26 + */ +#define EMPTY_STRUCT + +/* TODO: remove this -- this is only for transition of the config system */ +extern rsconf_t *ourConf; /* defined by syslogd.c, a hack for functions that do not + yet receive a copy, so that we can incrementially + compile and change... -- rgerhars, 2011-04-19 */ + +#endif /* multi-include protection */ +/* vim:set ai: + */ diff --git a/runtime/ruleset.c b/runtime/ruleset.c new file mode 100644 index 00000000..aacbdf57 --- /dev/null +++ b/runtime/ruleset.c @@ -0,0 +1,1051 @@ +/* ruleset.c - rsyslog's ruleset object + * + * We have a two-way structure of linked lists: one config-specifc linked list + * (conf->rulesets.llRulesets) hold alls rule sets that we know. Included in each + * list is a list of rules (which contain a list of actions, but that's + * a different story). + * + * Usually, only a single rule set is executed. However, there exist some + * situations where all rules must be iterated over, for example on HUP. Thus, + * we also provide interfaces to do that. + * + * Module begun 2009-06-10 by Rainer Gerhards + * + * Copyright 2009-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <ctype.h> + +#include "rsyslog.h" +#include "obj.h" +#include "cfsysline.h" +#include "msg.h" +#include "ruleset.h" +#include "errmsg.h" +#include "parser.h" +#include "batch.h" +#include "unicode-helper.h" +#include "rsconf.h" +#include "action.h" +#include "rainerscript.h" +#include "srUtils.h" +#include "modules.h" +#include "dirty.h" /* for main ruleset queue creation */ + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(parser) + +/* tables for interfacing with the v6 config system (as far as we need to) */ +static struct cnfparamdescr rspdescr[] = { + { "name", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "parser", eCmdHdlrArray, 0 } +}; +static struct cnfparamblk rspblk = + { CNFPARAMBLK_VERSION, + sizeof(rspdescr)/sizeof(struct cnfparamdescr), + rspdescr + }; + +/* forward definitions */ +static rsRetVal processBatch(batch_t *pBatch); +static rsRetVal scriptExec(struct cnfstmt *root, batch_t *pBatch, sbool *active); + + +/* ---------- linked-list key handling functions (ruleset) ---------- */ + +/* destructor for linked list keys. + */ +rsRetVal +rulesetKeyDestruct(void __attribute__((unused)) *pData) +{ + free(pData); + return RS_RET_OK; +} +/* ---------- END linked-list key handling functions (ruleset) ---------- */ + + +/* iterate over all actions in a script (stmt subtree) */ +static void +scriptIterateAllActions(struct cnfstmt *root, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + struct cnfstmt *stmt; + for(stmt = root ; stmt != NULL ; stmt = stmt->next) { + switch(stmt->nodetype) { + case S_NOP: + case S_STOP: + case S_CALL:/* call does not need to do anything - done in called ruleset! */ + break; + case S_ACT: + DBGPRINTF("iterateAllActions calling into action %p\n", stmt->d.act); + pFunc(stmt->d.act, pParam); + break; + case S_IF: + if(stmt->d.s_if.t_then != NULL) + scriptIterateAllActions(stmt->d.s_if.t_then, + pFunc, pParam); + if(stmt->d.s_if.t_else != NULL) + scriptIterateAllActions(stmt->d.s_if.t_else, + pFunc, pParam); + break; + case S_PRIFILT: + if(stmt->d.s_prifilt.t_then != NULL) + scriptIterateAllActions(stmt->d.s_prifilt.t_then, + pFunc, pParam); + if(stmt->d.s_prifilt.t_else != NULL) + scriptIterateAllActions(stmt->d.s_prifilt.t_else, + pFunc, pParam); + break; + case S_PROPFILT: + scriptIterateAllActions(stmt->d.s_propfilt.t_then, + pFunc, pParam); + break; + default: + dbgprintf("error: unknown stmt type %u during iterateAll\n", + (unsigned) stmt->nodetype); + break; + } + } +} + +/* driver to iterate over all of this ruleset actions */ +typedef struct iterateAllActions_s { + rsRetVal (*pFunc)(void*, void*); + void *pParam; +} iterateAllActions_t; +/* driver to iterate over all actions */ +DEFFUNC_llExecFunc(doIterateAllActions) +{ + DEFiRet; + ruleset_t* pThis = (ruleset_t*) pData; + iterateAllActions_t *pMyParam = (iterateAllActions_t*) pParam; + scriptIterateAllActions(pThis->root, pMyParam->pFunc, pMyParam->pParam); + RETiRet; +} +/* iterate over ALL actions present in the WHOLE system. + * this is often needed, for example when HUP processing + * must be done or a shutdown is pending. + */ +static rsRetVal +iterateAllActions(rsconf_t *conf, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + iterateAllActions_t params; + DEFiRet; + assert(pFunc != NULL); + + params.pFunc = pFunc; + params.pParam = pParam; + CHKiRet(llExecFunc(&(conf->rulesets.llRulesets), doIterateAllActions, ¶ms)); + +finalize_it: + RETiRet; +} + + +/* This function is similar to processBatch(), but works on a batch that + * contains rules from multiple rulesets. In this case, we can not push + * the whole batch through the ruleset. Instead, we examine it and + * partition it into sub-rulesets which we then push through the system. + * rgerhards, 2010-06-15 + */ +static inline rsRetVal +processBatchMultiRuleset(batch_t *pBatch) +{ + ruleset_t *currRuleset; + batch_t snglRuleBatch; + int i; + int iStart; /* start index of partial batch */ + int iNew; /* index for new (temporary) batch */ + int bHaveUnprocessed; /* do we (still) have unprocessed entries? (loop term predicate) */ + DEFiRet; + + do { + bHaveUnprocessed = 0; + /* search for first unprocessed element */ + for(iStart = 0 ; iStart < pBatch->nElem && pBatch->eltState[iStart] == BATCH_STATE_DISC ; ++iStart) + /* just search, no action */; + if(iStart == pBatch->nElem) + break; /* everything processed */ + + /* prepare temporary batch */ + CHKiRet(batchInit(&snglRuleBatch, pBatch->nElem)); + snglRuleBatch.pbShutdownImmediate = pBatch->pbShutdownImmediate; + currRuleset = batchElemGetRuleset(pBatch, iStart); + iNew = 0; + for(i = iStart ; i < pBatch->nElem ; ++i) { + if(batchElemGetRuleset(pBatch, i) == currRuleset) { + /* for performance reasons, we copy only those members that we actually need */ + snglRuleBatch.pElem[iNew].pMsg = pBatch->pElem[i].pMsg; + snglRuleBatch.eltState[iNew] = pBatch->eltState[i]; + ++iNew; + /* We indicate the element also as done, so it will not be processed again */ + pBatch->eltState[i] = BATCH_STATE_DISC; + } else { + bHaveUnprocessed = 1; + } + } + snglRuleBatch.nElem = iNew; /* was left just right by the for loop */ + batchSetSingleRuleset(&snglRuleBatch, 1); + /* process temp batch */ + processBatch(&snglRuleBatch); + batchFree(&snglRuleBatch); + } while(bHaveUnprocessed == 1); + +finalize_it: + RETiRet; +} + +/* return a new "active" structure for the batch. Free with freeActive(). */ +static inline sbool *newActive(batch_t *pBatch) +{ + return malloc(sizeof(sbool) * batchNumMsgs(pBatch)); + +} +static inline void freeActive(sbool *active) { free(active); } + + +/* for details, see scriptExec() header comment! */ +/* call action for all messages with filter on */ +static rsRetVal +execAct(struct cnfstmt *stmt, batch_t *pBatch, sbool *active) +{ + DEFiRet; +dbgprintf("RRRR: execAct [%s]: batch of %d elements, active %p\n", modGetName(stmt->d.act->pMod), batchNumMsgs(pBatch), active); + pBatch->active = active; + stmt->d.act->submitToActQ(stmt->d.act, pBatch); + RETiRet; +} + +static rsRetVal +execSet(struct cnfstmt *stmt, batch_t *pBatch, sbool *active) +{ + int i; + struct var result; + DEFiRet; + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + if( pBatch->eltState[i] != BATCH_STATE_DISC + && (active == NULL || active[i])) { + cnfexprEval(stmt->d.s_set.expr, &result, pBatch->pElem[i].pMsg); + msgSetJSONFromVar(pBatch->pElem[i].pMsg, stmt->d.s_set.varname, + &result); + varDelete(&result); + } + } + RETiRet; +} + +static rsRetVal +execUnset(struct cnfstmt *stmt, batch_t *pBatch, sbool *active) +{ + int i; + DEFiRet; + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + if( pBatch->eltState[i] != BATCH_STATE_DISC + && (active == NULL || active[i])) { + msgUnsetJSON(pBatch->pElem[i].pMsg, stmt->d.s_unset.varname); + } + } + RETiRet; +} + +/* for details, see scriptExec() header comment! */ +/* "stop" simply discards the filtered items - it's just a (hopefully more intuitive + * shortcut for users. + */ +static rsRetVal +execStop(batch_t *pBatch, sbool *active) +{ + int i; + DEFiRet; + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { + if( pBatch->eltState[i] != BATCH_STATE_DISC + && (active == NULL || active[i])) { + pBatch->eltState[i] = BATCH_STATE_DISC; + } + } + RETiRet; +} + +/* for details, see scriptExec() header comment! */ +// save current filter, evaluate new one +// perform then (if any message) +// if ELSE given: +// set new filter, inverted +// perform else (if any messages) +static rsRetVal +execIf(struct cnfstmt *stmt, batch_t *pBatch, sbool *active) +{ + sbool *newAct; + int i; + sbool bRet; + sbool allInactive = 1; + DEFiRet; + newAct = newActive(pBatch); + for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) { + if(*(pBatch->pbShutdownImmediate)) + FINALIZE; + if(pBatch->eltState[i] == BATCH_STATE_DISC) + continue; /* will be ignored in any case */ + if(active == NULL || active[i]) { + bRet = cnfexprEvalBool(stmt->d.s_if.expr, pBatch->pElem[i].pMsg); + allInactive = 0; + } else + bRet = 0; + newAct[i] = bRet; + DBGPRINTF("batch: item %d: expr eval: %d\n", i, bRet); + } + + if(allInactive) { + DBGPRINTF("execIf: all batch elements are inactive, holding execution\n"); + freeActive(newAct); + FINALIZE; + } + + if(stmt->d.s_if.t_then != NULL) { + scriptExec(stmt->d.s_if.t_then, pBatch, newAct); + } + if(stmt->d.s_if.t_else != NULL) { + for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) { + if(*(pBatch->pbShutdownImmediate)) + FINALIZE; + if(pBatch->eltState[i] != BATCH_STATE_DISC + && (active == NULL || active[i])) + newAct[i] = !newAct[i]; + } + scriptExec(stmt->d.s_if.t_else, pBatch, newAct); + } + freeActive(newAct); +finalize_it: + RETiRet; +} + +/* for details, see scriptExec() header comment! */ +static void +execPRIFILT(struct cnfstmt *stmt, batch_t *pBatch, sbool *active) +{ + sbool *newAct; + msg_t *pMsg; + int bRet; + int i; + newAct = newActive(pBatch); + for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) { + if(*(pBatch->pbShutdownImmediate)) + return; + if(pBatch->eltState[i] == BATCH_STATE_DISC) + continue; /* will be ignored in any case */ + pMsg = pBatch->pElem[i].pMsg; + if(active == NULL || active[i]) { + if( (stmt->d.s_prifilt.pmask[pMsg->iFacility] == TABLE_NOPRI) || + ((stmt->d.s_prifilt.pmask[pMsg->iFacility] + & (1<<pMsg->iSeverity)) == 0) ) + bRet = 0; + else + bRet = 1; + } else + bRet = 0; + newAct[i] = bRet; + DBGPRINTF("batch: item %d PRIFILT %d\n", i, newAct[i]); + } + + if(stmt->d.s_prifilt.t_then != NULL) { + scriptExec(stmt->d.s_prifilt.t_then, pBatch, newAct); + } + if(stmt->d.s_prifilt.t_else != NULL) { + for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) { + if(*(pBatch->pbShutdownImmediate)) + return; + if(pBatch->eltState[i] != BATCH_STATE_DISC + && (active == NULL || active[i])) + newAct[i] = !newAct[i]; + } + scriptExec(stmt->d.s_prifilt.t_else, pBatch, newAct); + } + freeActive(newAct); +} + + +/* helper to execPROPFILT(), as the evaluation itself is quite lengthy */ +static int +evalPROPFILT(struct cnfstmt *stmt, msg_t *pMsg) +{ + unsigned short pbMustBeFreed; + uchar *pszPropVal; + int bRet = 0; + rs_size_t propLen; + + if(stmt->d.s_propfilt.propID == PROP_INVALID) + goto done; + + pszPropVal = MsgGetProp(pMsg, NULL, stmt->d.s_propfilt.propID, + stmt->d.s_propfilt.propName, &propLen, + &pbMustBeFreed, NULL); + + /* Now do the compares (short list currently ;)) */ + switch(stmt->d.s_propfilt.operation ) { + case FIOP_CONTAINS: + if(rsCStrLocateInSzStr(stmt->d.s_propfilt.pCSCompValue, (uchar*) pszPropVal) != -1) + bRet = 1; + break; + case FIOP_ISEMPTY: + if(propLen == 0) + bRet = 1; /* process message! */ + break; + case FIOP_ISEQUAL: + if(rsCStrSzStrCmp(stmt->d.s_propfilt.pCSCompValue, + pszPropVal, propLen) == 0) + bRet = 1; /* process message! */ + break; + case FIOP_STARTSWITH: + if(rsCStrSzStrStartsWithCStr(stmt->d.s_propfilt.pCSCompValue, + pszPropVal, propLen) == 0) + bRet = 1; /* process message! */ + break; + case FIOP_REGEX: + if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue, + (unsigned char*) pszPropVal, 0, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK) + bRet = 1; + break; + case FIOP_EREREGEX: + if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue, + (unsigned char*) pszPropVal, 1, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK) + bRet = 1; + break; + default: + /* here, it handles NOP (for performance reasons) */ + assert(stmt->d.s_propfilt.operation == FIOP_NOP); + bRet = 1; /* as good as any other default ;) */ + break; + } + + /* now check if the value must be negated */ + if(stmt->d.s_propfilt.isNegated) + bRet = (bRet == 1) ? 0 : 1; + + if(Debug) { + char *cstr; + if(stmt->d.s_propfilt.propID == PROP_CEE) { + cstr = es_str2cstr(stmt->d.s_propfilt.propName, NULL); + DBGPRINTF("Filter: check for CEE property '%s' (value '%s') ", + cstr, pszPropVal); + free(cstr); + } else { + DBGPRINTF("Filter: check for property '%s' (value '%s') ", + propIDToName(stmt->d.s_propfilt.propID), pszPropVal); + } + if(stmt->d.s_propfilt.isNegated) + DBGPRINTF("NOT "); + if(stmt->d.s_propfilt.operation == FIOP_ISEMPTY) { + DBGPRINTF("%s : %s\n", + getFIOPName(stmt->d.s_propfilt.operation), + bRet ? "TRUE" : "FALSE"); + } else { + DBGPRINTF("%s '%s': %s\n", + getFIOPName(stmt->d.s_propfilt.operation), + rsCStrGetSzStrNoNULL(stmt->d.s_propfilt.pCSCompValue), + bRet ? "TRUE" : "FALSE"); + } + } + + /* cleanup */ + if(pbMustBeFreed) + free(pszPropVal); +done: + return bRet; +} + +/* for details, see scriptExec() header comment! */ +static void +execPROPFILT(struct cnfstmt *stmt, batch_t *pBatch, sbool *active) +{ + sbool *thenAct; + sbool bRet; + int i; + thenAct = newActive(pBatch); + for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) { + if(*(pBatch->pbShutdownImmediate)) + return; + if(pBatch->eltState[i] == BATCH_STATE_DISC) + continue; /* will be ignored in any case */ + if(active == NULL || active[i]) { + bRet = evalPROPFILT(stmt, pBatch->pElem[i].pMsg); + } else + bRet = 0; + thenAct[i] = bRet; + DBGPRINTF("batch: item %d PROPFILT %d\n", i, thenAct[i]); + } + + scriptExec(stmt->d.s_propfilt.t_then, pBatch, thenAct); + freeActive(thenAct); +} + +/* The rainerscript execution engine. It is debatable if that would be better + * contained in grammer/rainerscript.c, HOWEVER, that file focusses primarily + * on the parsing and object creation part. So as an actual executor, it is + * better suited here. + * param active: if NULL, all messages are active (to be processed), if non-null + * this is an array of the same size as the batch. If 1, the message + * is to be processed, otherwise not. + * NOTE: this function must receive batches which contain a single ruleset ONLY! + * rgerhards, 2012-09-04 + */ +static rsRetVal +scriptExec(struct cnfstmt *root, batch_t *pBatch, sbool *active) +{ + DEFiRet; + struct cnfstmt *stmt; + + for(stmt = root ; stmt != NULL ; stmt = stmt->next) { + if(Debug) { + dbgprintf("scriptExec: batch of %d elements, active %p, active[0]:%d\n", + batchNumMsgs(pBatch), active, (active == NULL ? 1 : active[0])); + cnfstmtPrintOnly(stmt, 2, 0); + } + switch(stmt->nodetype) { + case S_NOP: + break; + case S_STOP: + execStop(pBatch, active); + break; + case S_ACT: + execAct(stmt, pBatch, active); + break; + case S_SET: + execSet(stmt, pBatch, active); + break; + case S_UNSET: + execUnset(stmt, pBatch, active); + break; + case S_CALL: + scriptExec(stmt->d.s_call.stmt, pBatch, active); + break; + case S_IF: + execIf(stmt, pBatch, active); + break; + case S_PRIFILT: + execPRIFILT(stmt, pBatch, active); + break; + case S_PROPFILT: + execPROPFILT(stmt, pBatch, active); + break; + default: + dbgprintf("error: unknown stmt type %u during exec\n", + (unsigned) stmt->nodetype); + break; + } + } + RETiRet; +} + + +/* Process (consume) a batch of messages. Calls the actions configured. + * If the whole batch uses a singel ruleset, we can process the batch as + * a whole. Otherwise, we need to process it slower, on a message-by-message + * basis (what can be optimized to a per-ruleset basis) + * rgerhards, 2005-10-13 + */ +static rsRetVal +processBatch(batch_t *pBatch) +{ + ruleset_t *pThis; + DEFiRet; + assert(pBatch != NULL); + + DBGPRINTF("processBatch: batch of %d elements must be processed\n", pBatch->nElem); + if(pBatch->bSingleRuleset) { + pThis = batchGetRuleset(pBatch); + if(pThis == NULL) + pThis = ourConf->rulesets.pDflt; + ISOBJ_TYPE_assert(pThis, ruleset); + CHKiRet(scriptExec(pThis->root, pBatch, NULL)); + } else { + CHKiRet(processBatchMultiRuleset(pBatch)); + } + +finalize_it: + DBGPRINTF("ruleset.ProcessMsg() returns %d\n", iRet); + RETiRet; +} + + +/* return the ruleset-assigned parser list. NULL means use the default + * parser list. + * rgerhards, 2009-11-04 + */ +static parserList_t* +GetParserList(rsconf_t *conf, msg_t *pMsg) +{ + return (pMsg->pRuleset == NULL) ? conf->rulesets.pDflt->pParserLst : pMsg->pRuleset->pParserLst; +} + + +/* Add a script block to the current ruleset */ +static void +addScript(ruleset_t *pThis, struct cnfstmt *script) +{ + if(pThis->last == NULL) + pThis->root = pThis->last = script; + else { + pThis->last->next = script; + pThis->last = script; + } +} + + +/* set name for ruleset */ +static rsRetVal rulesetSetName(ruleset_t *pThis, uchar *pszName) +{ + DEFiRet; + free(pThis->pszName); + CHKmalloc(pThis->pszName = ustrdup(pszName)); + +finalize_it: + RETiRet; +} + + +/* get current ruleset + * We use a non-standard calling interface, as nothing can go wrong and it + * is really much more natural to return the pointer directly. + */ +static ruleset_t* +GetCurrent(rsconf_t *conf) +{ + return conf->rulesets.pCurr; +} + + +/* get main queue associated with ruleset. If no ruleset-specifc main queue + * is set, the primary main message queue is returned. + * We use a non-standard calling interface, as nothing can go wrong and it + * is really much more natural to return the pointer directly. + */ +static qqueue_t* +GetRulesetQueue(ruleset_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, ruleset); + return (pThis->pQueue == NULL) ? pMsgQueue : pThis->pQueue; +} + + +/* Find the ruleset with the given name and return a pointer to its object. + */ +rsRetVal +rulesetGetRuleset(rsconf_t *conf, ruleset_t **ppRuleset, uchar *pszName) +{ + DEFiRet; + assert(ppRuleset != NULL); + assert(pszName != NULL); + + CHKiRet(llFind(&(conf->rulesets.llRulesets), pszName, (void*) ppRuleset)); + +finalize_it: + RETiRet; +} + + +/* Set a new default rule set. If the default can not be found, no change happens. + */ +static rsRetVal +SetDefaultRuleset(rsconf_t *conf, uchar *pszName) +{ + ruleset_t *pRuleset; + DEFiRet; + assert(pszName != NULL); + + CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName)); + conf->rulesets.pDflt = pRuleset; + DBGPRINTF("default rule set changed to %p: '%s'\n", pRuleset, pszName); + +finalize_it: + RETiRet; +} + + +/* Set a new current rule set. If the ruleset can not be found, no change happens */ +static rsRetVal +SetCurrRuleset(rsconf_t *conf, uchar *pszName) +{ + ruleset_t *pRuleset; + DEFiRet; + assert(pszName != NULL); + + CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName)); + conf->rulesets.pCurr = pRuleset; + DBGPRINTF("current rule set changed to %p: '%s'\n", pRuleset, pszName); + +finalize_it: + RETiRet; +} + + +/* Standard-Constructor + */ +BEGINobjConstruct(ruleset) /* be sure to specify the object type also in END macro! */ + pThis->root = NULL; + pThis->last = NULL; +ENDobjConstruct(ruleset) + + +/* ConstructionFinalizer + * This also adds the rule set to the list of all known rulesets. + */ +static rsRetVal +rulesetConstructFinalize(rsconf_t *conf, ruleset_t *pThis) +{ + uchar *keyName; + DEFiRet; + ISOBJ_TYPE_assert(pThis, ruleset); + + /* we must duplicate our name, as the key destructer would also + * free it, resulting in a double-free. It's also cleaner to have + * two separate copies. + */ + CHKmalloc(keyName = ustrdup(pThis->pszName)); + CHKiRet(llAppend(&(conf->rulesets.llRulesets), keyName, pThis)); + + /* and also the default, if so far none has been set */ + if(conf->rulesets.pDflt == NULL) + conf->rulesets.pDflt = pThis; + +finalize_it: + RETiRet; +} + + +/* destructor for the ruleset object */ +BEGINobjDestruct(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(ruleset) + DBGPRINTF("destructing ruleset %p, name %p\n", pThis, pThis->pszName); + if(pThis->pQueue != NULL) { + qqueueDestruct(&pThis->pQueue); + } + if(pThis->pParserLst != NULL) { + parser.DestructParserList(&pThis->pParserLst); + } + free(pThis->pszName); + cnfstmtDestruct(pThis->root); +ENDobjDestruct(ruleset) + + +/* destruct ALL rule sets that reside in the system. This must + * be callable before unloading this module as the module may + * not be unloaded before unload of the actions is required. This is + * kind of a left-over from previous logic and may be optimized one + * everything runs stable again. -- rgerhards, 2009-06-10 + */ +static rsRetVal +destructAllActions(rsconf_t *conf) +{ + DEFiRet; + + CHKiRet(llDestroy(&(conf->rulesets.llRulesets))); + CHKiRet(llInit(&(conf->rulesets.llRulesets), rulesetDestructForLinkedList, rulesetKeyDestruct, strcasecmp)); + conf->rulesets.pDflt = NULL; + +finalize_it: + RETiRet; +} + +/* this is a special destructor for the linkedList class. LinkedList does NOT + * provide a pointer to the pointer, but rather the raw pointer itself. So we + * must map this, otherwise the destructor will abort. + */ +rsRetVal +rulesetDestructForLinkedList(void *pData) +{ + ruleset_t *pThis = (ruleset_t*) pData; + return rulesetDestruct(&pThis); +} + +/* debugprint for the ruleset object */ +BEGINobjDebugPrint(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(ruleset) + dbgoprint((obj_t*) pThis, "rsyslog ruleset %s:\n", pThis->pszName); + cnfstmtPrint(pThis->root, 0); + dbgoprint((obj_t*) pThis, "ruleset %s assigned parser list:\n", pThis->pszName); + printParserList(pThis->pParserLst); +ENDobjDebugPrint(ruleset) + + +/* helper for debugPrintAll(), prints a single ruleset */ +DEFFUNC_llExecFunc(doDebugPrintAll) +{ + return rulesetDebugPrint((ruleset_t*) pData); +} +/* debug print all rulesets + */ +static rsRetVal +debugPrintAll(rsconf_t *conf) +{ + DEFiRet; + dbgprintf("All Rulesets:\n"); + llExecFunc(&(conf->rulesets.llRulesets), doDebugPrintAll, NULL); + dbgprintf("End of Rulesets.\n"); + RETiRet; +} + +static inline void +rulesetOptimize(ruleset_t *pRuleset) +{ + if(Debug) { + dbgprintf("ruleset '%s' before optimization:\n", + pRuleset->pszName); + rulesetDebugPrint((ruleset_t*) pRuleset); + } + cnfstmtOptimize(pRuleset->root); + if(Debug) { + dbgprintf("ruleset '%s' after optimization:\n", + pRuleset->pszName); + rulesetDebugPrint((ruleset_t*) pRuleset); + } +} + +/* helper for rulsetOptimizeAll(), optimizes a single ruleset */ +DEFFUNC_llExecFunc(doRulesetOptimizeAll) +{ + rulesetOptimize((ruleset_t*) pData); + return RS_RET_OK; +} +/* optimize all rulesets + */ +rsRetVal +rulesetOptimizeAll(rsconf_t *conf) +{ + DEFiRet; + dbgprintf("begin ruleset optimization phase\n"); + llExecFunc(&(conf->rulesets.llRulesets), doRulesetOptimizeAll, NULL); + dbgprintf("ruleset optimization phase finished.\n"); + RETiRet; +} + + +/* Create a ruleset-specific "main" queue for this ruleset. If one is already + * defined, an error message is emitted but nothing else is done. + * Note: we use the main message queue parameters for queue creation and access + * syslogd.c directly to obtain these. This is far from being perfect, but + * considered acceptable for the time being. + * rgerhards, 2009-10-27 + */ +static inline rsRetVal +doRulesetCreateQueue(rsconf_t *conf, int *pNewVal) +{ + uchar *rsname; + DEFiRet; + + if(conf->rulesets.pCurr == NULL) { + errmsg.LogError(0, RS_RET_NO_CURR_RULESET, "error: currently no specific ruleset specified, thus a " + "queue can not be added to it"); + ABORT_FINALIZE(RS_RET_NO_CURR_RULESET); + } + + if(conf->rulesets.pCurr->pQueue != NULL) { + errmsg.LogError(0, RS_RET_RULES_QUEUE_EXISTS, "error: ruleset already has a main queue, can not " + "add another one"); + ABORT_FINALIZE(RS_RET_RULES_QUEUE_EXISTS); + } + + if(pNewVal == 0) + FINALIZE; /* if it is turned off, we do not need to change anything ;) */ + + rsname = (conf->rulesets.pCurr->pszName == NULL) ? (uchar*) "[ruleset]" : conf->rulesets.pCurr->pszName; + DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s'\n", rsname); + CHKiRet(createMainQueue(&conf->rulesets.pCurr->pQueue, rsname, NULL)); + +finalize_it: + RETiRet; +} + +static rsRetVal +rulesetCreateQueue(void __attribute__((unused)) *pVal, int *pNewVal) +{ + return doRulesetCreateQueue(ourConf, pNewVal); +} + +/* Add a ruleset specific parser to the ruleset. Note that adding the first + * parser automatically disables the default parsers. If they are needed as well, + * the must be added via explicit config directives. + * Note: this is the only spot in the code that requires the parser object. In order + * to solve some class init bootstrap sequence problems, we get the object handle here + * instead of during module initialization. Note that objUse() is capable of being + * called multiple times. + * rgerhards, 2009-11-04 + */ +static rsRetVal +doRulesetAddParser(ruleset_t *pRuleset, uchar *pName) +{ + parser_t *pParser; + DEFiRet; + + CHKiRet(objUse(parser, CORE_COMPONENT)); + iRet = parser.FindParser(&pParser, pName); + if(iRet == RS_RET_PARSER_NOT_FOUND) { + errmsg.LogError(0, RS_RET_PARSER_NOT_FOUND, "error: parser '%s' unknown at this time " + "(maybe defined too late in rsyslog.conf?)", pName); + ABORT_FINALIZE(RS_RET_NO_CURR_RULESET); + } else if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error trying to find parser '%s'\n", pName); + FINALIZE; + } + + CHKiRet(parser.AddParserToList(&pRuleset->pParserLst, pParser)); + + DBGPRINTF("added parser '%s' to ruleset '%s'\n", pName, pRuleset->pszName); + +finalize_it: + d_free(pName); /* no longer needed */ + + RETiRet; +} + +static rsRetVal +rulesetAddParser(void __attribute__((unused)) *pVal, uchar *pName) +{ + return doRulesetAddParser(ourConf->rulesets.pCurr, pName); +} + + +/* Process ruleset() objects */ +rsRetVal +rulesetProcessCnf(struct cnfobj *o) +{ + struct cnfparamvals *pvals; + rsRetVal localRet; + uchar *rsName = NULL; + uchar *parserName; + int nameIdx, parserIdx; + ruleset_t *pRuleset; + struct cnfarray *ar; + int i; + uchar *rsname; + DEFiRet; + + pvals = nvlstGetParams(o->nvlst, &rspblk, NULL); + if(pvals == NULL) { + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + DBGPRINTF("ruleset param blk after rulesetProcessCnf:\n"); + cnfparamsPrint(&rspblk, pvals); + nameIdx = cnfparamGetIdx(&rspblk, "name"); + rsName = (uchar*)es_str2cstr(pvals[nameIdx].val.d.estr, NULL); + localRet = rulesetGetRuleset(loadConf, &pRuleset, rsName); + if(localRet == RS_RET_OK) { + errmsg.LogError(0, RS_RET_RULESET_EXISTS, + "error: ruleset '%s' specified more than once", + rsName); + cnfstmtDestruct(o->script); + ABORT_FINALIZE(RS_RET_RULESET_EXISTS); + } else if(localRet != RS_RET_NOT_FOUND) { + ABORT_FINALIZE(localRet); + } + CHKiRet(rulesetConstruct(&pRuleset)); + CHKiRet(rulesetSetName(pRuleset, rsName)); + CHKiRet(rulesetConstructFinalize(loadConf, pRuleset)); + addScript(pRuleset, o->script); + + /* we have only two params, so we do NOT do the usual param loop */ + parserIdx = cnfparamGetIdx(&rspblk, "parser"); + if(parserIdx != -1 && pvals[parserIdx].bUsed) { + ar = pvals[parserIdx].val.d.ar; + for(i = 0 ; i < ar->nmemb ; ++i) { + parserName = (uchar*)es_str2cstr(ar->arr[i], NULL); + doRulesetAddParser(pRuleset, parserName); + free(parserName); + } + } + + /* pick up ruleset queue parameters */ + if(queueCnfParamsSet(o->nvlst)) { + rsname = (pRuleset->pszName == NULL) ? (uchar*) "[ruleset]" : pRuleset->pszName; + DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s'\n", rsname); + CHKiRet(createMainQueue(&pRuleset->pQueue, rsname, o->nvlst)); + } + +finalize_it: + free(rsName); + cnfparamvalsDestruct(pvals, &rspblk); + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(ruleset) +CODESTARTobjQueryInterface(ruleset) + if(pIf->ifVersion != rulesetCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = rulesetConstruct; + pIf->ConstructFinalize = rulesetConstructFinalize; + pIf->Destruct = rulesetDestruct; + pIf->DebugPrint = rulesetDebugPrint; + + pIf->IterateAllActions = iterateAllActions; + pIf->DestructAllActions = destructAllActions; + pIf->AddScript = addScript; + pIf->ProcessBatch = processBatch; + pIf->SetName = rulesetSetName; + pIf->DebugPrintAll = debugPrintAll; + pIf->GetCurrent = GetCurrent; + pIf->GetRuleset = rulesetGetRuleset; + pIf->SetDefaultRuleset = SetDefaultRuleset; + pIf->SetCurrRuleset = SetCurrRuleset; + pIf->GetRulesetQueue = GetRulesetQueue; + pIf->GetParserList = GetParserList; +finalize_it: +ENDobjQueryInterface(ruleset) + + +/* Exit the ruleset class. + * rgerhards, 2009-04-06 + */ +BEGINObjClassExit(ruleset, OBJ_IS_CORE_MODULE) /* class, version */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); +ENDObjClassExit(ruleset) + + +/* Initialize the ruleset class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(ruleset, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, rulesetDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, rulesetConstructFinalize); + + /* config file handlers */ + CHKiRet(regCfSysLineHdlr((uchar *)"rulesetparser", 0, eCmdHdlrGetWord, rulesetAddParser, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"rulesetcreatemainqueue", 0, eCmdHdlrBinary, rulesetCreateQueue, NULL, NULL)); +ENDObjClassInit(ruleset) + +/* vi:set ai: + */ diff --git a/runtime/ruleset.h b/runtime/ruleset.h new file mode 100644 index 00000000..cbf8243b --- /dev/null +++ b/runtime/ruleset.h @@ -0,0 +1,106 @@ +/* The ruleset object. + * + * This implements rulesets within rsyslog. + * + * Copyright 2009-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_RULESET_H +#define INCLUDED_RULESET_H + +#include "queue.h" +#include "linkedlist.h" +#include "rsconf.h" + +/* the ruleset object */ +struct ruleset_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pszName; /* name of our ruleset */ + qqueue_t *pQueue; /* "main" message queue, if the ruleset has its own (else NULL) */ + struct cnfstmt *root; + struct cnfstmt *last; + parserList_t *pParserLst;/* list of parsers to use for this ruleset */ +}; + +/* interfaces */ +BEGINinterface(ruleset) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(ruleset); + rsRetVal (*DebugPrintAll)(rsconf_t *conf); + rsRetVal (*Construct)(ruleset_t **ppThis); + rsRetVal (*ConstructFinalize)(rsconf_t *conf, ruleset_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(ruleset_t **ppThis); + rsRetVal (*DestructAllActions)(rsconf_t *conf); + rsRetVal (*SetName)(ruleset_t *pThis, uchar *pszName); + rsRetVal (*ProcessBatch)(batch_t*); + rsRetVal (*GetRuleset)(rsconf_t *conf, ruleset_t **ppThis, uchar*); + rsRetVal (*SetDefaultRuleset)(rsconf_t *conf, uchar*); + rsRetVal (*SetCurrRuleset)(rsconf_t *conf, uchar*); + ruleset_t* (*GetCurrent)(rsconf_t *conf); + qqueue_t* (*GetRulesetQueue)(ruleset_t*); + /* v3, 2009-11-04 */ + parserList_t* (*GetParserList)(rsconf_t *conf, msg_t *); + /* v5, 2011-04-19 + * added support for the rsconf object -- fundamental change + * v6, 2011-07-15 + * removed conf ptr from SetName, AddRule as the flex/bison based + * system uses globals in any case. + */ + /* v7, 2012-09-04 */ + /* AddRule() removed */ + /*TODO:REMOVE*/rsRetVal (*IterateAllActions)(rsconf_t *conf, rsRetVal (*pFunc)(void*, void*), void* pParam); + void (*AddScript)(ruleset_t *pThis, struct cnfstmt *script); +ENDinterface(ruleset) +#define rulesetCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(ruleset); + +/* TODO: remove these -- currently done dirty for config file + * redo -- rgerhards, 2011-04-19 + * rgerhards, 2012-04-19: actually, it may be way cooler not to remove + * them and use plain c-style conventions at least inside core objects. + */ +rsRetVal rulesetDestructForLinkedList(void *pData); +rsRetVal rulesetKeyDestruct(void __attribute__((unused)) *pData); + +/* Get name associated to ruleset. This function cannot fail (except, + * of course, if previously something went really wrong). Returned + * pointer is read-only. + * rgerhards, 2012-04-18 + */ +static inline uchar* +rulesetGetName(ruleset_t *pRuleset) +{ + return pRuleset->pszName; +} + + +/* we will most probably convert this module back to traditional C + * calling sequence, so here we go... + */ +rsRetVal rulesetGetRuleset(rsconf_t *conf, ruleset_t **ppRuleset, uchar *pszName); +rsRetVal rulesetOptimizeAll(rsconf_t *conf); +rsRetVal rulesetProcessCnf(struct cnfobj *o); + +/* Set a current rule set to already-known pointer */ +static inline void +rulesetSetCurrRulesetPtr(ruleset_t *pRuleset) { + loadConf->rulesets.pCurr = pRuleset; +} +#endif /* #ifndef INCLUDED_RULESET_H */ diff --git a/runtime/sd-daemon.c b/runtime/sd-daemon.c new file mode 100644 index 00000000..79d8ca37 --- /dev/null +++ b/runtime/sd-daemon.c @@ -0,0 +1,533 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#ifdef __BIONIC__ +# include <linux/fcntl.h> +#else +# include <sys/fcntl.h> +#endif +#include <netinet/in.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <stddef.h> +#include <limits.h> + +#if defined(__linux__) +# include <mqueue.h> +#endif + +#include "sd-daemon.h" + +#if (__GNUC__ >= 4) +# ifdef SD_EXPORT_SYMBOLS +/* Export symbols */ +# define _sd_export_ __attribute__ ((visibility("default"))) +# else +/* Don't export the symbols */ +# define _sd_export_ __attribute__ ((visibility("hidden"))) +# endif +#else +# define _sd_export_ +#endif + +_sd_export_ int sd_listen_fds(int unset_environment) { + +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + int r, fd; + const char *e; + char *p = NULL; + unsigned long l; + + e = getenv("LISTEN_PID"); + if (!e) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || p == e || *p || l <= 0) { + r = -EINVAL; + goto finish; + } + + /* Is this for us? */ + if (getpid() != (pid_t) l) { + r = 0; + goto finish; + } + + e = getenv("LISTEN_FDS"); + if (!e) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || p == e || *p) { + r = -EINVAL; + goto finish; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) { + int flags; + + flags = fcntl(fd, F_GETFD); + if (flags < 0) { + r = -errno; + goto finish; + } + + if (flags & FD_CLOEXEC) + continue; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + r = -errno; + goto finish; + } + } + + r = (int) l; + +finish: + if (unset_environment) { + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + } + + return r; +#endif +} + +_sd_export_ int sd_is_fifo(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISFIFO(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + } + + return 1; +} + +_sd_export_ int sd_is_special(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) + return st_path.st_rdev == st_fd.st_rdev; + else + return 0; + } + + return 1; +} + +static int sd_is_socket_internal(int fd, int type, int listening) { + struct stat st_fd; + + if (fd < 0 || type < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; +}; + +_sd_export_ int sd_is_socket(int fd, int family, int type, int listening) { + int r; + + if (family < 0) + return -EINVAL; + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (family > 0) { + union sockaddr_union sockaddr; + socklen_t l; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + return sockaddr.sa.sa_family == family; + } + + return 1; +} + +_sd_export_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if (family != 0 && family != AF_INET && family != AF_INET6) + return -EINVAL; + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_INET && + sockaddr.sa.sa_family != AF_INET6) + return 0; + + if (family > 0) + if (sockaddr.sa.sa_family != family) + return 0; + + if (port > 0) { + if (sockaddr.sa.sa_family == AF_INET) { + if (l < sizeof(struct sockaddr_in)) + return -EINVAL; + + return htons(port) == sockaddr.in4.sin_port; + } else { + if (l < sizeof(struct sockaddr_in6)) + return -EINVAL; + + return htons(port) == sockaddr.in6.sin6_port; + } + } + + return 1; +} + +_sd_export_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_UNIX) + return 0; + + if (path) { + if (length == 0) + length = strlen(path); + + if (length == 0) + /* Unnamed socket */ + return l == offsetof(struct sockaddr_un, sun_path); + + if (path[0]) + /* Normal path socket */ + return + (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && + memcmp(path, sockaddr.un.sun_path, length+1) == 0; + else + /* Abstract namespace socket */ + return + (l == offsetof(struct sockaddr_un, sun_path) + length) && + memcmp(path, sockaddr.un.sun_path, length) == 0; + } + + return 1; +} + +_sd_export_ int sd_is_mq(int fd, const char *path) { +#if !defined(__linux__) + return 0; +#else + struct mq_attr attr; + + if (fd < 0) + return -EINVAL; + + if (mq_getattr(fd, &attr) < 0) + return -errno; + + if (path) { + char fpath[PATH_MAX]; + struct stat a, b; + + if (path[0] != '/') + return -EINVAL; + + if (fstat(fd, &a) < 0) + return -errno; + + strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); + fpath[sizeof(fpath)-1] = 0; + + if (stat(fpath, &b) < 0) + return -errno; + + if (a.st_dev != b.st_dev || + a.st_ino != b.st_ino) + return 0; + } + + return 1; +#endif +} + +_sd_export_ int sd_notify(int unset_environment, const char *state) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC) + return 0; +#else + int fd = -1, r; + struct msghdr msghdr; + struct iovec iovec; + union sockaddr_union sockaddr; + const char *e; + + if (!state) { + r = -EINVAL; + goto finish; + } + + e = getenv("NOTIFY_SOCKET"); + if (!e) + return 0; + + /* Must be an abstract socket, or an absolute path */ + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + r = -EINVAL; + goto finish; + } + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) { + r = -errno; + goto finish; + } + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sa.sa_family = AF_UNIX; + strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); + + if (sockaddr.un.sun_path[0] == '@') + sockaddr.un.sun_path[0] = 0; + + memset(&iovec, 0, sizeof(iovec)); + iovec.iov_base = (char*) state; + iovec.iov_len = strlen(state); + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &sockaddr; + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); + + if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) + msghdr.msg_namelen = sizeof(struct sockaddr_un); + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) { + r = -errno; + goto finish; + } + + r = 1; + +finish: + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + if (fd >= 0) + close(fd); + + return r; +#endif +} + +_sd_export_ int sd_notifyf(int unset_environment, const char *format, ...) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + va_list ap; + char *p = NULL; + int r; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + + r = sd_notify(unset_environment, p); + free(p); + + return r; +#endif +} + +_sd_export_ int sd_booted(void) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + struct stat st; + + /* We test whether the runtime unit file directory has been + * created. This takes place in mount-setup.c, so is + * guaranteed to happen very early during boot. */ + + if (lstat("/run/systemd/system/", &st) < 0) + return 0; + + return !!S_ISDIR(st.st_mode); +#endif +} diff --git a/runtime/sd-daemon.h b/runtime/sd-daemon.h new file mode 100644 index 00000000..fb7456d5 --- /dev/null +++ b/runtime/sd-daemon.h @@ -0,0 +1,282 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosddaemonhfoo +#define foosddaemonhfoo + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <sys/types.h> +#include <inttypes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Reference implementation of a few systemd related interfaces for + writing daemons. These interfaces are trivial to implement. To + simplify porting we provide this reference implementation. + Applications are welcome to reimplement the algorithms described + here if they do not want to include these two source files. + + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + You may compile this with -DDISABLE_SYSTEMD to disable systemd + support. This makes all those calls NOPs that are directly related to + systemd (i.e. only sd_is_xxx() will stay useful). + + Since this is drop-in code we don't want any of our symbols to be + exported in any case. Hence we declare hidden visibility for all of + them. + + You may find an up-to-date version of these source files online: + + http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h + http://cgit.freedesktop.org/systemd/systemd/plain/src/libsystemd-daemon/sd-daemon.c + + This should compile on non-Linux systems, too, but with the + exception of the sd_is_xxx() calls all functions will become NOPs. + + See sd-daemon(3) for more information. +*/ + +#ifndef _sd_printf_attr_ +#if __GNUC__ >= 4 +#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#else +#define _sd_printf_attr_(a,b) +#endif +#endif + +/* + Log levels for usage on stderr: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + This is similar to printk() usage in the kernel. +*/ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Returns how many file descriptors have been passed, or a negative + errno code on failure. Optionally, removes the $LISTEN_FDS and + $LISTEN_PID file descriptors from the environment (recommended, but + problematic in threaded environments). If r is the return value of + this function you'll find the file descriptors passed as fds + SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative + errno style error code on failure. This function call ensures that + the FD_CLOEXEC flag is set for the passed file descriptors, to make + sure they are not passed on to child processes. If FD_CLOEXEC shall + not be set, the caller needs to unset it after this call for all file + descriptors that are used. + + See sd_listen_fds(3) for more information. +*/ +int sd_listen_fds(int unset_environment); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a FIFO in the file system stored under the + specified path, 0 otherwise. If path is NULL a path name check will + not be done and the call only verifies if the file descriptor + refers to a FIFO. Returns a negative errno style error code on + failure. + + See sd_is_fifo(3) for more information. +*/ +int sd_is_fifo(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a special character device on the file + system stored under the specified path, 0 otherwise. + If path is NULL a path name check will not be done and the call + only verifies if the file descriptor refers to a special character. + Returns a negative errno style error code on failure. + + See sd_is_special(3) for more information. +*/ +int sd_is_special(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of the specified family (AF_INET, + ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If + family is 0 a socket family check will not be done. If type is 0 a + socket type check will not be done and the call only verifies if + the file descriptor refers to a socket. If listening is > 0 it is + verified that the socket is in listening mode. (i.e. listen() has + been called) If listening is == 0 it is verified that the socket is + not in listening mode. If listening is < 0 no listening mode check + is done. Returns a negative errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int family, int type, int listening); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an Internet socket, of the specified family + (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, + SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version + check is not done. If type is 0 a socket type check will not be + done. If port is 0 a socket port check will not be done. The + listening flag is used the same way as in sd_is_socket(). Returns a + negative errno style error code on failure. + + See sd_is_socket_inet(3) for more information. +*/ +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an AF_UNIX socket of the specified type + (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 + a socket type check will not be done. If path is NULL a socket path + check will not be done. For normal AF_UNIX sockets set length to + 0. For abstract namespace sockets set length to the length of the + socket name (including the initial 0 byte), and pass the full + socket path in path (including the initial 0 byte). The listening + flag is used the same way as in sd_is_socket(). Returns a negative + errno style error code on failure. + + See sd_is_socket_unix(3) for more information. +*/ +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a POSIX Message Queue of the specified name, + 0 otherwise. If path is NULL a message queue name check is not + done. Returns a negative errno style error code on failure. +*/ +int sd_is_mq(int fd, const char *path); + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + READY=1 Tells systemd that daemon startup is finished (only + relevant for services of Type=notify). The passed + argument is a boolean "1" or "0". Since there is + little value in signaling non-readiness the only + value daemons should send is "READY=1". + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-from + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + MAINPID=... The main pid of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + WATCHDOG=1 Tells systemd to update the watchdog timestamp. + Services using this feature should do this in + regular intervals. A watchdog framework can use the + timestamps to detect failed services. + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ +int sd_notify(int unset_environment, const char *state); + +/* + Similar to sd_notify() but takes a format string. + + Example 1: A daemon could send the following after initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + Example 2: A daemon could send the following shortly before + exiting, on failure: + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + See sd_notifyf(3) for more information. +*/ +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3); + +/* + Returns > 0 if the system was booted with systemd. Returns < 0 on + error. Returns 0 if the system was not booted with systemd. Note + that all of the functions above handle non-systemd boots just + fine. You should NOT protect them with a call to this function. Also + note that this function checks whether the system, not the user + session is controlled by systemd. However the functions above work + for both user and system services. + + See sd_booted(3) for more information. +*/ +int sd_booted(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/runtime/sigprov.h b/runtime/sigprov.h new file mode 100644 index 00000000..82587b7d --- /dev/null +++ b/runtime/sigprov.h @@ -0,0 +1,37 @@ +/* The interface definition for (file) signature providers. + * + * This is just an abstract driver interface, which needs to be + * implemented by concrete classes. + * + * Copyright 2013 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_SIGPROV_H +#define INCLUDED_SIGPROV_H + +/* interface */ +BEGINinterface(sigprov) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(void *ppThis); + rsRetVal (*SetCnfParam)(void *ppThis, struct nvlst *lst); + rsRetVal (*Destruct)(void *ppThis); + rsRetVal (*OnFileOpen)(void *pThis, uchar *fn, void *pFileInstData); + rsRetVal (*OnRecordWrite)(void *pFileInstData, uchar *rec, rs_size_t lenRec); + rsRetVal (*OnFileClose)(void *pFileInstData); +ENDinterface(sigprov) +#define sigprovCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ +#endif /* #ifndef INCLUDED_SIGPROV_H */ diff --git a/runtime/srUtils.h b/runtime/srUtils.h new file mode 100644 index 00000000..8626a4bb --- /dev/null +++ b/runtime/srUtils.h @@ -0,0 +1,112 @@ +/*! \file srUtils.h + * \brief General, small utilities that fit nowhere else. + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2003-09-09 + * Coding begun. + * + * Copyright 2003-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __SRUTILS_H_INCLUDED__ +#define __SRUTILS_H_INCLUDED__ 1 + + +/* syslog names */ +#ifndef LOG_MAKEPRI +# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) +#endif +#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ +#define TABLE_NOPRI 0 /* Value to indicate no priority in f_pmask */ +#define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ +#define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ + +typedef struct syslogName_s { + char *c_name; + int c_val; +} syslogName_t; + +extern syslogName_t syslogPriNames[]; +extern syslogName_t syslogFacNames[]; + +/** + * A reimplementation of itoa(), as this is not available + * on all platforms. We used the chance to make an interface + * that fits us well, so it is no longer plain itoa(). + * + * This method works with the US-ASCII alphabet. If you port this + * to e.g. EBCDIC, you need to make a small adjustment. Keep in mind, + * that on the wire it MUST be US-ASCII, so basically all you need + * to do is replace the constant '0' with 0x30 ;). + * + * \param pBuf Caller-provided buffer that will receive the + * generated ASCII string. + * + * \param iLenBuf Length of the caller-provided buffer. + * + * \param iToConv The integer to be converted. + */ +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv); + +/** + * A method to duplicate a string for which the length is known. + * Len must be the length in characters WITHOUT the trailing + * '\0' byte. + * rgerhards, 2007-07-10 + */ +unsigned char *srUtilStrDup(unsigned char *pOld, size_t len); +/** + * A method to create a directory and all its missing parents for + * a given file name. Please not that the rightmost element is + * considered to be a file name and thus NO directory is being created + * for it. + * added 2007-07-17 by rgerhards + */ +int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, uid_t uid, gid_t gid, int bFailOnChown); +int execProg(uchar *program, int bWait, uchar *arg); +void skipWhiteSpace(uchar **pp); +rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, + size_t lenFName, long lNum, int lNumDigits); +int getNumberDigits(long lNum); +rsRetVal timeoutComp(struct timespec *pt, long iTimeout); +long timeoutVal(struct timespec *pt); +void mutexCancelCleanup(void *arg); +void srSleep(int iSeconds, int iuSeconds); +char *rs_strerror_r(int errnum, char *buf, size_t buflen); +int decodeSyslogName(uchar *name, syslogName_t *codetab); +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); +rsRetVal getFileSize(uchar *pszName, off_t *pSize); +int containsGlobWildcard(char *str); + +/* mutex operations */ +/* some useful constants */ +#define DEFVARS_mutexProtection\ + int bLockedOpIsLocked=0 +#define BEGIN_MTX_PROTECTED_OPERATIONS(mut, bMustLock) \ + if(bMustLock == LOCK_MUTEX) { \ + d_pthread_mutex_lock(mut); \ + assert(bLockedOpIsLocked == 0); \ + bLockedOpIsLocked = 1; \ + } +#define END_MTX_PROTECTED_OPERATIONS(mut) \ + if(bLockedOpIsLocked) { \ + d_pthread_mutex_unlock(mut); \ + bLockedOpIsLocked = 0; \ + } + +#endif diff --git a/runtime/srutils.c b/runtime/srutils.c new file mode 100644 index 00000000..8eb2459c --- /dev/null +++ b/runtime/srutils.c @@ -0,0 +1,659 @@ +/**\file srUtils.c + * \brief General utilties that fit nowhere else. + * + * The namespace for this file is "srUtil". + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2003-09-09 + * Coding begun. + * + * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <signal.h> +#include <assert.h> +#include <sys/wait.h> +#include <ctype.h> +#include "srUtils.h" +#include "obj.h" + +#if _POSIX_TIMERS <= 0 +#include <sys/time.h> +#endif + +/* here we host some syslog specific names. There currently is no better place + * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14 + * rgerhards, 2008-04-16: note in LGPL move: the code tables below exist in + * the same way in BSD, so it is not a problem to move them from GPLv3 to LGPL. + * And nobody modified them since it was under LGPL, so we can also move it + * to ASL 2.0. + */ +syslogName_t syslogPriNames[] = { + {"alert", LOG_ALERT}, + {"crit", LOG_CRIT}, + {"debug", LOG_DEBUG}, + {"emerg", LOG_EMERG}, + {"err", LOG_ERR}, + {"error", LOG_ERR}, /* DEPRECATED */ + {"info", LOG_INFO}, + {"none", INTERNAL_NOPRI}, /* INTERNAL */ + {"notice", LOG_NOTICE}, + {"panic", LOG_EMERG}, /* DEPRECATED */ + {"warn", LOG_WARNING}, /* DEPRECATED */ + {"warning", LOG_WARNING}, + {"*", TABLE_ALLPRI}, + {NULL, -1} +}; + +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif +syslogName_t syslogFacNames[] = { + {"auth", LOG_AUTH}, + {"authpriv", LOG_AUTHPRIV}, + {"cron", LOG_CRON}, + {"daemon", LOG_DAEMON}, + {"kern", LOG_KERN}, + {"lpr", LOG_LPR}, + {"mail", LOG_MAIL}, + {"mark", LOG_MARK}, /* INTERNAL */ + {"news", LOG_NEWS}, + {"security", LOG_AUTH}, /* DEPRECATED */ + {"bsd_security", (13<<3) }, /* BSD-specific, unfortunatly with duplicate name... */ + {"syslog", LOG_SYSLOG}, + {"user", LOG_USER}, + {"uucp", LOG_UUCP}, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif +#if defined(LOG_AUDIT) + {"audit", LOG_AUDIT}, +#endif + {"console", (14 << 3)}, /* BSD-specific priority */ + {"local0", LOG_LOCAL0}, + {"local1", LOG_LOCAL1}, + {"local2", LOG_LOCAL2}, + {"local3", LOG_LOCAL3}, + {"local4", LOG_LOCAL4}, + {"local5", LOG_LOCAL5}, + {"local6", LOG_LOCAL6}, + {"local7", LOG_LOCAL7}, + {NULL, -1}, +}; + +/* ################################################################# * + * private members * + * ################################################################# */ + +/* As this is not a "real" object, there won't be any private + * members in this file. + */ + +/* ################################################################# * + * public members * + * ################################################################# */ + +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv) +{ + int i; + int bIsNegative; + char szBuf[64]; /* sufficiently large for my lifespan and those of my children... ;) */ + + assert(pBuf != NULL); + assert(iLenBuf > 1); /* This is actually an app error and as thus checked for... */ + + if(iToConv < 0) + { + bIsNegative = RSTRUE; + iToConv *= -1; + } + else + bIsNegative = RSFALSE; + + /* first generate a string with the digits in the reverse direction */ + i = 0; + do + { + szBuf[i++] = iToConv % 10 + '0'; + iToConv /= 10; + } while(iToConv > 0); /* warning: do...while()! */ + --i; /* undo last increment - we were pointing at NEXT location */ + + /* make sure we are within bounds... */ + if(i + 2 > iLenBuf) /* +2 because: a) i starts at zero! b) the \0 byte */ + return RS_RET_PROVIDED_BUFFER_TOO_SMALL; + + /* then move it to the right direction... */ + if(bIsNegative == RSTRUE) + *pBuf++ = '-'; + while(i >= 0) + *pBuf++ = szBuf[i--]; + *pBuf = '\0'; /* terminate it!!! */ + + return RS_RET_OK; +} + +uchar *srUtilStrDup(uchar *pOld, size_t len) +{ + uchar *pNew; + + assert(pOld != NULL); + + if((pNew = MALLOC(len + 1)) != NULL) + memcpy(pNew, pOld, len + 1); + + return pNew; +} + + +/* creates a path recursively + * Return 0 on success, -1 otherwise. On failure, errno * hold the last OS error. + * Param "mode" holds the mode that all non-existing directories are to be + * created with. + * Note that we have a potential race inside that code, a race that even exists + * outside of the rsyslog process (if multiple instances run, or other programs + * generate directories): If the directory does not exist, a context switch happens, + * at that moment another process creates it, then our creation on the context + * switch back fails. This actually happened in practice, and depending on the + * configuration it is even likely to happen. We can not solve this situation + * with a mutex, as that works only within out process space. So the solution + * is that we take the optimistic approach, try the creation, and if it fails + * with "already exists" we go back and do one retry of the check/create + * sequence. That should then succeed. If the directory is still not found but + * the creation fails in the similar way, we return an error on that second + * try because otherwise we would potentially run into an endless loop. + * loop. -- rgerhards, 2010-03-25 + */ +int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, + uid_t uid, gid_t gid, int bFailOnChownFail) +{ + uchar *p; + uchar *pszWork; + size_t len; + int err; + int iTry = 0; + int bErr = 0; + + assert(szFile != NULL); + assert(lenFile > 0); + + len = lenFile + 1; /* add one for '\0'-byte */ + if((pszWork = MALLOC(sizeof(uchar) * len)) == NULL) + return -1; + memcpy(pszWork, szFile, len); + for(p = pszWork+1 ; *p ; p++) + if(*p == '/') { + /* temporarily terminate string, create dir and go on */ + *p = '\0'; +again: + if(access((char*)pszWork, F_OK)) { + if((err = mkdir((char*)pszWork, mode)) == 0) { + if(uid != (uid_t) -1 || gid != (gid_t) -1) { + /* we need to set owner/group */ + if(chown((char*)pszWork, uid, gid) != 0) + if(bFailOnChownFail) + bErr = 1; + /* silently ignore if configured + * to do so. + */ + } + } else { + if(err == EEXIST && iTry == 0) { + iTry = 1; + goto again; + } + bErr = 1; + } + if(bErr) { + int eSave = errno; + free(pszWork); + errno = eSave; + return -1; + } + } + *p = '/'; + } + free(pszWork); + return 0; +} + + +/* execute a program with a single argument + * returns child pid if everything ok, 0 on failure. if + * it fails, errno is set. if it fails after the fork(), the caller + * can not be notfied for obvious reasons. if bwait is set to 1, + * the code waits until the child terminates - that potentially takes + * a lot of time. + * implemented 2007-07-20 rgerhards + */ +int execProg(uchar *program, int bWait, uchar *arg) +{ + int pid; + int sig; + struct sigaction sigAct; + + dbgprintf("exec program '%s' with param '%s'\n", program, arg); + pid = fork(); + if (pid < 0) { + return 0; + } + + if(pid) { /* Parent */ + if(bWait) + if(waitpid(pid, NULL, 0) == -1) + if(errno != ECHILD) { + /* we do not use logerror(), because + * that might bring us into an endless + * loop. At some time, we may + * reconsider this behaviour. + */ + dbgprintf("could not wait on child after executing '%s'", + (char*)program); + } + return pid; + } + /* Child */ + alarm(0); /* create a clean environment before we exec the real child */ + + memset(&sigAct, 0, sizeof(sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + + for(sig = 1 ; sig < NSIG ; ++sig) + sigaction(sig, &sigAct, NULL); + + execlp((char*)program, (char*) program, (char*)arg, NULL); + /* In the long term, it's a good idea to implement some enhanced error + * checking here. However, it can not easily be done. For starters, we + * may run into endless loops if we log to syslog. The next problem is + * that output is typically not seen by the user. For the time being, + * we use no error reporting, which is quite consitent with the old + * system() way of doing things. rgerhards, 2007-07-20 + */ + perror("exec"); + exit(1); /* not much we can do in this case */ +} + + +/* skip over whitespace in a standard C string. The + * provided pointer is advanced to the first non-whitespace + * charater or the \0 byte, if there is none. It is never + * moved past the \0. + */ +void skipWhiteSpace(uchar **pp) +{ + register uchar *p; + + assert(pp != NULL); + assert(*pp != NULL); + + p = *pp; + while(*p && isspace((int) *p)) + ++p; + *pp = p; +} + + +/* generate a file name from four parts: + * <directory name>/<name>.<number> + * If number is negative, it is not used. If any of the strings is + * NULL, an empty string is used instead. Length must be provided. + * lNumDigits is the minimum number of digits that lNum should have. This + * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will + * result in "0003" being used inside the file name. Set lNumDigits to 0 + * to use as few space as possible. + * rgerhards, 2008-01-03 + */ +rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, + size_t lenFName, long lNum, int lNumDigits) +{ + DEFiRet; + uchar *pName; + uchar *pNameWork; + size_t lenName; + uchar szBuf[128]; /* buffer for number */ + char szFmtBuf[32]; /* buffer for snprintf format */ + size_t lenBuf; + + if(lNum < 0) { + szBuf[0] = '\0'; + lenBuf = 0; + } else { + if(lNumDigits > 0) { + snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%dld", lNumDigits); + lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum); + } else + lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%ld", lNum); + } + + lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */ + if((pName = MALLOC(sizeof(uchar) * lenName)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* got memory, now construct string */ + memcpy(pName, pDirName, lenDirName); + pNameWork = pName + lenDirName; + *pNameWork++ = '/'; + memcpy(pNameWork, pFName, lenFName); + pNameWork += lenFName; + if(lenBuf > 0) { + memcpy(pNameWork, szBuf, lenBuf); + pNameWork += lenBuf; + } + *pNameWork = '\0'; + + *ppName = pName; + +finalize_it: + RETiRet; +} + +/* get the number of digits required to represent a given number. We use an + * iterative approach as we do not like to draw in the floating point + * library just for log(). -- rgerhards, 2008-01-10 + */ +int getNumberDigits(long lNum) +{ + int iDig; + + if(lNum == 0) + iDig = 1; + else + for(iDig = 0 ; lNum != 0 ; ++iDig) + lNum /= 10; + + return iDig; +} + + +/* compute an absolute time timeout suitable for calls to pthread_cond_timedwait() + * iTimeout is in milliseconds + * rgerhards, 2008-01-14 + */ +rsRetVal +timeoutComp(struct timespec *pt, long iTimeout) +{ +# if _POSIX_TIMERS <= 0 + struct timeval tv; +# endif + + BEGINfunc + assert(pt != NULL); + /* compute timeout */ + +# if _POSIX_TIMERS > 0 + /* this is the "regular" code */ + clock_gettime(CLOCK_REALTIME, pt); +# else + gettimeofday(&tv, NULL); + pt->tv_sec = tv.tv_sec; + pt->tv_nsec = tv.tv_usec * 1000; +# endif + pt->tv_sec += iTimeout / 1000; + pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */ + if(pt->tv_nsec > 999999999) { /* overrun? */ + pt->tv_nsec -= 1000000000; + ++pt->tv_sec; + } + ENDfunc + return RS_RET_OK; /* so far, this is static... */ +} + + +/* This function is kind of the reverse of timeoutComp() - it takes an absolute + * timeout value and computes how far this is in the future. If the value is already + * in the past, 0 is returned. The return value is in ms. + * rgerhards, 2008-01-25 + */ +long +timeoutVal(struct timespec *pt) +{ + struct timespec t; + long iTimeout; +# if _POSIX_TIMERS <= 0 + struct timeval tv; +# endif + + BEGINfunc + assert(pt != NULL); + /* compute timeout */ +# if _POSIX_TIMERS > 0 + /* this is the "regular" code */ + clock_gettime(CLOCK_REALTIME, &t); +# else + gettimeofday(&tv, NULL); + t.tv_sec = tv.tv_sec; + t.tv_nsec = tv.tv_usec * 1000; +# endif + iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000; + iTimeout += (pt->tv_sec - t.tv_sec) * 1000; + + if(iTimeout < 0) + iTimeout = 0; + + ENDfunc + return iTimeout; +} + + +/* cancellation cleanup handler - frees provided mutex + * rgerhards, 2008-01-14 + */ +void +mutexCancelCleanup(void *arg) +{ + BEGINfunc + assert(arg != NULL); + d_pthread_mutex_unlock((pthread_mutex_t*) arg); + ENDfunc +} + + +/* rsSleep() - a fairly portable way to to sleep. It + * will wake up when + * a) the wake-time is over + * rgerhards, 2008-01-28 + */ +void +srSleep(int iSeconds, int iuSeconds) +{ + struct timeval tvSelectTimeout; + + BEGINfunc + tvSelectTimeout.tv_sec = iSeconds; + tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */ + select(0, NULL, NULL, NULL, &tvSelectTimeout); + ENDfunc +} + + +/* From varmojfekoj's mail on why he provided rs_strerror_r(): + * There are two problems with strerror_r(): + * I see you've rewritten some of the code which calls it to use only + * the supplied buffer; unfortunately the GNU implementation sometimes + * doesn't use the buffer at all and returns a pointer to some + * immutable string instead, as noted in the man page. + * + * The other problem is that on some systems strerror_r() has a return + * type of int. + * + * So I've written a wrapper function rs_strerror_r(), which should + * take care of all this and be used instead. + * + * Added 2008-01-30 + */ +char *rs_strerror_r(int errnum, char *buf, size_t buflen) { +#ifndef HAVE_STRERROR_R + char *pszErr; + pszErr = strerror(errnum); + snprintf(buf, buflen, "%s", pszErr); +#else +# ifdef STRERROR_R_CHAR_P + char *p = strerror_r(errnum, buf, buflen); + if (p != buf) { + strncpy(buf, p, buflen); + buf[buflen - 1] = '\0'; + } +# else + strerror_r(errnum, buf, buflen); +# endif +#endif /* #ifdef __hpux */ + return buf; +} + + +/* Decode a symbolic name to a numeric value */ +int decodeSyslogName(uchar *name, syslogName_t *codetab) +{ + register syslogName_t *c; + register uchar *p; + uchar buf[80]; + + ASSERT(name != NULL); + ASSERT(codetab != NULL); + + DBGPRINTF("symbolic name: %s", name); + if(isdigit((int) *name)) { + DBGPRINTF("\n"); + return (atoi((char*) name)); + } + strncpy((char*) buf, (char*) name, 79); + for(p = buf; *p; p++) { + if (isupper((int) *p)) + *p = tolower((int) *p); + } + for(c = codetab; c->c_name; c++) { + if(!strcmp((char*) buf, (char*) c->c_name)) { + DBGPRINTF(" ==> %d\n", c->c_val); + return (c->c_val); + } + } + DBGPRINTF("\n"); + return (-1); +} + + +/** + * getSubString + * + * Copy a string byte by byte until the occurrence + * of a given separator. + * + * \param ppSrc Pointer to a pointer of the source array of characters. If a + separator detected the Pointer points to the next char after the + separator. Except if the end of the string is dedected ('\n'). + Then it points to the terminator char. + * \param pDst Pointer to the destination array of characters. Here the substing + will be stored. + * \param DstSize Maximum numbers of characters to store. + * \param cSep Separator char. + * \ret int Returns 0 if no error occured. + * + * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time + * so that it treats ' ' as a request for whitespace. But in general, the function and its callers + * should be changed over time, this is not really very good code... + */ +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) +{ + uchar *pSrc = *ppSrc; + int iErr = 0; /* 0 = no error, >0 = error */ + while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { + *pDst++ = *(pSrc)++; + DstSize--; + } + /* check if the Dst buffer was to small */ + if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { + dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); + iErr = 1; + } + if (*pSrc == '\0' || *pSrc == '\n') + /* this line was missing, causing ppSrc to be invalid when it + * was returned in case of end-of-string. rgerhards 2005-07-29 + */ + *ppSrc = pSrc; + else + *ppSrc = pSrc+1; + *pDst = '\0'; + return iErr; +} + + +/* get the size of a file or return appropriate error code. If an error is returned, + * *pSize content is undefined. + * rgerhards, 2009-06-12 + */ +rsRetVal +getFileSize(uchar *pszName, off_t *pSize) +{ + int ret; + struct stat statBuf; + DEFiRet; + + ret = stat((char*) pszName, &statBuf); + if(ret == -1) { + switch(errno) { + case EACCES: ABORT_FINALIZE(RS_RET_NO_FILE_ACCESS); + case ENOTDIR: + case ENOENT: ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + default: ABORT_FINALIZE(RS_RET_FILE_NO_STAT); + } + } + + *pSize = statBuf.st_size; + +finalize_it: + RETiRet; +} + +/* Returns 1 if the given string contains a non-escaped glob(3) + * wildcard character and 0 otherwise (or if the string is empty). + */ +int +containsGlobWildcard(char *str) +{ + char *p; + if(!str) { + return 0; + } + /* From Linux Programmer's Guide: + * "A string is a wildcard pattern if it contains one of the characters '?', '*' or '['" + * "One can remove the special meaning of '?', '*' and '[' by preceding them by a backslash" + */ + for(p = str; *p != '\0'; p++) { + if((*p == '?' || *p == '*' || *p == '[') && + (p == str || *(p-1) != '\\')) { + return 1; + } + } + return 0; +} + +/* vim:set ai: + */ diff --git a/runtime/statsobj.c b/runtime/statsobj.c new file mode 100644 index 00000000..25275616 --- /dev/null +++ b/runtime/statsobj.c @@ -0,0 +1,379 @@ +/* The statsobj object. + * + * This object provides a statistics-gathering facility inside rsyslog. This + * functionality will be pragmatically implemented and extended. + * + * Copyright 2010-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <pthread.h> +#include <errno.h> +#include <assert.h> + +#include "rsyslog.h" +#include "unicode-helper.h" +#include "obj.h" +#include "statsobj.h" +#include "srUtils.h" +#include "stringbuf.h" + + +/* externally-visiable data (see statsobj.h for explanation) */ +int GatherStats = 0; + +/* static data */ +DEFobjStaticHelpers + +/* doubly linked list of stats objects. Object is automatically linked to it + * upon construction. Enqueue always happens at the front (simplifies logic). + */ +static statsobj_t *objRoot = NULL; +static statsobj_t *objLast = NULL; + +static pthread_mutex_t mutStats; + +/* ------------------------------ statsobj linked list maintenance ------------------------------ */ + +static inline void +addToObjList(statsobj_t *pThis) +{ + pthread_mutex_lock(&mutStats); + pThis->prev = objLast; + if(objLast != NULL) + objLast->next = pThis; + objLast = pThis; + if(objRoot == NULL) + objRoot = pThis; + pthread_mutex_unlock(&mutStats); +} + + +static inline void +removeFromObjList(statsobj_t *pThis) +{ + pthread_mutex_lock(&mutStats); + if(pThis->prev != NULL) + pThis->prev->next = pThis->next; + if(pThis->next != NULL) + pThis->next->prev = pThis->prev; + if(objLast == pThis) + objLast = pThis->prev; + if(objRoot == pThis) + objRoot = pThis->next; + pthread_mutex_unlock(&mutStats); +} + + +static inline void +addCtrToList(statsobj_t *pThis, ctr_t *pCtr) +{ + pthread_mutex_lock(&pThis->mutCtr); + pCtr->prev = pThis->ctrLast; + if(pThis->ctrLast != NULL) + pThis->ctrLast->next = pCtr; + pThis->ctrLast = pCtr; + if(pThis->ctrRoot == NULL) + pThis->ctrRoot = pCtr; + pthread_mutex_unlock(&pThis->mutCtr); +} + +/* ------------------------------ methods ------------------------------ */ + + +/* Standard-Constructor + */ +BEGINobjConstruct(statsobj) /* be sure to specify the object type also in END macro! */ + pthread_mutex_init(&pThis->mutCtr, NULL); + pThis->ctrLast = NULL; + pThis->ctrRoot = NULL; +ENDobjConstruct(statsobj) + + +/* ConstructionFinalizer + */ +static rsRetVal +statsobjConstructFinalize(statsobj_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, statsobj); + addToObjList(pThis); + RETiRet; +} + + +/* set name. Note that we make our own copy of the memory, caller is + * responsible to free up name it passes in (if required). + */ +static rsRetVal +setName(statsobj_t *pThis, uchar *name) +{ + DEFiRet; + CHKmalloc(pThis->name = ustrdup(name)); +finalize_it: + RETiRet; +} + + +/* add a counter to an object + * ctrName is duplicated, caller must free it if requried + * NOTE: The counter is READ-ONLY and MUST NOT be modified (most + * importantly, it must not be initialized, so the caller must + * ensure the counter is properly initialized before AddCounter() + * is called. + */ +static rsRetVal +addCounter(statsobj_t *pThis, uchar *ctrName, statsCtrType_t ctrType, void *pCtr) +{ + ctr_t *ctr; + DEFiRet; + + CHKmalloc(ctr = malloc(sizeof(ctr_t))); + ctr->next = NULL; + ctr->prev = NULL; + CHKmalloc(ctr->name = ustrdup(ctrName)); + ctr->ctrType = ctrType; + switch(ctrType) { + case ctrType_IntCtr: + ctr->val.pIntCtr = (intctr_t*) pCtr; + break; + case ctrType_Int: + ctr->val.pInt = (int*) pCtr; + break; + } + addCtrToList(pThis, ctr); + +finalize_it: + RETiRet; +} + +/* get all the object's countes together as CEE. */ +static rsRetVal +getStatsLineCEE(statsobj_t *pThis, cstr_t **ppcstr, int cee_cookie) +{ + cstr_t *pcstr; + ctr_t *pCtr; + DEFiRet; + + CHKiRet(cstrConstruct(&pcstr)); + + if (cee_cookie == 1) + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("@cee: "), 6); + + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("{"), 1); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("name"), 4); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(":"), 1); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1); + rsCStrAppendStr(pcstr, pThis->name); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(","), 1); + + /* now add all counters to this line */ + pthread_mutex_lock(&pThis->mutCtr); + for(pCtr = pThis->ctrRoot ; pCtr != NULL ; pCtr = pCtr->next) { + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1); + rsCStrAppendStr(pcstr, pCtr->name); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1); + cstrAppendChar(pcstr, ':'); + switch(pCtr->ctrType) { + case ctrType_IntCtr: + rsCStrAppendInt(pcstr, *(pCtr->val.pIntCtr)); // TODO: OK????? + break; + case ctrType_Int: + rsCStrAppendInt(pcstr, *(pCtr->val.pInt)); + break; + } + if (pCtr->next != NULL) { + cstrAppendChar(pcstr, ','); + } else { + cstrAppendChar(pcstr, '}'); + } + + } + pthread_mutex_unlock(&pThis->mutCtr); + + CHKiRet(cstrFinalize(pcstr)); + *ppcstr = pcstr; + +finalize_it: + RETiRet; +} + +/* get all the object's countes together with object name as one line. + */ +static rsRetVal +getStatsLine(statsobj_t *pThis, cstr_t **ppcstr) +{ + cstr_t *pcstr; + ctr_t *pCtr; + DEFiRet; + + CHKiRet(cstrConstruct(&pcstr)); + rsCStrAppendStr(pcstr, pThis->name); + rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(": "), 2); + + /* now add all counters to this line */ + pthread_mutex_lock(&pThis->mutCtr); + for(pCtr = pThis->ctrRoot ; pCtr != NULL ; pCtr = pCtr->next) { + rsCStrAppendStr(pcstr, pCtr->name); + cstrAppendChar(pcstr, '='); + switch(pCtr->ctrType) { + case ctrType_IntCtr: + rsCStrAppendInt(pcstr, *(pCtr->val.pIntCtr)); // TODO: OK????? + break; + case ctrType_Int: + rsCStrAppendInt(pcstr, *(pCtr->val.pInt)); + break; + } + cstrAppendChar(pcstr, ' '); + } + pthread_mutex_unlock(&pThis->mutCtr); + + CHKiRet(cstrFinalize(pcstr)); + *ppcstr = pcstr; + +finalize_it: + RETiRet; +} + + +/* this function can be used to obtain all stats lines. In this case, + * a callback must be provided. This module than iterates over all objects and + * submits each stats line to the callback. The callback has two parameters: + * the first one is a caller-provided void*, the second one the cstr_t with the + * line. If the callback reports an error, processing is stopped. + */ +static rsRetVal +getAllStatsLines(rsRetVal(*cb)(void*, cstr_t*), void *usrptr, statsFmtType_t fmt) +{ + statsobj_t *o; + cstr_t *cstr; + DEFiRet; + + for(o = objRoot ; o != NULL ; o = o->next) { + switch(fmt) { + case statsFmt_Legacy: + CHKiRet(getStatsLine(o, &cstr)); + break; + case statsFmt_CEE: + CHKiRet(getStatsLineCEE(o, &cstr, 1)); + break; + case statsFmt_JSON: + CHKiRet(getStatsLineCEE(o, &cstr, 0)); + break; + } + CHKiRet(cb(usrptr, cstr)); + rsCStrDestruct(&cstr); + } + +finalize_it: + RETiRet; +} + + +/* Enable statistics gathering. currently there is no function to disable it + * again, as this is right now not needed. + */ +static rsRetVal +enableStats() +{ + GatherStats = 1; + return RS_RET_OK; +} + + +/* destructor for the statsobj object */ +BEGINobjDestruct(statsobj) /* be sure to specify the object type also in END and CODESTART macros! */ + ctr_t *ctr, *ctrToDel; +CODESTARTobjDestruct(statsobj) + removeFromObjList(pThis); + + /* destruct counters */ + ctr = pThis->ctrRoot; + while(ctr != NULL) { + ctrToDel = ctr; + ctr = ctr->next; + free(ctrToDel->name); + free(ctrToDel); + } + + pthread_mutex_destroy(&pThis->mutCtr); + free(pThis->name); +ENDobjDestruct(statsobj) + + +/* debugprint for the statsobj object */ +BEGINobjDebugPrint(statsobj) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(statsobj) + dbgoprint((obj_t*) pThis, "statsobj object, currently no state info available\n"); +ENDobjDebugPrint(statsobj) + + +/* queryInterface function + */ +BEGINobjQueryInterface(statsobj) +CODESTARTobjQueryInterface(statsobj) + if(pIf->ifVersion != statsobjCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = statsobjConstruct; + pIf->ConstructFinalize = statsobjConstructFinalize; + pIf->Destruct = statsobjDestruct; + pIf->DebugPrint = statsobjDebugPrint; + pIf->SetName = setName; + pIf->GetStatsLine = getStatsLine; + pIf->GetAllStatsLines = getAllStatsLines; + pIf->AddCounter = addCounter; + pIf->EnableStats = enableStats; +finalize_it: +ENDobjQueryInterface(statsobj) + + +/* Initialize the statsobj class. Must be called as the very first method + * before anything else is called inside this class. + */ +BEGINAbstractObjClassInit(statsobj, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, statsobjDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, statsobjConstructFinalize); + + /* init other data items */ + pthread_mutex_init(&mutStats, NULL); + +ENDObjClassInit(statsobj) + +/* Exit the class. + */ +BEGINObjClassExit(statsobj, OBJ_IS_CORE_MODULE) /* class, version */ + /* release objects we no longer need */ + pthread_mutex_destroy(&mutStats); +ENDObjClassExit(statsobj) diff --git a/runtime/statsobj.h b/runtime/statsobj.h new file mode 100644 index 00000000..14b33215 --- /dev/null +++ b/runtime/statsobj.h @@ -0,0 +1,158 @@ +/* The statsobj object. + * + * Copyright 2010-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_STATSOBJ_H +#define INCLUDED_STATSOBJ_H + +#include "atomic.h" + +/* The following data item is somewhat dirty, in that it does not follow + * our usual object calling conventions. However, much like with "Debug", we + * do this to gain speed. If we finally come to a platform that does not + * provide resolution of names for dynamically loaded modules, we need to find + * a work-around, but until then, we use the direct access. + * If set to 0, statistics are not gathered, otherwise they are. + */ +extern int GatherStats; + +/* our basic counter type -- need 32 bit on 32 bit platform. + * IMPORTANT: this type *MUST* be supported by atomic instructions! + */ +typedef uint64 intctr_t; + +/* counter types */ +typedef enum statsCtrType_e { + ctrType_IntCtr, + ctrType_Int +} statsCtrType_t; + +/* stats line format types */ +typedef enum statsFmtType_e { + statsFmt_Legacy, + statsFmt_JSON, + statsFmt_CEE +} statsFmtType_t; + + +/* helper entity, the counter */ +typedef struct ctr_s { + uchar *name; + statsCtrType_t ctrType; + union { + intctr_t *pIntCtr; + int *pInt; + } val; + struct ctr_s *next, *prev; +} ctr_t; + +/* the statsobj object */ +struct statsobj_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *name; + pthread_mutex_t mutCtr; /* to guard counter linked-list ops */ + ctr_t *ctrRoot; /* doubly-linked list of statsobj counters */ + ctr_t *ctrLast; + /* used to link ourselves together */ + statsobj_t *prev; + statsobj_t *next; +}; + + +/* interfaces */ +BEGINinterface(statsobj) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(statsobj); + rsRetVal (*Construct)(statsobj_t **ppThis); + rsRetVal (*ConstructFinalize)(statsobj_t *pThis); + rsRetVal (*Destruct)(statsobj_t **ppThis); + rsRetVal (*SetName)(statsobj_t *pThis, uchar *name); + rsRetVal (*GetStatsLine)(statsobj_t *pThis, cstr_t **ppcstr); + rsRetVal (*GetAllStatsLines)(rsRetVal(*cb)(void*, cstr_t*), void *usrptr, statsFmtType_t fmt); + rsRetVal (*AddCounter)(statsobj_t *pThis, uchar *ctrName, statsCtrType_t ctrType, void *pCtr); + rsRetVal (*EnableStats)(void); +ENDinterface(statsobj) +#define statsobjCURR_IF_VERSION 10 /* increment whenever you change the interface structure! */ +/* Changes + * v2-v9 rserved for future use in "older" version branches + * v10, 2012-04-01: GetAllStatsLines got fmt parameter + */ + + +/* prototypes */ +PROTOTYPEObj(statsobj); + + +/* macros to handle stats counters + * These are to be used by "counter providers". Note that we MUST + * specify the mutex name, even though at first it looks like it + * could be automatically be generated via e.g. "mut##ctr". + * Unfortunately, this does not work if counter is e.g. "pThis->ctr". + * So we decided, for clarity, to always insist on specifying the mutex + * name (after all, it's just a few more keystrokes...). + * -------------------------------------------------------------------- + * NOTE WELL + * -------------------------------------------------------------------- + * There are actually two types of stats counters: "regular" counters, + * which are only used for stats purposes and "dual" counters, which + * are primarily used for other purposes but can be included in stats + * as well. ALL regular counters MUST be initialized with + * STATSCOUNTER_INIT and only be modified by STATSCOUNTER_* functions. + * They MUST NOT be used for any other purpose (if this seems to make + * sense, consider changing it to a dual counter). + * Dual counters are somewhat dangerous in that a single variable is + * used for two purposes: the actual application need and stats + * counting. However, this is supported for performance reasons, as it + * provides insight into the inner engine workings without need for + * additional counters (and their maintenance code). Dual counters + * MUST NOT be modified by STATSCOUNTER_* functions. Most importantly, + * it is expected that the actua application code provides proper + * (enough) synchronized access to these counters. Most importantly, + * this means they have NO stats-system mutex associated to them. + * + * The interface function AddCounter() is a read-only function. It + * only provides the stats subsystem with a reference to a counter. + * It is irrelevant if the counter is a regular or dual one. For that + * reason, AddCounter() must not modify the counter contents, as in + * the case of a dual counter application code may be broken. + */ +#define STATSCOUNTER_DEF(ctr, mut) \ + intctr_t ctr; \ + DEF_ATOMIC_HELPER_MUT64(mut); + +#define STATSCOUNTER_INIT(ctr, mut) \ + INIT_ATOMIC_HELPER_MUT64(mut); \ + ctr = 0; + +#define STATSCOUNTER_INC(ctr, mut) \ + if(GatherStats) \ + ATOMIC_INC_uint64(&ctr, &mut); + +#define STATSCOUNTER_DEC(ctr, mut) \ + if(GatherStats) \ + ATOMIC_DEC_uint64(&ctr, mut); + +/* the next macro works only if the variable is already guarded + * by mutex (or the users risks a wrong result). It is assumed + * that there are not concurrent operations that modify the counter. + */ +#define STATSCOUNTER_SETMAX_NOMUT(ctr, newmax) \ + if(GatherStats && ((newmax) > (ctr))) \ + ctr = newmax; + +#endif /* #ifndef INCLUDED_STATSOBJ_H */ diff --git a/runtime/stream.c b/runtime/stream.c new file mode 100644 index 00000000..53039056 --- /dev/null +++ b/runtime/stream.c @@ -0,0 +1,2036 @@ +/* The serial stream class. + * + * A serial stream provides serial data access. In theory, serial streams + * can be implemented via a number of methods (e.g. files or in-memory + * streams). In practice, there currently only exist the file type (aka + * "driver"). + * + * File begun on 2008-01-09 by RGerhards + * Large modifications in 2009-06 to support using it with omfile, including zip writer. + * Note that this file obtains the zlib wrapper object is needed, but it never frees it + * again. While this sounds like a leak (and one may argue it actually is), there is no + * harm associated with that. The reason is that strm is a core object, so it is terminated + * only when rsyslogd exists. As we could only release on termination (or else bear more + * overhead for keeping track of how many users we have), not releasing zlibw is OK, because + * it will be released when rsyslogd terminates. We may want to revisit this decision if + * it turns out to be problematic. Then, we need to quasi-refcount the number of accesses + * to the object. + * + * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> /* required for HP UX */ +#include <errno.h> +#include <pthread.h> + +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "stream.h" +#include "unicode-helper.h" +#include "module-template.h" +#include "cryprov.h" +#if HAVE_SYS_PRCTL_H +# include <sys/prctl.h> +#endif + +/* some platforms do not have large file support :( */ +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif +#ifndef HAVE_LSEEK64 + typedef off_t off64_t; +# define lseek64(fd, offset, whence) lseek(fd, offset, whence) +#endif + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(zlibw) + +/* forward definitions */ +static rsRetVal strmFlushInternal(strm_t *pThis, int bFlushZip); +static rsRetVal strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf); +static rsRetVal strmCloseFile(strm_t *pThis); +static void *asyncWriterThread(void *pPtr); +static rsRetVal doZipWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlush); +static rsRetVal doZipFinish(strm_t *pThis); +static rsRetVal strmPhysWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf); +static rsRetVal strmSeekCurrOffs(strm_t *pThis); + + +/* methods */ + +/* Try to resolve a size limit situation. This is used to support custom-file size handlers + * for omfile. It first runs the command, and then checks if we are still above the size + * treshold. Note that this works only with single file names, NOT with circular names. + * Note that pszCurrFName can NOT be taken from pThis, because the stream is closed when + * we are called (and that destroys pszCurrFName, as there is NO CURRENT file name!). So + * we need to receive the name as a parameter. + * initially wirtten 2005-06-21, moved to this class & updates 2009-06-01, both rgerhards + */ +static rsRetVal +resolveFileSizeLimit(strm_t *pThis, uchar *pszCurrFName) +{ + uchar *pParams; + uchar *pCmd; + uchar *p; + off_t actualFileSize; + rsRetVal localRet; + DEFiRet; + ISOBJ_TYPE_assert(pThis, strm); + assert(pszCurrFName != NULL); + + if(pThis->pszSizeLimitCmd == NULL) { + ABORT_FINALIZE(RS_RET_NON_SIZELIMITCMD); /* nothing we can do in this case... */ + } + + /* we first check if we have command line parameters. We assume this, + * when we have a space in the program name. If we find it, everything after + * the space is treated as a single argument. + */ + CHKmalloc(pCmd = ustrdup(pThis->pszSizeLimitCmd)); + + for(p = pCmd ; *p && *p != ' ' ; ++p) { + /* JUST SKIP */ + } + + if(*p == ' ') { + *p = '\0'; /* pretend string-end */ + pParams = p+1; + } else + pParams = NULL; + + /* the execProg() below is probably not great, but at least is is + * fairly secure now. Once we change the way file size limits are + * handled, we should also revisit how this command is run (and + * with which parameters). rgerhards, 2007-07-20 + */ + execProg(pCmd, 1, pParams); + + free(pCmd); + + localRet = getFileSize(pszCurrFName, &actualFileSize); + + if(localRet == RS_RET_OK && actualFileSize >= pThis->iSizeLimit) { + ABORT_FINALIZE(RS_RET_SIZELIMITCMD_DIDNT_RESOLVE); /* OK, it didn't work out... */ + } else if(localRet != RS_RET_FILE_NOT_FOUND) { + /* file not found is OK, the command may have moved away the file */ + ABORT_FINALIZE(localRet); + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(iRet == RS_RET_SIZELIMITCMD_DIDNT_RESOLVE) { + DBGPRINTF("file size limit cmd for file '%s' did no resolve situation\n", pszCurrFName); + } else { + DBGPRINTF("file size limit cmd for file '%s' failed with code %d.\n", pszCurrFName, iRet); + } + pThis->bDisabled = 1; + } + + RETiRet; +} + + +/* Check if the file has grown beyond the configured omfile iSizeLimit + * and, if so, initiate processing. + */ +static rsRetVal +doSizeLimitProcessing(strm_t *pThis) +{ + uchar *pszCurrFName = NULL; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + ASSERT(pThis->iSizeLimit != 0); + ASSERT(pThis->fd != -1); + + if(pThis->iCurrOffs >= pThis->iSizeLimit) { + /* strmCloseFile() destroys the current file name, so we + * need to preserve it. + */ + CHKmalloc(pszCurrFName = ustrdup(pThis->pszCurrFName)); + CHKiRet(strmCloseFile(pThis)); + CHKiRet(resolveFileSizeLimit(pThis, pszCurrFName)); + } + +finalize_it: + free(pszCurrFName); + RETiRet; +} + + +/* now, we define type-specific handlers. The provide a generic functionality, + * but for this specific type of strm. The mapping to these handlers happens during + * strm construction. Later on, handlers are called by pointers present in the + * strm instance object. + */ + +/* do the physical open() call on a file. + */ +static rsRetVal +doPhysOpen(strm_t *pThis) +{ + int iFlags = 0; + struct stat statOpen; + DEFiRet; + ISOBJ_TYPE_assert(pThis, strm); + + /* compute which flags we need to provide to open */ + switch(pThis->tOperationsMode) { + case STREAMMODE_READ: + iFlags = O_CLOEXEC | O_NOCTTY | O_RDONLY; + break; + case STREAMMODE_WRITE: /* legacy mode used inside queue engine */ + iFlags = O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT; + break; + case STREAMMODE_WRITE_TRUNC: + iFlags = O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT | O_TRUNC; + break; + case STREAMMODE_WRITE_APPEND: + iFlags = O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT | O_APPEND; + break; + default:assert(0); + break; + } + if(pThis->sType == STREAMTYPE_NAMED_PIPE) { + DBGPRINTF("Note: stream '%s' is a named pipe, open with O_NONBLOCK\n", pThis->pszCurrFName); + iFlags |= O_NONBLOCK; + } + + pThis->fd = open((char*)pThis->pszCurrFName, iFlags | O_LARGEFILE, pThis->tOpenMode); + DBGPRINTF("file '%s' opened as #%d with mode %d\n", pThis->pszCurrFName, + pThis->fd, (int) pThis->tOpenMode); + if(pThis->fd == -1) { + char errStr[1024]; + int err = errno; + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGOPRINT((obj_t*) pThis, "open error %d, file '%s': %s\n", errno, pThis->pszCurrFName, errStr); + if(err == ENOENT) + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + else + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if(pThis->tOperationsMode == STREAMMODE_READ) { + if(fstat(pThis->fd, &statOpen) == -1) { + DBGPRINTF("Error: cannot obtain inode# for file %s\n", pThis->pszCurrFName); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + pThis->inode = statOpen.st_ino; + } + + if(!ustrcmp(pThis->pszCurrFName, UCHAR_CONSTANT(_PATH_CONSOLE)) || isatty(pThis->fd)) { + DBGPRINTF("file %d is a tty-type file\n", pThis->fd); + pThis->bIsTTY = 1; + } else { + pThis->bIsTTY = 0; + } + + if(pThis->cryprov != NULL) { + CHKiRet(pThis->cryprov->OnFileOpen(pThis->cryprovData, + pThis->pszCurrFName, &pThis->cryprovFileData, + (pThis->tOperationsMode == STREAMMODE_READ) ? 'r' : 'w')); + pThis->cryprov->SetDeleteOnClose(pThis->cryprovFileData, pThis->bDeleteOnClose); + } +finalize_it: + RETiRet; +} + + +static rsRetVal +strmSetCurrFName(strm_t *pThis) +{ + DEFiRet; + + if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) { + CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir, + pThis->pszFName, pThis->lenFName, pThis->iCurrFNum, pThis->iFileNumDigits)); + } else { + if(pThis->pszDir == NULL) { + if((pThis->pszCurrFName = ustrdup(pThis->pszFName)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } else { + CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir, + pThis->pszFName, pThis->lenFName, -1, 0)); + } + } +finalize_it: + RETiRet; +} + +/* This function checks if the actual file has changed and, if so, resets the + * offset. This is support for monitoring files. It should be called after + * deserializing the strm object and before doing any other operation on it + * (most importantly not an open or seek!). + */ +static rsRetVal +CheckFileChange(strm_t *pThis) +{ + struct stat statName; + DEFiRet; + + CHKiRet(strmSetCurrFName(pThis)); + if(stat((char*) pThis->pszCurrFName, &statName) == -1) + ABORT_FINALIZE(RS_RET_IO_ERROR); + DBGPRINTF("stream/after deserialize checking for file change on '%s', " + "inode %u/%u, size/currOffs %llu/%llu\n", + pThis->pszCurrFName, (unsigned) pThis->inode, + (unsigned) statName.st_ino, statName.st_size, pThis->iCurrOffs); + if(pThis->inode != statName.st_ino || statName.st_size < pThis->iCurrOffs) { + DBGPRINTF("stream: file %s has changed\n", pThis->pszCurrFName); + pThis->iCurrOffs = 0; + } +finalize_it: + RETiRet; +} + + +/* open a strm file + * It is OK to call this function when the stream is already open. In that + * case, it returns immediately with RS_RET_OK + */ +static rsRetVal strmOpenFile(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->fd != -1) + ABORT_FINALIZE(RS_RET_OK); + pThis->pszCurrFName = NULL; /* used to prevent mem leak in case of error */ + + if(pThis->pszFName == NULL) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + CHKiRet(strmSetCurrFName(pThis)); + + CHKiRet(doPhysOpen(pThis)); + + pThis->iCurrOffs = 0; + if(pThis->tOperationsMode == STREAMMODE_WRITE_APPEND) { + /* we need to obtain the current offset */ + off_t offset; + CHKiRet(getFileSize(pThis->pszCurrFName, &offset)); + pThis->iCurrOffs = offset; + } + + DBGOPRINT((obj_t*) pThis, "opened file '%s' for %s as %d\n", pThis->pszCurrFName, + (pThis->tOperationsMode == STREAMMODE_READ) ? "READ" : "WRITE", pThis->fd); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pszCurrFName != NULL) { + free(pThis->pszCurrFName); + pThis->pszCurrFName = NULL; /* just to prevent mis-adressing down the road... */ + } + if(pThis->fd != -1) { + close(pThis->fd); + pThis->fd = -1; + } + } + RETiRet; +} + + +/* wait for the output writer thread to be done. This must be called before actions + * that require data to be persisted. May be called in non-async mode and is a null + * operation than. Must be called with the mutex locked. + */ +static inline void +strmWaitAsyncWriterDone(strm_t *pThis) +{ + BEGINfunc + if(pThis->bAsyncWrite) { + /* awake writer thread and make it write out everything */ + while(pThis->iCnt > 0) { + pthread_cond_signal(&pThis->notEmpty); + d_pthread_cond_wait(&pThis->isEmpty, &pThis->mut); + } + } + ENDfunc +} + + +/* close a strm file + * Note that the bDeleteOnClose flag is honored. If it is set, the file will be + * deleted after close. This is in support for the qRead thread. + * Note: it is valid to call this function when the physical file is closed. If so, + * strmCloseFile() will still check if there is any unwritten data inside buffers + * (this may be the case) and, if so, will open the file, write the data, and then + * close it again (this is done via strmFlushInternal and friends). + */ +static rsRetVal strmCloseFile(strm_t *pThis) +{ + off64_t currOffs; + DEFiRet; + + ASSERT(pThis != NULL); + DBGOPRINT((obj_t*) pThis, "file %d(%s) closing\n", pThis->fd, + (pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName); + + if(pThis->tOperationsMode != STREAMMODE_READ) { + strmFlushInternal(pThis, 0); + if(pThis->iZipLevel) { + doZipFinish(pThis); + } + if(pThis->bAsyncWrite) { + strmWaitAsyncWriterDone(pThis); + } + } + + /* if we have a signature provider, we must make sure that the crypto + * state files are opened and proper close processing happens. */ + if(pThis->cryprov != NULL && pThis->fd == -1) { + strmOpenFile(pThis); + } + + /* the file may already be closed (or never have opened), so guard + * against this. -- rgerhards, 2010-03-19 + */ + if(pThis->fd != -1) { + currOffs = lseek64(pThis->fd, 0, SEEK_CUR); + close(pThis->fd); + pThis->fd = -1; + pThis->inode = 0; + if(pThis->cryprov != NULL) { + pThis->cryprov->OnFileClose(pThis->cryprovFileData, currOffs); + pThis->cryprovFileData = NULL; + } + } + + if(pThis->fdDir != -1) { + /* close associated directory handle, if it is open */ + close(pThis->fdDir); + pThis->fdDir = -1; + } + + if(pThis->bDeleteOnClose) { + if(pThis->pszCurrFName == NULL) { + CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir, + pThis->pszFName, pThis->lenFName, pThis->iCurrFNum, + pThis->iFileNumDigits)); + } + DBGPRINTF("strmCloseFile: deleting '%s'\n", pThis->pszCurrFName); + if(unlink((char*) pThis->pszCurrFName) == -1) { + char errStr[1024]; + int err = errno; + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGPRINTF("error %d unlinking '%s' - ignored: %s\n", + errno, pThis->pszCurrFName, errStr); + } + free(pThis->pszCurrFName); + pThis->pszCurrFName = NULL; + } + + pThis->iCurrOffs = 0; /* we are back at begin of file */ + +finalize_it: + RETiRet; +} + + +/* switch to next strm file + * This method must only be called if we are in a multi-file mode! + */ +static rsRetVal +strmNextFile(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pThis->iMaxFiles != 0); + ASSERT(pThis->fd != -1); + + CHKiRet(strmCloseFile(pThis)); + + /* we do modulo operation to ensure we obey the iMaxFile property. This will always + * result in a file number lower than iMaxFile, so it if wraps, the name is back to + * 0, which results in the first file being overwritten. Not desired for queues, so + * make sure their iMaxFiles is large enough. But it is well-desired for other + * use cases, e.g. a circular output log file. -- rgerhards, 2008-01-10 + */ + pThis->iCurrFNum = (pThis->iCurrFNum + 1) % pThis->iMaxFiles; + +finalize_it: + RETiRet; +} + + +/* handle the eof case for monitored files. + * If we are monitoring a file, someone may have rotated it. In this case, we + * also need to close it and reopen it under the same name. + * rgerhards, 2008-02-13 + * The previous code also did a check for file truncation, in which case the + * file was considered rewritten. However, this potential border case turned + * out to be a big trouble spot on busy systems. It caused massive message + * duplication (I guess stat() can return a too-low number under some + * circumstances). So starting as of now, we only check the inode number and + * a file change is detected only if the inode changes. -- rgerhards, 2011-01-10 + */ +static rsRetVal +strmHandleEOFMonitor(strm_t *pThis) +{ + DEFiRet; + struct stat statName; + + ISOBJ_TYPE_assert(pThis, strm); + if(stat((char*) pThis->pszCurrFName, &statName) == -1) + ABORT_FINALIZE(RS_RET_IO_ERROR); + DBGPRINTF("stream checking for file change on '%s', inode %u/%u\n", + pThis->pszCurrFName, (unsigned) pThis->inode, + (unsigned) statName.st_ino); + if(pThis->inode == statName.st_ino) { + ABORT_FINALIZE(RS_RET_EOF); + } else { + /* we had a file change! */ + DBGPRINTF("we had a file change on '%s'\n", pThis->pszCurrFName); + CHKiRet(strmCloseFile(pThis)); + CHKiRet(strmOpenFile(pThis)); + } + +finalize_it: + RETiRet; +} + + +/* handle the EOF case of a stream + * The EOF case is somewhat complicated, as the proper action depends on the + * mode the stream is in. If there are multiple files (circular logs, most + * important use case is queue files!), we need to close the current file and + * try to open the next one. + * rgerhards, 2008-02-13 + */ +static rsRetVal +strmHandleEOF(strm_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + switch(pThis->sType) { + case STREAMTYPE_FILE_SINGLE: + case STREAMTYPE_NAMED_PIPE: + ABORT_FINALIZE(RS_RET_EOF); + break; + case STREAMTYPE_FILE_CIRCULAR: + /* we have multiple files and need to switch to the next one */ + /* TODO: think about emulating EOF in this case (not yet needed) */ + DBGOPRINT((obj_t*) pThis, "file %d EOF\n", pThis->fd); + CHKiRet(strmNextFile(pThis)); + break; + case STREAMTYPE_FILE_MONITOR: + CHKiRet(strmHandleEOFMonitor(pThis)); + break; + } + +finalize_it: + RETiRet; +} + +/* read the next buffer from disk + * rgerhards, 2008-02-13 + */ +static rsRetVal +strmReadBuf(strm_t *pThis, int *padBytes) +{ + DEFiRet; + int bRun; + long iLenRead; + size_t actualDataLen; + size_t toRead; + ssize_t bytesLeft; + + ISOBJ_TYPE_assert(pThis, strm); + /* We need to try read at least twice because we may run into EOF and need to switch files. */ + bRun = 1; + while(bRun) { + /* first check if we need to (re)open the file. We may have switched to a new one in + * circular mode or it may have been rewritten (rotated) if we monitor a file + * rgerhards, 2008-02-13 + */ + CHKiRet(strmOpenFile(pThis)); + if(pThis->cryprov == NULL) { + toRead = pThis->sIOBufSize; + } else { + CHKiRet(pThis->cryprov->GetBytesLeftInBlock(pThis->cryprovFileData, &bytesLeft)); + if(bytesLeft == -1 || bytesLeft > (ssize_t) pThis->sIOBufSize) { + toRead = pThis->sIOBufSize; + } else { + toRead = (size_t) bytesLeft; + } + } + iLenRead = read(pThis->fd, pThis->pIOBuf, toRead); + DBGOPRINT((obj_t*) pThis, "file %d read %ld bytes\n", pThis->fd, iLenRead); + /* end crypto */ + if(iLenRead == 0) { + CHKiRet(strmHandleEOF(pThis)); + } else if(iLenRead < 0) + ABORT_FINALIZE(RS_RET_IO_ERROR); + else { /* good read */ + /* here we place our crypto interface */ + if(pThis->cryprov != NULL) { + actualDataLen = iLenRead; + pThis->cryprov->Decrypt(pThis->cryprovFileData, pThis->pIOBuf, &actualDataLen); + *padBytes = iLenRead - actualDataLen; + iLenRead = actualDataLen; + DBGOPRINT((obj_t*) pThis, "encrypted file %d pad bytes %d, actual " + "data %ld\n", pThis->fd, *padBytes, iLenRead); + } else { + *padBytes = 0; + } + pThis->iBufPtrMax = iLenRead; + bRun = 0; /* exit loop */ + } + } + /* if we reach this point, we had a good read */ + pThis->iBufPtr = 0; + +finalize_it: + RETiRet; +} + + +/* logically "read" a character from a file. What actually happens is that + * data is taken from the buffer. Only if the buffer is full, data is read + * directly from file. In that case, a read is performed blockwise. + * rgerhards, 2008-01-07 + * NOTE: needs to be enhanced to support sticking with a strm entry (if not + * deleted). + */ +static rsRetVal strmReadChar(strm_t *pThis, uchar *pC) +{ + int padBytes = 0; /* in crypto mode, we may have some padding (non-data) bytes */ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pC != NULL); + + /* DEV debug only: DBGOPRINT((obj_t*) pThis, "strmRead index %d, max %d\n", pThis->iBufPtr, pThis->iBufPtrMax); */ + if(pThis->iUngetC != -1) { /* do we have an "unread" char that we need to provide? */ + *pC = pThis->iUngetC; + ++pThis->iCurrOffs; /* one more octet read */ + pThis->iUngetC = -1; + ABORT_FINALIZE(RS_RET_OK); + } + + /* do we need to obtain a new buffer? */ + if(pThis->iBufPtr >= pThis->iBufPtrMax) { + CHKiRet(strmReadBuf(pThis, &padBytes)); + } + pThis->iCurrOffs += padBytes; + + /* if we reach this point, we have data available in the buffer */ + + *pC = pThis->pIOBuf[pThis->iBufPtr++]; + ++pThis->iCurrOffs; /* one more octet read */ + +finalize_it: + RETiRet; +} + + +/* unget a single character just like ungetc(). As with that call, there is only a single + * character buffering capability. + * rgerhards, 2008-01-07 + */ +static rsRetVal strmUnreadChar(strm_t *pThis, uchar c) +{ + ASSERT(pThis != NULL); + ASSERT(pThis->iUngetC == -1); + pThis->iUngetC = c; + --pThis->iCurrOffs; /* one less octet read - NOTE: this can cause problems if we got a file change + and immediately do an unread and the file is on a buffer boundary and the stream is then persisted. + With the queue, this can not happen as an Unread is only done on record begin, which is never split + accross files. For other cases we accept the very remote risk. -- rgerhards, 2008-01-12 */ + + return RS_RET_OK; +} + +/* read a 'paragraph' from a strm file. + * A paragraph may be terminated by a LF, by a LFLF, or by LF<not whitespace> depending on the option set. + * The termination LF characters are read, but are + * not returned in the buffer (it is discared). The caller is responsible for + * destruction of the returned CStr object! -- dlang 2010-12-13 + */ +static rsRetVal +strmReadLine(strm_t *pThis, cstr_t **ppCStr, int mode) +{ + /* mode = 0 single line mode (equivalent to ReadLine) + * mode = 1 LFLF mode (paragraph, blank line between entries) + * mode = 2 LF <not whitespace> mode, a log line starts at the beginning of a line, but following lines that are indented are part of the same log entry + * This modal interface is not nearly as flexible as being able to define a regex for when a new record starts, but it's also not nearly as hard (or as slow) to implement + */ + uchar c; + uchar finished; + rsRetVal readCharRet; + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(ppCStr != NULL); + + CHKiRet(cstrConstruct(ppCStr)); + CHKiRet(strmReadChar(pThis, &c)); + + if(mode == 0) { + /* append previous message to current message if necessary */ + if(pThis->prevLineSegment != NULL) { + CHKiRet(cstrAppendCStr(*ppCStr, pThis->prevLineSegment)); + cstrDestruct(&pThis->prevLineSegment); + } + while(c != '\n') { + CHKiRet(cstrAppendChar(*ppCStr, c)); + readCharRet = strmReadChar(pThis, &c); + if(readCharRet == RS_RET_EOF) {/* end of file reached without \n? */ + CHKiRet(rsCStrConstructFromCStr(&pThis->prevLineSegment, *ppCStr)); + } + CHKiRet(readCharRet); + } + CHKiRet(cstrFinalize(*ppCStr)); + } else if(mode == 1) { + finished=0; + while(finished == 0){ + if(c != '\n') { + CHKiRet(cstrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } else { + if ((((*ppCStr)->iStrLen) > 0) ){ + if ((*ppCStr)->pBuf[(*ppCStr)->iStrLen -1 ] == '\n'){ + rsCStrTruncate(*ppCStr,1); /* remove the prior newline */ + finished=1; + } else { + CHKiRet(cstrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } + } else { + finished=1; /* this is a blank line, a \n with nothing since the last complete record */ + } + } + } + CHKiRet(cstrFinalize(*ppCStr)); + } else if(mode == 2) { + /* indented follow-up lines */ + finished=0; + while(finished == 0){ + if ((*ppCStr)->iStrLen == 0){ + if(c != '\n') { + /* nothing in the buffer, and it's not a newline, add it to the buffer */ + CHKiRet(cstrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } else { + finished=1; /* this is a blank line, a \n with nothing since the last complete record */ + } + } else { + if ((*ppCStr)->pBuf[(*ppCStr)->iStrLen -1 ] != '\n'){ + /* not the first character after a newline, add it to the buffer */ + CHKiRet(cstrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } else { + if ((c == ' ') || (c == '\t')){ + CHKiRet(cstrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } else { + /* clean things up by putting the character we just read back into + * the input buffer and removing the LF character that is currently at the + * end of the output string */ + CHKiRet(strmUnreadChar(pThis, c)); + rsCStrTruncate(*ppCStr,1); + finished=1; + } + } + } + } + CHKiRet(cstrFinalize(*ppCStr)); + } + +finalize_it: + if(iRet != RS_RET_OK && *ppCStr != NULL) + cstrDestruct(ppCStr); + + RETiRet; +} + + +/* Standard-Constructor for the strm object + */ +BEGINobjConstruct(strm) /* be sure to specify the object type also in END macro! */ + pThis->iCurrFNum = 1; + pThis->fd = -1; + pThis->fdDir = -1; + pThis->iUngetC = -1; + pThis->bVeryReliableZip = 0; + pThis->sType = STREAMTYPE_FILE_SINGLE; + pThis->sIOBufSize = glblGetIOBufSize(); + pThis->tOpenMode = 0600; + pThis->prevLineSegment = NULL; +ENDobjConstruct(strm) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal strmConstructFinalize(strm_t *pThis) +{ + rsRetVal localRet; + int i; + DEFiRet; + + ASSERT(pThis != NULL); + + pThis->iBufPtrMax = 0; /* results in immediate read request */ + if(pThis->iZipLevel) { /* do we need a zip buf? */ + localRet = objUse(zlibw, LM_ZLIBW_FILENAME); + if(localRet != RS_RET_OK) { + pThis->iZipLevel = 0; + DBGPRINTF("stream was requested with zip mode, but zlibw module unavailable (%d) - using " + "without zip\n", localRet); + } else { + /* we use the same size as the original buf, as we would like + * to make sure we can write out everything with a SINGLE api call! + * We add another 128 bytes to take care of the gzip header and "all eventualities". + */ + CHKmalloc(pThis->pZipBuf = (Bytef*) MALLOC(sizeof(uchar) * (pThis->sIOBufSize + 128))); + } + } + + /* if we are set to sync, we must obtain a file handle to the directory for fsync() purposes */ + if(pThis->bSync && !pThis->bIsTTY) { + pThis->fdDir = open((char*)pThis->pszDir, O_RDONLY | O_CLOEXEC | O_NOCTTY); + if(pThis->fdDir == -1) { + char errStr[1024]; + int err = errno; + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGPRINTF("error %d opening directory file for fsync() use - fsync for directory disabled: %s\n", + errno, errStr); + } + } + + DBGPRINTF("file stream %s params: flush interval %d, async write %d\n", + (pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName, + pThis->iFlushInterval, pThis->bAsyncWrite); + /* if we have a flush interval, we need to do async writes in any case */ + if(pThis->iFlushInterval != 0) { + pThis->bAsyncWrite = 1; + } + + /* if we work asynchronously, we need a couple of synchronization objects */ + if(pThis->bAsyncWrite) { + pthread_mutex_init(&pThis->mut, 0); + pthread_cond_init(&pThis->notFull, 0); + pthread_cond_init(&pThis->notEmpty, 0); + pthread_cond_init(&pThis->isEmpty, 0); + pThis->iCnt = pThis->iEnq = pThis->iDeq = 0; + for(i = 0 ; i < STREAM_ASYNC_NUMBUFS ; ++i) { + CHKmalloc(pThis->asyncBuf[i].pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->sIOBufSize)); + } + pThis->pIOBuf = pThis->asyncBuf[0].pBuf; + pThis->bStopWriter = 0; + if(pthread_create(&pThis->writerThreadID, +#ifdef HAVE_PTHREAD_SETSCHEDPARAM + &default_thread_attr, +#else + NULL, +#endif + asyncWriterThread, pThis) != 0) + DBGPRINTF("ERROR: stream %p cold not create writer thread\n", pThis); + } else { + /* we work synchronously, so we need to alloc a fixed pIOBuf */ + CHKmalloc(pThis->pIOBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->sIOBufSize)); + } + +finalize_it: + RETiRet; +} + + +/* stop the writer thread (we MUST be runnnig asynchronously when this method + * is called!). Note that the mutex must be locked! -- rgerhards, 2009-07-06 + */ +static inline void +stopWriter(strm_t *pThis) +{ + BEGINfunc + pThis->bStopWriter = 1; + pthread_cond_signal(&pThis->notEmpty); + d_pthread_mutex_unlock(&pThis->mut); + pthread_join(pThis->writerThreadID, NULL); + ENDfunc +} + + +/* destructor for the strm object */ +BEGINobjDestruct(strm) /* be sure to specify the object type also in END and CODESTART macros! */ + int i; +CODESTARTobjDestruct(strm) + /* we need to stop the ZIP writer */ + if(pThis->bAsyncWrite) + /* Note: mutex will be unlocked in stopWriter! */ + d_pthread_mutex_lock(&pThis->mut); + + /* strmClose() will handle read-only files as well as need to open + * files that have unwritten buffers. -- rgerhards, 2010-03-09 + */ + strmCloseFile(pThis); + + if(pThis->bAsyncWrite) { + stopWriter(pThis); + pthread_mutex_destroy(&pThis->mut); + pthread_cond_destroy(&pThis->notFull); + pthread_cond_destroy(&pThis->notEmpty); + pthread_cond_destroy(&pThis->isEmpty); + for(i = 0 ; i < STREAM_ASYNC_NUMBUFS ; ++i) { + free(pThis->asyncBuf[i].pBuf); + } + } else { + free(pThis->pIOBuf); + } + + /* Finally, we can free the resources. + * IMPORTANT: we MUST free this only AFTER the ansyncWriter has been stopped, else + * we get random errors... + */ + free(pThis->pszDir); + free(pThis->pZipBuf); + free(pThis->pszCurrFName); + free(pThis->pszFName); + pThis->bStopWriter = 2; /* RG: use as flag for destruction */ +ENDobjDestruct(strm) + + +/* check if we need to open a new file (in output mode only). + * The decision is based on file size AND record delimition state. + * This method may also be called on a closed file, in which case + * it immediately returns. + */ +static rsRetVal strmCheckNextOutputFile(strm_t *pThis) +{ + DEFiRet; + + if(pThis->fd == -1) + FINALIZE; + + /* wait for output to be empty, so that our counts are correct */ + strmWaitAsyncWriterDone(pThis); + + if(pThis->iCurrOffs >= pThis->iMaxFileSize) { + DBGOPRINT((obj_t*) pThis, "max file size %ld reached for %d, now %ld - starting new file\n", + (long) pThis->iMaxFileSize, pThis->fd, (long) pThis->iCurrOffs); + CHKiRet(strmNextFile(pThis)); + } + +finalize_it: + RETiRet; +} + + +/* try to recover a tty after a write error. This may have happend + * due to vhangup(), and, if so, we can simply re-open it. + */ +#ifdef linux +# define ERR_TTYHUP EIO +#else +# define ERR_TTYHUP EBADF +#endif +static rsRetVal +tryTTYRecover(strm_t *pThis, int err) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strm); + if(err == ERR_TTYHUP) { + close(pThis->fd); + CHKiRet(doPhysOpen(pThis)); + } + +finalize_it: + RETiRet; +} +#undef ER_TTYHUP + + +/* issue write() api calls until either the buffer is completely + * written or an error occured (it may happen that multiple writes + * are required, what is perfectly legal. On exit, *pLenBuf contains + * the number of bytes actually written. + * rgerhards, 2009-06-08 + */ +static rsRetVal +doWriteCall(strm_t *pThis, uchar *pBuf, size_t *pLenBuf) +{ + ssize_t lenBuf; + ssize_t iTotalWritten; + ssize_t iWritten; + char *pWriteBuf; + DEFiRet; + ISOBJ_TYPE_assert(pThis, strm); + + lenBuf = *pLenBuf; + pWriteBuf = (char*) pBuf; + iTotalWritten = 0; + do { + iWritten = write(pThis->fd, pWriteBuf, lenBuf); + if(iWritten < 0) { + char errStr[1024]; + int err = errno; + iWritten = 0; /* we have written NO bytes! */ + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGPRINTF("log file (%d) write error %d: %s\n", pThis->fd, err, errStr); + if(err == EINTR) { + /*NO ERROR, just continue */; + } else { + if(pThis->bIsTTY) { + CHKiRet(tryTTYRecover(pThis, err)); + } else { + ABORT_FINALIZE(RS_RET_IO_ERROR); + /* Would it make sense to cover more error cases? So far, I + * do not see good reason to do so. + */ + } + } + } + /* advance buffer to next write position */ + iTotalWritten += iWritten; + lenBuf -= iWritten; + pWriteBuf += iWritten; + } while(lenBuf > 0); /* Warning: do..while()! */ + + DBGOPRINT((obj_t*) pThis, "file %d write wrote %d bytes\n", pThis->fd, (int) iWritten); + +finalize_it: + *pLenBuf = iTotalWritten; + RETiRet; +} + + + +/* write memory buffer to a stream object. + */ +static inline rsRetVal +doWriteInternal(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlush) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->iZipLevel) { + CHKiRet(doZipWrite(pThis, pBuf, lenBuf, bFlush)); + } else { + /* write without zipping */ + CHKiRet(strmPhysWrite(pThis, pBuf, lenBuf)); + } + +finalize_it: + RETiRet; +} + + +/* This function is called to "do" an async write call, what primarily means that + * the data is handed over to the writer thread (which will then do the actual write + * in parallel). Note that the stream mutex has already been locked by the + * strmWrite...() calls. Also note that we always have only a single producer, + * so we can simply serially assign the next free buffer to it and be sure that + * the very some producer comes back in sequence to submit the then-filled buffers. + * This also enables us to timout on partially written buffers. -- rgerhards, 2009-07-06 + */ +static inline rsRetVal +doAsyncWriteInternal(strm_t *pThis, size_t lenBuf) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strm); + + /* the -1 below is important, because we need one buffer for the main thread! */ + while(pThis->iCnt >= STREAM_ASYNC_NUMBUFS - 1) + d_pthread_cond_wait(&pThis->notFull, &pThis->mut); + + pThis->asyncBuf[pThis->iEnq % STREAM_ASYNC_NUMBUFS].lenBuf = lenBuf; + pThis->pIOBuf = pThis->asyncBuf[++pThis->iEnq % STREAM_ASYNC_NUMBUFS].pBuf; + + pThis->bDoTimedWait = 0; /* everything written, no need to timeout partial buffer writes */ + if(++pThis->iCnt == 1) + pthread_cond_signal(&pThis->notEmpty); + + RETiRet; +} + + +/* schedule writing to the stream. Depending on our concurrency settings, + * this either directly writes to the stream or schedules writing via + * the background thread. -- rgerhards, 2009-07-07 + */ +static rsRetVal +strmSchedWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlushZip) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* we need to reset the buffer pointer BEFORE calling the actual write + * function. Otherwise, in circular mode, the write function will + * potentially close the file, then close will flush and as the + * buffer pointer is nonzero, will re-call into this code here. In + * the end result, we than have a problem (and things are screwed + * up). So we reset the buffer pointer first, and all this can + * not happen. It is safe to do so, because that pointer is NOT + * used inside the write functions. -- rgerhads, 2010-03-10 + */ + pThis->iBufPtr = 0; /* we are at the begin of a new buffer */ + if(pThis->bAsyncWrite) { + CHKiRet(doAsyncWriteInternal(pThis, lenBuf)); + } else { + CHKiRet(doWriteInternal(pThis, pBuf, lenBuf, bFlushZip)); + } + + +finalize_it: + RETiRet; +} + + + +/* This is the writer thread for asynchronous mode. + * -- rgerhards, 2009-07-06 + */ +static void* +asyncWriterThread(void *pPtr) +{ + int iDeq; + struct timespec t; + sbool bTimedOut = 0; + strm_t *pThis = (strm_t*) pPtr; + int err; + uchar thrdName[256] = "rs:"; + ISOBJ_TYPE_assert(pThis, strm); + + BEGINfunc + ustrncpy(thrdName+3, pThis->pszFName, sizeof(thrdName)-4); + dbgOutputTID((char*)thrdName); +# if HAVE_PRCTL && defined PR_SET_NAME + if(prctl(PR_SET_NAME, (char*)thrdName, 0, 0, 0) != 0) { + DBGPRINTF("prctl failed, not setting thread name for '%s'\n", "stream writer"); + } +# endif + + d_pthread_mutex_lock(&pThis->mut); + while(1) { /* loop broken inside */ + while(pThis->iCnt == 0) { + if(pThis->bStopWriter) { + pthread_cond_broadcast(&pThis->isEmpty); + d_pthread_mutex_unlock(&pThis->mut); + goto finalize_it; /* break main loop */ + } + if(bTimedOut && pThis->iBufPtr > 0) { + /* if we timed out, we need to flush pending data */ + strmFlushInternal(pThis, 0); + bTimedOut = 0; + d_pthread_mutex_unlock(&pThis->mut); + continue; + } + bTimedOut = 0; + timeoutComp(&t, pThis->iFlushInterval * 1000); /* *1000 millisconds */ + if(pThis->bDoTimedWait) { + if((err = pthread_cond_timedwait(&pThis->notEmpty, &pThis->mut, &t)) != 0) { + bTimedOut = 1; /* simulate in any case */ + if(err != ETIMEDOUT) { + char errStr[1024]; + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGPRINTF("stream async writer timeout with error (%d): %s - ignoring\n", + err, errStr); + } + } + } else { + d_pthread_cond_wait(&pThis->notEmpty, &pThis->mut); + } + } + + bTimedOut = 0; /* we may have timed out, but there *is* work to do... */ + + iDeq = pThis->iDeq++ % STREAM_ASYNC_NUMBUFS; + + /* now we can do the actual write in parallel */ + d_pthread_mutex_unlock(&pThis->mut); + doWriteInternal(pThis, pThis->asyncBuf[iDeq].pBuf, pThis->asyncBuf[iDeq].lenBuf, 0); // TODO: flush state + // TODO: error check????? 2009-07-06 + d_pthread_mutex_lock(&pThis->mut); + + --pThis->iCnt; + if(pThis->iCnt < STREAM_ASYNC_NUMBUFS) { + pthread_cond_signal(&pThis->notFull); + if(pThis->iCnt == 0) + pthread_cond_broadcast(&pThis->isEmpty); + } + } + d_pthread_mutex_unlock(&pThis->mut); + +finalize_it: + ENDfunc + return NULL; /* to keep pthreads happy */ +} + + +/* sync the file to disk, so that any unwritten data is persisted. This + * also syncs the directory and thus makes sure that the file survives + * fatal failure. Note that we do NOT return an error status if the + * sync fails. Doing so would probably cause more trouble than it + * is worth (read: data loss may occur where we otherwise might not + * have it). -- rgerhards, 2009-06-08 + */ +#undef SYNCCALL +#if HAVE_FDATASYNC +# define SYNCCALL(x) fdatasync(x) +#else +# define SYNCCALL(x) fsync(x) +#endif +static rsRetVal +syncFile(strm_t *pThis) +{ + int ret; + DEFiRet; + + if(pThis->bIsTTY) + FINALIZE; /* TTYs can not be synced */ + + DBGPRINTF("syncing file %d\n", pThis->fd); + ret = SYNCCALL(pThis->fd); + if(ret != 0) { + char errStr[1024]; + int err = errno; + rs_strerror_r(err, errStr, sizeof(errStr)); + DBGPRINTF("sync failed for file %d with error (%d): %s - ignoring\n", + pThis->fd, err, errStr); + } + + if(pThis->fdDir != -1) { + ret = fsync(pThis->fdDir); + } + +finalize_it: + RETiRet; +} +#undef SYNCCALL + +/* physically write to the output file. the provided data is ready for + * writing (e.g. zipped if we are requested to do that). + * Note that if the write() API fails, we do not reset any pointers, but return + * an error code. That means we may redo work in the next iteration. + * rgerhards, 2009-06-04 + */ +static rsRetVal +strmPhysWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf) +{ + size_t iWritten; + DEFiRet; + ISOBJ_TYPE_assert(pThis, strm); + + DBGPRINTF("strmPhysWrite, stream %p, len %u\n", pThis, (unsigned)lenBuf); + if(pThis->fd == -1) + CHKiRet(strmOpenFile(pThis)); + + /* here we place our crypto interface */ + if(pThis->cryprov != NULL) { + pThis->cryprov->Encrypt(pThis->cryprovFileData, pBuf, &lenBuf); + } + /* end crypto */ + + iWritten = lenBuf; + CHKiRet(doWriteCall(pThis, pBuf, &iWritten)); + + pThis->iCurrOffs += iWritten; + /* update user counter, if provided */ + if(pThis->pUsrWCntr != NULL) + *pThis->pUsrWCntr += iWritten; + + if(pThis->bSync) { + CHKiRet(syncFile(pThis)); + } + + if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) { + CHKiRet(strmCheckNextOutputFile(pThis)); + } else if(pThis->iSizeLimit != 0) { + CHKiRet(doSizeLimitProcessing(pThis)); + } + +finalize_it: + RETiRet; +} + + +/* write the output buffer in zip mode + * This means we compress it first and then do a physical write. + * Note that we always do a full deflateInit ... deflate ... deflateEnd + * sequence. While this is not optimal, we need to do it because we need + * to ensure that the file is readable even when we are aborted. Doing the + * full sequence brings us as far towards this goal as possible (and not + * doing it would be a total failure). It may be worth considering to + * add a config switch so that the user can decide the risk he is ready + * to take, but so far this is not yet implemented (not even requested ;)). + * rgerhards, 2009-06-04 + */ +static rsRetVal +doZipWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlush) +{ + int zRet; /* zlib return state */ + DEFiRet; + unsigned outavail; + assert(pThis != NULL); + assert(pBuf != NULL); + + if(!pThis->bzInitDone) { + /* allocate deflate state */ + pThis->zstrm.zalloc = Z_NULL; + pThis->zstrm.zfree = Z_NULL; + pThis->zstrm.opaque = Z_NULL; + /* see note in file header for the params we use with deflateInit2() */ + zRet = zlibw.DeflateInit2(&pThis->zstrm, pThis->iZipLevel, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY); + if(zRet != Z_OK) { + DBGPRINTF("error %d returned from zlib/deflateInit2()\n", zRet); + ABORT_FINALIZE(RS_RET_ZLIB_ERR); + } + pThis->bzInitDone = RSTRUE; + } + + /* now doing the compression */ + pThis->zstrm.next_in = (Bytef*) pBuf; + pThis->zstrm.avail_in = lenBuf; + /* run deflate() on buffer until everything has been compressed */ + do { + DBGPRINTF("in deflate() loop, avail_in %d, total_in %ld\n", pThis->zstrm.avail_in, pThis->zstrm.total_in); + pThis->zstrm.avail_out = pThis->sIOBufSize; + pThis->zstrm.next_out = pThis->pZipBuf; + zRet = zlibw.Deflate(&pThis->zstrm, bFlush ? Z_SYNC_FLUSH : Z_NO_FLUSH); /* no bad return value */ + DBGPRINTF("after deflate, ret %d, avail_out %d\n", zRet, pThis->zstrm.avail_out); + outavail =pThis->sIOBufSize - pThis->zstrm.avail_out; + if(outavail != 0) { + CHKiRet(strmPhysWrite(pThis, (uchar*)pThis->pZipBuf, outavail)); + } + } while (pThis->zstrm.avail_out == 0); + +finalize_it: + if(pThis->bzInitDone && pThis->bVeryReliableZip) { + doZipFinish(pThis); + } + RETiRet; +} + + + +/* finish zlib buffer, to be called before closing the ZIP file (if + * running in stream mode). + */ +static rsRetVal +doZipFinish(strm_t *pThis) +{ + int zRet; /* zlib return state */ + DEFiRet; + unsigned outavail; + assert(pThis != NULL); + + if(!pThis->bzInitDone) + goto done; + + pThis->zstrm.avail_in = 0; + /* run deflate() on buffer until everything has been compressed */ + do { + DBGPRINTF("in deflate() loop, avail_in %d, total_in %ld\n", pThis->zstrm.avail_in, pThis->zstrm.total_in); + pThis->zstrm.avail_out = pThis->sIOBufSize; + pThis->zstrm.next_out = pThis->pZipBuf; + zRet = zlibw.Deflate(&pThis->zstrm, Z_FINISH); /* no bad return value */ + DBGPRINTF("after deflate, ret %d, avail_out %d\n", zRet, pThis->zstrm.avail_out); + outavail = pThis->sIOBufSize - pThis->zstrm.avail_out; + if(outavail != 0) { + CHKiRet(strmPhysWrite(pThis, (uchar*)pThis->pZipBuf, outavail)); + } + } while (pThis->zstrm.avail_out == 0); + +finalize_it: + zRet = zlibw.DeflateEnd(&pThis->zstrm); + if(zRet != Z_OK) { + DBGPRINTF("error %d returned from zlib/deflateEnd()\n", zRet); + } + + pThis->bzInitDone = 0; +done: RETiRet; +} + +/* flush stream output buffer to persistent storage. This can be called at any time + * and is automatically called when the output buffer is full. + * rgerhards, 2008-01-10 + */ +static rsRetVal +strmFlushInternal(strm_t *pThis, int bFlushZip) +{ + DEFiRet; + + ASSERT(pThis != NULL); + DBGOPRINT((obj_t*) pThis, "file %d(%s) flush, buflen %ld%s\n", pThis->fd, + (pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName, + (long) pThis->iBufPtr, (pThis->iBufPtr == 0) ? " (no need to flush)" : ""); + + if(pThis->tOperationsMode != STREAMMODE_READ && pThis->iBufPtr > 0) { + iRet = strmSchedWrite(pThis, pThis->pIOBuf, pThis->iBufPtr, bFlushZip); + } + + RETiRet; +} + + +/* flush stream output buffer to persistent storage. This can be called at any time + * and is automatically called when the output buffer is full. This function is for + * use by EXTERNAL callers. Do NOT use it internally. It locks the async writer + * mutex if ther is need to do so. + * rgerhards, 2010-03-18 + */ +static rsRetVal +strmFlush(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->bAsyncWrite) + d_pthread_mutex_lock(&pThis->mut); + CHKiRet(strmFlushInternal(pThis, 1)); + +finalize_it: + if(pThis->bAsyncWrite) + d_pthread_mutex_unlock(&pThis->mut); + + RETiRet; +} + + +/* seek a stream to a specific location. Pending writes are flushed, read data + * is invalidated. + * rgerhards, 2008-01-12 + */ +static rsRetVal strmSeek(strm_t *pThis, off64_t offs) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(pThis->fd == -1) { + CHKiRet(strmOpenFile(pThis)); + } else { + CHKiRet(strmFlushInternal(pThis, 0)); + } + long long i; + DBGOPRINT((obj_t*) pThis, "file %d seek, pos %llu\n", pThis->fd, (long long unsigned) offs); + i = lseek64(pThis->fd, offs, SEEK_SET); + if(i != offs) { + DBGPRINTF("strmSeek: error %lld seeking to offset %lld\n", i, offs); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + pThis->iCurrOffs = offs; /* we are now at *this* offset */ + pThis->iBufPtr = 0; /* buffer invalidated */ + +finalize_it: + RETiRet; +} + +/* multi-file seek, seeks to file number & offset within file. This + * is a support function for the queue, in circular mode. DO NOT USE + * IT FOR OTHER NEEDS - it may not work as expected. It will + * seek to the new position and delete interim files, as it skips them. + * Note: this code can be removed when the queue gets a new disk store + * handler (if and when it does ;)). + * The output parameter bytesDel receives the number of bytes that have + * been deleted (if a file is deleted) or 0 if nothing was deleted. + * rgerhards, 2012-11-07 + */ +rsRetVal +strmMultiFileSeek(strm_t *pThis, int FNum, off64_t offs, off64_t *bytesDel) +{ + struct stat statBuf; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(FNum == 0 && offs == 0) { /* happens during queue init */ + *bytesDel = 0; + FINALIZE; + } + + if(pThis->iCurrFNum != FNum) { + /* Note: we assume that no more than one file is skipped - an + * assumption that is being used also by the whole rest of the + * code and most notably the queue subsystem. + */ + CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir, + pThis->pszFName, pThis->lenFName, pThis->iCurrFNum, + pThis->iFileNumDigits)); + stat((char*)pThis->pszCurrFName, &statBuf); + *bytesDel = statBuf.st_size; + DBGPRINTF("strmMultiFileSeek: detected new filenum, was %d, new %d, " + "deleting '%s' (%lld bytes)\n", pThis->iCurrFNum, FNum, + pThis->pszCurrFName, (long long) *bytesDel); + unlink((char*)pThis->pszCurrFName); + if(pThis->cryprov != NULL) + pThis->cryprov->DeleteStateFiles(pThis->pszCurrFName); + free(pThis->pszCurrFName); + pThis->pszCurrFName = NULL; + pThis->iCurrFNum = FNum; + } else { + *bytesDel = 0; + } + pThis->iCurrOffs = offs; + +finalize_it: + RETiRet; +} + + +/* seek to current offset. This is primarily a helper to readjust the OS file + * pointer after a strm object has been deserialized. + */ +static rsRetVal strmSeekCurrOffs(strm_t *pThis) +{ + off64_t targetOffs; + uchar c; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(pThis->cryprov == NULL || pThis->tOperationsMode != STREAMMODE_READ) { + iRet = strmSeek(pThis, pThis->iCurrOffs); + FINALIZE; + } + + /* As the cryprov may use CBC or similiar things, we need to read skip data */ + targetOffs = pThis->iCurrOffs; + pThis->iCurrOffs = 0; + DBGOPRINT((obj_t*) pThis, "encrypted, doing skip read of %lld bytes\n", + (long long) targetOffs); + while(targetOffs != pThis->iCurrOffs) { + CHKiRet(strmReadChar(pThis, &c)); + } +finalize_it: + RETiRet; +} + + +/* write a *single* character to a stream object -- rgerhards, 2008-01-10 + */ +static rsRetVal strmWriteChar(strm_t *pThis, uchar c) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->bAsyncWrite) + d_pthread_mutex_lock(&pThis->mut); + + if(pThis->bDisabled) + ABORT_FINALIZE(RS_RET_STREAM_DISABLED); + + /* if the buffer is full, we need to flush before we can write */ + if(pThis->iBufPtr == pThis->sIOBufSize) { + CHKiRet(strmFlushInternal(pThis, 0)); + } + /* we now always have space for one character, so we simply copy it */ + *(pThis->pIOBuf + pThis->iBufPtr) = c; + pThis->iBufPtr++; + +finalize_it: + if(pThis->bAsyncWrite) + d_pthread_mutex_unlock(&pThis->mut); + + RETiRet; +} + + +/* write an integer value (actually a long) to a stream object + * Note that we do not need to lock the mutex here, because we call + * strmWrite(), which does the lock (aka: we must not lock it, else we + * would run into a recursive lock, resulting in a deadlock!) + */ +static rsRetVal strmWriteLong(strm_t *pThis, long i) +{ + DEFiRet; + uchar szBuf[32]; + + ASSERT(pThis != NULL); + + CHKiRet(srUtilItoA((char*)szBuf, sizeof(szBuf), i)); + CHKiRet(strmWrite(pThis, szBuf, strlen((char*)szBuf))); + +finalize_it: + RETiRet; +} + + +/* write memory buffer to a stream object. + * process the data in chunks and copy it over to our buffer. The caller-provided data + * may theoritically be larger than our buffer. In that case, we do multiple copies. One + * may argue if it were more efficient to write out the caller-provided buffer in that case + * and earlier versions of rsyslog did this. However, this introduces a lot of complexity + * inside the buffered writer and potential performance bottlenecks when trying to solve + * it. Now keep in mind that we actually do (almost?) never have a case where the + * caller-provided buffer is larger than our one. So instead of optimizing a case + * which normally does not exist, we expect some degradation in its case but make us + * perform better in the regular cases. -- rgerhards, 2009-07-07 + * Note: the pThis->iBufPtr == pThis->sIOBufSize logic below looks a bit like an + * on-off error. In fact, it is not, because iBufPtr always points to the next + * *free* byte in the buffer. So if it is sIOBufSize - 1, there actually is one + * free byte left. This came up during a code walkthrough and was considered + * worth nothing. -- rgerhards, 2010-03-10 + */ +static rsRetVal +strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf) +{ + DEFiRet; + size_t iWrite; + size_t iOffset; + + ASSERT(pThis != NULL); + ASSERT(pBuf != NULL); + + /* DEV DEBUG ONLY DBGPRINTF("strmWrite(%p[%s], '%65.65s', %ld);, disabled %d, sizelim %ld, size %lld\n", pThis, pThis->pszCurrFName, pBuf,(long) lenBuf, pThis->bDisabled, (long) pThis->iSizeLimit, (long long) pThis->iCurrOffs); */ + if(pThis->bDisabled) + ABORT_FINALIZE(RS_RET_STREAM_DISABLED); + + if(pThis->bAsyncWrite) + d_pthread_mutex_lock(&pThis->mut); + + iOffset = 0; + do { + if(pThis->iBufPtr == pThis->sIOBufSize) { + CHKiRet(strmFlushInternal(pThis, 0)); /* get a new buffer for rest of data */ + } + iWrite = pThis->sIOBufSize - pThis->iBufPtr; /* this fits in current buf */ + if(iWrite > lenBuf) + iWrite = lenBuf; + memcpy(pThis->pIOBuf + pThis->iBufPtr, pBuf + iOffset, iWrite); + pThis->iBufPtr += iWrite; + iOffset += iWrite; + lenBuf -= iWrite; + } while(lenBuf > 0); + + /* now check if the buffer right at the end of the write is full and, if so, + * write it. This seems more natural than waiting (hours?) for the next message... + */ + if(pThis->iBufPtr == pThis->sIOBufSize) { + CHKiRet(strmFlushInternal(pThis, 0)); /* get a new buffer for rest of data */ + } + +finalize_it: + if(pThis->bAsyncWrite) { + if(pThis->bDoTimedWait == 0) { + /* we potentially have a partial buffer, so re-activate the + * writer thread that it can set and pick up timeouts. + */ + pThis->bDoTimedWait = 1; + pthread_cond_signal(&pThis->notEmpty); + } + d_pthread_mutex_unlock(&pThis->mut); + } + + RETiRet; +} + + +/* property set methods */ +/* simple ones first */ +DEFpropSetMeth(strm, iMaxFileSize, int) +DEFpropSetMeth(strm, iFileNumDigits, int) +DEFpropSetMeth(strm, tOperationsMode, int) +DEFpropSetMeth(strm, tOpenMode, mode_t) +DEFpropSetMeth(strm, sType, strmType_t) +DEFpropSetMeth(strm, iZipLevel, int) +DEFpropSetMeth(strm, bVeryReliableZip, int) +DEFpropSetMeth(strm, bSync, int) +DEFpropSetMeth(strm, sIOBufSize, size_t) +DEFpropSetMeth(strm, iSizeLimit, off_t) +DEFpropSetMeth(strm, iFlushInterval, int) +DEFpropSetMeth(strm, pszSizeLimitCmd, uchar*) +DEFpropSetMeth(strm, cryprov, cryprov_if_t*) +DEFpropSetMeth(strm, cryprovData, void*) + +static rsRetVal strmSetbDeleteOnClose(strm_t *pThis, int val) +{ + pThis->bDeleteOnClose = val; + if(pThis->cryprov != NULL) { + pThis->cryprov->SetDeleteOnClose(pThis->cryprovFileData, pThis->bDeleteOnClose); + } + return RS_RET_OK; +} + +static rsRetVal strmSetiMaxFiles(strm_t *pThis, int iNewVal) +{ + pThis->iMaxFiles = iNewVal; + pThis->iFileNumDigits = getNumberDigits(iNewVal); + return RS_RET_OK; +} + + +/* set the stream's file prefix + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +static rsRetVal +strmSetFName(strm_t *pThis, uchar *pszName, size_t iLenName) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pszName != NULL); + + if(iLenName < 1) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + if(pThis->pszFName != NULL) + free(pThis->pszFName); + + if((pThis->pszFName = MALLOC(sizeof(uchar) * (iLenName + 1))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszFName, pszName, iLenName + 1); /* always think about the \0! */ + pThis->lenFName = iLenName; + +finalize_it: + RETiRet; +} + + +/* set the stream's directory + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +static rsRetVal +strmSetDir(strm_t *pThis, uchar *pszDir, size_t iLenDir) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pszDir != NULL); + + if(iLenDir < 1) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + CHKmalloc(pThis->pszDir = MALLOC(sizeof(uchar) * (iLenDir + 1))); + + memcpy(pThis->pszDir, pszDir, iLenDir + 1); /* always think about the \0! */ + pThis->lenDir = iLenDir; + +finalize_it: + RETiRet; +} + + +/* support for data records + * The stream class is able to write to multiple files. However, there are + * situation (actually quite common), where a single data record should not + * be split across files. This may be problematic if multiple stream write + * calls are used to create the record. To support that, we provide the + * bInRecord status variable. If it is set, no file spliting occurs. Once + * it is set to 0, a check is done if a split is necessary and it then + * happens. For a record-oriented caller, the proper sequence is: + * + * strmRecordBegin() + * strmWrite...() + * strmRecordEnd() + * + * Please note that records do not affect the writing of output buffers. They + * are always written when full. The only thing affected is circular files + * creation. So it is safe to write large records. + * + * IMPORTANT: RecordBegin() can not be nested! It is a programming error + * if RecordBegin() is called while already in a record! + * + * rgerhards, 2008-01-10 + */ +static rsRetVal strmRecordBegin(strm_t *pThis) +{ + ASSERT(pThis != NULL); + ASSERT(pThis->bInRecord == 0); + pThis->bInRecord = 1; + return RS_RET_OK; +} + +static rsRetVal strmRecordEnd(strm_t *pThis) +{ + DEFiRet; + ASSERT(pThis != NULL); + ASSERT(pThis->bInRecord == 1); + + pThis->bInRecord = 0; + iRet = strmCheckNextOutputFile(pThis); /* check if we need to switch files */ + + RETiRet; +} +/* end stream record support functions */ + + +/* This method serializes a stream object. That means the whole + * object is modified into text form. That text form is suitable for + * later reconstruction of the object. + * The most common use case for this method is the creation of an + * on-disk representation of the message object. + * We do not serialize the dynamic properties. + * rgerhards, 2008-01-10 + */ +static rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm) +{ + DEFiRet; + int i; + int64 l; + + ISOBJ_TYPE_assert(pThis, strm); + ISOBJ_TYPE_assert(pStrm, strm); + + strmFlushInternal(pThis, 0); + CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); + + objSerializeSCALAR(pStrm, iCurrFNum, INT); + objSerializePTR(pStrm, pszFName, PSZ); + objSerializeSCALAR(pStrm, iMaxFiles, INT); + objSerializeSCALAR(pStrm, bDeleteOnClose, INT); + + i = pThis->sType; + objSerializeSCALAR_VAR(pStrm, sType, INT, i); + + i = pThis->tOperationsMode; + objSerializeSCALAR_VAR(pStrm, tOperationsMode, INT, i); + + i = pThis->tOpenMode; + objSerializeSCALAR_VAR(pStrm, tOpenMode, INT, i); + + l = pThis->iCurrOffs; + objSerializeSCALAR_VAR(pStrm, iCurrOffs, INT64, l); + + l = pThis->inode; + objSerializeSCALAR_VAR(pStrm, inode, INT64, l); + + objSerializePTR(pStrm, prevLineSegment, PSZ); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + +/* duplicate a stream object excluding dynamic properties. This function is + * primarily meant to provide a duplicate that later on can be used to access + * the data. This is needed, for example, for a restart of the disk queue. + * Note that ConstructFinalize() is NOT called. So our caller may change some + * properties before finalizing things. + * rgerhards, 2009-05-26 + */ +rsRetVal +strmDup(strm_t *pThis, strm_t **ppNew) +{ + strm_t *pNew = NULL; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + assert(ppNew != NULL); + + CHKiRet(strmConstruct(&pNew)); + pNew->sType = pThis->sType; + pNew->iCurrFNum = pThis->iCurrFNum; + CHKmalloc(pNew->pszFName = ustrdup(pThis->pszFName)); + pNew->lenFName = pThis->lenFName; + CHKmalloc(pNew->pszDir = ustrdup(pThis->pszDir)); + pNew->lenDir = pThis->lenDir; + pNew->tOperationsMode = pThis->tOperationsMode; + pNew->tOpenMode = pThis->tOpenMode; + pNew->iMaxFileSize = pThis->iMaxFileSize; + pNew->iMaxFiles = pThis->iMaxFiles; + pNew->iFileNumDigits = pThis->iFileNumDigits; + pNew->bDeleteOnClose = pThis->bDeleteOnClose; + pNew->iCurrOffs = pThis->iCurrOffs; + + *ppNew = pNew; + pNew = NULL; + +finalize_it: + if(pNew != NULL) + strmDestruct(&pNew); + + RETiRet; +} + +/* set a user write-counter. This counter is initialized to zero and + * receives the number of bytes written. It is accurate only after a + * flush(). This hook is provided as a means to control disk size usage. + * The pointer must be valid at all times (so if it is on the stack, be sure + * to remove it when you exit the function). Pointers are removed by + * calling strmSetWCntr() with a NULL param. Only one pointer is settable, + * any new set overwrites the previous one. + * rgerhards, 2008-02-27 + */ +static rsRetVal +strmSetWCntr(strm_t *pThis, number_t *pWCnt) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(pWCnt != NULL) + *pWCnt = 0; + pThis->pUsrWCntr = pWCnt; + + RETiRet; +} + + +#include "stringbuf.h" + +/* This function can be used as a generic way to set properties. + * rgerhards, 2008-01-11 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, UCHAR_CONSTANT(name), sizeof(name) - 1) +static rsRetVal strmSetProperty(strm_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + ASSERT(pProp != NULL); + + if(isProp("sType")) { + CHKiRet(strmSetsType(pThis, (strmType_t) pProp->val.num)); + } else if(isProp("iCurrFNum")) { + pThis->iCurrFNum = pProp->val.num; + } else if(isProp("pszFName")) { + CHKiRet(strmSetFName(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr))); + } else if(isProp("tOperationsMode")) { + CHKiRet(strmSettOperationsMode(pThis, pProp->val.num)); + } else if(isProp("tOpenMode")) { + CHKiRet(strmSettOpenMode(pThis, pProp->val.num)); + } else if(isProp("iCurrOffs")) { + pThis->iCurrOffs = pProp->val.num; + } else if(isProp("inode")) { + pThis->inode = (ino_t) pProp->val.num; + } else if(isProp("iMaxFileSize")) { + CHKiRet(strmSetiMaxFileSize(pThis, pProp->val.num)); + } else if(isProp("iMaxFiles")) { + CHKiRet(strmSetiMaxFiles(pThis, pProp->val.num)); + } else if(isProp("iFileNumDigits")) { + CHKiRet(strmSetiFileNumDigits(pThis, pProp->val.num)); + } else if(isProp("bDeleteOnClose")) { + CHKiRet(strmSetbDeleteOnClose(pThis, pProp->val.num)); + } else if(isProp("prevLineSegment")) { + CHKiRet(rsCStrConstructFromCStr(&pThis->prevLineSegment, pProp->val.pStr)); + } + +finalize_it: + RETiRet; +} +#undef isProp + + +/* return the current offset inside the stream. Note that on two consequtive calls, the offset + * reported on the second call may actually be lower than on the first call. This is due to + * file circulation. A caller must deal with that. -- rgerhards, 2008-01-30 + */ +static rsRetVal +strmGetCurrOffset(strm_t *pThis, int64 *pOffs) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + ASSERT(pOffs != NULL); + + *pOffs = pThis->iCurrOffs; + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(strm) +CODESTARTobjQueryInterface(strm) + if(pIf->ifVersion != strmCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = strmConstruct; + pIf->ConstructFinalize = strmConstructFinalize; + pIf->Destruct = strmDestruct; + pIf->ReadChar = strmReadChar; + pIf->UnreadChar = strmUnreadChar; + pIf->ReadLine = strmReadLine; + pIf->SeekCurrOffs = strmSeekCurrOffs; + pIf->Write = strmWrite; + pIf->WriteChar = strmWriteChar; + pIf->WriteLong = strmWriteLong; + pIf->SetFName = strmSetFName; + pIf->SetDir = strmSetDir; + pIf->Flush = strmFlush; + pIf->RecordBegin = strmRecordBegin; + pIf->RecordEnd = strmRecordEnd; + pIf->Serialize = strmSerialize; + pIf->GetCurrOffset = strmGetCurrOffset; + pIf->Dup = strmDup; + pIf->SetWCntr = strmSetWCntr; + pIf->CheckFileChange = CheckFileChange; + /* set methods */ + pIf->SetbDeleteOnClose = strmSetbDeleteOnClose; + pIf->SetiMaxFileSize = strmSetiMaxFileSize; + pIf->SetiMaxFiles = strmSetiMaxFiles; + pIf->SetiFileNumDigits = strmSetiFileNumDigits; + pIf->SettOperationsMode = strmSettOperationsMode; + pIf->SettOpenMode = strmSettOpenMode; + pIf->SetsType = strmSetsType; + pIf->SetiZipLevel = strmSetiZipLevel; + pIf->SetbVeryReliableZip = strmSetbVeryReliableZip; + pIf->SetbSync = strmSetbSync; + pIf->SetsIOBufSize = strmSetsIOBufSize; + pIf->SetiSizeLimit = strmSetiSizeLimit; + pIf->SetiFlushInterval = strmSetiFlushInterval; + pIf->SetpszSizeLimitCmd = strmSetpszSizeLimitCmd; + pIf->Setcryprov = strmSetcryprov; + pIf->SetcryprovData = strmSetcryprovData; +finalize_it: +ENDobjQueryInterface(strm) + + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(strm, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + + OBJSetMethodHandler(objMethod_SERIALIZE, strmSerialize); + OBJSetMethodHandler(objMethod_SETPROPERTY, strmSetProperty); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strmConstructFinalize); +ENDObjClassInit(strm) + +/* vi:set ai: + */ diff --git a/runtime/stream.h b/runtime/stream.h new file mode 100644 index 00000000..61d5ede2 --- /dev/null +++ b/runtime/stream.h @@ -0,0 +1,212 @@ +/* Definition of serial stream class (strm). + * + * A serial stream provides serial data access. In theory, serial streams + * can be implemented via a number of methods (e.g. files or in-memory + * streams). In practice, there currently only exist the file type (aka + * "driver"). + * + * In practice, many stream features are bound to files. I have not yet made + * any serious effort, except for the naming of this class, to try to make + * the interfaces very generic. However, I assume that we could work much + * like in the strm class, where some properties are simply ignored when + * the wrong strm mode is selected (which would translate here to the wrong + * stream mode). + * + * Most importantly, this class provides generic input and output functions + * which can directly be used to work with the strms and file output. It + * provides such useful things like a circular file buffer and, hopefully + * at a later stage, a lazy writer. The object is also seriazable and thus + * can easily be persistet. The bottom line is that it makes much sense to + * use this class whereever possible as its features may grow in the future. + * + * An important note on writing gzip format via zlib (kept anonymous + * by request): + * + * -------------------------------------------------------------------------- + * We'd like to make sure the output file is in full gzip format + * (compatible with gzip -d/zcat etc). There is a flag in how the output + * is initialized within zlib to properly add the gzip wrappers to the + * output. (gzip is effectively a small metadata wrapper around raw + * zstream output.) + * + * I had written an old bit of code to do this - the documentation on + * deflatInit2() was pretty tricky to nail down on this specific feature: + * + * int deflateInit2 (z_streamp strm, int level, int method, int windowBits, + * int memLevel, int strategy); + * + * I believe "31" would be the value for the "windowBits" field that you'd + * want to try: + * + * deflateInit2(zstrmptr, 6, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY); + * -------------------------------------------------------------------------- + * + * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef STREAM_H_INCLUDED +#define STREAM_H_INCLUDED + +#include <pthread.h> +#include "obj-types.h" +#include "glbl.h" +#include "stream.h" +#include "zlibw.h" +#include "cryprov.h" + +/* stream types */ +typedef enum { + STREAMTYPE_FILE_SINGLE = 0, /**< read a single file */ + STREAMTYPE_FILE_CIRCULAR = 1, /**< circular files */ + STREAMTYPE_FILE_MONITOR = 2, /**< monitor a (third-party) file */ + STREAMTYPE_NAMED_PIPE = 3 /**< file is a named pipe (so far, tested for output only) */ +} strmType_t; + +typedef enum { /* when extending, do NOT change existing modes! */ + STREAMMMODE_INVALID = 0, + STREAMMODE_READ = 1, + STREAMMODE_WRITE = 2, + STREAMMODE_WRITE_TRUNC = 3, + STREAMMODE_WRITE_APPEND = 4 +} strmMode_t; + +#define STREAM_ASYNC_NUMBUFS 2 /* must be a power of 2 -- TODO: make configurable */ +/* The strm_t data structure */ +typedef struct strm_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + strmType_t sType; + /* descriptive properties */ + int iCurrFNum;/* current file number (NOT descriptor, but the number in the file name!) */ + uchar *pszFName; /* prefix for generated filenames */ + int lenFName; + strmMode_t tOperationsMode; + mode_t tOpenMode; + int64 iMaxFileSize;/* maximum size a file may grow to */ + int iMaxFiles; /* maximum number of files if a circular mode is in use */ + int iFileNumDigits;/* min number of digits to use in file number (only in circular mode) */ + sbool bDeleteOnClose; /* set to 1 to auto-delete on close -- be careful with that setting! */ + int64 iCurrOffs;/* current offset */ + int64 *pUsrWCntr; /* NULL or a user-provided counter that receives the nbr of bytes written since the last CntrSet() */ + /* dynamic properties, valid only during file open, not to be persistet */ + sbool bDisabled; /* should file no longer be written to? (currently set only if omfile file size limit fails) */ + sbool bSync; /* sync this file after every write? */ + size_t sIOBufSize;/* size of IO buffer */ + uchar *pszDir; /* Directory */ + int lenDir; + int fd; /* the file descriptor, -1 if closed */ + int fdDir; /* the directory's descriptor, in case bSync is requested (-1 if closed) */ + ino_t inode; /* current inode for files being monitored (undefined else) */ + uchar *pszCurrFName; /* name of current file (if open) */ + uchar *pIOBuf; /* the iobuffer currently in use to gather data */ + size_t iBufPtrMax; /* current max Ptr in Buffer (if partial read!) */ + size_t iBufPtr; /* pointer into current buffer */ + int iUngetC; /* char set via UngetChar() call or -1 if none set */ + sbool bInRecord; /* if 1, indicates that we are currently writing a not-yet complete record */ + int iZipLevel; /* zip level (0..9). If 0, zip is completely disabled */ + Bytef *pZipBuf; + /* support for async flush procesing */ + sbool bAsyncWrite; /* do asynchronous writes (always if a flush interval is given) */ + sbool bStopWriter; /* shall writer thread terminate? */ + sbool bDoTimedWait; /* instruct writer thread to do a times wait to support flush timeouts */ + sbool bzInitDone; /* did we do an init of zstrm already? */ + sbool bVeryReliableZip; /* shall we write interim headers to create a very reliable ZIP file? */ + int iFlushInterval; /* flush in which interval - 0, no flushing */ + pthread_mutex_t mut;/* mutex for flush in async mode */ + pthread_cond_t notFull; + pthread_cond_t notEmpty; + pthread_cond_t isEmpty; + unsigned short iEnq; /* this MUST be unsigned as we use module arithmetic (else invalid indexing happens!) */ + unsigned short iDeq; /* this MUST be unsigned as we use module arithmetic (else invalid indexing happens!) */ + cryprov_if_t *cryprov; /* ptr to crypto provider; NULL = do not encrypt */ + void *cryprovData; /* opaque data ptr for provider use */ + void *cryprovFileData;/* opaque data ptr for file instance */ + short iCnt; /* current nbr of elements in buffer */ + z_stream zstrm; /* zip stream to use */ + struct { + uchar *pBuf; + size_t lenBuf; + } asyncBuf[STREAM_ASYNC_NUMBUFS]; + pthread_t writerThreadID; + /* support for omfile size-limiting commands, special counters, NOT persisted! */ + off_t iSizeLimit; /* file size limit, 0 = no limit */ + uchar *pszSizeLimitCmd; /* command to carry out when size limit is reached */ + sbool bIsTTY; /* is this a tty file? */ + cstr_t *prevLineSegment; /* for ReadLine, previous, unwritten part of file */ +} strm_t; + + +/* interfaces */ +BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(strm_t **ppThis); + rsRetVal (*ConstructFinalize)(strm_t *pThis); + rsRetVal (*Destruct)(strm_t **ppThis); + rsRetVal (*SetMaxFileSize)(strm_t *pThis, int64 iMaxFileSize); + rsRetVal (*SetFileName)(strm_t *pThis, uchar *pszName, size_t iLenName); + rsRetVal (*ReadChar)(strm_t *pThis, uchar *pC); + rsRetVal (*UnreadChar)(strm_t *pThis, uchar c); + rsRetVal (*SeekCurrOffs)(strm_t *pThis); + rsRetVal (*Write)(strm_t *pThis, uchar *pBuf, size_t lenBuf); + rsRetVal (*WriteChar)(strm_t *pThis, uchar c); + rsRetVal (*WriteLong)(strm_t *pThis, long i); + rsRetVal (*SetFName)(strm_t *pThis, uchar *pszPrefix, size_t iLenPrefix); + rsRetVal (*SetDir)(strm_t *pThis, uchar *pszDir, size_t iLenDir); + rsRetVal (*Flush)(strm_t *pThis); + rsRetVal (*RecordBegin)(strm_t *pThis); + rsRetVal (*RecordEnd)(strm_t *pThis); + rsRetVal (*Serialize)(strm_t *pThis, strm_t *pStrm); + rsRetVal (*GetCurrOffset)(strm_t *pThis, int64 *pOffs); + rsRetVal (*SetWCntr)(strm_t *pThis, number_t *pWCnt); + rsRetVal (*Dup)(strm_t *pThis, strm_t **ppNew); + INTERFACEpropSetMeth(strm, bDeleteOnClose, int); + INTERFACEpropSetMeth(strm, iMaxFileSize, int); + INTERFACEpropSetMeth(strm, iMaxFiles, int); + INTERFACEpropSetMeth(strm, iFileNumDigits, int); + INTERFACEpropSetMeth(strm, tOperationsMode, int); + INTERFACEpropSetMeth(strm, tOpenMode, mode_t); + INTERFACEpropSetMeth(strm, sType, strmType_t); + INTERFACEpropSetMeth(strm, iZipLevel, int); + INTERFACEpropSetMeth(strm, bSync, int); + INTERFACEpropSetMeth(strm, sIOBufSize, size_t); + INTERFACEpropSetMeth(strm, iSizeLimit, off_t); + INTERFACEpropSetMeth(strm, iFlushInterval, int); + INTERFACEpropSetMeth(strm, pszSizeLimitCmd, uchar*); + /* v6 added */ + rsRetVal (*ReadLine)(strm_t *pThis, cstr_t **ppCStr, int mode); + /* v7 added 2012-09-14 */ + INTERFACEpropSetMeth(strm, bVeryReliableZip, int); + /* v8 added 2013-03-21 */ + rsRetVal (*CheckFileChange)(strm_t *pThis); + /* v9 added 2013-04-04 */ + INTERFACEpropSetMeth(strm, cryprov, cryprov_if_t*); + INTERFACEpropSetMeth(strm, cryprovData, void*); +ENDinterface(strm) +#define strmCURR_IF_VERSION 9 /* increment whenever you change the interface structure! */ + +static inline int +strmGetCurrFileNum(strm_t *pStrm) { + return pStrm->iCurrFNum; +} + +/* prototypes */ +PROTOTYPEObjClassInit(strm); +rsRetVal strmMultiFileSeek(strm_t *pThis, int fileNum, off64_t offs, off64_t *bytesDel); + +#endif /* #ifndef STREAM_H_INCLUDED */ diff --git a/runtime/strgen.c b/runtime/strgen.c new file mode 100644 index 00000000..46be1236 --- /dev/null +++ b/runtime/strgen.c @@ -0,0 +1,279 @@ +/* strgen.c + * Module to handle string generators. These are C modules that receive + * the message object and return a custom-built string. The primary purpose + * for their existance is performance -- they do the same as template strings, but + * potentially faster (if well implmented). + * + * Module begun 2010-06-01 by Rainer Gerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "rsyslog.h" +#include "msg.h" +#include "obj.h" +#include "errmsg.h" +#include "strgen.h" +#include "ruleset.h" +#include "unicode-helper.h" +#include "cfsysline.h" + +/* definitions for objects we access */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(ruleset) + +/* static data */ + +/* config data */ + +/* This is the list of all strgens known to us. + * This is also used to unload all modules on shutdown. + */ +strgenList_t *pStrgenLstRoot = NULL; + + +/* intialize (but NOT allocate) a strgen list. Primarily meant as a hook + * which can be used to extend the list in the future. So far, just sets + * it to NULL. + */ +static rsRetVal +InitStrgenList(strgenList_t **pListRoot) +{ + *pListRoot = NULL; + return RS_RET_OK; +} + + +/* destruct a strgen list. The list elements are destroyed, but the strgen objects + * themselves are not modified. (That is done at a late stage during rsyslogd + * shutdown and need not be considered here.) + */ +static rsRetVal +DestructStrgenList(strgenList_t **ppListRoot) +{ + strgenList_t *pStrgenLst; + strgenList_t *pStrgenLstDel; + + pStrgenLst = *ppListRoot; + while(pStrgenLst != NULL) { + pStrgenLstDel = pStrgenLst; + pStrgenLst = pStrgenLst->pNext; + free(pStrgenLstDel); + } + *ppListRoot = NULL; + return RS_RET_OK; +} + + +/* Add a strgen to the list. We use a VERY simple and ineffcient algorithm, + * but it is employed only for a few milliseconds during config processing. So + * I prefer to keep it very simple and with simple data structures. Unfortunately, + * we need to preserve the order, but I don't like to add a tail pointer as that + * would require a container object. So I do the extra work to skip to the tail + * when adding elements... + */ +static rsRetVal +AddStrgenToList(strgenList_t **ppListRoot, strgen_t *pStrgen) +{ + strgenList_t *pThis; + strgenList_t *pTail; + DEFiRet; + + CHKmalloc(pThis = MALLOC(sizeof(strgenList_t))); + pThis->pStrgen = pStrgen; + pThis->pNext = NULL; + + if(*ppListRoot == NULL) { + pThis->pNext = *ppListRoot; + *ppListRoot = pThis; + } else { + /* find tail first */ + for(pTail = *ppListRoot ; pTail->pNext != NULL ; pTail = pTail->pNext) + /* just search, do nothing else */; + /* add at tail */ + pTail->pNext = pThis; + } + +finalize_it: + RETiRet; +} + + +/* find a strgen based on the provided name */ +static rsRetVal +FindStrgen(strgen_t **ppStrgen, uchar *pName) +{ + strgenList_t *pThis; + DEFiRet; + + for(pThis = pStrgenLstRoot ; pThis != NULL ; pThis = pThis->pNext) { + if(ustrcmp(pThis->pStrgen->pName, pName) == 0) { + *ppStrgen = pThis->pStrgen; + FINALIZE; /* found it, iRet still eq. OK! */ + } + } + + iRet = RS_RET_PARSER_NOT_FOUND; + +finalize_it: + RETiRet; +} + + +/* --- END helper functions for strgen list handling --- */ + + +BEGINobjConstruct(strgen) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(strgen) + +/* ConstructionFinalizer. The most important chore is to add the strgen object + * to our global list of available strgens. + * rgerhards, 2009-11-03 + */ +rsRetVal strgenConstructFinalize(strgen_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strgen); + CHKiRet(AddStrgenToList(&pStrgenLstRoot, pThis)); + DBGPRINTF("Strgen '%s' added to list of available strgens.\n", pThis->pName); + +finalize_it: + RETiRet; +} + +BEGINobjDestruct(strgen) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(strgen) + dbgprintf("destructing strgen '%s'\n", pThis->pName); + free(pThis->pName); +ENDobjDestruct(strgen) + +/* set the strgen name - string is copied over, call can continue to use it, + * but must free it if desired. + */ +static rsRetVal +SetName(strgen_t *pThis, uchar *name) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strgen); + assert(name != NULL); + + if(pThis->pName != NULL) { + free(pThis->pName); + pThis->pName = NULL; + } + + CHKmalloc(pThis->pName = ustrdup(name)); + +finalize_it: + RETiRet; +} + + +/* set a pointer to "our" module. Note that no module + * pointer must already be set. + */ +static rsRetVal +SetModPtr(strgen_t *pThis, modInfo_t *pMod) +{ + ISOBJ_TYPE_assert(pThis, strgen); + assert(pMod != NULL); + assert(pThis->pModule == NULL); + pThis->pModule = pMod; + return RS_RET_OK; +} + + +/* queryInterface function-- rgerhards, 2009-11-03 + */ +BEGINobjQueryInterface(strgen) +CODESTARTobjQueryInterface(strgen) + if(pIf->ifVersion != strgenCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = strgenConstruct; + pIf->ConstructFinalize = strgenConstructFinalize; + pIf->Destruct = strgenDestruct; + pIf->SetName = SetName; + pIf->SetModPtr = SetModPtr; + pIf->InitStrgenList = InitStrgenList; + pIf->DestructStrgenList = DestructStrgenList; + pIf->AddStrgenToList = AddStrgenToList; + pIf->FindStrgen = FindStrgen; +finalize_it: +ENDobjQueryInterface(strgen) + + +/* This destroys the master strgenlist and all of its strgen entries. MUST only be + * done when the module is shut down. Strgen modules are NOT unloaded, rsyslog + * does that at a later stage for all dynamically loaded modules. + */ +static void +destroyMasterStrgenList(void) +{ + strgenList_t *pStrgenLst; + strgenList_t *pStrgenLstDel; + + pStrgenLst = pStrgenLstRoot; + while(pStrgenLst != NULL) { + strgenDestruct(&pStrgenLst->pStrgen); + pStrgenLstDel = pStrgenLst; + pStrgenLst = pStrgenLst->pNext; + free(pStrgenLstDel); + } +} + +/* Exit our class. + * rgerhards, 2009-11-04 + */ +BEGINObjClassExit(strgen, OBJ_IS_CORE_MODULE) /* class, version */ + destroyMasterStrgenList(); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); +ENDObjClassExit(strgen) + + +/* Initialize the strgen class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2009-11-02 + */ +BEGINObjClassInit(strgen, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + InitStrgenList(&pStrgenLstRoot); +ENDObjClassInit(strgen) + diff --git a/runtime/strgen.h b/runtime/strgen.h new file mode 100644 index 00000000..3819dccd --- /dev/null +++ b/runtime/strgen.h @@ -0,0 +1,60 @@ +/* header for strgen.c + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_STRGEN_H +#define INCLUDED_STRGEN_H + + +/* we create a small helper object, a list of strgens, that we can use to + * build a chain of them whereever this is needed. + */ +struct strgenList_s { + strgen_t *pStrgen; + strgenList_t *pNext; +}; + + +/* the strgen object, a dummy because we have only static methods */ +struct strgen_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pName; /* name of this strgen */ + modInfo_t *pModule; /* pointer to strgen's module */ +}; + +/* interfaces */ +BEGINinterface(strgen) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(strgen_t **ppThis); + rsRetVal (*ConstructFinalize)(strgen_t *pThis); + rsRetVal (*Destruct)(strgen_t **ppThis); + rsRetVal (*SetName)(strgen_t *pThis, uchar *name); + rsRetVal (*SetModPtr)(strgen_t *pThis, modInfo_t *pMod); + rsRetVal (*FindStrgen)(strgen_t **ppThis, uchar*name); + rsRetVal (*InitStrgenList)(strgenList_t **pListRoot); + rsRetVal (*DestructStrgenList)(strgenList_t **pListRoot); + rsRetVal (*AddStrgenToList)(strgenList_t **pListRoot, strgen_t *pStrgen); +ENDinterface(strgen) +#define strgenCURR_IF_VERSION 1 /* increment whenever you change the interface above! */ + + +/* prototypes */ +PROTOTYPEObj(strgen); + +#endif /* #ifndef INCLUDED_STRGEN_H */ diff --git a/runtime/stringbuf.c b/runtime/stringbuf.c new file mode 100644 index 00000000..cb4f0457 --- /dev/null +++ b/runtime/stringbuf.c @@ -0,0 +1,1055 @@ +/* This is the byte-counted string class for rsyslog. It is a replacement + * for classical \0 terminated string functions. We introduce it in + * the hope it will make the program more secure, obtain some performance + * and, most importantly, lay they foundation for syslog-protocol, which + * requires strings to be able to handle embedded \0 characters. + * Please see syslogd.c for license information. + * All functions in this "class" start with rsCStr (rsyslog Counted String). + * begun 2005-09-07 rgerhards + * did some optimization (read: bugs!) rgerhards, 2009-06-16 + * + * Copyright (C) 2007-2012 Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> +#include <stdarg.h> +#include <sys/types.h> +#include <libestr.h> +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "regexp.h" +#include "obj.h" + +uchar* rsCStrGetSzStr(cstr_t *pThis); + +/* ################################################################# * + * private members * + * ################################################################# */ + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(regexp) + +/* ################################################################# * + * public members * + * ################################################################# */ + + +rsRetVal cstrConstruct(cstr_t **ppThis) +{ + DEFiRet; + cstr_t *pThis; + + ASSERT(ppThis != NULL); + + CHKmalloc(pThis = (cstr_t*) calloc(1, sizeof(cstr_t))); + + rsSETOBJTYPE(pThis, OIDrsCStr); + pThis->pBuf = NULL; + pThis->pszBuf = NULL; + pThis->iBufSize = 0; + pThis->iStrLen = 0; + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* construct from sz string + * rgerhards 2005-09-15 + */ +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = strlen((char *) sz); + if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, sz, pThis->iStrLen); + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* a helper function for rsCStr*Strf() + */ +static rsRetVal rsCStrConstructFromszStrv(cstr_t **ppThis, uchar *fmt, va_list ap) +{ + DEFiRet; + cstr_t *pThis; + va_list ap2; + int len; + + assert(ppThis != NULL); + + va_copy(ap2, ap); + len = vsnprintf(NULL, 0, (char*)fmt, ap2); + va_end(ap2); + + if(len < 0) + ABORT_FINALIZE(RS_RET_ERR); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = len; + len++; /* account for the \0 written by vsnprintf */ + if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * len)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + vsnprintf((char*)pThis->pBuf, len, (char*)fmt, ap); + *ppThis = pThis; +finalize_it: + RETiRet; +} + + +/* construct from a printf-style formated string + */ +rsRetVal rsCStrConstructFromszStrf(cstr_t **ppThis, char *fmt, ...) +{ + DEFiRet; + va_list ap; + + va_start(ap, fmt); + iRet = rsCStrConstructFromszStrv(ppThis, (uchar*)fmt, ap); + va_end(ap); + + RETiRet; +} + + +/* construct from es_str_t string + * rgerhards 2010-12-03 + */ +rsRetVal cstrConstructFromESStr(cstr_t **ppThis, es_str_t *str) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = es_strlen(str); + if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, es_getBufAddr(str), pThis->iStrLen); + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + +/* construct from CStr object. only the counted string is + * copied, not the szString. + * rgerhards 2005-10-18 + */ +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + rsCHECKVALIDOBJECT(pFrom, OIDrsCStr); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = pFrom->iStrLen; + if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* copy properties */ + memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen); + + *ppThis = pThis; +finalize_it: + RETiRet; +} + + +void rsCStrDestruct(cstr_t **ppThis) +{ + cstr_t *pThis = *ppThis; + + free(pThis->pBuf); + free(pThis->pszBuf); + RSFREEOBJ(pThis); + *ppThis = NULL; +} + + +/* extend the string buffer if its size is insufficient. + * Param iMinNeeded is the minumum free space needed. If it is larger + * than the default alloc increment, space for at least this amount is + * allocated. In practice, a bit more is allocated because we envision that + * some more characters may be added after these. + * rgerhards, 2008-01-07 + * changed to utilized realloc() -- rgerhards, 2009-06-16 + */ +rsRetVal +rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded) +{ + uchar *pNewBuf; + size_t iNewSize; + DEFiRet; + + /* first compute the new size needed */ + if(iMinNeeded > RS_STRINGBUF_ALLOC_INCREMENT) { + /* we allocate "n" ALLOC_INCREMENTs. Usually, that should + * leave some room after the absolutely needed one. It also + * reduces memory fragmentation. Note that all of this are + * integer operations (very important to understand what is + * going on)! Parenthesis are for better readibility. + */ + iNewSize = (iMinNeeded / RS_STRINGBUF_ALLOC_INCREMENT + 1) * RS_STRINGBUF_ALLOC_INCREMENT; + } else { + iNewSize = pThis->iBufSize + RS_STRINGBUF_ALLOC_INCREMENT; + } + iNewSize += pThis->iBufSize; /* add current size */ + + /* DEV debugging only: dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize); */ + CHKmalloc(pNewBuf = (uchar*) realloc(pThis->pBuf, iNewSize * sizeof(uchar))); + pThis->iBufSize = iNewSize; + pThis->pBuf = pNewBuf; + +finalize_it: + RETiRet; +} + + +/* append a string of known length. In this case, we make sure we do at most + * one additional memory allocation. + * I optimized this function to use memcpy(), among others. Consider it a + * rewrite (which may be good to know in case of bugs) -- rgerhards, 2008-01-07 + */ +rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen) +{ + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(psz != NULL); + + /* does the string fit? */ + if(pThis->iStrLen + iStrLen > pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */ + } + + /* ok, now we always have sufficient continues memory to do a memcpy() */ + memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen); + pThis->iStrLen += iStrLen; + +finalize_it: + RETiRet; +} + + +/* changed to be a wrapper to rsCStrAppendStrWithLen() so that + * we can save some time when we have the length but do not + * need to change existing code. + * rgerhards, 2007-07-03 + */ +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz) +{ + return rsCStrAppendStrWithLen(pThis, psz, strlen((char*) psz)); +} + + +/* append the contents of one cstr_t object to another + * rgerhards, 2008-02-25 + */ +rsRetVal cstrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend) +{ + return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen); +} + + +/* append a printf-style formated string + */ +rsRetVal rsCStrAppendStrf(cstr_t *pThis, uchar *fmt, ...) +{ + DEFiRet; + va_list ap; + cstr_t *pStr = NULL; + + va_start(ap, fmt); + iRet = rsCStrConstructFromszStrv(&pStr, fmt, ap); + va_end(ap); + + CHKiRet(iRet); + + iRet = cstrAppendCStr(pThis, pStr); + rsCStrDestruct(&pStr); +finalize_it: + RETiRet; +} + + +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i) +{ + DEFiRet; + uchar szBuf[32]; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), i)); + + iRet = rsCStrAppendStr(pThis, szBuf); +finalize_it: + RETiRet; +} + + +/* Sets the string object to the classigal sz-string provided. + * Any previously stored vlaue is discarded. If a NULL pointer + * the the new value (pszNew) is provided, an empty string is + * created (this is NOT an error!). + * rgerhards, 2005-10-18 + */ +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + free(pThis->pBuf); + free(pThis->pszBuf); + if(pszNew == NULL) { + pThis->iStrLen = 0; + pThis->iBufSize = 0; + pThis->pBuf = NULL; + pThis->pszBuf = NULL; + } else { + pThis->iStrLen = strlen((char*)pszNew); + pThis->iBufSize = pThis->iStrLen; + pThis->pszBuf = NULL; + + /* now save the new value */ + if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + return RS_RET_OUT_OF_MEMORY; + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, pszNew, pThis->iStrLen); + } + + return RS_RET_OK; +} + +/* Converts the CStr object to a classical sz string and returns that. + * Same restrictions as in rsCStrGetSzStr() applies (see there!). This + * function here guarantees that a valid string is returned, even if + * the CStr object currently holds a NULL pointer string buffer. If so, + * "" is returned. + * rgerhards 2005-10-19 + * WARNING: The returned pointer MUST NOT be freed, as it may be + * obtained from that constant memory pool (in case of NULL!) + */ +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + if(pThis->pBuf == NULL) + return (uchar*) ""; + else + return rsCStrGetSzStr(pThis); +} + + +/* Converts the CStr object to a classical zero-terminated C string + * and returns that string. The caller must not free it and must not + * destroy the CStr object as long as the ascii string is used. + * This function may return NULL, if the string is currently NULL. This + * is a feature, not a bug. If you need non-NULL in any case, use + * rsCStrGetSzStrNoNULL() instead. + * rgerhards, 2005-09-15 + */ +uchar* rsCStrGetSzStr(cstr_t *pThis) +{ + size_t i; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->pBuf != NULL) + if(pThis->pszBuf == NULL) { + /* we do not yet have a usable sz version - so create it... */ + if((pThis->pszBuf = MALLOC((pThis->iStrLen + 1) * sizeof(uchar))) == NULL) { + /* TODO: think about what to do - so far, I have no bright + * idea... rgerhards 2005-09-07 + */ + } + else { /* we can create the sz String */ + /* now copy it while doing a sanity check. The string might contain a + * \0 byte. There is no way how a sz string can handle this. For + * the time being, we simply replace it with space - something that + * could definitely be improved (TODO). + * 2005-09-15 rgerhards + */ + for(i = 0 ; i < pThis->iStrLen ; ++i) { + if(pThis->pBuf[i] == '\0') + pThis->pszBuf[i] = ' '; + else + pThis->pszBuf[i] = pThis->pBuf[i]; + } + /* write terminator... */ + pThis->pszBuf[i] = '\0'; + } + } + + return(pThis->pszBuf); +} + + +/* Converts the CStr object to a classical zero-terminated C string, + * returns that string and destroys the CStr object. The returned string + * MUST be freed by the caller. The function might return NULL if + * no memory can be allocated. + * + * This is the NEW replacement for rsCStrConvSzStrAndDestruct which does + * no longer utilize a special buffer but soley works on pBuf (and also + * assumes that cstrFinalize had been called). + * + * Parameters are as follows: + * pointer to the object, pointer to string-pointer to receive string and + * bRetNULL: 0 - must not return NULL on empty string, return "" in that + * case, 1 - return NULL instead of an empty string. + * PLEASE NOTE: the caller must free the memory returned in ppSz in any case + * (except, of course, if it is NULL). + */ +rsRetVal cstrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL) +{ + DEFiRet; + uchar* pRetBuf; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(ppSz != NULL); + assert(bRetNULL == 0 || bRetNULL == 1); + + if(pThis->pBuf == NULL) { + if(bRetNULL == 0) { + CHKmalloc(pRetBuf = MALLOC(sizeof(uchar))); + *pRetBuf = '\0'; + } else { + pRetBuf = NULL; + } + } else + pRetBuf = pThis->pBuf; + + *ppSz = pRetBuf; + +finalize_it: + /* We got it, now free the object ourselfs. Please note + * that we can NOT use the rsCStrDestruct function as it would + * also free the sz String buffer, which we pass on to the user. + */ + RSFREEOBJ(pThis); + RETiRet; +} + + +/* return the length of the current string + * 2005-09-09 rgerhards + * Please note: this is only a function in a debug build. + * For release builds, it is a macro defined in stringbuf.h. + * This is due to performance reasons. + */ +#ifndef NDEBUG +int cstrLen(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + return(pThis->iStrLen); +} +#endif + +/* Truncate characters from the end of the string. + * rgerhards 2005-09-15 + */ +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->iStrLen < nTrunc) + return RS_TRUNCAT_TOO_LARGE; + + pThis->iStrLen -= nTrunc; + + if(pThis->pszBuf != NULL) { + /* in this case, we adjust the psz representation + * by writing a new \0 terminator - this is by far + * the fastest way and outweights the additional memory + * required. 2005-9-19 rgerhards. + */ + pThis->pszBuf[pThis->iStrLen] = '\0'; + } + + return RS_RET_OK; +} + +/* Trim trailing whitespace from a given string + */ +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis) +{ + register int i; + register uchar *pC; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + i = pThis->iStrLen; + pC = pThis->pBuf + i - 1; + while(i > 0 && isspace((int)*pC)) { + --pC; + --i; + } + /* i now is the new string length! */ + pThis->iStrLen = i; + + return RS_RET_OK; +} + +/* Trim trailing whitespace from a given string + */ +rsRetVal cstrTrimTrailingWhiteSpace(cstr_t *pThis) +{ + register int i; + register uchar *pC; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->iStrLen == 0) + goto done; /* empty string -> nothing to trim ;) */ + i = pThis->iStrLen; + pC = pThis->pBuf + i - 1; + while(i > 0 && isspace((int)*pC)) { + --pC; + --i; + } + /* i now is the new string length! */ + pThis->iStrLen = i; + pThis->pBuf[pThis->iStrLen] = '0'; /* we always have this space */ + +done: return RS_RET_OK; +} + +/* compare two string objects - works like strcmp(), but operates + * on CStr objects. Please note that this version here is + * faster in the majority of cases, simply because it can + * rely on StrLen. + * rgerhards 2005-09-19 + * fixed bug, in which only the last byte was actually compared + * in equal-size strings. + * rgerhards, 2005-09-26 + */ +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2) +{ + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + rsCHECKVALIDOBJECT(pCS2, OIDrsCStr); + if(pCS1->iStrLen == pCS2->iStrLen) + if(pCS1->iStrLen == 0) + return 0; /* zero-sized string are equal ;) */ + else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < pCS1->iStrLen ; ++i) { + if(pCS1->pBuf[i] != pCS2->pBuf[i]) + return pCS1->pBuf[i] - pCS2->pBuf[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + } + else + return pCS1->iStrLen - pCS2->iStrLen; +} + + +/* check if a sz-type string starts with a CStr object. This function + * is initially written to support the "startswith" property-filter + * comparison operation. Maybe it also has other needs. + * This functions is modelled after the strcmp() series, thus a + * return value of 0 indicates that the string starts with the + * sequence while -1 indicates it does not! + * rgerhards 2005-10-19 + */ +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register int i; + int iMax; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(iLenSz >= pCS1->iStrLen) { + /* we need to checkusing pCS1->iStrLen charactes at maximum, thus + * we move it to iMax. + */ + iMax = pCS1->iStrLen; + if(iMax == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iMax ; ++i) { + if(psz[i] != pCS1->pBuf[i]) + return psz[i] - pCS1->pBuf[i]; + } + /* if we arrive here, the string actually starts with pCS1 */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* check if a CStr object starts with a sz-type string. + * This functions is modelled after the strcmp() series, thus a + * return value of 0 indicates that the string starts with the + * sequence while -1 indicates it does not! + * rgerhards 2005-09-26 + */ +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register size_t i; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen >= iLenSz) { + /* we are using iLenSz below, because we need to check + * iLenSz characters at maximum (start with!) + */ + if(iLenSz == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i] != psz[i]) + return pCS1->pBuf[i] - psz[i]; + } + /* if we arrive here, the string actually starts with psz */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* The same as rsCStrStartsWithSzStr(), but does a case-insensitive + * comparison. TODO: consolidate the two. + * rgerhards 2008-02-28 + */ +int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register size_t i; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen >= iLenSz) { + /* we are using iLenSz below, because we need to check + * iLenSz characters at maximum (start with!) + */ + if(iLenSz == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iLenSz ; ++i) { + if(tolower(pCS1->pBuf[i]) != tolower(psz[i])) + return tolower(pCS1->pBuf[i]) - tolower(psz[i]); + } + /* if we arrive here, the string actually starts with psz */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* check if a CStr object matches a regex. + * msamia@redhat.com 2007-07-12 + * @return returns 0 if matched + * bug: doesn't work for CStr containing \0 + * rgerhards, 2007-07-16: bug is no real bug, because rsyslogd ensures there + * never is a \0 *inside* a property string. + * Note that the function returns -1 if regexp functionality is not available. + * rgerhards: 2009-03-04: ERE support added, via parameter iType: 0 - BRE, 1 - ERE + * Arnaud Cornet/rgerhards: 2009-04-02: performance improvement by caching compiled regex + * If a caller does not need the cached version, it must still provide memory for it + * and must call rsCStrRegexDestruct() afterwards. + */ +rsRetVal rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz, int iType, void *rc) +{ + regex_t **cache = (regex_t**) rc; + int ret; + DEFiRet; + + assert(pCS1 != NULL); + assert(psz != NULL); + assert(cache != NULL); + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + if (*cache == NULL) { + *cache = calloc(sizeof(regex_t), 1); + regexp.regcomp(*cache, (char*) rsCStrGetSzStr(pCS1), (iType == 1 ? REG_EXTENDED : 0) | REG_NOSUB); + } + ret = regexp.regexec(*cache, (char*) psz, 0, NULL, 0); + if(ret != 0) + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } else { + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + +finalize_it: + RETiRet; +} + + +/* free a cached compiled regex + * Caller must provide a pointer to a buffer that was created by + * rsCStrSzStrMatchRegexCache() + */ +void rsCStrRegexDestruct(void *rc) +{ + regex_t **cache = rc; + + assert(cache != NULL); + assert(*cache != NULL); + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + regexp.regfree(*cache); + free(*cache); + *cache = NULL; + } +} + + +/* compare a rsCStr object with a classical sz string. This function + * is almost identical to rsCStrZsStrCmp(), but it also takes an offset + * to the CStr object from where the comparison is to start. + * I have thought quite a while if it really makes sense to more or + * less duplicate the code. After all, if you call it with an offset of + * zero, the functionality is exactly the same. So it looks natural to + * just have a single function. However, supporting the offset requires + * some (few) additional integer operations. While they are few, they + * happen at places in the code that is run very frequently. All in all, + * I have opted for performance and thus duplicated the code. I hope + * this is a good, or at least acceptable, compromise. + * rgerhards, 2005-09-26 + * This function also has an offset-pointer which allows to + * specify *where* the compare operation should begin in + * the CStr. If everything is to be compared, it must be set + * to 0. If some leading bytes are to be skipped, it must be set + * to the first index that is to be compared. It must not be + * set higher than the string length (this is considered a + * program bug and will lead to unpredictable results and program aborts). + * rgerhards 2005-09-26 + */ +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) +{ + BEGINfunc + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(iOffset < pCS1->iStrLen); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if((pCS1->iStrLen - iOffset) == iLenSz) { + /* we are using iLenSz below, because the lengths + * are equal and iLenSz is faster to access + */ + if(iLenSz == 0) { + return 0; /* zero-sized strings are equal ;) */ + ENDfunc + } else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i+iOffset] != psz[i]) + return pCS1->pBuf[i+iOffset] - psz[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + ENDfunc + } + } + else { + return pCS1->iStrLen - iOffset - iLenSz; + ENDfunc + } +} + + +/* Converts a string to a number. If the string dos not contain a number, + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber) +{ + DEFiRet; + number_t n; + int bIsNegative; + size_t i; + + ASSERT(pStr != NULL); + ASSERT(pNumber != NULL); + + if(pStr->iStrLen == 0) { + /* can be converted to 0! (by convention) */ + pNumber = 0; + FINALIZE; + } + + /* first skip whitespace (if present) */ + for(i = 0 ; i < pStr->iStrLen && isspace(pStr->pBuf[i]) ; ++i) { + /*DO NOTHING*/ + } + + /* we have a string, so let's check its syntax */ + if(pStr->pBuf[i] == '+') { + ++i; /* skip that char */ + bIsNegative = 0; + } else if(pStr->pBuf[0] == '-') { + ++i; /* skip that char */ + bIsNegative = 1; + } else { + bIsNegative = 0; + } + + /* TODO: octal? hex? */ + n = 0; + while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) { + n = n * 10 + pStr->pBuf[i] - '0'; + ++i; + } + + if(i < pStr->iStrLen) /* non-digits before end of string? */ + ABORT_FINALIZE(RS_RET_NOT_A_NUMBER); + + if(bIsNegative) + n *= -1; + + /* we got it, so return the number */ + *pNumber = n; + +finalize_it: + RETiRet; +} + + +/* Converts a string to a boolen. First tries to convert to a number. If + * that succeeds, we are done (number is then used as boolean value). If + * that fails, we look if the string is "yes" or "true". If so, a value + * of 1 is returned. In all other cases, a value of 0 is returned. Please + * note that we do not have a specific boolean type, so we return a number. + * so, these are + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToBool(cstr_t *pStr, number_t *pBool) +{ + DEFiRet; + + ASSERT(pStr != NULL); + ASSERT(pBool != NULL); + + iRet = rsCStrConvertToNumber(pStr, pBool); + + if(iRet != RS_RET_NOT_A_NUMBER) { + FINALIZE; /* in any case, we have nothing left to do */ + } + + /* TODO: maybe we can do better than strcasecmp ;) -- overhead! */ + if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "true")) { + *pBool = 1; + } else if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "yes")) { + *pBool = 1; + } else { + *pBool = 0; + } + +finalize_it: + RETiRet; +} + + +/* compare a rsCStr object with a classical sz string. + * Just like rsCStrCStrCmp, just for a different data type. + * There must not only the sz string but also its length be + * provided. If the caller does not know the length he can + * call with + * rsCstrSzStrCmp(pCS, psz, strlen((char*)psz)); + * we are not doing the strlen((char*)) ourselfs as the caller might + * already know the length and in such cases we can save the + * overhead of doing it one more time (strelen() is costly!). + * The bottom line is that the provided length MUST be correct! + * The to sz string pointer must not be NULL! + * rgerhards 2005-09-26 + */ +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen == iLenSz) + /* we are using iLenSz below, because the lengths + * are equal and iLenSz is faster to access + */ + if(iLenSz == 0) + return 0; /* zero-sized strings are equal ;) */ + else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + return strncmp((char*)pCS1->pBuf, (char*)psz, iLenSz); + } + else + return pCS1->iStrLen - iLenSz; +} + + +/* Locate the first occurence of this rsCStr object inside a standard sz string. + * Returns the offset (0-bound) of this first occurrence. If not found, -1 is + * returned. Both parameters MUST be given (NULL is not allowed). + * rgerhards 2005-09-19 + */ +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz) +{ + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(sz != NULL); + + if(pThis->iStrLen == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = strlen((char*)sz) - pThis->iStrLen; + + bFound = 0; + i = 0; + while(i <= iMax && !bFound) { + size_t iCheck; + uchar *pComp = sz + i; + for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) + if(*(pComp + iCheck) != *(pThis->pBuf + iCheck)) + break; + if(iCheck == pThis->iStrLen) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} + + +/* This is the same as rsCStrLocateInSzStr(), but does a case-insensitve + * comparison. + * TODO: over time, consolidate the two. + * rgerhards, 2008-02-28 + */ +int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz) +{ + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(sz != NULL); + + if(pThis->iStrLen == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = strlen((char*)sz) - pThis->iStrLen; + + bFound = 0; + i = 0; + while(i <= iMax && !bFound) { + size_t iCheck; + uchar *pComp = sz + i; + for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) + if(tolower(*(pComp + iCheck)) != tolower(*(pThis->pBuf + iCheck))) + break; + if(iCheck == pThis->iStrLen) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} + + +/* our exit function. TODO: remove once converted to a class + * rgerhards, 2008-03-11 + */ +rsRetVal strExit() +{ + DEFiRet; + objRelease(regexp, LM_REGEXP_FILENAME); + RETiRet; +} + + +/* our init function. TODO: remove once converted to a class + */ +rsRetVal strInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + +finalize_it: + RETiRet; +} + + +/* vi:set ai: + */ diff --git a/runtime/stringbuf.h b/runtime/stringbuf.h new file mode 100644 index 00000000..d0502a5b --- /dev/null +++ b/runtime/stringbuf.h @@ -0,0 +1,230 @@ +/* stringbuf.h + * The counted string object + * + * This is the byte-counted string class for rsyslog. It is a replacement + * for classical \0 terminated string functions. We introduce it in + * the hope it will make the program more secure, obtain some performance + * and, most importantly, lay they foundation for syslog-protocol, which + * requires strings to be able to handle embedded \0 characters. + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2005-09-07 + * Initial version begun. + * + * Copyright 2005-2012 Adiscon GmbH. All Rights Reserved. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _STRINGBUF_H_INCLUDED__ +#define _STRINGBUF_H_INCLUDED__ 1 + +#include <assert.h> +#include <libestr.h> + +/** + * The dynamic string buffer object. + */ +typedef struct cstr_s +{ +#ifndef NDEBUG + rsObjID OID; /**< object ID */ +#endif + uchar *pBuf; /**< pointer to the string buffer, may be NULL if string is empty */ + uchar *pszBuf; /**< pointer to the sz version of the string (after it has been created )*/ + size_t iBufSize; /**< current maximum size of the string buffer */ + size_t iStrLen; /**< length of the string in characters. */ +} cstr_t; + + +/** + * Construct a rsCStr object. + */ +rsRetVal cstrConstruct(cstr_t **ppThis); +#define rsCStrConstruct(x) cstrConstruct((x)) +rsRetVal cstrConstructFromESStr(cstr_t **ppThis, es_str_t *str); +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz); +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom); +rsRetVal rsCStrConstructFromszStrf(cstr_t **ppThis, char *fmt, ...) __attribute__((format(printf,2, 3))); + +/** + * Destruct the string buffer object. + */ +void rsCStrDestruct(cstr_t **ppThis); +#define cstrDestruct(x) rsCStrDestruct((x)) + + +/* Append a character to the current string object. This may only be done until + * cstrFinalize() is called. + * rgerhards, 2009-06-16 + */ +rsRetVal rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded); /* our helper, NOT a public interface! */ +static inline rsRetVal cstrAppendChar(cstr_t *pThis, uchar c) +{ + rsRetVal iRet = RS_RET_OK; + + if(pThis->iStrLen >= pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */ + } + + /* ok, when we reach this, we have sufficient memory */ + *(pThis->pBuf + pThis->iStrLen++) = c; + +finalize_it: + return iRet; +} + + +/* some inline functions for things that are really frequently called... */ + +/* Finalize the string object. This must be called after all data is added to it + * but before that data is used. + * rgerhards, 2009-06-16 + */ +static inline rsRetVal +cstrFinalize(cstr_t *pThis) +{ + rsRetVal iRet = RS_RET_OK; + + if(pThis->iStrLen > 0) { + /* terminate string only if one exists */ + CHKiRet(cstrAppendChar(pThis, '\0')); + --pThis->iStrLen; /* do NOT count the \0 byte */ + } + +finalize_it: + return iRet; +} + + +/* Returns the cstr data as a classical C sz string. We use that the + * Finalizer did properly terminate our string (but we may stil be NULL). + * So it is vital that the finalizer is called BEFORe this function here! + * The caller must not free or otherwise manipulate the returned string and must not + * destroy the CStr object as long as the ascii string is used. + * This function may return NULL, if the string is currently NULL. This + * is a feature, not a bug. If you need non-NULL in any case, use + * cstrGetSzStrNoNULL() instead. + * Note that due to the new single-buffer interface this function almost does nothing! + * rgerhards, 2006-09-16 + */ +static inline uchar* cstrGetSzStr(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + return(pThis->pBuf); +} + + +/* Converts the CStr object to a classical sz string and returns that. + * Same restrictions as in cstrGetSzStr() applies (see there!). This + * function here guarantees that a valid string is returned, even if + * the CStr object currently holds a NULL pointer string buffer. If so, + * "" is returned. + * rgerhards 2005-10-19 + * WARNING: The returned pointer MUST NOT be freed, as it may be + * obtained from that constant memory pool (in case of NULL!) + */ +static inline uchar* cstrGetSzStrNoNULL(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + if(pThis->pBuf == NULL) + return (uchar*) ""; + else + return cstrGetSzStr(pThis); +} + + +/** + * Truncate "n" number of characters from the end of the + * string. The buffer remains unchanged, just the + * string length is manipulated. This is for performance + * reasons. + */ +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc); + +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis); +rsRetVal cstrTrimTrailingWhiteSpace(cstr_t *pThis); + +/** + * Append a string to the buffer. For performance reasons, + * use rsCStrAppenStrWithLen() if you know the length. + * + * \param psz pointer to string to be appended. Must not be NULL. + */ +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz); + +/** + * Append a string to the buffer. + * + * \param psz pointer to string to be appended. Must not be NULL. + * \param iStrLen the length of the string pointed to by psz + */ +rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen); + +/** + * Append a printf-style formated string to the buffer. + * + * \param fmt pointer to the format string (see man 3 printf for details). Must not be NULL. + */ +rsRetVal rsCStrAppendStrf(cstr_t *pThis, uchar *fmt, ...); + +/** + * Append an integer to the string. No special formatting is + * done. + */ +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i); + + +rsRetVal strExit(void); /* TODO: remove once we have a real object interface! */ +uchar* __attribute__((deprecated)) rsCStrGetSzStr(cstr_t *pThis); +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis); +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew); +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2); +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz); +int rsCStrLocateSzStr(cstr_t *pCStr, uchar *sz); +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz); +int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz); +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +rsRetVal rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz, int iType, void *cache); +void rsCStrRegexDestruct(void *rc); +rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber); +rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool); + +/* in migration */ +#define rsCStrAppendCStr(pThis, pstrAppend) cstrAppendCStr(pThis, pstrAppend) + +/* new calling interface */ +rsRetVal cstrFinalize(cstr_t *pThis); +rsRetVal cstrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL); +rsRetVal cstrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend); + +/* now come inline-like functions */ +#ifdef NDEBUG +# define cstrLen(x) ((int)((x)->iStrLen)) +#else + int cstrLen(cstr_t *pThis); +#endif +#define rsCStrLen(s) cstrLen((s)) + +#define rsCStrGetBufBeg(x) ((x)->pBuf) + +rsRetVal strInit(); +rsRetVal strExit(); + +#endif /* single include */ diff --git a/runtime/strms_sess.c b/runtime/strms_sess.c new file mode 100644 index 00000000..2537e8d8 --- /dev/null +++ b/runtime/strms_sess.c @@ -0,0 +1,303 @@ +/* strms_sess.c + * + * This implements a session of the strmsrv object. For general + * comments, see header of strmsrv.c. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> + +#include "rsyslog.h" +#include "dirty.h" +#include "module-template.h" +#include "net.h" +#include "strmsrv.h" +#include "strms_sess.h" +#include "obj.h" +#include "errmsg.h" +#include "netstrm.h" +#include "msg.h" +#include "prop.h" +#include "datetime.h" + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(datetime) + +static int iMaxLine; /* maximum size of a single message */ + +/* forward definitions */ +static rsRetVal Close(strms_sess_t *pThis); + + +/* Standard-Constructor */ +BEGINobjConstruct(strms_sess) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(strms_sess) + + +/* ConstructionFinalizer + */ +static rsRetVal +strms_sessConstructFinalize(strms_sess_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strms_sess); + if(pThis->pSrv->OnSessConstructFinalize != NULL) { + CHKiRet(pThis->pSrv->OnSessConstructFinalize(&pThis->pUsr)); + } + +finalize_it: + RETiRet; +} + + +/* destructor for the strms_sess object */ +BEGINobjDestruct(strms_sess) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(strms_sess) + if(pThis->pStrm != NULL) + netstrm.Destruct(&pThis->pStrm); + + if(pThis->pSrv->pOnSessDestruct != NULL) { + pThis->pSrv->pOnSessDestruct(&pThis->pUsr); + } + /* now destruct our own properties */ + free(pThis->fromHost); + if(pThis->fromHostIP != NULL) + prop.Destruct(&pThis->fromHostIP); +ENDobjDestruct(strms_sess) + + +/* debugprint for the strms_sess object */ +BEGINobjDebugPrint(strms_sess) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(strms_sess) +ENDobjDebugPrint(strms_sess) + + +/* set property functions */ +/* set the hostname. Note that the caller *hands over* the string. That is, + * the caller no longer controls it once SetHost() has received it. Most importantly, + * the caller must not free it. -- rgerhards, 2008-04-24 + */ +static rsRetVal +SetHost(strms_sess_t *pThis, uchar *pszHost) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strms_sess); + free(pThis->fromHost); + pThis->fromHost = pszHost; + RETiRet; +} + +/* set the remote host's IP. Note that the caller *hands over* the property. That is, + * the caller no longer controls it once SetHostIP() has received it. Most importantly, + * the caller must not destruct it. -- rgerhards, 2008-05-16 + */ +static rsRetVal +SetHostIP(strms_sess_t *pThis, prop_t *ip) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strms_sess); + if(pThis->fromHostIP != NULL) + prop.Destruct(&pThis->fromHostIP); + pThis->fromHostIP = ip; + RETiRet; +} + +static rsRetVal +SetStrm(strms_sess_t *pThis, netstrm_t *pStrm) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strms_sess); + pThis->pStrm = pStrm; + RETiRet; +} + + +/* set our parent, the strmsrv object */ +static rsRetVal +SetStrmsrv(strms_sess_t *pThis, strmsrv_t *pSrv) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strms_sess); + ISOBJ_TYPE_assert(pSrv, strmsrv); + pThis->pSrv = pSrv; + RETiRet; +} + + +/* set our parent listener info*/ +static rsRetVal +SetLstnInfo(strms_sess_t *pThis, strmLstnPortList_t *pLstnInfo) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strms_sess); + assert(pLstnInfo != NULL); + pThis->pLstnInfo = pLstnInfo; + RETiRet; +} + + +static rsRetVal +SetUsrP(strms_sess_t *pThis, void *pUsr) +{ + DEFiRet; + pThis->pUsr = pUsr; + RETiRet; +} + + +static void * +GetUsrP(strms_sess_t *pThis) +{ + return pThis->pUsr; +} + + +/* Closes a STRM session + * No attention is paid to the return code + * of close, so potential-double closes are not detected. + */ +static rsRetVal +Close(strms_sess_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strms_sess); + netstrm.Destruct(&pThis->pStrm); + free(pThis->fromHost); + pThis->fromHost = NULL; /* not really needed, but... */ + if(pThis->fromHostIP != NULL) + prop.Destruct(&pThis->fromHostIP); + + RETiRet; +} + + + +/* Processes the data received via a STRM session. If there + * is no other way to handle it, data is discarded. + * Input parameter data is the data received, iLen is its + * len as returned from recv(). iLen must be 1 or more (that + * is errors must be handled by caller!). iSTRMSess must be + * the index of the STRM session that received the data. + * rgerhards 2005-07-04 + * And another change while generalizing. We now return either + * RS_RET_OK, which means the session should be kept open + * or anything else, which means it must be closed. + * rgerhards, 2008-03-01 + */ +static rsRetVal +DataRcvd(strms_sess_t *pThis, char *pData, size_t iLen) +{ + DEFiRet; + char *pEnd; + + ISOBJ_TYPE_assert(pThis, strms_sess); + assert(pData != NULL); + assert(iLen > 0); + + /* We now copy the message to the session buffer. */ + pEnd = pData + iLen; /* this is one off, which is intensional */ + + while(pData < pEnd) { + CHKiRet(pThis->pSrv->OnCharRcvd(pThis, (uchar)*pData++)); + } + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(strms_sess) +CODESTARTobjQueryInterface(strms_sess) + if(pIf->ifVersion != strms_sessCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->DebugPrint = strms_sessDebugPrint; + pIf->Construct = strms_sessConstruct; + pIf->ConstructFinalize = strms_sessConstructFinalize; + pIf->Destruct = strms_sessDestruct; + + pIf->Close = Close; + pIf->DataRcvd = DataRcvd; + + pIf->SetUsrP = SetUsrP; + pIf->GetUsrP = GetUsrP; + pIf->SetStrmsrv = SetStrmsrv; + pIf->SetLstnInfo = SetLstnInfo; + pIf->SetHost = SetHost; + pIf->SetHostIP = SetHostIP; + pIf->SetStrm = SetStrm; +finalize_it: +ENDobjQueryInterface(strms_sess) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(strms_sess, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(strms_sess) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(datetime, CORE_COMPONENT); +ENDObjClassExit(strms_sess) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINObjClassInit(strms_sess, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + CHKiRet(objUse(glbl, CORE_COMPONENT)); + iMaxLine = glbl.GetMaxLine(); /* get maximum size we currently support */ + objRelease(glbl, CORE_COMPONENT); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, strms_sessDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strms_sessConstructFinalize); +ENDObjClassInit(strms_sess) + +/* vim:set ai: + */ diff --git a/runtime/strms_sess.h b/runtime/strms_sess.h new file mode 100644 index 00000000..86f692a8 --- /dev/null +++ b/runtime/strms_sess.h @@ -0,0 +1,74 @@ +/* Definitions for strms_sess class. This implements a session of the + * generic stream server. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_STRMS_SESS_H +#define INCLUDED_STRMS_SESS_H + +#include "obj.h" + +/* a forward-definition, we are somewhat cyclic */ +struct strmsrv_s; + +/* the strms_sess object */ +struct strms_sess_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + strmsrv_t *pSrv; /* pointer back to my server (e.g. for callbacks) */ + strmLstnPortList_t *pLstnInfo; /* pointer back to listener info */ + netstrm_t *pStrm; + uchar *fromHost; + prop_t *fromHostIP; + void *pUsr; /* a user-pointer */ +}; + + +/* interfaces */ +BEGINinterface(strms_sess) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(strms_sess); + rsRetVal (*Construct)(strms_sess_t **ppThis); + rsRetVal (*ConstructFinalize)(strms_sess_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(strms_sess_t **ppThis); + rsRetVal (*Close)(strms_sess_t *pThis); + rsRetVal (*DataRcvd)(strms_sess_t *pThis, char *pData, size_t iLen); + /* set methods */ + rsRetVal (*SetStrmsrv)(strms_sess_t *pThis, struct strmsrv_s *pSrv); + rsRetVal (*SetLstnInfo)(strms_sess_t *pThis, strmLstnPortList_t *pLstnInfo); + rsRetVal (*SetUsrP)(strms_sess_t*, void*); + void* (*GetUsrP)(strms_sess_t*); + rsRetVal (*SetHost)(strms_sess_t *pThis, uchar*); + rsRetVal (*SetHostIP)(strms_sess_t *pThis, prop_t*); + rsRetVal (*SetStrm)(strms_sess_t *pThis, netstrm_t*); + rsRetVal (*SetOnMsgReceive)(strms_sess_t *pThis, rsRetVal (*OnMsgReceive)(strms_sess_t*, uchar*, int)); +ENDinterface(strms_sess) +#define strms_sessCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ +/* interface changes + * to version v2, rgerhards, 2009-05-22 + * - Data structures changed + * - SetLstnInfo entry point added + * version 3, rgerhads, 2013-01-21: + * - signature of SetHostIP() changed + */ + + +/* prototypes */ +PROTOTYPEObj(strms_sess); + + +#endif /* #ifndef INCLUDED_STRMS_SESS_H */ diff --git a/runtime/strmsrv.c b/runtime/strmsrv.c new file mode 100644 index 00000000..e8b544b8 --- /dev/null +++ b/runtime/strmsrv.c @@ -0,0 +1,972 @@ +/* strmsrv.c + * + * This builds a basic stream server. It handles connection creation but + * not any protocol. Instead, it calls a "data received" entry point of the + * caller with any data received, in which case the caller must react accordingly. + * This module works together with the netstream drivers. + * + * There are actually two classes within the stream server code: one is + * the strmsrv itself, the other one is its sessions. This is a helper + * class to strmsrv. + * + * File begun on 2009-06-01 by RGerhards based on strmsrv.c. Note that strmsrv is + * placed under LGPL, which is possible because I carefully evaluated and + * eliminated all those parts of strmsrv which were not written by me. + * + * TODO: I would consider it useful to migrate tcpsrv.c/tcps_sess.c to this stream + * class here. The requires a little bit redesign, but should not be too hard. The + * core idea, already begun here, is that we still support lots of callbacks, but + * provide "canned" implementations for standard cases. That way, most upper-layer + * modules can be kept rather simple and without any extra overhead. Note that + * to support this, tcps_sess.c would need to extract the message reception state + * machine to a separate module which then is called via the DoCharRcvd() interface + * of this class here. -- rgerhards, 2009-06-01 + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "srUtils.h" +#include "conf.h" +#include "strmsrv.h" +#include "obj.h" +#include "glbl.h" +#include "netstrms.h" +#include "netstrm.h" +#include "nssel.h" +#include "errmsg.h" +#include "prop.h" +#include "unicode-helper.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* defines */ +#define STRMSESS_MAX_DEFAULT 200 /* default for nbr of strm sessions if no number is given */ +#define STRMLSTN_MAX_DEFAULT 20 /* default for nbr of listeners */ + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(conf) +DEFobjCurrIf(glbl) +DEFobjCurrIf(strms_sess) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrms) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(nssel) +DEFobjCurrIf(prop) + +/* forward definitions */ +static rsRetVal create_strm_socket(strmsrv_t *pThis); + +/* standard callbacks, if the caller did not provide us with them (this helps keep us + * flexible while at the same time permits very simple upper-layer modules) + */ +/* this shall go into a specific ACL module! */ +static int +isPermittedHost(struct sockaddr __attribute__((unused)) *addr, char __attribute__((unused)) *fromHostFQDN, + void __attribute__((unused)) *pUsrSrv, void __attribute__((unused)) *pUsrSess) +{ + return 1; +} + + +static rsRetVal +doOpenLstnSocks(strmsrv_t *pSrv) +{ + ISOBJ_TYPE_assert(pSrv, strmsrv); + return create_strm_socket(pSrv); +} + + +static rsRetVal +doRcvData(strms_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd) +{ + DEFiRet; + assert(pSess != NULL); + assert(piLenRcvd != NULL); + + *piLenRcvd = lenBuf; + CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd)); +finalize_it: + RETiRet; +} + +static rsRetVal +onRegularClose(strms_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + /* process any incomplete frames left over */ + //strms_sess.PrepareClose(pSess); + /* Session closed */ + strms_sess.Close(pSess); + RETiRet; +} + + +static rsRetVal +onErrClose(strms_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + strms_sess.Close(pSess); + RETiRet; +} + +/* ------------------------------ end callbacks ------------------------------ */ + +/* add new listener port to listener port list + * rgerhards, 2009-05-21 + */ +static inline rsRetVal +addNewLstnPort(strmsrv_t *pThis, uchar *pszPort) +{ + strmLstnPortList_t *pEntry; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strmsrv); + + /* create entry */ + CHKmalloc(pEntry = MALLOC(sizeof(strmLstnPortList_t))); + pEntry->pszPort = pszPort; + pEntry->pSrv = pThis; + CHKmalloc(pEntry->pszInputName = ustrdup(pThis->pszInputName)); + + /* and add to list */ + pEntry->pNext = pThis->pLstnPorts; + pThis->pLstnPorts = pEntry; + +finalize_it: + RETiRet; +} + + +/* configure STRM listener settings. + * Note: pszPort is handed over to us - the caller MUST NOT free it! + * rgerhards, 2008-03-20 + */ +static rsRetVal +configureSTRMListen(strmsrv_t *pThis, uchar *pszPort) +{ + int i; + uchar *pPort = pszPort; + DEFiRet; + + assert(pszPort != NULL); + ISOBJ_TYPE_assert(pThis, strmsrv); + + /* extract port */ + i = 0; + while(isdigit((int) *pPort)) { + i = i * 10 + *pPort++ - '0'; + } + + if(i >= 0 && i <= 65535) { + CHKiRet(addNewLstnPort(pThis, pszPort)); + } else { + errmsg.LogError(0, NO_ERRCODE, "Invalid STRM listen port %s - ignored.\n", pszPort); + } + +finalize_it: + RETiRet; +} + + +/* Initialize the session table + * returns 0 if OK, somewhat else otherwise + */ +static rsRetVal +STRMSessTblInit(strmsrv_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strmsrv); + assert(pThis->pSessions == NULL); + + dbgprintf("Allocating buffer for %d STRM sessions.\n", pThis->iSessMax); + if((pThis->pSessions = (strms_sess_t **) calloc(pThis->iSessMax, sizeof(strms_sess_t *))) == NULL) { + dbgprintf("Error: STRMSessInit() could not alloc memory for STRM session table.\n"); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + RETiRet; +} + + +/* find a free spot in the session table. If the table + * is full, -1 is returned, else the index of the free + * entry (0 or higher). + */ +static int +STRMSessTblFindFreeSpot(strmsrv_t *pThis) +{ + register int i; + + ISOBJ_TYPE_assert(pThis, strmsrv); + + for(i = 0 ; i < pThis->iSessMax ; ++i) { + if(pThis->pSessions[i] == NULL) + break; + } + + return((i < pThis->iSessMax) ? i : -1); +} + + +/* Get the next session index. Free session tables entries are + * skipped. This function is provided the index of the last + * session entry, or -1 if no previous entry was obtained. It + * returns the index of the next session or -1, if there is no + * further entry in the table. Please note that the initial call + * might as well return -1, if there is no session at all in the + * session table. + */ +static int +STRMSessGetNxtSess(strmsrv_t *pThis, int iCurr) +{ + register int i; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, strmsrv); + assert(pThis->pSessions != NULL); + for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) { + if(pThis->pSessions[i] != NULL) + break; + } + + ENDfunc + return((i < pThis->iSessMax) ? i : -1); +} + + +/* De-Initialize STRM listner sockets. + * This function deinitializes everything, including freeing the + * session table. No STRM listen receive operations are permitted + * unless the subsystem is reinitialized. + * rgerhards, 2007-06-21 + */ +static void deinit_strm_listener(strmsrv_t *pThis) +{ + int i; + strmLstnPortList_t *pEntry; + strmLstnPortList_t *pDel; + + ISOBJ_TYPE_assert(pThis, strmsrv); + + if(pThis->pSessions != NULL) { + /* close all STRM connections! */ + i = STRMSessGetNxtSess(pThis, -1); + while(i != -1) { + strms_sess.Destruct(&pThis->pSessions[i]); + /* now get next... */ + i = STRMSessGetNxtSess(pThis, i); + } + + /* we are done with the session table - so get rid of it... */ + free(pThis->pSessions); + pThis->pSessions = NULL; /* just to make sure... */ + } + + /* free list of strm listen ports */ + pEntry = pThis->pLstnPorts; + while(pEntry != NULL) { + free(pEntry->pszPort); + free(pEntry->pszInputName); + pDel = pEntry; + pEntry = pEntry->pNext; + free(pDel); + } + + /* finally close our listen streams */ + for(i = 0 ; i < pThis->iLstnMax ; ++i) { + netstrm.Destruct(pThis->ppLstn + i); + } +} + + +/* add a listen socket to our listen socket array. This is a callback + * invoked from the netstrm class. -- rgerhards, 2008-04-23 + */ +static rsRetVal +addStrmLstn(void *pUsr, netstrm_t *pLstn) +{ + strmLstnPortList_t *pPortList = (strmLstnPortList_t *) pUsr; + strmsrv_t *pThis = pPortList->pSrv; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strmsrv); + ISOBJ_TYPE_assert(pLstn, netstrm); + + if(pThis->iLstnMax >= STRMLSTN_MAX_DEFAULT) + ABORT_FINALIZE(RS_RET_MAX_LSTN_REACHED); + + pThis->ppLstn[pThis->iLstnMax] = pLstn; + pThis->ppLstnPort[pThis->iLstnMax] = pPortList; + ++pThis->iLstnMax; + +finalize_it: + RETiRet; +} + + +/* Initialize STRM listener socket for a single port + * rgerhards, 2009-05-21 + */ +static inline rsRetVal +initSTRMListener(strmsrv_t *pThis, strmLstnPortList_t *pPortEntry) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strmsrv); + assert(pPortEntry != NULL); + + /* TODO: add capability to specify local listen address! */ + CHKiRet(netstrm.LstnInit(pThis->pNS, (void*)pPortEntry, addStrmLstn, pPortEntry->pszPort, NULL, pThis->iSessMax)); + +finalize_it: + RETiRet; +} + + +/* Initialize STRM sockets (for listener) and listens on them */ +static rsRetVal +create_strm_socket(strmsrv_t *pThis) +{ + strmLstnPortList_t *pEntry; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strmsrv); + + /* init all configured ports */ + pEntry = pThis->pLstnPorts; + while(pEntry != NULL) { + CHKiRet(initSTRMListener(pThis, pEntry)); + pEntry = pEntry->pNext; + } + + /* OK, we had success. Now it is also time to + * initialize our connections + */ + if(STRMSessTblInit(pThis) != 0) { + /* OK, we are in some trouble - we could not initialize the + * session table, so we can not continue. We need to free all + * we have assigned so far, because we can not really use it... + */ + errmsg.LogError(0, RS_RET_ERR, "Could not initialize STRM session table, suspending STRM message reception."); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + RETiRet; +} + + +/* Accept new STRM connection; make entry in session table. If there + * is no more space left in the connection table, the new STRM + * connection is immediately dropped. + * ppSess has a pointer to the newly created session, if it succeeds. + * If it does not succeed, no session is created and ppSess is + * undefined. If the user has provided an OnSessAccept Callback, + * this one is executed immediately after creation of the + * session object, so that it can do its own initialization. + * rgerhards, 2008-03-02 + */ +static rsRetVal +SessAccept(strmsrv_t *pThis, strmLstnPortList_t *pLstnInfo, strms_sess_t **ppSess, netstrm_t *pStrm) +{ + DEFiRet; + strms_sess_t *pSess = NULL; + netstrm_t *pNewStrm = NULL; + int iSess = -1; + struct sockaddr_storage *addr; + uchar *fromHostFQDN = NULL; + prop_t *ip = NULL; + + ISOBJ_TYPE_assert(pThis, strmsrv); + assert(pLstnInfo != NULL); + + CHKiRet(netstrm.AcceptConnReq(pStrm, &pNewStrm)); + + /* Add to session list */ + iSess = STRMSessTblFindFreeSpot(pThis); + if(iSess == -1) { + errno = 0; + errmsg.LogError(0, RS_RET_MAX_SESS_REACHED, "too many strm sessions - dropping incoming request"); + ABORT_FINALIZE(RS_RET_MAX_SESS_REACHED); + } + + if(pThis->bUseKeepAlive) { + CHKiRet(netstrm.EnableKeepAlive(pNewStrm)); + } + + /* we found a free spot and can construct our session object */ + CHKiRet(strms_sess.Construct(&pSess)); + CHKiRet(strms_sess.SetStrmsrv(pSess, pThis)); + CHKiRet(strms_sess.SetLstnInfo(pSess, pLstnInfo)); + + /* get the host name */ + CHKiRet(netstrm.GetRemoteHName(pNewStrm, &fromHostFQDN)); + CHKiRet(netstrm.GetRemoteIP(pNewStrm, &ip)); + CHKiRet(netstrm.GetRemAddr(pNewStrm, &addr)); + /* TODO: check if we need to strip the domain name here -- rgerhards, 2008-04-24 */ + + /* Here we check if a host is permitted to send us messages. If it isn't, we do not further + * process the message but log a warning (if we are configured to do this). + * rgerhards, 2005-09-26 + */ + if(pThis->pIsPermittedHost != NULL + && !pThis->pIsPermittedHost((struct sockaddr*) addr, (char*) fromHostFQDN, pThis->pUsr, pSess->pUsr)) { + dbgprintf("%s is not an allowed sender\n", fromHostFQDN); + if(glbl.GetOption_DisallowWarning()) { + errno = 0; + errmsg.LogError(0, RS_RET_HOST_NOT_PERMITTED, "STRM message from disallowed sender %s discarded", fromHostFQDN); + } + ABORT_FINALIZE(RS_RET_HOST_NOT_PERMITTED); + } + + /* OK, we have an allowed sender, so let's continue, what + * means we can finally fill in the session object. + */ + CHKiRet(strms_sess.SetHost(pSess, fromHostFQDN)); + fromHostFQDN = NULL; /* we handed this string over */ + CHKiRet(strms_sess.SetHostIP(pSess, ip)); + ip = NULL; /* we handed this string over */ + CHKiRet(strms_sess.SetStrm(pSess, pNewStrm)); + pNewStrm = NULL; /* prevent it from being freed in error handler, now done in strms_sess! */ + CHKiRet(strms_sess.ConstructFinalize(pSess)); + + /* check if we need to call our callback */ + if(pThis->pOnSessAccept != NULL) { + CHKiRet(pThis->pOnSessAccept(pThis, pSess)); + } + + *ppSess = pSess; + pThis->pSessions[iSess] = pSess; + pSess = NULL; /* this is now also handed over */ + +finalize_it: + if(iRet != RS_RET_OK) { + if(pSess != NULL) + strms_sess.Destruct(&pSess); + if(pNewStrm != NULL) + netstrm.Destruct(&pNewStrm); + free(fromHostFQDN); + if(ip != NULL) + prop.Destruct(&ip); + } + + RETiRet; +} + + +static void +RunCancelCleanup(void *arg) +{ + nssel_t **ppSel = (nssel_t**) arg; + + if(*ppSel != NULL) + nssel.Destruct(ppSel); +} + + +/* This function is called to gather input. */ +#pragma GCC diagnostic ignored "-Wempty-body" +static rsRetVal +Run(strmsrv_t *pThis) +{ + DEFiRet; + int nfds; + int i; + int iSTRMSess; + int bIsReady; + strms_sess_t *pNewSess; + nssel_t *pSel; + ssize_t iRcvd; + rsRetVal localRet; + + ISOBJ_TYPE_assert(pThis, strmsrv); + + /* this is an endless loop - it is terminated by the framework canelling + * this thread. Thus, we also need to instantiate a cancel cleanup handler + * to prevent us from leaking anything. -- rgerharsd, 20080-04-24 + */ + pthread_cleanup_push(RunCancelCleanup, (void*) &pSel); + while(1) { + CHKiRet(nssel.Construct(&pSel)); + // TODO: set driver + CHKiRet(nssel.ConstructFinalize(pSel)); + + /* Add the STRM listen sockets to the list of read descriptors. */ + for(i = 0 ; i < pThis->iLstnMax ; ++i) { + CHKiRet(nssel.Add(pSel, pThis->ppLstn[i], NSDSEL_RD)); + } + + /* do the sessions */ + iSTRMSess = STRMSessGetNxtSess(pThis, -1); + while(iSTRMSess != -1) { + /* TODO: access to pNsd is NOT really CLEAN, use method... */ + CHKiRet(nssel.Add(pSel, pThis->pSessions[iSTRMSess]->pStrm, NSDSEL_RD)); + /* now get next... */ + iSTRMSess = STRMSessGetNxtSess(pThis, iSTRMSess); + } + + /* wait for io to become ready */ + CHKiRet(nssel.Wait(pSel, &nfds)); + + for(i = 0 ; i < pThis->iLstnMax ; ++i) { + CHKiRet(nssel.IsReady(pSel, pThis->ppLstn[i], NSDSEL_RD, &bIsReady, &nfds)); + if(bIsReady) { + dbgprintf("New connect on NSD %p.\n", pThis->ppLstn[i]); + SessAccept(pThis, pThis->ppLstnPort[i], &pNewSess, pThis->ppLstn[i]); + --nfds; /* indicate we have processed one */ + } + } + + /* now check the sessions */ + iSTRMSess = STRMSessGetNxtSess(pThis, -1); + while(nfds && iSTRMSess != -1) { + CHKiRet(nssel.IsReady(pSel, pThis->pSessions[iSTRMSess]->pStrm, NSDSEL_RD, &bIsReady, &nfds)); + if(bIsReady) { + char buf[8*1024]; /* reception buffer - may hold a partial or multiple messages */ + dbgprintf("netstream %p with new data\n", pThis->pSessions[iSTRMSess]->pStrm); + + /* Receive message */ + iRet = pThis->pRcvData(pThis->pSessions[iSTRMSess], buf, sizeof(buf), &iRcvd); + switch(iRet) { + case RS_RET_CLOSED: + pThis->pOnRegularClose(pThis->pSessions[iSTRMSess]); + strms_sess.Destruct(&pThis->pSessions[iSTRMSess]); + break; + case RS_RET_RETRY: + /* we simply ignore retry - this is not an error, but we also have not received anything */ + break; + case RS_RET_OK: + /* valid data received, process it! */ + localRet = strms_sess.DataRcvd(pThis->pSessions[iSTRMSess], buf, iRcvd); + if(localRet != RS_RET_OK) { + /* in this case, something went awfully wrong. + * We are instructed to terminate the session. + */ + errmsg.LogError(0, localRet, "Tearing down STRM Session %d - see " + "previous messages for reason(s)\n", iSTRMSess); + pThis->pOnErrClose(pThis->pSessions[iSTRMSess]); + strms_sess.Destruct(&pThis->pSessions[iSTRMSess]); + } + break; + default: + errno = 0; + errmsg.LogError(0, iRet, "netstream session %p will be closed due to error\n", + pThis->pSessions[iSTRMSess]->pStrm); + pThis->pOnErrClose(pThis->pSessions[iSTRMSess]); + strms_sess.Destruct(&pThis->pSessions[iSTRMSess]); + break; + } + --nfds; /* indicate we have processed one */ + } + iSTRMSess = STRMSessGetNxtSess(pThis, iSTRMSess); + } + CHKiRet(nssel.Destruct(&pSel)); +finalize_it: /* this is a very special case - this time only we do not exit the function, + * because that would not help us either. So we simply retry it. Let's see + * if that actually is a better idea. Exiting the loop wasn't we always + * crashed, which made sense (the rest of the engine was not prepared for + * that) -- rgerhards, 2008-05-19 + */ + /*EMPTY*/; + } + + /* note that this point is usually not reached */ + pthread_cleanup_pop(0); /* remove cleanup handler */ + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* Standard-Constructor */ +BEGINobjConstruct(strmsrv) /* be sure to specify the object type also in END macro! */ + pThis->iSessMax = STRMSESS_MAX_DEFAULT; /* TODO: useful default ;) */ + /* set default callbacks (used if caller does not overwrite them) */ + pThis->pIsPermittedHost = isPermittedHost; + pThis->OpenLstnSocks = doOpenLstnSocks; + pThis->pRcvData = doRcvData; + pThis->pOnRegularClose = onRegularClose; + pThis->pOnErrClose = onErrClose; + /* session specific callbacks */ + //pThis->OnSessConstructFinalize = + //pThis->pOnSessDestruct = +ENDobjConstruct(strmsrv) + + +/* ConstructionFinalizer */ +static rsRetVal +strmsrvConstructFinalize(strmsrv_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strmsrv); + + /* prepare network stream subsystem */ + CHKiRet(netstrms.Construct(&pThis->pNS)); + CHKiRet(netstrms.SetDrvrMode(pThis->pNS, pThis->iDrvrMode)); + if(pThis->pszDrvrAuthMode != NULL) + CHKiRet(netstrms.SetDrvrAuthMode(pThis->pNS, pThis->pszDrvrAuthMode)); + if(pThis->pPermPeers != NULL) + CHKiRet(netstrms.SetDrvrPermPeers(pThis->pNS, pThis->pPermPeers)); + // TODO: set driver! + CHKiRet(netstrms.ConstructFinalize(pThis->pNS)); + + /* set up listeners */ + CHKmalloc(pThis->ppLstn = calloc(STRMLSTN_MAX_DEFAULT, sizeof(netstrm_t*))); + CHKmalloc(pThis->ppLstnPort = calloc(STRMLSTN_MAX_DEFAULT, sizeof(strmLstnPortList_t*))); + iRet = pThis->OpenLstnSocks(pThis); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pNS != NULL) + netstrms.Destruct(&pThis->pNS); + } + RETiRet; +} + + +/* destructor for the strmsrv object */ +BEGINobjDestruct(strmsrv) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(strmsrv) + if(pThis->OnDestruct != NULL) + pThis->OnDestruct(pThis->pUsr); + + deinit_strm_listener(pThis); + + if(pThis->pNS != NULL) + netstrms.Destruct(&pThis->pNS); + free(pThis->pszDrvrAuthMode); + free(pThis->ppLstn); + free(pThis->ppLstnPort); + free(pThis->pszInputName); +ENDobjDestruct(strmsrv) + + +/* debugprint for the strmsrv object */ +BEGINobjDebugPrint(strmsrv) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(strmsrv) +ENDobjDebugPrint(strmsrv) + +/* set functions */ +static rsRetVal +SetCBIsPermittedHost(strmsrv_t *pThis, int (*pCB)(struct sockaddr *addr, char *fromHostFQDN, void*, void*)) +{ + DEFiRet; + pThis->pIsPermittedHost = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessAccept(strmsrv_t *pThis, rsRetVal (*pCB)(strmsrv_t*, strms_sess_t*)) +{ + DEFiRet; + pThis->pOnSessAccept = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnDestruct(strmsrv_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->OnDestruct = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessConstructFinalize(strmsrv_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->OnSessConstructFinalize = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessDestruct(strmsrv_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->pOnSessDestruct = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnRegularClose(strmsrv_t *pThis, rsRetVal (*pCB)(strms_sess_t*)) +{ + DEFiRet; + pThis->pOnRegularClose = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnErrClose(strmsrv_t *pThis, rsRetVal (*pCB)(strms_sess_t*)) +{ + DEFiRet; + pThis->pOnErrClose = pCB; + RETiRet; +} + +static rsRetVal +SetCBOpenLstnSocks(strmsrv_t *pThis, rsRetVal (*pCB)(strmsrv_t*)) +{ + DEFiRet; + pThis->OpenLstnSocks = pCB; + RETiRet; +} + +static rsRetVal +SetUsrP(strmsrv_t *pThis, void *pUsr) +{ + DEFiRet; + pThis->pUsr = pUsr; + RETiRet; +} + +static rsRetVal +SetKeepAlive(strmsrv_t *pThis, int iVal) +{ + DEFiRet; + dbgprintf("strmsrv: keep-alive set to %d\n", iVal); + pThis->bUseKeepAlive = iVal; + RETiRet; +} + +static rsRetVal +SetOnCharRcvd(strmsrv_t *pThis, rsRetVal (*OnCharRcvd)(strms_sess_t*, uchar)) +{ + DEFiRet; + assert(OnCharRcvd != NULL); + pThis->OnCharRcvd = OnCharRcvd; + RETiRet; +} + +/* Set the input name to use -- rgerhards, 2008-12-10 */ +static rsRetVal +SetInputName(strmsrv_t *pThis, uchar *name) +{ + uchar *pszName; + DEFiRet; + ISOBJ_TYPE_assert(pThis, strmsrv); + if(name == NULL) + pszName = NULL; + else + CHKmalloc(pszName = ustrdup(name)); + free(pThis->pszInputName); + pThis->pszInputName = pszName; +finalize_it: + RETiRet; +} + + +/* here follows a number of methods that shuffle authentication settings down + * to the drivers. Drivers not supporting these settings may return an error + * state. + * -------------------------------------------------------------------------- */ + +/* set the driver mode -- rgerhards, 2008-04-30 */ +static rsRetVal +SetDrvrMode(strmsrv_t *pThis, int iMode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strmsrv); + pThis->iDrvrMode = iMode; + RETiRet; +} + + +/* set the driver authentication mode -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrAuthMode(strmsrv_t *pThis, uchar *mode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strmsrv); + CHKmalloc(pThis->pszDrvrAuthMode = ustrdup(mode)); +finalize_it: + RETiRet; +} + + +/* set the driver's permitted peers -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrPermPeers(strmsrv_t *pThis, permittedPeers_t *pPermPeers) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strmsrv); + pThis->pPermPeers = pPermPeers; + RETiRet; +} + + +/* End of methods to shuffle autentication settings to the driver.; + + * -------------------------------------------------------------------------- */ + + +/* set max number of sessions + * this must be called before ConstructFinalize, or it will have no effect! + * rgerhards, 2009-04-09 + */ +static rsRetVal +SetSessMax(strmsrv_t *pThis, int iMax) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, strmsrv); + pThis->iSessMax = iMax; + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(strmsrv) +CODESTARTobjQueryInterface(strmsrv) + if(pIf->ifVersion != strmsrvCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->DebugPrint = strmsrvDebugPrint; + pIf->Construct = strmsrvConstruct; + pIf->ConstructFinalize = strmsrvConstructFinalize; + pIf->Destruct = strmsrvDestruct; + + pIf->configureSTRMListen = configureSTRMListen; + pIf->create_strm_socket = create_strm_socket; + pIf->Run = Run; + + pIf->SetKeepAlive = SetKeepAlive; + pIf->SetUsrP = SetUsrP; + pIf->SetInputName = SetInputName; + pIf->SetSessMax = SetSessMax; + pIf->SetDrvrMode = SetDrvrMode; + pIf->SetDrvrAuthMode = SetDrvrAuthMode; + pIf->SetDrvrPermPeers = SetDrvrPermPeers; + pIf->SetCBIsPermittedHost = SetCBIsPermittedHost; + pIf->SetCBOpenLstnSocks = SetCBOpenLstnSocks; + pIf->SetCBOnSessAccept = SetCBOnSessAccept; + pIf->SetCBOnSessConstructFinalize = SetCBOnSessConstructFinalize; + pIf->SetCBOnSessDestruct = SetCBOnSessDestruct; + pIf->SetCBOnDestruct = SetCBOnDestruct; + pIf->SetCBOnRegularClose = SetCBOnRegularClose; + pIf->SetCBOnErrClose = SetCBOnErrClose; + pIf->SetOnCharRcvd = SetOnCharRcvd; + +finalize_it: +ENDobjQueryInterface(strmsrv) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(strmsrv, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(strmsrv) + /* release objects we no longer need */ + objRelease(strms_sess, DONT_LOAD_LIB); + objRelease(conf, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrms, DONT_LOAD_LIB); + objRelease(nssel, DONT_LOAD_LIB); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(net, LM_NET_FILENAME); +ENDObjClassExit(strmsrv) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINObjClassInit(strmsrv, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(netstrm, DONT_LOAD_LIB)); + CHKiRet(objUse(nssel, DONT_LOAD_LIB)); + CHKiRet(objUse(strms_sess, DONT_LOAD_LIB)); + CHKiRet(objUse(conf, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, strmsrvDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strmsrvConstructFinalize); +ENDObjClassInit(strmsrv) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + /* de-init in reverse order! */ + strmsrvClassExit(); + strms_sessClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(strms_sessClassInit(pModInfo)); + CHKiRet(strmsrvClassInit(pModInfo)); /* must be done after strms_sess, as we use it */ +ENDmodInit + +/* vim:set ai: + */ diff --git a/runtime/strmsrv.h b/runtime/strmsrv.h new file mode 100644 index 00000000..9ef28e47 --- /dev/null +++ b/runtime/strmsrv.h @@ -0,0 +1,110 @@ +/* Definitions for strmsrv class. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_STRMSRV_H +#define INCLUDED_STRMSRV_H + +#include "obj.h" +#include "strms_sess.h" + +/* list of strm listen ports */ +struct strmLstnPortList_s { + uchar *pszPort; /**< the ports the listener shall listen on */ + uchar *pszInputName; /**< value to be used as input name */ + strmsrv_t *pSrv; /**< pointer to higher-level server instance */ + strmLstnPortList_t *pNext; /**< next port or NULL */ +}; + + +/* the strmsrv object */ +struct strmsrv_s { + BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */ + int bUseKeepAlive; /**< use socket layer KEEPALIVE handling? */ + netstrms_t *pNS; /**< pointer to network stream subsystem */ + int iDrvrMode; /**< mode of the stream driver to use */ + uchar *pszDrvrAuthMode; /**< auth mode of the stream driver to use */ + uchar *pszInputName; /**< value to be used as input name */ + permittedPeers_t *pPermPeers;/**< driver's permitted peers */ + int iLstnMax; /**< max nbr of listeners currently supported */ + netstrm_t **ppLstn; /**< our netstream listners */ + strmLstnPortList_t **ppLstnPort; /**< pointer to relevant listen port description */ + int iSessMax; /**< max number of sessions supported */ + strmLstnPortList_t *pLstnPorts; /**< head pointer for listen ports */ + int addtlFrameDelim; /**< additional frame delimiter for plain STRM syslog framing (e.g. to handle NetScreen) */ + strms_sess_t **pSessions;/**< array of all of our sessions */ + void *pUsr; /**< a user-settable pointer (provides extensibility for "derived classes")*/ + /* callbacks */ + int (*pIsPermittedHost)(struct sockaddr *addr, char *fromHostFQDN, void*pUsrSrv, void*pUsrSess); + rsRetVal (*pRcvData)(strms_sess_t*, char*, size_t, ssize_t *); + rsRetVal (*OpenLstnSocks)(struct strmsrv_s*); + rsRetVal (*pOnListenDeinit)(void*); + rsRetVal (*OnDestruct)(void*); + rsRetVal (*pOnRegularClose)(strms_sess_t *pSess); + rsRetVal (*pOnErrClose)(strms_sess_t *pSess); + /* session specific callbacks */ + rsRetVal (*pOnSessAccept)(strmsrv_t *, strms_sess_t*); + rsRetVal (*OnSessConstructFinalize)(void*); + rsRetVal (*pOnSessDestruct)(void*); + rsRetVal (*OnCharRcvd)(strms_sess_t*, uchar); +}; + + +/* interfaces */ +BEGINinterface(strmsrv) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(strmsrv); + rsRetVal (*Construct)(strmsrv_t **ppThis); + rsRetVal (*ConstructFinalize)(strmsrv_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(strmsrv_t **ppThis); + rsRetVal (*configureSTRMListen)(strmsrv_t*, uchar *pszPort); + //rsRetVal (*SessAccept)(strmsrv_t *pThis, strmLstnPortList_t*, strms_sess_t **ppSess, netstrm_t *pStrm); + rsRetVal (*create_strm_socket)(strmsrv_t *pThis); + rsRetVal (*Run)(strmsrv_t *pThis); + /* set methods */ + rsRetVal (*SetAddtlFrameDelim)(strmsrv_t*, int); + rsRetVal (*SetInputName)(strmsrv_t*, uchar*); + rsRetVal (*SetKeepAlive)(strmsrv_t*, int); + rsRetVal (*SetUsrP)(strmsrv_t*, void*); + rsRetVal (*SetCBIsPermittedHost)(strmsrv_t*, int (*) (struct sockaddr *addr, char*, void*, void*)); + rsRetVal (*SetCBOpenLstnSocks)(strmsrv_t *, rsRetVal (*)(strmsrv_t*)); + rsRetVal (*SetCBOnDestruct)(strmsrv_t*, rsRetVal (*) (void*)); + rsRetVal (*SetCBOnRegularClose)(strmsrv_t*, rsRetVal (*) (strms_sess_t*)); + rsRetVal (*SetCBOnErrClose)(strmsrv_t*, rsRetVal (*) (strms_sess_t*)); + rsRetVal (*SetDrvrMode)(strmsrv_t *pThis, int iMode); + rsRetVal (*SetDrvrAuthMode)(strmsrv_t *pThis, uchar *pszMode); + rsRetVal (*SetDrvrPermPeers)(strmsrv_t *pThis, permittedPeers_t*); + /* session specifics */ + rsRetVal (*SetCBOnSessAccept)(strmsrv_t*, rsRetVal (*) (strmsrv_t*, strms_sess_t*)); + rsRetVal (*SetCBOnSessDestruct)(strmsrv_t*, rsRetVal (*) (void*)); + rsRetVal (*SetCBOnSessConstructFinalize)(strmsrv_t*, rsRetVal (*) (void*)); + rsRetVal (*SetSessMax)(strmsrv_t *pThis, int iMaxSess); + rsRetVal (*SetOnCharRcvd)(strmsrv_t *pThis, rsRetVal (*OnMsgCharRcvd)(strms_sess_t*, uchar)); +ENDinterface(strmsrv) +#define strmsrvCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ +/* change for v?: + */ + + +/* prototypes */ +PROTOTYPEObj(strmsrv); + +/* the name of our library binary */ +#define LM_STRMSRV_FILENAME "lmstrmsrv" + +#endif /* #ifndef INCLUDED_STRMSRV_H */ diff --git a/runtime/syslogd-types.h b/runtime/syslogd-types.h new file mode 100644 index 00000000..6947a110 --- /dev/null +++ b/runtime/syslogd-types.h @@ -0,0 +1,111 @@ +/* syslogd-type.h + * This file contains type defintions used by syslogd and its modules. + * It is a required input for any module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef SYSLOGD_TYPES_INCLUDED +#define SYSLOGD_TYPES_INCLUDED 1 + +#include "stringbuf.h" +#include <sys/param.h> +#if HAVE_SYSLOG_H +#include <syslog.h> +#endif + +/* we use RSTRUE/FALSE to prevent name claches with other packages */ +#define RSFALSE 0 +#define RSTRUE 1 + +#ifdef UT_NAMESIZE +# define UNAMESZ UT_NAMESIZE /* length of a login name */ +#else +# define UNAMESZ 8 /* length of a login name */ +#endif +#define MAXUNAMES 20 /* maximum number of user names */ +#define MAXFNAME 200 /* max file pathname length */ + +#define _DB_MAXDBLEN 128 /* maximum number of db */ +#define _DB_MAXUNAMELEN 128 /* maximum number of user name */ +#define _DB_MAXPWDLEN 128 /* maximum number of user's pass */ +#define _DB_DELAYTIMEONERROR 20 /* If an error occur we stop logging until + a delayed time is over */ + + +/* we define features of the syslog code. This features can be used + * to check if modules are compatible with them - and possible other + * applications I do not yet envision. -- rgerhards, 2007-07-24 + */ +typedef enum _syslogFeature { + sFEATURERepeatedMsgReduction = 1, /* for output modules */ + sFEATURENonCancelInputTermination = 2, /* for input modules */ + sFEATUREAutomaticSanitazion = 3, /* for parser modules */ + sFEATUREAutomaticPRIParsing = 4 /* for parser modules */ +} syslogFeature; + +/* we define our own facility and severities */ +/* facility and severity codes */ +typedef struct _syslogCode { + char *c_name; + int c_val; +} syslogCODE; + +/* values for host comparisons specified with host selector blocks + * (+host, -host). rgerhards 2005-10-18. + */ +enum _EHostnameCmpMode { + HN_NO_COMP = 0, /* do not compare hostname */ + HN_COMP_MATCH = 1, /* hostname must match */ + HN_COMP_NOMATCH = 2 /* hostname must NOT match */ +}; +typedef enum _EHostnameCmpMode EHostnameCmpMode; + +/* time type numerical values for structure below */ +#define TIME_TYPE_UNINIT 0 +#define TIME_TYPE_RFC3164 1 +#define TIME_TYPE_RFC5424 2 +/* rgerhards 2004-11-11: the following structure represents + * a time as it is used in syslog. + * rgerhards, 2009-06-23: packed structure for better cache performance + * (but left ultimate decision about packing to compiler) + */ +struct syslogTime { + intTiny timeType; /* 0 - unitinialized , 1 - RFC 3164, 2 - syslog-protocol */ + intTiny month; + intTiny day; + intTiny hour; /* 24 hour clock */ + intTiny minute; + intTiny second; + intTiny secfracPrecision; + intTiny OffsetMinute; /* UTC offset in minutes */ + intTiny OffsetHour; /* UTC offset in hours + * full UTC offset minutes = OffsetHours*60 + OffsetMinute. Then use + * OffsetMode to know the direction. + */ + char OffsetMode; /* UTC offset + or - */ + short year; + int secfrac; /* fractional seconds (must be 32 bit!) */ +}; +typedef struct syslogTime syslogTime_t; + +#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */ +/* vi:set ai: + */ diff --git a/runtime/typedefs.h b/runtime/typedefs.h new file mode 100644 index 00000000..5cc24e4a --- /dev/null +++ b/runtime/typedefs.h @@ -0,0 +1,203 @@ +/* This defines some types commonly used. Do NOT include any other + * rsyslog runtime file. + * + * Begun 2010-11-25 RGerhards + * + * Copyright (C) 2005-2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_TYPEDEFS_H +#define INCLUDED_TYPEDEFS_H + +/* some universal fixed size integer defines ... */ +typedef long long int64; +typedef long long unsigned uint64; +typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */ +typedef char intTiny; /* 0..127! */ +typedef unsigned char uintTiny; /* 0..255! */ + +/* define some base data types */ + +typedef unsigned char uchar;/* get rid of the unhandy "unsigned char" */ +typedef struct aUsrp_s aUsrp_t; +typedef struct thrdInfo thrdInfo_t; +typedef struct obj_s obj_t; +typedef struct ruleset_s ruleset_t; +typedef struct rule_s rule_t; +//typedef struct filed selector_t;/* TODO: this so far resides in syslogd.c, think about modularization */ +typedef struct NetAddr netAddr_t; +typedef struct netstrms_s netstrms_t; +typedef struct netstrm_s netstrm_t; +typedef struct nssel_s nssel_t; +typedef struct nspoll_s nspoll_t; +typedef enum nsdsel_waitOp_e nsdsel_waitOp_t; +typedef struct nsd_ptcp_s nsd_ptcp_t; +typedef struct nsd_gtls_s nsd_gtls_t; +typedef struct nsd_gsspi_s nsd_gsspi_t; +typedef struct nsd_nss_s nsd_nss_t; +typedef struct nsdsel_ptcp_s nsdsel_ptcp_t; +typedef struct nsdsel_gtls_s nsdsel_gtls_t; +typedef struct nsdpoll_ptcp_s nsdpoll_ptcp_t; +typedef struct wti_s wti_t; +typedef struct msg msg_t; +typedef struct queue_s qqueue_t; +typedef struct prop_s prop_t; +typedef struct interface_s interface_t; +typedef struct objInfo_s objInfo_t; +typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ +typedef rsRetVal (*errLogFunc_t)(uchar*); /* this is a trick to store a function ptr to a function returning a function ptr... */ +typedef struct permittedPeers_s permittedPeers_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ +typedef struct permittedPeerWildcard_s permittedPeerWildcard_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ +typedef struct tcpsrv_s tcpsrv_t; +typedef struct tcps_sess_s tcps_sess_t; +typedef struct strmsrv_s strmsrv_t; +typedef struct strms_sess_s strms_sess_t; +typedef struct vmstk_s vmstk_t; +typedef struct batch_obj_s batch_obj_t; +typedef struct batch_s batch_t; +typedef struct wtp_s wtp_t; +typedef struct modInfo_s modInfo_t; +typedef struct parser_s parser_t; +typedef struct parserList_s parserList_t; +typedef struct strgen_s strgen_t; +typedef struct strgenList_s strgenList_t; +typedef struct statsobj_s statsobj_t; +typedef struct nsd_epworkset_s nsd_epworkset_t; +typedef struct templates_s templates_t; +typedef struct queuecnf_s queuecnf_t; +typedef struct rulesets_s rulesets_t; +typedef struct globals_s globals_t; +typedef struct defaults_s defaults_t; +typedef struct actions_s actions_t; +typedef struct rsconf_s rsconf_t; +typedef struct cfgmodules_s cfgmodules_t; +typedef struct cfgmodules_etry_s cfgmodules_etry_t; +typedef struct outchannels_s outchannels_t; +typedef struct modConfData_s modConfData_t; +typedef struct instanceConf_s instanceConf_t; +typedef struct ratelimit_s ratelimit_t; +typedef struct action_s action_t; +typedef int rs_size_t; /* we do never need more than 2Gig strings, signed permits to + * use -1 as a special flag. */ +typedef rsRetVal (*prsf_t)(struct vmstk_s*, int); /* pointer to a RainerScript function */ +typedef uint64 qDeqID; /* queue Dequeue order ID. 32 bits is considered dangerously few */ + +typedef struct tcpLstnPortList_s tcpLstnPortList_t; // TODO: rename? +typedef struct strmLstnPortList_s strmLstnPortList_t; // TODO: rename? + +/* under Solaris (actually only SPARC), we need to redefine some types + * to be void, so that we get void* pointers. Otherwise, we will see + * alignment errors. + */ +#ifdef OS_SOLARIS + typedef void * obj_t_ptr; + typedef void nsd_t; + typedef void nsdsel_t; + typedef void nsdpoll_t; +#else + typedef obj_t *obj_t_ptr; + typedef obj_t nsd_t; + typedef obj_t nsdsel_t; + typedef obj_t nsdpoll_t; +#endif + + +#ifdef __hpux +typedef unsigned int u_int32_t; /* TODO: is this correct? */ +typedef int socklen_t; +#endif + +typedef struct epoll_event epoll_event_t; + +typedef char sbool; /* (small bool) I intentionally use char, to keep it slim so that many fit into the CPU cache! */ + +/* settings for flow control + * TODO: is there a better place for them? -- rgerhards, 2008-03-14 + */ +typedef enum { + eFLOWCTL_NO_DELAY = 0, /**< UDP and other non-delayable sources */ + eFLOWCTL_LIGHT_DELAY = 1, /**< some light delay possible, but no extended period of time */ + eFLOWCTL_FULL_DELAY = 2 /**< delay possible for extended period of time */ +} flowControl_t; + +/* filter operations */ +typedef enum { + FIOP_NOP = 0, /* do not use - No Operation */ + FIOP_CONTAINS = 1, /* contains string? */ + FIOP_ISEQUAL = 2, /* is (exactly) equal? */ + FIOP_STARTSWITH = 3, /* starts with a string? */ + FIOP_REGEX = 4, /* matches a (BRE) regular expression? */ + FIOP_EREREGEX = 5, /* matches a ERE regular expression? */ + FIOP_ISEMPTY = 6 /* string empty <=> strlen(s) == 0 ?*/ +} fiop_t; + +/* types of configuration handlers + */ +typedef enum cslCmdHdlrType { + eCmdHdlrInvalid = 0, /* invalid handler type - indicates a coding error */ + eCmdHdlrCustomHandler, /* custom handler, just call handler function */ + eCmdHdlrUID, + eCmdHdlrGID, + eCmdHdlrBinary, + eCmdHdlrFileCreateMode, + eCmdHdlrInt, + eCmdHdlrNonNegInt, + eCmdHdlrPositiveInt, + eCmdHdlrSize, + eCmdHdlrGetChar, + eCmdHdlrFacility, + eCmdHdlrSeverity, + eCmdHdlrGetWord, + eCmdHdlrString, + eCmdHdlrArray, + eCmdHdlrQueueType, + eCmdHdlrGoneAway /* statment existed, but is no longer supported */ +} ecslCmdHdrlType; + + +/* the next type describes $Begin .. $End block object types + */ +typedef enum cslConfObjType { + eConfObjGlobal = 0, /* global directives */ + eConfObjAction, /* action-specific directives */ + /* now come states that indicate that we wait for a block-end. These are + * states that permit us to do some safety checks and they hopefully ease + * migration to a "real" parser/grammar. + */ + eConfObjActionWaitEnd, + eConfObjAlways /* always valid, very special case (guess $End only!) */ +} ecslConfObjType; + + +/* multi-submit support. + * This is done via a simple data structure, which holds the number of elements + * as well as an array of to-be-submitted messages. + * rgerhards, 2009-06-16 + */ +typedef struct multi_submit_s multi_submit_t; +struct multi_submit_s { + short maxElem; /* maximum number of Elements */ + short nElem; /* current number of Elements, points to the next one FREE */ + msg_t **ppMsgs; +}; + +#endif /* multi-include protection */ +/* vim:set ai: + */ diff --git a/runtime/unicode-helper.h b/runtime/unicode-helper.h new file mode 100644 index 00000000..b7db2769 --- /dev/null +++ b/runtime/unicode-helper.h @@ -0,0 +1,67 @@ +/* This is the header file for unicode support. + * + * Currently, this is a dummy module. + * The following functions are wrappers which hopefully enable us to move + * from 8-bit chars to unicode with relative ease when we finally attack this + * + * Note: while we prefer inline functions, this leads to invalid references in + * core dumps. So in a debug build, we use macros where appropriate... + * + * Begun 2009-05-21 RGerhards + * + * Copyright (C) 2009-2012 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_UNICODE_HELPER_H +#define INCLUDED_UNICODE_HELPER_H + +#include <string.h> + +#ifdef DEBUG +# define ustrncpy(psz1, psz2, len) strncpy((char*)(psz1), (char*)(psz2), (len)) +# define ustrdup(psz) (uchar*)strdup((char*)(psz)) +#else + static inline uchar* ustrncpy(uchar *psz1, uchar *psz2, size_t len) + { + return (uchar*) strncpy((char*) psz1, (char*) psz2, len); + } + + static inline uchar* ustrdup(uchar *psz) + { + return (uchar*) strdup((char*)psz); + } + +#endif /* #ifdef DEBUG */ + +static inline int ustrcmp(uchar *psz1, uchar *psz2) +{ + return strcmp((char*) psz1, (char*) psz2); +} + +static inline int ustrlen(uchar *psz) +{ + return strlen((char*) psz); +} + + +#define UCHAR_CONSTANT(x) ((uchar*) (x)) +#define CHAR_CONVERT(x) ((char*) (x)) + +#endif /* multi-include protection */ +/* vim:set ai: + */ diff --git a/runtime/unlimited_select.h b/runtime/unlimited_select.h new file mode 100644 index 00000000..ec1e4498 --- /dev/null +++ b/runtime/unlimited_select.h @@ -0,0 +1,45 @@ +/* unlimited_select.h + * Tweak the macros for accessing fd_set so that the select() syscall + * won't be limited to a particular number of file descriptors. + * + * Copyright 2009-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UNLIMITED_SELECT_H_INCLUDED +#define UNLIMITED_SELECT_H_INCLUDED + +#include <string.h> +#include <stdlib.h> +#include <sys/select.h> +#include "glbl.h" + +#ifdef USE_UNLIMITED_SELECT +# undef FD_ZERO +# define FD_ZERO(set) memset((set), 0, glbl.GetFdSetSize()); +#endif + +#ifdef USE_UNLIMITED_SELECT +void freeFdSet(fd_set *p) { + free(p); +} +#else +# define freeFdSet(x) +#endif + +#endif /* #ifndef UNLIMITED_SELECT_H_INCLUDED */ diff --git a/runtime/var.c b/runtime/var.c new file mode 100644 index 00000000..eecc5d6a --- /dev/null +++ b/runtime/var.c @@ -0,0 +1,128 @@ +/* var.c - a typeless variable class + * + * This class is used to represent variable values, which may have any type. + * Among others, it will be used inside rsyslog's expression system, but + * also internally at any place where a typeless variable is needed. + * + * Module begun 2008-02-20 by Rainer Gerhards, with some code taken + * from the obj.c/.h files. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "srUtils.h" +#include "var.h" + +/* static data */ +DEFobjStaticHelpers + + +/* Standard-Constructor + */ +BEGINobjConstruct(var) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(var) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal varConstructFinalize(var_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + + RETiRet; +} + + +/* destructor for the var object */ +BEGINobjDestruct(var) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(var) + if(pThis->pcsName != NULL) + rsCStrDestruct(&pThis->pcsName); + if(pThis->varType == VARTYPE_STR) { + if(pThis->val.pStr != NULL) + rsCStrDestruct(&pThis->val.pStr); + } +ENDobjDestruct(var) + + +/* DebugPrint support for the var object */ +BEGINobjDebugPrint(var) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(var) + switch(pThis->varType) { + case VARTYPE_STR: + dbgoprint((obj_t*) pThis, "type: cstr, val '%s'\n", rsCStrGetSzStr(pThis->val.pStr)); + break; + case VARTYPE_NUMBER: + dbgoprint((obj_t*) pThis, "type: number, val %lld\n", pThis->val.num); + break; + default: + dbgoprint((obj_t*) pThis, "type %d currently not suppored in debug output\n", pThis->varType); + break; + } +ENDobjDebugPrint(var) + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(var) +CODESTARTobjQueryInterface(var) + if(pIf->ifVersion != varCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = varConstruct; + pIf->ConstructFinalize = varConstructFinalize; + pIf->Destruct = varDestruct; + pIf->DebugPrint = varDebugPrint; +finalize_it: +ENDobjQueryInterface(var) + + +/* Initialize the var class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(var, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, varDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, varConstructFinalize); +ENDObjClassInit(var) + +/* vi:set ai: + */ diff --git a/runtime/var.h b/runtime/var.h new file mode 100644 index 00000000..3d0847d9 --- /dev/null +++ b/runtime/var.h @@ -0,0 +1,63 @@ +/* The var object. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_VAR_H +#define INCLUDED_VAR_H + +#include "stringbuf.h" + +/* data types */ +typedef enum { + VARTYPE_NONE = 0, /* currently no value set */ + VARTYPE_STR = 1, + VARTYPE_NUMBER = 2, + VARTYPE_SYSLOGTIME = 3 +} varType_t; + +/* the var object */ +typedef struct var_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + cstr_t *pcsName; + varType_t varType; + union { + number_t num; + es_str_t *str; + cstr_t *pStr; + syslogTime_t vSyslogTime; + + } val; +} var_t; + + +/* interfaces */ +BEGINinterface(var) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(var); + rsRetVal (*Construct)(var_t **ppThis); + rsRetVal (*ConstructFinalize)(var_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(var_t **ppThis); +ENDinterface(var) +#define varCURR_IF_VERSION 2 /* increment whenever you change the interface above! */ +/* v2 - 2011-07-15/rger: on the way to remove var */ + + +/* prototypes */ +PROTOTYPEObj(var); + +#endif /* #ifndef INCLUDED_VAR_H */ diff --git a/runtime/wti.c b/runtime/wti.c new file mode 100644 index 00000000..f91fb5a9 --- /dev/null +++ b/runtime/wti.c @@ -0,0 +1,399 @@ +/* wti.c + * + * This file implements the worker thread instance (wti) class. + * + * File begun on 2008-01-20 by RGerhards based on functions from the + * previous queue object class (the wti functions have been extracted) + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <errno.h> + +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "wtp.h" +#include "wti.h" +#include "obj.h" +#include "glbl.h" +#include "atomic.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) + +/* forward-definitions */ + +/* methods */ + +/* get the header for debug messages + * The caller must NOT free or otherwise modify the returned string! + */ +static inline uchar * +wtiGetDbgHdr(wti_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, wti); + + if(pThis->pszDbgHdr == NULL) + return (uchar*) "wti"; /* should not normally happen */ + else + return pThis->pszDbgHdr; +} + + +/* return the current worker processing state. For the sake of + * simplicity, we do not use the iRet interface. -- rgerhards, 2009-07-17 + */ +sbool +wtiGetState(wti_t *pThis) +{ + return ATOMIC_FETCH_32BIT(&pThis->bIsRunning, &pThis->mutIsRunning); +} + + +/* Set this thread to "always running" state (can not be unset) + * rgerhards, 2009-07-20 + */ +rsRetVal +wtiSetAlwaysRunning(wti_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, wti); + pThis->bAlwaysRunning = RSTRUE; + return RS_RET_OK; +} + +/* Set status (thread is running or not), actually an property of + * use for wtp, but we need to have it per thread instance (thus it + * is inside wti). -- rgerhards, 2009-07-17 + */ +rsRetVal +wtiSetState(wti_t *pThis, sbool bNewVal) +{ + ISOBJ_TYPE_assert(pThis, wti); + if(bNewVal) { + ATOMIC_STORE_1_TO_INT(&pThis->bIsRunning, &pThis->mutIsRunning); + } else { + ATOMIC_STORE_0_TO_INT(&pThis->bIsRunning, &pThis->mutIsRunning); + } + return RS_RET_OK; +} + + +/* advise all workers to start by interrupting them. That should unblock all srSleep() + * calls. + */ +rsRetVal +wtiWakeupThrd(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + + + if(wtiGetState(pThis)) { + /* we first try the cooperative "cancel" interface */ + pthread_kill(pThis->thrdID, SIGTTIN); + DBGPRINTF("sent SIGTTIN to worker thread 0x%x\n", (unsigned) pThis->thrdID); + } + + RETiRet; +} + + +/* Cancel the thread. If the thread is not running. But it is save and legal to + * call wtiCancelThrd() in such situations. This function only returns when the + * thread has terminated. Else we may get race conditions all over the code... + * Note that when waiting for the thread to terminate, we do a busy wait, checking + * progress every 10ms. It is very unlikely that we will ever cancel a thread + * and, if so, it will only happen at the end of the rsyslog run. So doing this + * kind of non-optimal wait is considered preferable over using condition variables. + * rgerhards, 2008-02-26 + */ +rsRetVal +wtiCancelThrd(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + + + if(wtiGetState(pThis)) { + /* we first try the cooperative "cancel" interface */ + pthread_kill(pThis->thrdID, SIGTTIN); + DBGPRINTF("sent SIGTTIN to worker thread 0x%x, giving it a chance to terminate\n", (unsigned) pThis->thrdID); + srSleep(0, 10000); + } + + if(wtiGetState(pThis)) { + DBGPRINTF("cooperative worker termination failed, using cancellation...\n"); + DBGOPRINT((obj_t*) pThis, "canceling worker thread\n"); + pthread_cancel(pThis->thrdID); + /* now wait until the thread terminates... */ + while(wtiGetState(pThis)) { + srSleep(0, 10000); + } + } + + RETiRet; +} + + +/* Destructor */ +BEGINobjDestruct(wti) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(wti) + /* actual destruction */ + batchFree(&pThis->batch); + DESTROY_ATOMIC_HELPER_MUT(pThis->mutIsRunning); + + free(pThis->pszDbgHdr); +ENDobjDestruct(wti) + + +/* Standard-Constructor for the wti object + */ +BEGINobjConstruct(wti) /* be sure to specify the object type also in END macro! */ + INIT_ATOMIC_HELPER_MUT(pThis->mutIsRunning); +ENDobjConstruct(wti) + + +/* Construction finalizer + * rgerhards, 2008-01-17 + */ +rsRetVal +wtiConstructFinalize(wti_t *pThis) +{ + DEFiRet; + int iDeqBatchSize; + + ISOBJ_TYPE_assert(pThis, wti); + + DBGPRINTF("%s: finalizing construction of worker instance data\n", wtiGetDbgHdr(pThis)); + + /* initialize our thread instance descriptor (no concurrency here) */ + pThis->bIsRunning = RSFALSE; + + /* we now alloc the array for user pointers. We obtain the max from the queue itself. */ + CHKiRet(pThis->pWtp->pfGetDeqBatchSize(pThis->pWtp->pUsr, &iDeqBatchSize)); + CHKiRet(batchInit(&pThis->batch, iDeqBatchSize)); + +finalize_it: + RETiRet; +} + + +/* cancellation cleanup handler for queueWorker () + * Most importantly, it must bring back the batch into a consistent state. + * Keep in mind that cancellation is disabled if we run into + * the cancel cleanup handler (and have been cancelled). + * rgerhards, 2008-01-16 + */ +static void +wtiWorkerCancelCleanup(void *arg) +{ + wti_t *pThis = (wti_t*) arg; + wtp_t *pWtp; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wti); + pWtp = pThis->pWtp; + ISOBJ_TYPE_assert(pWtp, wtp); + + DBGPRINTF("%s: cancelation cleanup handler called.\n", wtiGetDbgHdr(pThis)); + pWtp->pfObjProcessed(pWtp->pUsr, pThis); + DBGPRINTF("%s: done cancelation cleanup handler.\n", wtiGetDbgHdr(pThis)); + + ENDfunc +} + + +/* wait for queue to become non-empty or timeout + * helper to wtiWorker. Note the the predicate is + * re-tested by the caller, so it is OK to NOT do it here. + * rgerhards, 2009-05-20 + */ +static inline void +doIdleProcessing(wti_t *pThis, wtp_t *pWtp, int *pbInactivityTOOccured) +{ + struct timespec t; + + BEGINfunc + DBGPRINTF("%s: worker IDLE, waiting for work.\n", wtiGetDbgHdr(pThis)); + + if(pThis->bAlwaysRunning) { + /* never shut down any started worker */ + d_pthread_cond_wait(pWtp->pcondBusy, pWtp->pmutUsr); + } else { + timeoutComp(&t, pWtp->toWrkShutdown);/* get absolute timeout */ + if(d_pthread_cond_timedwait(pWtp->pcondBusy, pWtp->pmutUsr, &t) != 0) { + DBGPRINTF("%s: inactivity timeout, worker terminating...\n", wtiGetDbgHdr(pThis)); + *pbInactivityTOOccured = 1; /* indicate we had a timeout */ + } + } + DBGOPRINT((obj_t*) pThis, "worker awoke from idle processing\n"); + ENDfunc +} + + +/* generic worker thread framework. Note that we prohibit cancellation + * during almost all times, because it can have very undesired side effects. + * However, we may need to cancel a thread if the consumer blocks for too + * long (during shutdown). So what we do is block cancellation, and every + * consumer must enable it during the periods where it is safe. + */ +#pragma GCC diagnostic ignored "-Wempty-body" +rsRetVal +wtiWorker(wti_t *pThis) +{ + wtp_t *pWtp; /* our worker thread pool */ + int bInactivityTOOccured = 0; + rsRetVal localRet; + rsRetVal terminateRet; + int iCancelStateSave; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + pWtp = pThis->pWtp; /* shortcut */ + ISOBJ_TYPE_assert(pWtp, wtp); + + dbgSetThrdName(pThis->pszDbgHdr); + pthread_cleanup_push(wtiWorkerCancelCleanup, pThis); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + + /* now we have our identity, on to real processing */ + while(1) { /* loop will be broken below - need to do mutex locks */ + if(pWtp->pfRateLimiter != NULL) { /* call rate-limiter, if defined */ + pWtp->pfRateLimiter(pWtp->pUsr); + } + + d_pthread_mutex_lock(pWtp->pmutUsr); + + /* first check if we are in shutdown process (but evaluate a bit later) */ + terminateRet = wtpChkStopWrkr(pWtp, MUTEX_ALREADY_LOCKED); + if(terminateRet == RS_RET_TERMINATE_NOW) { + /* we now need to free the old batch */ + localRet = pWtp->pfObjProcessed(pWtp->pUsr, pThis); + DBGOPRINT((obj_t*) pThis, "terminating worker because of TERMINATE_NOW mode, del iRet %d\n", + localRet); + d_pthread_mutex_unlock(pWtp->pmutUsr); + break; + } + + /* try to execute and process whatever we have */ + /* Note that this function releases and re-aquires the mutex. The returned + * information on idle state must be processed before releasing the mutex again. + */ + localRet = pWtp->pfDoWork(pWtp->pUsr, pThis); + + if(localRet == RS_RET_ERR_QUEUE_EMERGENCY) { + d_pthread_mutex_unlock(pWtp->pmutUsr); + break; /* end of loop */ + } else if(localRet == RS_RET_IDLE) { + if(terminateRet == RS_RET_TERMINATE_WHEN_IDLE || bInactivityTOOccured) { + d_pthread_mutex_unlock(pWtp->pmutUsr); + DBGOPRINT((obj_t*) pThis, "terminating worker terminateRet=%d, bInactivityTOOccured=%d\n", + terminateRet, bInactivityTOOccured); + break; /* end of loop */ + } + doIdleProcessing(pThis, pWtp, &bInactivityTOOccured); + d_pthread_mutex_unlock(pWtp->pmutUsr); + continue; /* request next iteration */ + } + + d_pthread_mutex_unlock(pWtp->pmutUsr); + + bInactivityTOOccured = 0; /* reset for next run */ + } + + /* indicate termination */ + pthread_cleanup_pop(0); /* remove cleanup handler */ + pthread_setcancelstate(iCancelStateSave, NULL); + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* some simple object access methods */ +DEFpropSetMeth(wti, pWtp, wtp_t*) + +/* set the debug header message + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. Must be called only before object is finalized. + * rgerhards, 2008-01-09 + */ +rsRetVal +wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + assert(pszMsg != NULL); + + if(lenMsg < 1) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + if(pThis->pszDbgHdr != NULL) { + free(pThis->pszDbgHdr); + } + + if((pThis->pszDbgHdr = MALLOC(sizeof(uchar) * lenMsg + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */ + +finalize_it: + RETiRet; +} + + +/* dummy */ +rsRetVal wtiQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* exit our class + */ +BEGINObjClassExit(wti, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdsel_gtls) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); +ENDObjClassExit(wti) + + +/* Initialize the wti class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(wti, 1, OBJ_IS_CORE_MODULE) /* one is the object version (most important for persisting) */ + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); +ENDObjClassInit(wti) + +/* vi:set ai: + */ diff --git a/runtime/wti.h b/runtime/wti.h new file mode 100644 index 00000000..014251f0 --- /dev/null +++ b/runtime/wti.h @@ -0,0 +1,59 @@ +/* Definition of the worker thread instance (wti) class. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WTI_H_INCLUDED +#define WTI_H_INCLUDED + +#include <pthread.h> +#include "wtp.h" +#include "obj.h" +#include "batch.h" + + +/* the worker thread instance class */ +struct wti_s { + BEGINobjInstance; + pthread_t thrdID; /* thread ID */ + int bIsRunning; /* is this thread currently running? (must be int for atomic op!) */ + sbool bAlwaysRunning; /* should this thread always run? */ + wtp_t *pWtp; /* my worker thread pool (important if only the work thread instance is passed! */ + batch_t batch; /* pointer to an object array meaningful for current user pointer (e.g. queue pUsr data elemt) */ + uchar *pszDbgHdr; /* header string for debug messages */ + DEF_ATOMIC_HELPER_MUT(mutIsRunning); +}; + + +/* prototypes */ +rsRetVal wtiConstruct(wti_t **ppThis); +rsRetVal wtiConstructFinalize(wti_t *pThis); +rsRetVal wtiDestruct(wti_t **ppThis); +rsRetVal wtiWorker(wti_t *pThis); +rsRetVal wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg); +rsRetVal wtiCancelThrd(wti_t *pThis); +rsRetVal wtiSetAlwaysRunning(wti_t *pThis); +rsRetVal wtiSetState(wti_t *pThis, sbool bNew); +rsRetVal wtiWakeupThrd(wti_t *pThis); +sbool wtiGetState(wti_t *pThis); +PROTOTYPEObjClassInit(wti); +PROTOTYPEpropSetMeth(wti, pszDbgHdr, uchar*); +PROTOTYPEpropSetMeth(wti, pWtp, wtp_t*); + +#endif /* #ifndef WTI_H_INCLUDED */ diff --git a/runtime/wtp.c b/runtime/wtp.c new file mode 100644 index 00000000..19151e7c --- /dev/null +++ b/runtime/wtp.c @@ -0,0 +1,553 @@ +/* wtp.c + * + * This file implements the worker thread pool (wtp) class. + * + * File begun on 2008-01-20 by RGerhards + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * Copyright 2008,2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <atomic.h> +#if HAVE_SYS_PRCTL_H +# include <sys/prctl.h> +#endif + +/// TODO: check on solaris if this is any longer needed - I don't think so - rgerhards, 2009-09-20 +//#ifdef OS_SOLARIS +//# include <sched.h> +//#endif + +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "wtp.h" +#include "wti.h" +#include "obj.h" +#include "unicode-helper.h" +#include "glbl.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) + +/* forward-definitions */ + +/* methods */ + +/* get the header for debug messages + * The caller must NOT free or otherwise modify the returned string! + */ +static inline uchar * +wtpGetDbgHdr(wtp_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, wtp); + + if(pThis->pszDbgHdr == NULL) + return (uchar*) "wtp"; /* should not normally happen */ + else + return pThis->pszDbgHdr; +} + + + +/* Not implemented dummy function for constructor */ +static rsRetVal NotImplementedDummy() { return RS_RET_NOT_IMPLEMENTED; } +/* Standard-Constructor for the wtp object + */ +BEGINobjConstruct(wtp) /* be sure to specify the object type also in END macro! */ + pthread_mutex_init(&pThis->mutWtp, NULL); + pthread_cond_init(&pThis->condThrdTrm, NULL); + pthread_attr_init(&pThis->attrThrd); + /* Set thread scheduling policy to default */ +#ifdef HAVE_PTHREAD_SETSCHEDPARAM + pthread_attr_setschedpolicy(&pThis->attrThrd, default_thr_sched_policy); + pthread_attr_setschedparam(&pThis->attrThrd, &default_sched_param); + pthread_attr_setinheritsched(&pThis->attrThrd, PTHREAD_EXPLICIT_SCHED); +#endif + pthread_attr_setdetachstate(&pThis->attrThrd, PTHREAD_CREATE_DETACHED); + /* set all function pointers to "not implemented" dummy so that we can safely call them */ + pThis->pfChkStopWrkr = NotImplementedDummy; + pThis->pfGetDeqBatchSize = NotImplementedDummy; + pThis->pfDoWork = NotImplementedDummy; + pThis->pfObjProcessed = NotImplementedDummy; + INIT_ATOMIC_HELPER_MUT(pThis->mutCurNumWrkThrd); + INIT_ATOMIC_HELPER_MUT(pThis->mutWtpState); +ENDobjConstruct(wtp) + + +/* Construction finalizer + * rgerhards, 2008-01-17 + */ +rsRetVal +wtpConstructFinalize(wtp_t *pThis) +{ + DEFiRet; + int i; + uchar pszBuf[64]; + size_t lenBuf; + wti_t *pWti; + + ISOBJ_TYPE_assert(pThis, wtp); + + DBGPRINTF("%s: finalizing construction of worker thread pool\n", wtpGetDbgHdr(pThis)); + /* alloc and construct workers - this can only be done in finalizer as we previously do + * not know the max number of workers + */ + CHKmalloc(pThis->pWrkr = MALLOC(sizeof(wti_t*) * pThis->iNumWorkerThreads)); + + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + CHKiRet(wtiConstruct(&pThis->pWrkr[i])); + pWti = pThis->pWrkr[i]; + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s/w%d", wtpGetDbgHdr(pThis), i); + CHKiRet(wtiSetDbgHdr(pWti, pszBuf, lenBuf)); + CHKiRet(wtiSetpWtp(pWti, pThis)); + CHKiRet(wtiConstructFinalize(pWti)); + } + + +finalize_it: + RETiRet; +} + + +/* Destructor */ +BEGINobjDestruct(wtp) /* be sure to specify the object type also in END and CODESTART macros! */ + int i; +CODESTARTobjDestruct(wtp) + /* destruct workers */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) + wtiDestruct(&pThis->pWrkr[i]); + + free(pThis->pWrkr); + pThis->pWrkr = NULL; + + /* actual destruction */ + pthread_cond_destroy(&pThis->condThrdTrm); + pthread_mutex_destroy(&pThis->mutWtp); + pthread_attr_destroy(&pThis->attrThrd); + DESTROY_ATOMIC_HELPER_MUT(pThis->mutCurNumWrkThrd); + DESTROY_ATOMIC_HELPER_MUT(pThis->mutWtpState); + + free(pThis->pszDbgHdr); +ENDobjDestruct(wtp) + + +/* Sent a specific state for the worker thread pool. -- rgerhards, 2008-01-21 + * We do not need to do atomic instructions as set operations are only + * called when terminating the pool, and then in strict sequence. So we + * can never overwrite each other. On the other hand, it also doesn't + * matter if the read operation obtains an older value, as we then simply + * do one more iteration, what is perfectly legal (during shutdown + * they are awoken in any case). -- rgerhards, 2009-07-20 + */ +rsRetVal +wtpSetState(wtp_t *pThis, wtpState_t iNewState) +{ + ISOBJ_TYPE_assert(pThis, wtp); + pThis->wtpState = iNewState; // TODO: do we need a mutex here? 2010-04-26 + return RS_RET_OK; +} + + +/* check if the worker shall shutdown (1 = yes, 0 = no) + * Note: there may be two mutexes locked, the bLockUsrMutex is the one in our "user" + * (e.g. the queue clas) + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpChkStopWrkr(wtp_t *pThis, int bLockUsrMutex) +{ + DEFiRet; + wtpState_t wtpState; + + ISOBJ_TYPE_assert(pThis, wtp); + /* we need a consistent value, but it doesn't really matter if it is changed + * right after the fetch - then we simply do one more iteration in the worker + */ + wtpState = (wtpState_t) ATOMIC_FETCH_32BIT((int*)&pThis->wtpState, &pThis->mutWtpState); + + if(wtpState == wtpState_SHUTDOWN_IMMEDIATE) { + ABORT_FINALIZE(RS_RET_TERMINATE_NOW); + } else if(wtpState == wtpState_SHUTDOWN) { + ABORT_FINALIZE(RS_RET_TERMINATE_WHEN_IDLE); + } + + /* try customer handler if one was set and we do not yet have a definite result */ + if(pThis->pfChkStopWrkr != NULL) { + iRet = pThis->pfChkStopWrkr(pThis->pUsr, bLockUsrMutex); + } + +finalize_it: + RETiRet; +} + + +#pragma GCC diagnostic ignored "-Wempty-body" +/* Send a shutdown command to all workers and see if they terminate. + * A timeout may be specified. This function may also be called with + * the current number of workers being 0, in which case it does not + * shut down any worker. + * rgerhards, 2008-01-14 + */ +rsRetVal +wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout) +{ + DEFiRet; + int bTimedOut; + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + /* lock mutex to prevent races (may otherwise happen during idle processing and such...) */ + d_pthread_mutex_lock(pThis->pmutUsr); + wtpSetState(pThis, tShutdownCmd); + pthread_cond_broadcast(pThis->pcondBusy); /* wake up all workers */ + /* awake workers in retry loop */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + wtiWakeupThrd(pThis->pWrkr[i]); + } + d_pthread_mutex_unlock(pThis->pmutUsr); + + /* wait for worker thread termination */ + d_pthread_mutex_lock(&pThis->mutWtp); + pthread_cleanup_push(mutexCancelCleanup, &pThis->mutWtp); + bTimedOut = 0; + while(pThis->iCurNumWrkThrd > 0 && !bTimedOut) { + DBGPRINTF("%s: waiting %ldms on worker thread termination, %d still running\n", + wtpGetDbgHdr(pThis), timeoutVal(ptTimeout), + ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd)); + + if(d_pthread_cond_timedwait(&pThis->condThrdTrm, &pThis->mutWtp, ptTimeout) != 0) { + DBGPRINTF("%s: timeout waiting on worker thread termination\n", wtpGetDbgHdr(pThis)); + bTimedOut = 1; /* we exit the loop on timeout */ + } + + /* awake workers in retry loop */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + wtiWakeupThrd(pThis->pWrkr[i]); + } + + } + pthread_cleanup_pop(1); + + if(bTimedOut) + iRet = RS_RET_TIMED_OUT; + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* Unconditionally cancel all running worker threads. + * rgerhards, 2008-01-14 + */ +rsRetVal +wtpCancelAll(wtp_t *pThis) +{ + DEFiRet; + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + /* go through all workers and cancel those that are active */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + wtiCancelThrd(pThis->pWrkr[i]); + } + + RETiRet; +} + + +/* this function contains shared code for both regular worker shutdown as + * well as shutdown via cancellation. We can not simply use pthread_cleanup_pop(1) + * as this introduces a race in the debug system (RETiRet system). + * rgerhards, 2009-10-26 + */ +static inline void +wtpWrkrExecCleanup(wti_t *pWti) +{ + wtp_t *pThis; + + BEGINfunc + ISOBJ_TYPE_assert(pWti, wti); + pThis = pWti->pWtp; + ISOBJ_TYPE_assert(pThis, wtp); + + /* the order of the next two statements is important! */ + wtiSetState(pWti, WRKTHRD_STOPPED); + ATOMIC_DEC(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd); + + DBGPRINTF("%s: Worker thread %lx, terminated, num workers now %d\n", + wtpGetDbgHdr(pThis), (unsigned long) pWti, + ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd)); + + ENDfunc +} + + +/* cancellation cleanup handler for executing worker decrements the worker counter. + * rgerhards, 2009-07-20 + */ +static void +wtpWrkrExecCancelCleanup(void *arg) +{ + wti_t *pWti = (wti_t*) arg; + wtp_t *pThis; + + BEGINfunc + ISOBJ_TYPE_assert(pWti, wti); + pThis = pWti->pWtp; + ISOBJ_TYPE_assert(pThis, wtp); + DBGPRINTF("%s: Worker thread %lx requested to be cancelled.\n", + wtpGetDbgHdr(pThis), (unsigned long) pWti); + + wtpWrkrExecCleanup(pWti); + + ENDfunc + /* NOTE: we must call ENDfunc FIRST, because otherwise the schedule may activate the main + * thread after the broadcast, which could destroy the debug class, resulting in a potential + * segfault. So we need to do the broadcast as actually the last action in our processing + */ + pthread_cond_broadcast(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */ +} + + +/* wtp worker shell. This is started and calls into the actual + * wti worker. + * rgerhards, 2008-01-21 + */ +#pragma GCC diagnostic ignored "-Wempty-body" +static void * +wtpWorker(void *arg) /* the arg is actually a wti object, even though we are in wtp! */ +{ + wti_t *pWti = (wti_t*) arg; + wtp_t *pThis; + sigset_t sigSet; +# if HAVE_PRCTL && defined PR_SET_NAME + uchar *pszDbgHdr; + uchar thrdName[32] = "rs:"; +# endif + + BEGINfunc + ISOBJ_TYPE_assert(pWti, wti); + pThis = pWti->pWtp; + ISOBJ_TYPE_assert(pThis, wtp); + + /* block all signals */ + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + + /* but ignore SIGTTN, which we (ab)use to signal the thread to shutdown -- rgerhards, 2009-07-20 */ + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGTTIN); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + +# if HAVE_PRCTL && defined PR_SET_NAME + /* set thread name - we ignore if the call fails, has no harsh consequences... */ + pszDbgHdr = wtpGetDbgHdr(pThis); + ustrncpy(thrdName+3, pszDbgHdr, 20); + if(prctl(PR_SET_NAME, thrdName, 0, 0, 0) != 0) { + DBGPRINTF("prctl failed, not setting thread name for '%s'\n", wtpGetDbgHdr(pThis)); + } + dbgOutputTID((char*)thrdName); +# endif + + pthread_cleanup_push(wtpWrkrExecCancelCleanup, pWti); + wtiWorker(pWti); + pthread_cleanup_pop(0); + wtpWrkrExecCleanup(pWti); + + ENDfunc + /* NOTE: we must call ENDfunc FIRST, because otherwise the schedule may activate the main + * thread after the broadcast, which could destroy the debug class, resulting in a potential + * segfault. So we need to do the broadcast as actually the last action in our processing + */ + pthread_cond_broadcast(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */ + pthread_exit(0); +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* start a new worker */ +static rsRetVal +wtpStartWrkr(wtp_t *pThis) +{ + wti_t *pWti; + int i; + int iState; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + + d_pthread_mutex_lock(&pThis->mutWtp); + + /* find free spot in thread table. */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + if(wtiGetState(pThis->pWrkr[i]) == WRKTHRD_STOPPED) { + break; + } + } + + if(i == pThis->iNumWorkerThreads) + ABORT_FINALIZE(RS_RET_NO_MORE_THREADS); + + if(i == 0 || pThis->toWrkShutdown == -1) { + wtiSetAlwaysRunning(pThis->pWrkr[i]); + } + + pWti = pThis->pWrkr[i]; + wtiSetState(pWti, WRKTHRD_RUNNING); + iState = pthread_create(&(pWti->thrdID), &pThis->attrThrd, wtpWorker, (void*) pWti); + ATOMIC_INC(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd); /* we got one more! */ + + DBGPRINTF("%s: started with state %d, num workers now %d\n", + wtpGetDbgHdr(pThis), iState, + ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd)); + +finalize_it: + d_pthread_mutex_unlock(&pThis->mutWtp); + RETiRet; +} + + +/* set the number of worker threads that should be running. If less than currently running, + * a new worker may be started. Please note that there is no guarantee the number of workers + * said will be running after we exit this function. It is just a hint. If the number is + * higher than one, and no worker is started, the "busy" condition is signaled to awake a worker. + * So the caller can assume that there is at least one worker re-checking if there is "work to do" + * after this function call. + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr) +{ + DEFiRet; + int nMissing; /* number workers missing to run */ + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + if(nMaxWrkr == 0) + FINALIZE; + + if(nMaxWrkr > pThis->iNumWorkerThreads) /* limit to configured maximum */ + nMaxWrkr = pThis->iNumWorkerThreads; + + nMissing = nMaxWrkr - ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd); + + if(nMissing > 0) { + DBGPRINTF("%s: high activity - starting %d additional worker thread(s).\n", + wtpGetDbgHdr(pThis), nMissing); + /* start the rqtd nbr of workers */ + for(i = 0 ; i < nMissing ; ++i) { + CHKiRet(wtpStartWrkr(pThis)); + } + } else { + pthread_cond_signal(pThis->pcondBusy); + } + + +finalize_it: + RETiRet; +} + + +/* some simple object access methods */ +DEFpropSetMeth(wtp, toWrkShutdown, long) +DEFpropSetMeth(wtp, wtpState, wtpState_t) +DEFpropSetMeth(wtp, iNumWorkerThreads, int) +DEFpropSetMeth(wtp, pUsr, void*) +DEFpropSetMethPTR(wtp, pmutUsr, pthread_mutex_t) +DEFpropSetMethPTR(wtp, pcondBusy, pthread_cond_t) +DEFpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int)) +DEFpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*)) +DEFpropSetMethFP(wtp, pfGetDeqBatchSize, rsRetVal(*pVal)(void*, int*)) +DEFpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*)) +DEFpropSetMethFP(wtp, pfObjProcessed, rsRetVal(*pVal)(void*, wti_t*)) + + +/* set the debug header message + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. Must be called only before object is finalized. + * rgerhards, 2008-01-09 + */ +rsRetVal +wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + assert(pszMsg != NULL); + + if(lenMsg < 1) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + if(pThis->pszDbgHdr != NULL) { + free(pThis->pszDbgHdr); + pThis->pszDbgHdr = NULL; + } + + if((pThis->pszDbgHdr = MALLOC(sizeof(uchar) * lenMsg + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */ + +finalize_it: + RETiRet; +} + +/* dummy */ +rsRetVal wtpQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* exit our class + */ +BEGINObjClassExit(wtp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdsel_gtls) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); +ENDObjClassExit(wtp) + + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(wtp, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); +ENDObjClassInit(wtp) + +/* vi:set ai: + */ diff --git a/runtime/wtp.h b/runtime/wtp.h new file mode 100644 index 00000000..25992f7f --- /dev/null +++ b/runtime/wtp.h @@ -0,0 +1,100 @@ +/* Definition of the worker thread pool (wtp) object. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WTP_H_INCLUDED +#define WTP_H_INCLUDED + +#include <pthread.h> +#include "obj.h" +#include "atomic.h" + +/* states for worker threads. */ +#define WRKTHRD_STOPPED RSFALSE +#define WRKTHRD_RUNNING RSTRUE + + +/* possible states of a worker thread pool */ +typedef enum { + wtpState_RUNNING = 0, /* runs in regular mode */ + wtpState_SHUTDOWN = 1, /* worker threads shall shutdown when idle */ + wtpState_SHUTDOWN_IMMEDIATE = 2 /* worker threads shall shutdown ASAP, even if not idle */ +} wtpState_t; + + +/* the worker thread pool (wtp) object */ +struct wtp_s { + BEGINobjInstance; + wtpState_t wtpState; + int iNumWorkerThreads;/* number of worker threads to use */ + int iCurNumWrkThrd;/* current number of active worker threads */ + struct wti_s **pWrkr;/* array with control structure for the worker thread(s) associated with this wtp */ + int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */ + rsRetVal (*pConsumer)(void *); /* user-supplied consumer function for dewtpd messages */ + /* synchronization variables */ + pthread_mutex_t mutWtp; /* mutex for the wtp's thread management */ + pthread_cond_t condThrdTrm;/* signalled when threads terminate */ + /* end sync variables */ + /* user objects */ + void *pUsr; /* pointer to user object (in this case, the queue the wtp belongs to) */ + pthread_attr_t attrThrd;/* attribute for new threads (created just once and cached here) */ + pthread_mutex_t *pmutUsr; + pthread_cond_t *pcondBusy; /* condition the user will signal "busy again, keep runing" on (awakes worker) */ + rsRetVal (*pfChkStopWrkr)(void *pUsr, int); + rsRetVal (*pfGetDeqBatchSize)(void *pUsr, int*); /* obtains max dequeue count from queue config */ + rsRetVal (*pfObjProcessed)(void *pUsr, wti_t *pWti); /* indicate user object is processed */ + rsRetVal (*pfRateLimiter)(void *pUsr); + rsRetVal (*pfDoWork)(void *pUsr, void *pWti); + /* end user objects */ + uchar *pszDbgHdr; /* header string for debug messages */ + DEF_ATOMIC_HELPER_MUT(mutCurNumWrkThrd); + DEF_ATOMIC_HELPER_MUT(mutWtpState); +}; + +/* some symbolic constants for easier reference */ + + +/* prototypes */ +rsRetVal wtpConstruct(wtp_t **ppThis); +rsRetVal wtpConstructFinalize(wtp_t *pThis); +rsRetVal wtpDestruct(wtp_t **ppThis); +rsRetVal wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr); +rsRetVal wtpProcessThrdChanges(wtp_t *pThis); +rsRetVal wtpChkStopWrkr(wtp_t *pThis, int bLockUsrMutex); +rsRetVal wtpSetState(wtp_t *pThis, wtpState_t iNewState); +rsRetVal wtpWakeupAllWrkr(wtp_t *pThis); +rsRetVal wtpCancelAll(wtp_t *pThis); +rsRetVal wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg); +rsRetVal wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout); +PROTOTYPEObjClassInit(wtp); +PROTOTYPEpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*)); +PROTOTYPEpropSetMethFP(wtp, pfGetDeqBatchSize, rsRetVal(*pVal)(void*, int*)); +PROTOTYPEpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*)); +PROTOTYPEpropSetMethFP(wtp, pfObjProcessed, rsRetVal(*pVal)(void*, wti_t*)); +PROTOTYPEpropSetMeth(wtp, toWrkShutdown, long); +PROTOTYPEpropSetMeth(wtp, wtpState, wtpState_t); +PROTOTYPEpropSetMeth(wtp, iMaxWorkerThreads, int); +PROTOTYPEpropSetMeth(wtp, pUsr, void*); +PROTOTYPEpropSetMeth(wtp, iNumWorkerThreads, int); +PROTOTYPEpropSetMethPTR(wtp, pmutUsr, pthread_mutex_t); +PROTOTYPEpropSetMethPTR(wtp, pcondBusy, pthread_cond_t); + +#endif /* #ifndef WTP_H_INCLUDED */ diff --git a/runtime/zlibw.c b/runtime/zlibw.c new file mode 100644 index 00000000..31963cc1 --- /dev/null +++ b/runtime/zlibw.c @@ -0,0 +1,124 @@ +/* The zlibwrap object. + * + * This is an rsyslog object wrapper around zlib. + * + * Copyright 2009-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include <string.h> +#include <assert.h> +#include <zlib.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "zlibw.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + +/* zlib make strong use of macros for its interface functions, so we can not simply + * pass function pointers to them. Instead, we create very small wrappers which call + * the relevant entry points. + */ + +static int myDeflateInit(z_streamp strm, int level) +{ + return deflateInit(strm, level); +} + +static int myDeflateInit2(z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy) +{ + return deflateInit2(strm, level, method, windowBits, memLevel, strategy); +} + +static int myDeflateEnd(z_streamp strm) +{ + return deflateEnd(strm); +} + +static int myDeflate(z_streamp strm, int flush) +{ + return deflate(strm, flush); +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(zlibw) +CODESTARTobjQueryInterface(zlibw) + if(pIf->ifVersion != zlibwCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->DeflateInit = myDeflateInit; + pIf->DeflateInit2 = myDeflateInit2; + pIf->Deflate = myDeflate; + pIf->DeflateEnd = myDeflateEnd; +finalize_it: +ENDobjQueryInterface(zlibw) + + +/* Initialize the zlibw class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(zlibw, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ +ENDObjClassInit(zlibw) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + CHKiRet(zlibwClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + /* Initialize all classes that are in our module - this includes ourselfs */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/zlibw.h b/runtime/zlibw.h new file mode 100644 index 00000000..2dee1b18 --- /dev/null +++ b/runtime/zlibw.h @@ -0,0 +1,44 @@ +/* The zlibw object. It encapsulates the zlib functionality. The primary + * purpose of this wrapper class is to enable rsyslogd core to be build without + * zlib libraries. + * + * Copyright 2009-2012 Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_ZLIBW_H +#define INCLUDED_ZLIBW_H + +#include <zlib.h> + +/* interfaces */ +BEGINinterface(zlibw) /* name must also be changed in ENDinterface macro! */ + int (*DeflateInit)(z_streamp strm, int); + int (*DeflateInit2)(z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy); + int (*Deflate)(z_streamp strm, int); + int (*DeflateEnd)(z_streamp strm); +ENDinterface(zlibw) +#define zlibwCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(zlibw); + +/* the name of our library binary */ +#define LM_ZLIBW_FILENAME "lmzlibw" + +#endif /* #ifndef INCLUDED_ZLIBW_H */ diff --git a/sample.conf b/sample.conf new file mode 100644 index 00000000..a27de0c1 --- /dev/null +++ b/sample.conf @@ -0,0 +1,284 @@ +# This is a sample configuation file for rsyslogd. See the +# doc/manual.html for details. If you can not find the +# manual set, please visit +# +# http://www.rsyslog.com/doc/ +# +# to obtain it online. +# +# WARNING: We do NOT keep the comments in this file always +# up to date. Be sure to consult the doc set that +# came with your package, especially the file on +# rsyslog.conf - it probably has some better information +# than is provided here in comments. The main purpose +# of sample.conf is to show you some actual directives, +# not to be the authorative doc source. +# +# Please note that rsyslogd by default +# reads /etc/rsyslogd.conf (and NOT /etc/syslogd.conf!). +# +# A commented sample configuration. More a man page than a real +# sample ;) +# +# We try to keep things as consistent with existing syslog implementation +# as possible. We use "$" to start lines that contain new directives. + +# We limit who can send us messages: +$AllowedSender UDP, 192.0.2.0/24, 10.0.0.1 # all machines in 192.0.2 as well as 10.0.0.1 +$AllowedSender TCP, 10.0.0.1 # for TCP, we allow only 10.0.0.1 +# remove the AllowedSender directives if you do not want to limit +# who can send rsyslogd messages (not recommended) + +# Templates are a key feature of rsyslog. They allow to specify any +# format a user might want. Every output in rsyslog uses templates - this +# holds true for files, user messages and so on. The database writer +# expects its template to be a proper SQL statement - so this is highly +# customizable too. You might ask how does all of this work when no templates +# at all are specified. Good question ;) The answer is simple, though. Templates +# compatible with the stock syslogd formats are hardcoded into rsyslog. So if +# no template is specified, we use one of these hardcoded templates. Search for +# "template_" in syslogd.c and you will find the hardcoded ones. +# +# A template consists of a template directive, a name, the actual template text +# and optional options. A sample is: +# +# $template MyTemplateName,"\7Text %property% some more text\n",<options> +# +# The "$template" is the template directive. It tells rsyslog that this +# line contains a template. +# +# "MyTemplateName" is the template name. All other config lines refer to +# this name. +# +# The text within quotes is the actual template text. The backslash is +# a escape character, much as in C. It does all these "cool" things. For +# example, \7 rings the bell (this is an ASCII value), \n is a new line. +# C programmers and perl coders have the advantage of knowing this, but the +# set in rsyslog is a bit restricted currently. All text in the template +# is used literally, except for things within percent signs. These are +# properties and allow you access to the contents of the syslog message. +# Properties are accessed via the property replacer (nice name, huh) and +# it can do cool things, too. For example, it can pick a substring or +# do date-specific formatting. More on this is below, on some lines of the +# property replacer. +# +# The <options> part is optional. It carries options that influence the +# template as whole. Details are below. Be sure NOT to mistake template +# options with property options - the later ones are processed by the +# property replacer and apply to a SINGLE property, only (and not the +# whole template). +# +# Template options are case-insensitive. Currently defined are: +# sql - format the string suitable for a SQL statement. This will replace single +# quotes ("'") by two single quotes ("''") inside each field. This option MUST +# be specified when a template is used for writing to a database, otherwise SQL +# injection might occur. +# +# Please note that the database writer *checks* that the sql option is +# present in the template. If it is not present, the write database action +# is disabled. This is to guard you against accidential forgetting it and +# then becoming vulnerable for SQL injection. +# The sql option can also be useful with files - especially if you want +# to run them on another machine for performance reasons. However, do NOT +# use it if you do not have a real need for it - among others, it takes +# some toll on the processing time. Not much, but on a really busy system +# you might notice it ;) +# +# To escape: +# % = \% +# \ = \\ +# --> '\' is used to escape (as in C) +#$template TraditionalFormat,%timegenerated% %HOSTNAME% %syslogtag%%msg%\n" +# +# Properties can be accessed by the property replacer. They are accessed +# inside the template by putting them between percent signs. Properties +# can be modifed by the property replacer. The full syntax is as follows: +# +# %propname:fromChar:toChar:options% +# +# propname is the name of the property to access. This IS case-sensitive! +# Currently supported are: +# msg the MSG part of the message (aka "the message" ;)) +# rawmsg the message excactly as it was received from the +# socket. Should be useful for debugging. +# UxTradMsg will disappear soon - do NOT use! +# HOSTNAME hostname from the message +# source alias for HOSTNAME +# syslogtag TAG from the message +# PRI PRI part of the message - undecoded (single value) +# IUT the monitorware InfoUnitType - used when talking to a +# MonitorWare backend (also for phpLogCon) +# syslogfacility the facility from the message - in numerical form +# syslogpriority the priority (actully severity!) from the +# message - in numerical form +# timegenerated timestamp when the message was RECEIVED. Always in high +# resolution +# timereported timestamp from the message. Resolution depends on what +# was provided in the message (in most cases, only seconds) +# TIMESTAMP alias for timereported +# +# Other properties might be available at the time you read this. Be sure +# to consult the property replacer documentation in the doc set for all +# properties. +# +# FromChar and toChar are used to build substrings. They specify the +# offset within the string that should be copied. Offset counting +# starts at 1, so if you need to obtain the first 2 characters of the +# message text, you can use this syntax: "%msg:1:2%". +# If you do not whish to specify from and to, but you want to +# specify options, you still need to include the colons. For example, +# if you would like to convert the full message text to lower case +# only, use "%msg:::lowercase%". +# +# property options are case-insensitive, currently defined are: +# uppercase convert property to lowercase only +# lowercase convert property text to uppercase only +# drop-last-lf The last LF in the message (if any), is dropped. +# Especially useful for PIX. +# date-mysql format as mysql date +# date-rfc3164 format as RFC 3164 date +# date-rfc3339 format as RFC 3339 date +# escape-cc NOT yet implemented + +# Below find some samples of what a template can do. Have a good +# time finding out what they do [or just tun them] ;) + +# A template that resambles traditional syslogd file output: +$template TraditionalFormat,"%timegenerated% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n" + +# a template useful for debugging format issues +$template DEBUG,"Debug line with all properties:\nFROMHOST: '%FROMHOST%', HOSTNAME: '%HOSTNAME%', PRI: %PRI%,\nsyslogtag '%syslogtag%', programname: '%programname%', APP-NAME: '%APP-NAME%', PROCID: '%PROCID%', MSGID: '%MSGID%',\nTIMESTAMP: '%TIMESTAMP%', STRUCTURED-DATA: '%STRUCTURED-DATA%',\nmsg: '%msg%'\nescaped msg: '%msg:::drop-cc%'\nrawmsg: '%rawmsg%'\n\n" +# +# A template that resembles RFC 3164 on-the-wire format: +# (yes, there is NO space betwen syslogtag and msg! that's important!) +$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%" + +# a template resembling traditional wallmessage format: +$template wallmsg,"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r" + +# The template below emulates winsyslog format, but we need to check the time +# stamps used. for now, it is good enough ;) This format works best with +# other members of the MonitorWare product family. It is also a good sample +# where you can see the property replacer in action. +$template WinSyslogFmt,"%HOSTNAME%,%timegenerated:1:10:date-rfc3339%,%timegenerated:12:19:date-rfc3339%,%timegenerated:1:10:date-rfc3339%,%timegenerated:12:19:date-rfc3339%,%syslogfacility%,%syslogpriority%,%syslogtag%%msg%\n" + +# A template used for database writing (notice it *is* an actual +# sql-statement): +$template dbFormat,"insert into SystemEvents (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%',%syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')",sql + +# Selector lines are somewhat different from stock syslogd. With +# rsyslog, you can add a semicolon ";" after the target and then +# the template name. That will assign this template to the respective +# action. If no template name is given, a hardcoded template is used. +# If a template name is given, but the template was not defined, the +# selector line is DEACTIVATED. +# +# ############# +# # IMPORTANT # +# ############# +# Templates MUST be defined BEFORE they are used! It is OK to +# intermix template definitions and selector lines within the +# config file, but each template MUST be defined before it is +# used the first time! +# + +# We have some very rough samples here - This sample file focusses +# on the new syntax, so we do NOT describe all possible selections. +# Use the syslog.conf if you are interested to see how to select +# based on facility and severits (aka priority). + +*.* /var/log/winsyslog-like.log;WinSyslogFmt + +# A selector using the traditional format defined above: +*.* /var/log/traditionalfile.log;TraditionalFormat + +# And another one using the hardcoded traditional format: +*.* /var/log/anothertraditionalfile.log + +# Templates are also fully supportd for forwarding: +*.* @172.19.2.16;RFC3164fmt + +# And this finally is a database action +# The semicolon at the end is not necessary, +# but some previous versions of rsyslogd had a bug that +# made them abort if it were missing. As Dennis Olvany +# pointed out, it would be extremely nice to have this +# semicolon in the sample conf - so we did in a previous +# version and it still sticks around ;) +*.* >hostname,dbname,userid,password; +# It uses the default schema (MonitorWare format). The parameters +# should be self-explanatory. + +# And this one uses the template defined above: +*.* >hostname,dbname,userid,password;dbFormat + + +# +# Rsyslog supports TCP-based syslog. To enable receiving TCP messages, +# use the -t <port> command line option (where port is the port it +# shall listen to. To forward messages to the remote host, you must +# specify a forwarding action and include the host and port. TCP +# and UDP-based forwarding has basically the same syntax, except that +# TCP delivery is triggered by specifying a second at-sign (@) in the +# message. +# This is UDP forwarding to port 514: +*.* @172.19.2.16 +# This is UDP forwarding to port 1514: +*.* @172.19.2.16:1514 +# This is TCP forwarding to port 1514: +*.* @@172.19.2.16:1514 +# The second @-sign is all you need (except, of course, a tcp-capable +# syslogd like rsyslogd ;)). +# Of course, you can also specify a template with TCP: +*.* @@172.19.2.16:1514;RFC3164Fmt +# There are also some options you can select. These come between +# paranthesis. Available are: +# z<number> - turn on compression, number is compression mode 0 - none, 9 max +# o - (tcp only) use octet counting for framing EXPERIMENTAL +# +# Forward via TCP with maximum compression and octet couting framing: +*.* @@(z9,o)172.19.2.16:1514;RFC3164Fmt +# Forward via UDP with maximum compression to port 1514 +*.* @(z9)172.19.2.16:1514 + +# We also support property-based filters, which allow for nice +# things. Let's for example assume that you receive a lot of +# nonsense messages with "ID-4711" in the message text. You know +# that you will never need these messages. So you simply discard them +:msg, contains, "ID-4711" ~ + +# or you would like to store messages from a specific host to +# a different file: +:FROMHOST, isequal,"myhost.example.com" /var/log/myhost.log + +# everyting that does not contain "error" should also be +# discarded +:msg, !contains, "error" ~ +# and the rest go to a seperate file +*.* /var/log/error +# (keep in mind that the two directives shown immediately +# above must be kept in that order to actually work) + +# you can also execute a script. Let's assume, for example, you need +# to execute "turn-diesel-generator-on" when "power failed" is contained +# in a message... ;) +:msg, contains, "power failed" ^turn-diesel-generator-on +# (The script is passed the syslog message as first and only parameter. +# Other parameters can currently not be specified.) + +# Note that boolean operations (other than not [!]) are not +# currently supported. As such, you can not filter out different +# facilities from different machines - hopefully later ;) + +# +# A final world. rsyslog is considered a part of Adiscon's MonitorWare product line. +# As such, you can find current information as well as information on the +# other product line members on http://www.monitorware.com. Please be warned, there +# are a number of closed-source commercial Windows applications among these products ;) +# +# You might want to check the GPL'ed phpLogCon (http://www.phplogcon.org) +# as a web-based front-end to a syslog message database. +# +# I hope this work is useful. +# 2005-09-27 Rainer Gerhards <rgerhards@adiscon.com> +# diff --git a/solaris/README b/solaris/README new file mode 100644 index 00000000..3f88431d --- /dev/null +++ b/solaris/README @@ -0,0 +1,38 @@ +Notes for Solaris + +Rsyslog will be fully supported on Solaris in the future. To build it, the GNU build +tools (and most of the GNU environment) is needed. This software can be +found at the excellent http://www.blastwave.org site. + +PREQUISITES +It is strongly recommended to use GCC4 with support for +atomic instructions (if available for the platform). While rsyslog can +be built without atomic instructin support (and will work well then), +it then falls back to POSIX semaphores, which require much more CPU +time than atomic instructions. Note that even on intel platforms the +(current, as of 2010-03-25) blastwave gcc4 version targets too-old +processors by default. To change that, use "-imarch=I686" in your +CFLAGS. + +CONFIGURE OPTIONS +A number of GNU tools are renamed g* so that they not conflict with +the native Solaris tools. As we need the GNU replacements, this +must be specified on the ./configure line. +Also, we must tell the linker where to find the glibc library when +building the plugins. This is done via the LDFLAGS variable as +shown below (based on the good information availabe at +http://prefetch.net/articles/linkers.badldlibrary.html + +The working sample configure sequence I use is: + +export LDFLAGS="-R/opt/csw/gcc4/lib" +./configure AR=gar ...other options... + +As a "quick and dirty" fix, one may set the following library +path before executing rsyslog (may be useful to avoid recompile): + +export LD_LIBRARY_PATH=/opt/csw/gcc4/lib + +NOT YET SUPPORTED +* local log socket +* kernel log diff --git a/solaris/cddllicense.txt b/solaris/cddllicense.txt new file mode 100644 index 00000000..a10bba27 --- /dev/null +++ b/solaris/cddllicense.txt @@ -0,0 +1,242 @@ + COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)
+ Version 1.0
+
+
+1. Definitions.
+
+ 1.1. “Contributor” means each individual or entity that creates or contributes to the creation of Modifications.
+
+ 1.2. “Contributor Version” means the combination of the Original Software, prior Modifications used by a Contributor
+ (if any), and the Modifications made by that particular Contributor.
+
+ 1.3. “Covered Software” means (a) the Original Software, or (b) Modifications, or (c) the combination
+ of files containing Original Software with files containing Modifications, in each case including portions thereof.
+
+ 1.4. “Executable” means the Covered Software in any form other than Source Code.
+
+ 1.5. “Initial Developer” means the individual or entity that first makes Original Software available under this License.
+
+ 1.6. “Larger Work” means a work which combines Covered Software or portions thereof with code not governed by the terms
+ of this License.
+
+ 1.7. “License” means this document.
+
+ 1.8. “Licensable” means having the right to grant, to the maximum extent possible, whether at the time of the
+ initial grant or subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. “Modifications” means the Source Code and Executable form of any of the following:
+ A. Any file that results from an addition to, deletion from or modification of the contents of a file
+ containing Original Software or previous Modifications;
+ B. Any new file that contains any part of the Original Software or previous Modification; or
+ C. Any new file that is contributed or otherwise made available under the terms of this License.
+
+ 1.10. “Original Software” means the Source Code and Executable form of computer software code that is originally
+ released under this License.
+
+ 1.11. “Patent Claims” means any patent claim(s), now owned or hereafter acquired, including without limitation,
+ method, process, and apparatus claims, in any patent Licensable by grantor.
+
+ 1.12. “Source Code” means (a) the common form of computer software code in which modifications are made and
+ (b) associated documentation included in or with such code.
+
+ 1.13. “You” (or “Your”) means an individual or a legal entity exercising rights under, and complying with all of
+ the terms of, this License. For legal entities, “You” includes any entity which controls, is controlled by, or is
+ under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more
+ than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. License Grants.
+
+ 2.1. The Initial Developer Grant.
+ Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims,
+ the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer,
+ to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions
+ thereof), with or without Modifications, and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made,
+ use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof).
+
+ (c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first
+ distributes or otherwise makes the Original Software available to a third party under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from
+ the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software,
+ or (ii) the combination of the Original Software with other software or devices.
+
+ 2.2. Contributor Grant.
+ Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims,
+ each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use,
+ reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor
+ (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or
+ as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor
+ either alone and/or in combination with its Contributor Version (or portions of such combination), to make,
+ use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor
+ (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes
+ or otherwise makes the Modifications available to a third party.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has
+ deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of
+ Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software
+ (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered
+ Software in the absence of Modifications made by that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Availability of Source Code.
+ Any Covered Software that You distribute or otherwise make available in Executable form must also be made available
+ in Source Code form and that Source Code form must be distributed only under the terms of this License. You must
+ include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or
+ otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they
+ can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used
+ for software exchange.
+
+ 3.2. Modifications.
+ The Modifications that You create or to which You contribute are governed by the terms of this License. You
+ represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to
+ grant the rights conveyed by this License.
+
+ 3.3. Required Notices.
+ You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification.
+ You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or
+ any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer.
+
+ 3.4. Application of Additional Terms.
+ You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the
+ applicable version of this License or the recipients’ rights hereunder. You may choose to offer, and to charge a
+ fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software.
+ However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor.
+ You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered
+ by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability
+ incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability
+ terms You offer.
+
+ 3.5. Distribution of Executable Versions.
+ You may distribute the Executable form of the Covered Software under the terms of this License or under the terms
+ of a license of Your choice, which may contain terms different from this License, provided that You are in compliance
+ with the terms of this License and that the license for the Executable form does not attempt to limit or alter
+ the recipient’s rights in the Source Code form from the rights set forth in this License. If You distribute the
+ Covered Software in Executable form under a different license, You must make it absolutely clear that any terms
+ which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby
+ agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer
+ or such Contributor as a result of any such terms You offer.
+
+ 3.6. Larger Works.
+ You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License
+ and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this
+ License are fulfilled for the Covered Software.
+
+4. Versions of the License.
+
+ 4.1. New Versions.
+ Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License
+ from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3,
+ no one other than the license steward has the right to modify this License.
+
+ 4.2. Effect of New Versions.
+ You may always continue to use, distribute or otherwise make the Covered Software available under the terms of
+ the version of the License under which You originally received the Covered Software. If the Initial Developer
+ includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under
+ any subsequent version of the License, You must distribute and make the Covered Software available under the terms
+ of the version of the License under which You originally received the Covered Software. Otherwise, You may also
+ choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version
+ of the License published by the license steward.
+
+ 4.3. Modified Versions.
+ When You are an Initial Developer and You want to create a new license for Your Original Software, You may create
+ and use a modified version of this License if You: (a) rename the license and remove any references to the name of
+ the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that
+ the license contains terms which differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN “AS IS” BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A
+PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU.
+SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+6. TERMINATION.
+
+ 6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms
+ herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License shall survive.
+
+ 6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as
+ “Participant”) alleging that the Participant Software (meaning the Contributor Version where the Participant
+ is a Contributor or the Original Software where the Participant is the Initial Developer) directly or
+ indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant,
+ the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1
+ and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically
+ at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with
+ respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement
+ with Participant.
+
+ 6.3. If You assert a patent infringement claim against Participant alleging that the Participant Software directly
+ or indirectly infringes any patent where such claim is resolved (such as by license or settlement) prior to the
+ initiation of patent infringement litigation, then the reasonable value of the licenses granted by such Participant
+ under Sections 2.1 or 2.2 shall be taken into account in determining the amount or value of any payment or license.
+
+ 6.4. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted
+ by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor)
+ shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
+INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR
+LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY
+SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
+INJURY RESULTING FROM SUCH PARTY’S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+The Covered Software is a “commercial item,” as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of
+“commercial computer software” (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1)) and “commercial computer software documentation”
+as such terms are used in 48 C.F.R. 12.212 Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of,
+and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License.
+
+9. MISCELLANEOUS.
+
+This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed
+by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any,
+provides otherwise), excluding such jurisdiction’s conflict-of-law provisions. Any litigation relating to this License shall be subject
+to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the
+losing party responsible for costs, including, without limitation, court costs and reasonable attorneys’ fees and expenses. The application of
+the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that
+the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for
+compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use,
+distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of
+its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on
+an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. + +-------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION +LICENSE (CDDL) + +The OpenSolaris code released under the CDDL shall be governed by the laws +of the State of California (excluding conflict-of-law provisions). Any +litigation relating to this License shall be subject to the jurisdiction of +the Federal Courts of the Northern District of California and the state +courts of the State of California, with venue lying in Santa Clara County, +California. + diff --git a/syslog-tst.conf b/syslog-tst.conf new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/syslog-tst.conf diff --git a/tcpclt.c b/tcpclt.c new file mode 100644 index 00000000..af3dcf23 --- /dev/null +++ b/tcpclt.c @@ -0,0 +1,515 @@ +/* tcpclt.c + * + * This is the implementation of TCP-based syslog clients (the counterpart + * of the tcpsrv class). + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "dirty.h" +#include "syslogd-types.h" +#include "net.h" +#include "tcpclt.h" +#include "module-template.h" +#include "srUtils.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers + +/* Initialize TCP sockets (for sender) + */ +static int +CreateSocket(struct addrinfo *addrDest) +{ + int fd; + struct addrinfo *r; + + r = addrDest; + + while(r != NULL) { + fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (fd != -1) { + /* We can not allow the TCP sender to block syslogd, at least + * not in a single-threaded design. That would cause rsyslogd to + * loose input messages - which obviously also would affect + * other selector lines, too. So we do set it to non-blocking and + * handle the situation ourselfs (by discarding messages). IF we run + * dual-threaded, however, the situation is different: in this case, + * the receivers and the selector line processing are only loosely + * coupled via a memory buffer. Now, I think, we can afford the extra + * wait time. Thus, we enable blocking mode for TCP if we compile with + * pthreads. -- rgerhards, 2005-10-25 + * And now, we always run on multiple threads... -- rgerhards, 2007-12-20 + */ + if (connect (fd, r->ai_addr, r->ai_addrlen) != 0) { + if(errno == EINPROGRESS) { + /* this is normal - will complete later select */ + return fd; + } else { + char errStr[1024]; + dbgprintf("create tcp connection failed, reason %s", + rs_strerror_r(errno, errStr, sizeof(errStr))); + } + + } + else { + return fd; + } + close(fd); + } + else { + char errStr[1024]; + dbgprintf("couldn't create send socket, reason %s", rs_strerror_r(errno, errStr, sizeof(errStr))); + } + r = r->ai_next; + } + + dbgprintf("no working socket could be obtained"); + + return -1; +} + + + +/* Build frame based on selected framing + * This function was created by pulling code from TCPSend() + * on 2007-12-27 by rgerhards. Older comments are still relevant. + * + * In order to support compressed messages via TCP, we must support an + * octet-counting based framing (LF may be part of the compressed message). + * We are now supporting the same mode that is available in IETF I-D + * syslog-transport-tls-05 (current at the time of this writing). This also + * eases things when we go ahead and implement that framing. I have now made + * available two cases where this framing is used: either by explitely + * specifying it in the config file or implicitely when sending a compressed + * message. In the later case, compressed and uncompressed messages within + * the same session have different framings. If it is explicitely set to + * octet-counting, only this framing mode is used within the session. + * rgerhards, 2006-12-07 + */ +static rsRetVal +TCPSendBldFrame(tcpclt_t *pThis, char **pmsg, size_t *plen, int *pbMustBeFreed) +{ + DEFiRet; + TCPFRAMINGMODE framingToUse; + int bIsCompressed; + size_t len; + char *msg; + char *buf = NULL; /* if this is non-NULL, it MUST be freed before return! */ + + assert(plen != NULL); + assert(pbMustBeFreed != NULL); + assert(pmsg != NULL); + + msg = *pmsg; + len = *plen; + bIsCompressed = *msg == 'z'; /* cache this, so that we can modify the message buffer */ + /* select framing for this record. If we have a compressed record, we always need to + * use octet counting because the data potentially contains all control characters + * including LF. + */ + framingToUse = bIsCompressed ? TCP_FRAMING_OCTET_COUNTING : pThis->tcp_framing; + + /* now check if we need to add a line terminator. We need to + * copy the string in memory in this case, this is probably + * quicker than using writev and definitely quicker than doing + * two socket calls. + * rgerhards 2005-07-22 + * + * Some messages already contain a \n character at the end + * of the message. We append one only if we there is not + * already one. This seems the best fit, though this also + * means the message does not arrive unaltered at the final + * destination. But in the spirit of legacy syslog, this is + * probably the best to do... + * rgerhards 2005-07-20 + */ + + /* Build frame based on selected framing */ + if(framingToUse == TCP_FRAMING_OCTET_STUFFING) { + if((*(msg+len-1) != '\n')) { + /* in the malloc below, we need to add 2 to the length. The + * reason is that we a) add one character and b) len does + * not take care of the '\0' byte. Up until today, it was just + * +1 , which caused rsyslogd to sometimes dump core. + * I have added this comment so that the logic is not accidently + * changed again. rgerhards, 2005-10-25 + */ + if((buf = MALLOC((len + 2) * sizeof(char))) == NULL) { + /* extreme mem shortage, try to solve + * as good as we can. No point in calling + * any alarms, they might as well run out + * of memory (the risk is very high, so we + * do NOT risk that). If we have a message of + * more than 1 byte (what I guess), we simply + * overwrite the last character. + * rgerhards 2005-07-22 + */ + if(len > 1) { + *(msg+len-1) = '\n'; + } else { + /* we simply can not do anything in + * this case (its an error anyhow...). + */ + } + } else { + /* we got memory, so we can copy the message */ + memcpy(buf, msg, len); /* do not copy '\0' */ + *(buf+len) = '\n'; + *(buf+len+1) = '\0'; + msg = buf; /* use new one */ + ++len; /* care for the \n */ + } + } + } else { + /* Octect-Counting + * In this case, we need to always allocate a buffer. This is because + * we need to put a header in front of the message text + */ + char szLenBuf[16]; + int iLenBuf; + + /* important: the printf-mask is "%d<sp>" because there must be a + * space after the len! + *//* The chairs of the IETF syslog-sec WG have announced that it is + * consensus to do the octet count on the SYSLOG-MSG part only. I am + * now changing the code to reflect this. Hopefully, it will not change + * once again (there can no compatibility layer programmed for this). + * To be on the save side, I just comment the code out. I mark these + * comments with "IETF20061218". + * rgerhards, 2006-12-19 + */ + iLenBuf = snprintf(szLenBuf, sizeof(szLenBuf)/sizeof(char), "%d ", (int) len); + /* IETF20061218 iLenBuf = + snprintf(szLenBuf, sizeof(szLenBuf)/sizeof(char), "%d ", len + iLenBuf);*/ + + if((buf = MALLOC((len + iLenBuf) * sizeof(char))) == NULL) { + /* we are out of memory. This is an extreme situation. We do not + * call any alarm handlers because they most likely run out of mem, + * too. We are brave enough to call debug output, though. Other than + * that, there is nothing left to do. We can not sent the message (as + * in case of the other framing, because the message is incomplete. + * We could, however, send two chunks (header and text separate), but + * that would cause a lot of complexity in the code. So we think it + * is appropriate enough to just make sure we do not crash in this + * very unlikely case. For this, it is justified just to loose + * the message. Rgerhards, 2006-12-07 + */ + dbgprintf("Error: out of memory when building TCP octet-counted " + "frame. Message is lost, trying to continue.\n"); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + memcpy(buf, szLenBuf, iLenBuf); /* header */ + memcpy(buf + iLenBuf, msg, len); /* message */ + len += iLenBuf; /* new message size */ + msg = buf; /* set message buffer */ + } + + /* frame building complete, on to actual sending */ + + *plen = len; + if(buf == NULL) { + /* msg not modified */ + *pbMustBeFreed = 0; + } else { + *pmsg = msg; + *pbMustBeFreed = 1; + } + +finalize_it: + RETiRet; +} + + +/* Sends a TCP message. It is first checked if the + * session is open and, if not, it is opened. Then the send + * is tried. If it fails, one silent re-try is made. If the send + * fails again, an error status (-1) is returned. If all goes well, + * 0 is returned. The TCP session is NOT torn down. + * For now, EAGAIN is ignored (causing message loss) - but it is + * hard to do something intelligent in this case. With this + * implementation here, we can not block and/or defer. Things are + * probably a bit better when we move to liblogging. The alternative + * would be to enhance the current select server with buffering and + * write descriptors. This seems not justified, given the expected + * short life span of this code (and the unlikeliness of this event). + * rgerhards 2005-07-06 + * This function is now expected to stay. Libloging won't be used for + * that purpose. I have added the param "len", because it is known by the + * caller and so saves us some time. Also, it MUST be given because there + * may be NULs inside msg so that we can not rely on strlen(). Please note + * that the restrictions outlined above do not existin in multi-threaded + * mode, which we assume will now be most often used. So there is no + * real issue with the potential message loss in single-threaded builds. + * rgerhards, 2006-11-30 + * I greatly restructured the function to be more generic and work + * with function pointers. So it now can be used with any type of transport, + * as long as it follows stream semantics. This was initially done to + * support plain TCP and GSS via common code. + */ +static int +Send(tcpclt_t *pThis, void *pData, char *msg, size_t len) +{ + DEFiRet; + int bDone = 0; + int retry = 0; + int bMsgMustBeFreed = 0;/* must msg be freed at end of function? 0 - no, 1 - yes */ + + ISOBJ_TYPE_assert(pThis, tcpclt); + assert(pData != NULL); + assert(msg != NULL); + assert(len > 0); + + CHKiRet(TCPSendBldFrame(pThis, &msg, &len, &bMsgMustBeFreed)); + + if(pThis->iRebindInterval > 0 && ++pThis->iNumMsgs == pThis->iRebindInterval) { + /* we need to rebind, and use the retry logic for this*/ + CHKiRet(pThis->prepRetryFunc(pData)); /* try to recover */ + pThis->iNumMsgs = 0; + } + + while(!bDone) { /* loop is broken when send succeeds or error occurs */ + CHKiRet(pThis->initFunc(pData)); + iRet = pThis->sendFunc(pData, msg, len); + + if(iRet == RS_RET_OK || iRet == RS_RET_DEFER_COMMIT || iRet == RS_RET_PREVIOUS_COMMITTED) { + /* we are done, we also use this as indication that the previous + * message was succesfully received (it's not always the case, but its at + * least our best shot at it -- rgerhards, 2008-03-12 + * As of 2008-06-09, we have implemented an algorithm which detects connection + * loss quite good in some (common) scenarios. Thus, the probability of + * message duplication due to the code below has increased. We so now have + * a config setting, default off, that enables the user to request retransmits. + * However, if not requested, we do NOT need to do all the stuff needed for it. + */ + if(pThis->bResendLastOnRecon == 1) { + if(pThis->prevMsg != NULL) + free(pThis->prevMsg); + /* if we can not alloc a new buffer, we silently ignore it. The worst that + * happens is that we lose our message recovery buffer - anything else would + * be worse, so don't try anything ;) -- rgerhards, 2008-03-12 + */ + if((pThis->prevMsg = MALLOC(len)) != NULL) { + memcpy(pThis->prevMsg, msg, len); + pThis->lenPrevMsg = len; + } + } + + /* we are done with this record */ + bDone = 1; + } else { + if(retry == 0) { /* OK, one retry */ + ++retry; + CHKiRet(pThis->prepRetryFunc(pData)); /* try to recover */ + /* now try to send our stored previous message (which most probably + * didn't make it. Note that if bResendLastOnRecon is 0, prevMsg will + * never become non-NULL, so the check below covers all cases. + */ + if(pThis->prevMsg != NULL) { + CHKiRet(pThis->initFunc(pData)); + CHKiRet(pThis->sendFunc(pData, pThis->prevMsg, pThis->lenPrevMsg)); + } + } else { + /* OK, max number of retries reached, nothing we can do */ + bDone = 1; + } + } + } + +finalize_it: + if(bMsgMustBeFreed) + free(msg); + RETiRet; +} + + +/* set functions */ +static rsRetVal +SetResendLastOnRecon(tcpclt_t *pThis, int bResendLastOnRecon) +{ + DEFiRet; + pThis->bResendLastOnRecon = (short) bResendLastOnRecon; + RETiRet; +} +static rsRetVal +SetSendInit(tcpclt_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->initFunc = pCB; + RETiRet; +} +static rsRetVal +SetSendPrepRetry(tcpclt_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->prepRetryFunc = pCB; + RETiRet; +} +static rsRetVal +SetSendFrame(tcpclt_t *pThis, rsRetVal (*pCB)(void*, char*, size_t)) +{ + DEFiRet; + pThis->sendFunc = pCB; + RETiRet; +} +static rsRetVal +SetFraming(tcpclt_t *pThis, TCPFRAMINGMODE framing) +{ + DEFiRet; + pThis->tcp_framing = framing; + RETiRet; +} +static rsRetVal +SetRebindInterval(tcpclt_t *pThis, int iRebindInterval) +{ + DEFiRet; + pThis->iRebindInterval = iRebindInterval; + RETiRet; +} + + +/* Standard-Constructor + */ +BEGINobjConstruct(tcpclt) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(tcpclt) + + +/* ConstructionFinalizer + */ +static rsRetVal +tcpcltConstructFinalize(tcpclt_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpclt); + + RETiRet; +} + + +/* destructor for the tcpclt object */ +BEGINobjDestruct(tcpclt) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(tcpclt) + if(pThis->prevMsg != NULL) + free(pThis->prevMsg); +ENDobjDestruct(tcpclt) + + +/* ------------------------------ handling the interface plumbing ------------------------------ */ + +/* queryInterface function + * rgerhards, 2008-03-12 + */ +BEGINobjQueryInterface(tcpclt) +CODESTARTobjQueryInterface(tcpclt) + if(pIf->ifVersion != tcpcltCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = tcpcltConstruct; + pIf->ConstructFinalize = tcpcltConstructFinalize; + pIf->Destruct = tcpcltDestruct; + + pIf->CreateSocket = CreateSocket; + pIf->Send = Send; + + /* set functions */ + pIf->SetResendLastOnRecon = SetResendLastOnRecon; + pIf->SetSendInit = SetSendInit; + pIf->SetSendFrame = SetSendFrame; + pIf->SetSendPrepRetry = SetSendPrepRetry; + pIf->SetFraming = SetFraming; + pIf->SetRebindInterval = SetRebindInterval; + +finalize_it: +ENDobjQueryInterface(tcpclt) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(tcpclt, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(tcpclt) + /* release objects we no longer need */ +ENDObjClassExit(tcpclt) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINObjClassInit(tcpclt, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcpcltConstructFinalize); +ENDObjClassInit(tcpclt) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + /* de-init in reverse order! */ + tcpcltClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(tcpcltClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit + + +/* + * vi:set ai: + */ diff --git a/tcpclt.h b/tcpclt.h new file mode 100644 index 00000000..bcca0abe --- /dev/null +++ b/tcpclt.h @@ -0,0 +1,73 @@ +/* tcpclt.h + * + * This are the definitions for the TCP based clients class. + * + * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TCPCLT_H_INCLUDED +#define TCPCLT_H_INCLUDED 1 + +#include "obj.h" + +/* the tcpclt object */ +typedef struct tcpclt_s { + BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */ + TCPFRAMINGMODE tcp_framing; + char *prevMsg; + short bResendLastOnRecon; /* should the last message be resent on a successful reconnect? */ + size_t lenPrevMsg; + /* session specific callbacks */ + int iRebindInterval; /* how often should the send socket be rebound? */ + int iNumMsgs; /* number of messages during current "rebind session" */ + rsRetVal (*initFunc)(void*); + rsRetVal (*sendFunc)(void*, char*, size_t); + rsRetVal (*prepRetryFunc)(void*); +} tcpclt_t; + + +/* interfaces */ +BEGINinterface(tcpclt) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(tcpclt_t **ppThis); + rsRetVal (*ConstructFinalize)(tcpclt_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(tcpclt_t **ppThis); + int (*Send)(tcpclt_t *pThis, void*pData, char*msg, size_t len); + int (*CreateSocket)(struct addrinfo *addrDest); + /* set methods */ + rsRetVal (*SetResendLastOnRecon)(tcpclt_t*, int); + rsRetVal (*SetSendInit)(tcpclt_t*, rsRetVal (*)(void*)); + rsRetVal (*SetSendFrame)(tcpclt_t*, rsRetVal (*)(void*, char*, size_t)); + rsRetVal (*SetSendPrepRetry)(tcpclt_t*, rsRetVal (*)(void*)); + rsRetVal (*SetFraming)(tcpclt_t*, TCPFRAMINGMODE framing); + /* v3, 2009-07-14*/ + rsRetVal (*SetRebindInterval)(tcpclt_t*, int iRebindInterval); +ENDinterface(tcpclt) +#define tcpcltCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(tcpclt); + +/* the name of our library binary */ +#define LM_TCPCLT_FILENAME "lmtcpclt" + +#endif /* #ifndef TCPCLT_H_INCLUDED */ +/* vim:set ai: + */ diff --git a/tcps_sess.c b/tcps_sess.c new file mode 100644 index 00000000..5821e443 --- /dev/null +++ b/tcps_sess.c @@ -0,0 +1,557 @@ +/* tcps_sess.c + * + * This implements a session of the tcpsrv object. For general + * comments, see header of tcpsrv.c. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2008-03-01 by RGerhards (extracted from tcpsrv.c, which + * based on the BSD-licensed syslogd.c) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> + +#include "rsyslog.h" +#include "dirty.h" +#include "unicode-helper.h" +#include "module-template.h" +#include "net.h" +#include "tcpsrv.h" +#include "tcps_sess.h" +#include "obj.h" +#include "errmsg.h" +#include "netstrm.h" +#include "msg.h" +#include "datetime.h" +#include "prop.h" +#include "ratelimit.h" +#include "debug.h" + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(prop) +DEFobjCurrIf(datetime) + +static int iMaxLine; /* maximum size of a single message */ + + +/* forward definitions */ +static rsRetVal Close(tcps_sess_t *pThis); + + +/* Standard-Constructor */ +BEGINobjConstruct(tcps_sess) /* be sure to specify the object type also in END macro! */ + pThis->iMsg = 0; /* just make sure... */ + pThis->bAtStrtOfFram = 1; /* indicate frame header expected */ + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; /* just make sure... */ + /* now allocate the message reception buffer */ + CHKmalloc(pThis->pMsg = (uchar*) MALLOC(sizeof(uchar) * iMaxLine + 1)); +finalize_it: +ENDobjConstruct(tcps_sess) + + +/* ConstructionFinalizer + */ +static rsRetVal +tcps_sessConstructFinalize(tcps_sess_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + if(pThis->pSrv->OnSessConstructFinalize != NULL) { + CHKiRet(pThis->pSrv->OnSessConstructFinalize(&pThis->pUsr)); + } + +finalize_it: + RETiRet; +} + + +/* destructor for the tcps_sess object */ +BEGINobjDestruct(tcps_sess) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(tcps_sess) +//printf("sess %p destruct, pStrm %p\n", pThis, pThis->pStrm); + if(pThis->pStrm != NULL) + netstrm.Destruct(&pThis->pStrm); + + if(pThis->pSrv->pOnSessDestruct != NULL) { + pThis->pSrv->pOnSessDestruct(&pThis->pUsr); + } + /* now destruct our own properties */ + if(pThis->fromHost != NULL) + CHKiRet(prop.Destruct(&pThis->fromHost)); + if(pThis->fromHostIP != NULL) + CHKiRet(prop.Destruct(&pThis->fromHostIP)); + free(pThis->pMsg); +ENDobjDestruct(tcps_sess) + + +/* debugprint for the tcps_sess object */ +BEGINobjDebugPrint(tcps_sess) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(tcps_sess) +ENDobjDebugPrint(tcps_sess) + + +/* set property functions */ +/* set the hostname. Note that the caller *hands over* the string. That is, + * the caller no longer controls it once SetHost() has received it. Most importantly, + * the caller must not free it. -- rgerhards, 2008-04-24 + */ +static rsRetVal +SetHost(tcps_sess_t *pThis, uchar *pszHost) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->fromHost == NULL) + CHKiRet(prop.Construct(&pThis->fromHost)); + + CHKiRet(prop.SetString(pThis->fromHost, pszHost, ustrlen(pszHost))); + +finalize_it: + free(pszHost); /* we must free according to our (old) calling conventions */ + RETiRet; +} + +/* set the remote host's IP. Note that the caller *hands over* the property. That is, + * the caller no longer controls it once SetHostIP() has received it. Most importantly, + * the caller must not destruct it. -- rgerhards, 2008-05-16 + */ +static rsRetVal +SetHostIP(tcps_sess_t *pThis, prop_t *ip) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->fromHostIP != NULL) { + prop.Destruct(&pThis->fromHostIP); + } + pThis->fromHostIP = ip; + RETiRet; +} + +static rsRetVal +SetStrm(tcps_sess_t *pThis, netstrm_t *pStrm) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + pThis->pStrm = pStrm; + RETiRet; +} + + +static rsRetVal +SetMsgIdx(tcps_sess_t *pThis, int idx) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + pThis->iMsg = idx; + RETiRet; +} + + +/* set our parent, the tcpsrv object */ +static rsRetVal +SetTcpsrv(tcps_sess_t *pThis, tcpsrv_t *pSrv) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + ISOBJ_TYPE_assert(pSrv, tcpsrv); + pThis->pSrv = pSrv; + RETiRet; +} + + +/* set our parent listener info*/ +static rsRetVal +SetLstnInfo(tcps_sess_t *pThis, tcpLstnPortList_t *pLstnInfo) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + assert(pLstnInfo != NULL); + pThis->pLstnInfo = pLstnInfo; + /* set cached elements */ + pThis->bSuppOctetFram = pLstnInfo->bSuppOctetFram; + RETiRet; +} + + +static rsRetVal +SetUsrP(tcps_sess_t *pThis, void *pUsr) +{ + DEFiRet; + pThis->pUsr = pUsr; + RETiRet; +} + + +static rsRetVal +SetOnMsgReceive(tcps_sess_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int)) +{ + DEFiRet; + pThis->DoSubmitMessage = OnMsgReceive; + RETiRet; +} + + +/* This is a helper for submitting the message to the rsyslog core. + * It does some common processing, including resetting the various + * state variables to a "processed" state. + * Note that this function is also called if we had a buffer overflow + * due to a too-long message. So far, there is no indication this + * happened and it may be worth thinking about different handling + * of this case (what obviously would require a change to this + * function or some related code). + * rgerhards, 2009-04-23 + */ +static rsRetVal +defaultDoSubmitMessage(tcps_sess_t *pThis, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub) +{ + msg_t *pMsg; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->iMsg == 0) { + DBGPRINTF("discarding zero-sized message\n"); + FINALIZE; + } + + if(pThis->DoSubmitMessage != NULL) { + pThis->DoSubmitMessage(pThis, pThis->pMsg, pThis->iMsg); + FINALIZE; + } + + /* we now create our own message object and submit it to the queue */ + CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime)); + MsgSetRawMsg(pMsg, (char*)pThis->pMsg, pThis->iMsg); + MsgSetInputName(pMsg, pThis->pLstnInfo->pInputName); + MsgSetFlowControlType(pMsg, pThis->pSrv->bUseFlowControl + ? eFLOWCTL_LIGHT_DELAY : eFLOWCTL_NO_DELAY); + pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME; + MsgSetRcvFrom(pMsg, pThis->fromHost); + CHKiRet(MsgSetRcvFromIP(pMsg, pThis->fromHostIP)); + MsgSetRuleset(pMsg, pThis->pLstnInfo->pRuleset); + + STATSCOUNTER_INC(pThis->pLstnInfo->ctrSubmit, pThis->pLstnInfo->mutCtrSubmit); + ratelimitAddMsg(pThis->pLstnInfo->ratelimiter, pMultiSub, pMsg); + +finalize_it: + /* reset status variables */ + pThis->bAtStrtOfFram = 1; + pThis->iMsg = 0; + + RETiRet; +} + + + +/* This should be called before a normal (non forced) close + * of a TCP session. This function checks if there is any unprocessed + * message left in the TCP stream. Such a message is probably a + * fragement. If evrything goes well, we must be right at the + * beginnig of a new frame without any data received from it. If + * not, there is some kind of a framing error. I think I remember that + * some legacy syslog/TCP implementations have non-LF terminated + * messages at the end of the stream. For now, we allow this behaviour. + * Later, it should probably become a configuration option. + * rgerhards, 2006-12-07 + */ +static rsRetVal +PrepareClose(tcps_sess_t *pThis) +{ + struct syslogTime stTime; + time_t ttGenTime; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->bAtStrtOfFram == 1) { + /* this is how it should be. There is no unprocessed + * data left and such we have nothing to do. For simplicity + * reasons, we immediately return in that case. + */ + FINALIZE; + } + + /* we have some data left! */ + if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) { + /* In this case, we have an invalid frame count and thus + * generate an error message and discard the frame. + */ + errmsg.LogError(0, NO_ERRCODE, "Incomplete frame at end of stream in session %p - " + "ignoring extra data (a message may be lost).\n", pThis->pStrm); + /* nothing more to do */ + } else { /* here, we have traditional framing. Missing LF at the end + * of message may occur. As such, we process the message in + * this case. + */ + DBGPRINTF("Extra data at end of stream in legacy syslog/tcp message - processing\n"); + datetime.getCurrTime(&stTime, &ttGenTime); + defaultDoSubmitMessage(pThis, &stTime, ttGenTime, NULL); + } + +finalize_it: + RETiRet; +} + + +/* Closes a TCP session + * No attention is paid to the return code + * of close, so potential-double closes are not detected. + */ +static rsRetVal +Close(tcps_sess_t *pThis) +{ + DEFiRet; + +//printf("sess %p close\n", pThis); + ISOBJ_TYPE_assert(pThis, tcps_sess); + netstrm.Destruct(&pThis->pStrm); + if(pThis->fromHost != NULL) { + prop.Destruct(&pThis->fromHost); + } + if(pThis->fromHostIP != NULL) + prop.Destruct(&pThis->fromHostIP); + + RETiRet; +} + + +/* process the data received. As TCP is stream based, we need to process the + * data inside a state machine. The actual data received is passed in byte-by-byte + * from DataRcvd, and this function here compiles messages from them and submits + * the end result to the queue. Introducing this function fixes a long-term bug ;) + * rgerhards, 2008-03-14 + */ +static rsRetVal +processDataRcvd(tcps_sess_t *pThis, char c, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->inputState == eAtStrtFram) { + if(pThis->bSuppOctetFram && c >= '0' && c <= '9') { + pThis->inputState = eInOctetCnt; + pThis->iOctetsRemain = 0; + pThis->eFraming = TCP_FRAMING_OCTET_COUNTING; + } else { + pThis->inputState = eInMsg; + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; + } + } + + if(pThis->inputState == eInOctetCnt) { + if(c >= '0' && c <= '9') { /* isdigit() the faster way */ + pThis->iOctetsRemain = pThis->iOctetsRemain * 10 + c - '0'; + } else { /* done with the octet count, so this must be the SP terminator */ + DBGPRINTF("TCP Message with octet-counter, size %d.\n", pThis->iOctetsRemain); + if(c != ' ') { + errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: " + "delimiter is not SP but has ASCII value %d.\n", c); + } + if(pThis->iOctetsRemain < 1) { + /* TODO: handle the case where the octet count is 0! */ + DBGPRINTF("Framing Error: invalid octet count\n"); + errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: " + "invalid octet count %d.\n", pThis->iOctetsRemain); + } else if(pThis->iOctetsRemain > iMaxLine) { + /* while we can not do anything against it, we can at least log an indication + * that something went wrong) -- rgerhards, 2008-03-14 + */ + DBGPRINTF("truncating message with %d octets - max msg size is %d\n", + pThis->iOctetsRemain, iMaxLine); + errmsg.LogError(0, NO_ERRCODE, "received oversize message: size is %d bytes, " + "max msg size is %d, truncating...\n", pThis->iOctetsRemain, iMaxLine); + } + pThis->inputState = eInMsg; + } + } else { + assert(pThis->inputState == eInMsg); + if(pThis->iMsg >= iMaxLine) { + /* emergency, we now need to flush, no matter if we are at end of message or not... */ + DBGPRINTF("error: message received is larger than max msg size, we split it\n"); + defaultDoSubmitMessage(pThis, stTime, ttGenTime, pMultiSub); + /* we might think if it is better to ignore the rest of the + * message than to treat it as a new one. Maybe this is a good + * candidate for a configuration parameter... + * rgerhards, 2006-12-04 + */ + } + + if(( ((c == '\n') && !pThis->pSrv->bDisableLFDelim) + || ((pThis->pSrv->addtlFrameDelim != TCPSRV_NO_ADDTL_DELIMITER) && (c == pThis->pSrv->addtlFrameDelim)) + ) && pThis->eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delimiter? */ + defaultDoSubmitMessage(pThis, stTime, ttGenTime, pMultiSub); + pThis->inputState = eAtStrtFram; + } else { + /* IMPORTANT: here we copy the actual frame content to the message - for BOTH framing modes! + * If we have a message that is larger than the max msg size, we truncate it. This is the best + * we can do in light of what the engine supports. -- rgerhards, 2008-03-14 + */ + if(pThis->iMsg < iMaxLine) { + *(pThis->pMsg + pThis->iMsg++) = c; + } + } + + if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) { + /* do we need to find end-of-frame via octet counting? */ + pThis->iOctetsRemain--; + if(pThis->iOctetsRemain < 1) { + /* we have end of frame! */ + defaultDoSubmitMessage(pThis, stTime, ttGenTime, pMultiSub); + pThis->inputState = eAtStrtFram; + } + } + } + + RETiRet; +} + + +/* Processes the data received via a TCP session. If there + * is no other way to handle it, data is discarded. + * Input parameter data is the data received, iLen is its + * len as returned from recv(). iLen must be 1 or more (that + * is errors must be handled by caller!). iTCPSess must be + * the index of the TCP session that received the data. + * rgerhards 2005-07-04 + * And another change while generalizing. We now return either + * RS_RET_OK, which means the session should be kept open + * or anything else, which means it must be closed. + * rgerhards, 2008-03-01 + * As a performance optimization, we pick up the timestamp here. Acutally, + * this *is* the *correct* reception step for all the data we received, because + * we have just received a bunch of data! -- rgerhards, 2009-06-16 + */ +#define NUM_MULTISUB 1024 +static rsRetVal +DataRcvd(tcps_sess_t *pThis, char *pData, size_t iLen) +{ + multi_submit_t multiSub; + msg_t *pMsgs[NUM_MULTISUB]; + struct syslogTime stTime; + time_t ttGenTime; + char *pEnd; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + assert(pData != NULL); + assert(iLen > 0); + + datetime.getCurrTime(&stTime, &ttGenTime); + multiSub.ppMsgs = pMsgs; + multiSub.maxElem = NUM_MULTISUB; + multiSub.nElem = 0; + + /* We now copy the message to the session buffer. */ + pEnd = pData + iLen; /* this is one off, which is intensional */ + + while(pData < pEnd) { + CHKiRet(processDataRcvd(pThis, *pData++, &stTime, ttGenTime, &multiSub)); + } + iRet = multiSubmitFlush(&multiSub); + +finalize_it: + RETiRet; +} +#undef NUM_MULTISUB + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(tcps_sess) +CODESTARTobjQueryInterface(tcps_sess) + if(pIf->ifVersion != tcps_sessCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->DebugPrint = tcps_sessDebugPrint; + pIf->Construct = tcps_sessConstruct; + pIf->ConstructFinalize = tcps_sessConstructFinalize; + pIf->Destruct = tcps_sessDestruct; + + pIf->PrepareClose = PrepareClose; + pIf->Close = Close; + pIf->DataRcvd = DataRcvd; + + pIf->SetUsrP = SetUsrP; + pIf->SetTcpsrv = SetTcpsrv; + pIf->SetLstnInfo = SetLstnInfo; + pIf->SetHost = SetHost; + pIf->SetHostIP = SetHostIP; + pIf->SetStrm = SetStrm; + pIf->SetMsgIdx = SetMsgIdx; + pIf->SetOnMsgReceive = SetOnMsgReceive; +finalize_it: +ENDobjQueryInterface(tcps_sess) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(tcps_sess, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(tcps_sess) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(datetime, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); +ENDObjClassExit(tcps_sess) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINObjClassInit(tcps_sess, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + CHKiRet(objUse(glbl, CORE_COMPONENT)); + iMaxLine = glbl.GetMaxLine(); /* get maximum size we currently support */ + objRelease(glbl, CORE_COMPONENT); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, tcps_sessDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcps_sessConstructFinalize); +ENDObjClassInit(tcps_sess) + +/* vim:set ai: + */ diff --git a/tcps_sess.h b/tcps_sess.h new file mode 100644 index 00000000..4506cf07 --- /dev/null +++ b/tcps_sess.h @@ -0,0 +1,88 @@ +/* Definitions for tcps_sess class. This implements a session of the + * plain TCP server. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_TCPS_SESS_H +#define INCLUDED_TCPS_SESS_H + +#include "obj.h" +#include "prop.h" + +/* a forward-definition, we are somewhat cyclic */ +struct tcpsrv_s; + +/* the tcps_sess object */ +struct tcps_sess_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + tcpsrv_t *pSrv; /* pointer back to my server (e.g. for callbacks) */ + tcpLstnPortList_t *pLstnInfo; /* pointer back to listener info */ + netstrm_t *pStrm; + int iMsg; /* index of next char to store in msg */ + sbool bAtStrtOfFram; /* are we at the very beginning of a new frame? */ + sbool bSuppOctetFram; /**< copy from listener, to speed up access */ + enum { + eAtStrtFram, + eInOctetCnt, + eInMsg + } inputState; /* our current state */ + int iOctetsRemain; /* Number of Octets remaining in message */ + TCPFRAMINGMODE eFraming; + uchar *pMsg; /* message (fragment) received */ + prop_t *fromHost; /* host name we received messages from */ + prop_t *fromHostIP; + void *pUsr; /* a user-pointer */ + rsRetVal (*DoSubmitMessage)(tcps_sess_t*, uchar*, int); /* submit message callback */ +}; + + +/* interfaces */ +BEGINinterface(tcps_sess) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(tcps_sess); + rsRetVal (*Construct)(tcps_sess_t **ppThis); + rsRetVal (*ConstructFinalize)(tcps_sess_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(tcps_sess_t **ppThis); + rsRetVal (*PrepareClose)(tcps_sess_t *pThis); + rsRetVal (*Close)(tcps_sess_t *pThis); + rsRetVal (*DataRcvd)(tcps_sess_t *pThis, char *pData, size_t iLen); + /* set methods */ + rsRetVal (*SetTcpsrv)(tcps_sess_t *pThis, struct tcpsrv_s *pSrv); + rsRetVal (*SetLstnInfo)(tcps_sess_t *pThis, tcpLstnPortList_t *pLstnInfo); + rsRetVal (*SetUsrP)(tcps_sess_t*, void*); + rsRetVal (*SetHost)(tcps_sess_t *pThis, uchar*); + rsRetVal (*SetHostIP)(tcps_sess_t *pThis, prop_t*); + rsRetVal (*SetStrm)(tcps_sess_t *pThis, netstrm_t*); + rsRetVal (*SetMsgIdx)(tcps_sess_t *pThis, int); + rsRetVal (*SetOnMsgReceive)(tcps_sess_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int)); +ENDinterface(tcps_sess) +#define tcps_sessCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ +/* interface changes + * to version v2, rgerhards, 2009-05-22 + * - Data structures changed + * - SetLstnInfo entry point added + * version 3, rgerhards, 2013-01-21: + * - signature of SetHostIP() changed + */ + + +/* prototypes */ +PROTOTYPEObj(tcps_sess); + + +#endif /* #ifndef INCLUDED_TCPS_SESS_H */ diff --git a/tcpsrv.c b/tcpsrv.c new file mode 100644 index 00000000..c1033ef9 --- /dev/null +++ b/tcpsrv.c @@ -0,0 +1,1433 @@ +/* tcpsrv.c + * + * Common code for plain TCP syslog based servers. This is currently being + * utilized by imtcp and imgssapi. + * + * NOTE: this is *not* a generic TCP server, but one for syslog servers. For + * generic stream servers, please use ./runtime/strmsrv.c! + * + * There are actually two classes within the tcpserver code: one is + * the tcpsrv itself, the other one is its sessions. This is a helper + * class to tcpsrv. + * + * The common code here calls upon specific functionality by using + * callbacks. The specialised input modules need to set the proper + * callbacks before the code is run. The tcpsrv then calls back + * into the specific input modules at the appropriate time. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-12-21 by RGerhards (extracted from syslogd.c[which was + * licensed under BSD at the time of the rsyslog fork]) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <pthread.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "srUtils.h" +#include "conf.h" +#include "tcpsrv.h" +#include "obj.h" +#include "glbl.h" +#include "netstrms.h" +#include "netstrm.h" +#include "nssel.h" +#include "nspoll.h" +#include "errmsg.h" +#include "ruleset.h" +#include "ratelimit.h" +#include "unicode-helper.h" + + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* defines */ +#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */ +#define TCPLSTN_MAX_DEFAULT 20 /* default for nbr of listeners */ + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(conf) +DEFobjCurrIf(glbl) +DEFobjCurrIf(ruleset) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrms) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(nssel) +DEFobjCurrIf(nspoll) +DEFobjCurrIf(prop) +DEFobjCurrIf(statsobj) + +static void startWorkerPool(void); + +/* The following structure controls the worker threads. Global data is + * needed for their access. + */ +static struct wrkrInfo_s { + pthread_t tid; /* the worker's thread ID */ + pthread_cond_t run; + int idx; + tcpsrv_t *pSrv; /* pSrv == NULL -> idle */ + nspoll_t *pPoll; + void *pUsr; + sbool enabled; + long long unsigned numCalled; /* how often was this called */ +} wrkrInfo[4]; +static sbool bWrkrRunning; /* are the worker threads running? */ +static pthread_mutex_t wrkrMut; +static pthread_cond_t wrkrIdle; +static int wrkrMax = 4; +static int wrkrRunning; + +/* add new listener port to listener port list + * rgerhards, 2009-05-21 + */ +static inline rsRetVal +addNewLstnPort(tcpsrv_t *pThis, uchar *pszPort, int bSuppOctetFram) +{ + tcpLstnPortList_t *pEntry; + uchar statname[64]; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* create entry */ + CHKmalloc(pEntry = MALLOC(sizeof(tcpLstnPortList_t))); + CHKmalloc(pEntry->pszPort = ustrdup(pszPort)); + pEntry->pSrv = pThis; + pEntry->pRuleset = pThis->pRuleset; + pEntry->bSuppOctetFram = bSuppOctetFram; + + /* we need to create a property */ + CHKiRet(prop.Construct(&pEntry->pInputName)); + CHKiRet(prop.SetString(pEntry->pInputName, pThis->pszInputName, ustrlen(pThis->pszInputName))); + CHKiRet(prop.ConstructFinalize(pEntry->pInputName)); + + /* and add to list */ + pEntry->pNext = pThis->pLstnPorts; + pThis->pLstnPorts = pEntry; + + /* support statistics gathering */ + CHKiRet(statsobj.Construct(&(pEntry->stats))); + snprintf((char*)statname, sizeof(statname), "%s(%s)", pThis->pszInputName, pszPort); + statname[sizeof(statname)-1] = '\0'; /* just to be on the save side... */ + CHKiRet(statsobj.SetName(pEntry->stats, statname)); + CHKiRet(ratelimitNew(&pEntry->ratelimiter, "tcperver", NULL)); + ratelimitSetLinuxLike(pEntry->ratelimiter, pThis->ratelimitInterval, pThis->ratelimitBurst); + ratelimitSetThreadSafe(pEntry->ratelimiter); + STATSCOUNTER_INIT(pEntry->ctrSubmit, pEntry->mutCtrSubmit); + CHKiRet(statsobj.AddCounter(pEntry->stats, UCHAR_CONSTANT("submitted"), + ctrType_IntCtr, &(pEntry->ctrSubmit))); + CHKiRet(statsobj.ConstructFinalize(pEntry->stats)); + +finalize_it: + RETiRet; +} + + +/* configure TCP listener settings. + * Note: pszPort is handed over to us - the caller MUST NOT free it! + * rgerhards, 2008-03-20 + */ +static rsRetVal +configureTCPListen(tcpsrv_t *pThis, uchar *pszPort, int bSuppOctetFram) +{ + int i; + uchar *pPort = pszPort; + DEFiRet; + + assert(pszPort != NULL); + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* extract port */ + i = 0; + while(isdigit((int) *pPort)) { + i = i * 10 + *pPort++ - '0'; + } + + if(i >= 0 && i <= 65535) { + CHKiRet(addNewLstnPort(pThis, pszPort, bSuppOctetFram)); + } else { + errmsg.LogError(0, NO_ERRCODE, "Invalid TCP listen port %s - ignored.\n", pszPort); + } + +finalize_it: + RETiRet; +} + + +/* Initialize the session table + * returns 0 if OK, somewhat else otherwise + */ +static rsRetVal +TCPSessTblInit(tcpsrv_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pThis->pSessions == NULL); + + DBGPRINTF("Allocating buffer for %d TCP sessions.\n", pThis->iSessMax); + if((pThis->pSessions = (tcps_sess_t **) calloc(pThis->iSessMax, sizeof(tcps_sess_t *))) == NULL) { + DBGPRINTF("Error: TCPSessInit() could not alloc memory for TCP session table.\n"); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + RETiRet; +} + + +/* find a free spot in the session table. If the table + * is full, -1 is returned, else the index of the free + * entry (0 or higher). + */ +static int +TCPSessTblFindFreeSpot(tcpsrv_t *pThis) +{ + register int i; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + for(i = 0 ; i < pThis->iSessMax ; ++i) { + if(pThis->pSessions[i] == NULL) + break; + } + + return((i < pThis->iSessMax) ? i : -1); +} + + +/* Get the next session index. Free session tables entries are + * skipped. This function is provided the index of the last + * session entry, or -1 if no previous entry was obtained. It + * returns the index of the next session or -1, if there is no + * further entry in the table. Please note that the initial call + * might as well return -1, if there is no session at all in the + * session table. + */ +static int +TCPSessGetNxtSess(tcpsrv_t *pThis, int iCurr) +{ + register int i; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pThis->pSessions != NULL); + for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) { + if(pThis->pSessions[i] != NULL) + break; + } + + ENDfunc + return((i < pThis->iSessMax) ? i : -1); +} + + +/* De-Initialize TCP listner sockets. + * This function deinitializes everything, including freeing the + * session table. No TCP listen receive operations are permitted + * unless the subsystem is reinitialized. + * rgerhards, 2007-06-21 + */ +static void deinit_tcp_listener(tcpsrv_t *pThis) +{ + int i; + tcpLstnPortList_t *pEntry; + tcpLstnPortList_t *pDel; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + if(pThis->pSessions != NULL) { + /* close all TCP connections! */ + if(!pThis->bUsingEPoll) { + i = TCPSessGetNxtSess(pThis, -1); + while(i != -1) { + tcps_sess.Destruct(&pThis->pSessions[i]); + /* now get next... */ + i = TCPSessGetNxtSess(pThis, i); + } + } + + /* we are done with the session table - so get rid of it... */ + free(pThis->pSessions); + pThis->pSessions = NULL; /* just to make sure... */ + } + + /* free list of tcp listen ports */ + pEntry = pThis->pLstnPorts; + while(pEntry != NULL) { + free(pEntry->pszPort); + prop.Destruct(&pEntry->pInputName); + ratelimitDestruct(pEntry->ratelimiter); + pDel = pEntry; + pEntry = pEntry->pNext; + free(pDel); + } + + /* finally close our listen streams */ + for(i = 0 ; i < pThis->iLstnCurr ; ++i) { + netstrm.Destruct(pThis->ppLstn + i); + } +} + + +/* add a listen socket to our listen socket array. This is a callback + * invoked from the netstrm class. -- rgerhards, 2008-04-23 + */ +static rsRetVal +addTcpLstn(void *pUsr, netstrm_t *pLstn) +{ + tcpLstnPortList_t *pPortList = (tcpLstnPortList_t *) pUsr; + tcpsrv_t *pThis = pPortList->pSrv; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + ISOBJ_TYPE_assert(pLstn, netstrm); + + if(pThis->iLstnCurr >= pThis->iLstnMax) + ABORT_FINALIZE(RS_RET_MAX_LSTN_REACHED); + + pThis->ppLstn[pThis->iLstnCurr] = pLstn; + pThis->ppLstnPort[pThis->iLstnCurr] = pPortList; + ++pThis->iLstnCurr; + +finalize_it: + RETiRet; +} + + +/* Initialize TCP listener socket for a single port + * rgerhards, 2009-05-21 + */ +static inline rsRetVal +initTCPListener(tcpsrv_t *pThis, tcpLstnPortList_t *pPortEntry) +{ + DEFiRet; + uchar *TCPLstnPort; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pPortEntry != NULL); + + if(!ustrcmp(pPortEntry->pszPort, UCHAR_CONSTANT("0"))) + TCPLstnPort = UCHAR_CONSTANT("514"); + /* use default - we can not do service db update, because there is + * no IANA-assignment for syslog/tcp. In the long term, we might + * re-use RFC 3195 port of 601, but that would probably break to + * many existing configurations. + * rgerhards, 2007-06-28 + */ + else + TCPLstnPort = pPortEntry->pszPort; + + /* TODO: add capability to specify local listen address! */ + CHKiRet(netstrm.LstnInit(pThis->pNS, (void*)pPortEntry, addTcpLstn, TCPLstnPort, NULL, pThis->iSessMax)); + +finalize_it: + RETiRet; +} + + +/* Initialize TCP sockets (for listener) and listens on them */ +static rsRetVal +create_tcp_socket(tcpsrv_t *pThis) +{ + DEFiRet; + rsRetVal localRet; + tcpLstnPortList_t *pEntry; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* init all configured ports */ + pEntry = pThis->pLstnPorts; + while(pEntry != NULL) { + localRet = initTCPListener(pThis, pEntry); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "Could not create tcp listener, ignoring port %s.", pEntry->pszPort); + } + pEntry = pEntry->pNext; + } + + /* OK, we had success. Now it is also time to + * initialize our connections + */ + if(TCPSessTblInit(pThis) != 0) { + /* OK, we are in some trouble - we could not initialize the + * session table, so we can not continue. We need to free all + * we have assigned so far, because we can not really use it... + */ + errmsg.LogError(0, RS_RET_ERR, "Could not initialize TCP session table, suspending TCP message reception."); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + RETiRet; +} + + +/* Accept new TCP connection; make entry in session table. If there + * is no more space left in the connection table, the new TCP + * connection is immediately dropped. + * ppSess has a pointer to the newly created session, if it succeeds. + * If it does not succeed, no session is created and ppSess is + * undefined. If the user has provided an OnSessAccept Callback, + * this one is executed immediately after creation of the + * session object, so that it can do its own initialization. + * rgerhards, 2008-03-02 + */ +static rsRetVal +SessAccept(tcpsrv_t *pThis, tcpLstnPortList_t *pLstnInfo, tcps_sess_t **ppSess, netstrm_t *pStrm) +{ + DEFiRet; + tcps_sess_t *pSess = NULL; + netstrm_t *pNewStrm = NULL; + int iSess = -1; + struct sockaddr_storage *addr; + uchar *fromHostFQDN = NULL; + prop_t *fromHostIP; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pLstnInfo != NULL); + + CHKiRet(netstrm.AcceptConnReq(pStrm, &pNewStrm)); + + /* Add to session list */ + iSess = TCPSessTblFindFreeSpot(pThis); + if(iSess == -1) { + errno = 0; + errmsg.LogError(0, RS_RET_MAX_SESS_REACHED, "too many tcp sessions - dropping incoming request"); + ABORT_FINALIZE(RS_RET_MAX_SESS_REACHED); + } + + if(pThis->bUseKeepAlive) { + CHKiRet(netstrm.EnableKeepAlive(pNewStrm)); + } + + /* we found a free spot and can construct our session object */ + CHKiRet(tcps_sess.Construct(&pSess)); + CHKiRet(tcps_sess.SetTcpsrv(pSess, pThis)); + CHKiRet(tcps_sess.SetLstnInfo(pSess, pLstnInfo)); + if(pThis->OnMsgReceive != NULL) + CHKiRet(tcps_sess.SetOnMsgReceive(pSess, pThis->OnMsgReceive)); + + /* get the host name */ + CHKiRet(netstrm.GetRemoteHName(pNewStrm, &fromHostFQDN)); + CHKiRet(netstrm.GetRemoteIP(pNewStrm, &fromHostIP)); + CHKiRet(netstrm.GetRemAddr(pNewStrm, &addr)); + /* TODO: check if we need to strip the domain name here -- rgerhards, 2008-04-24 */ + + /* Here we check if a host is permitted to send us messages. If it isn't, we do not further + * process the message but log a warning (if we are configured to do this). + * rgerhards, 2005-09-26 + */ + if(!pThis->pIsPermittedHost((struct sockaddr*) addr, (char*) fromHostFQDN, pThis->pUsr, pSess->pUsr)) { + DBGPRINTF("%s is not an allowed sender\n", fromHostFQDN); + if(glbl.GetOption_DisallowWarning()) { + errno = 0; + errmsg.LogError(0, RS_RET_HOST_NOT_PERMITTED, "TCP message from disallowed sender %s discarded", fromHostFQDN); + } + ABORT_FINALIZE(RS_RET_HOST_NOT_PERMITTED); + } + + /* OK, we have an allowed sender, so let's continue, what + * means we can finally fill in the session object. + */ + CHKiRet(tcps_sess.SetHost(pSess, fromHostFQDN)); + fromHostFQDN = NULL; /* we handed this string over */ + CHKiRet(tcps_sess.SetHostIP(pSess, fromHostIP)); + CHKiRet(tcps_sess.SetStrm(pSess, pNewStrm)); + pNewStrm = NULL; /* prevent it from being freed in error handler, now done in tcps_sess! */ + CHKiRet(tcps_sess.SetMsgIdx(pSess, 0)); + CHKiRet(tcps_sess.ConstructFinalize(pSess)); + + /* check if we need to call our callback */ + if(pThis->pOnSessAccept != NULL) { + CHKiRet(pThis->pOnSessAccept(pThis, pSess)); + } + + *ppSess = pSess; + if(!pThis->bUsingEPoll) + pThis->pSessions[iSess] = pSess; + pSess = NULL; /* this is now also handed over */ + +finalize_it: + if(iRet != RS_RET_OK) { + if(pSess != NULL) + tcps_sess.Destruct(&pSess); + if(pNewStrm != NULL) + netstrm.Destruct(&pNewStrm); + free(fromHostFQDN); + } + + RETiRet; +} + + +static void +RunCancelCleanup(void *arg) +{ + nssel_t **ppSel = (nssel_t**) arg; + + if(*ppSel != NULL) + nssel.Destruct(ppSel); +} + + +/* helper to close a session. Takes status of poll vs. select into consideration. + * rgerhards, 2009-11-25 + */ +static inline rsRetVal +closeSess(tcpsrv_t *pThis, tcps_sess_t **ppSess, nspoll_t *pPoll) { + DEFiRet; + if(pPoll != NULL) { + CHKiRet(nspoll.Ctl(pPoll, (*ppSess)->pStrm, 0, *ppSess, NSDPOLL_IN, NSDPOLL_DEL)); + } + pThis->pOnRegularClose(*ppSess); + tcps_sess.Destruct(ppSess); +finalize_it: + RETiRet; +} + + +/* process a receive request on one of the streams + * If pPoll is non-NULL, we have a netstream in epoll mode, which means we need + * to remove any descriptor we close from the epoll set. + * rgerhards, 2009-07-020 + */ +static rsRetVal +doReceive(tcpsrv_t *pThis, tcps_sess_t **ppSess, nspoll_t *pPoll) +{ + char buf[128*1024]; /* reception buffer - may hold a partial or multiple messages */ + ssize_t iRcvd; + rsRetVal localRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + DBGPRINTF("netstream %p with new data\n", (*ppSess)->pStrm); + /* Receive message */ + iRet = pThis->pRcvData(*ppSess, buf, sizeof(buf), &iRcvd); + switch(iRet) { + case RS_RET_CLOSED: + if(pThis->bEmitMsgOnClose) { + uchar *pszPeer; + int lenPeer; + errno = 0; + prop.GetString((*ppSess)->fromHostIP, &pszPeer, &lenPeer); + errmsg.LogError(0, RS_RET_PEER_CLOSED_CONN, "Netstream session %p closed by remote peer %s.\n", + (*ppSess)->pStrm, pszPeer); + } + CHKiRet(closeSess(pThis, ppSess, pPoll)); + break; + case RS_RET_RETRY: + /* we simply ignore retry - this is not an error, but we also have not received anything */ + break; + case RS_RET_OK: + /* valid data received, process it! */ + localRet = tcps_sess.DataRcvd(*ppSess, buf, iRcvd); + if(localRet != RS_RET_OK && localRet != RS_RET_QUEUE_FULL) { + /* in this case, something went awfully wrong. + * We are instructed to terminate the session. + */ + errmsg.LogError(0, localRet, "Tearing down TCP Session - see " + "previous messages for reason(s)\n"); + CHKiRet(closeSess(pThis, ppSess, pPoll)); + } + break; + default: + errno = 0; + errmsg.LogError(0, iRet, "netstream session %p will be closed due to error\n", + (*ppSess)->pStrm); + CHKiRet(closeSess(pThis, ppSess, pPoll)); + break; + } + +finalize_it: + RETiRet; +} + +/* process a single workset item + */ +static inline rsRetVal +processWorksetItem(tcpsrv_t *pThis, nspoll_t *pPoll, int idx, void *pUsr) +{ + tcps_sess_t *pNewSess = NULL; + DEFiRet; + + DBGPRINTF("tcpsrv: processing item %d, pUsr %p, bAbortConn\n", idx, pUsr); + if(pUsr == pThis->ppLstn) { + DBGPRINTF("New connect on NSD %p.\n", pThis->ppLstn[idx]); + iRet = SessAccept(pThis, pThis->ppLstnPort[idx], &pNewSess, pThis->ppLstn[idx]); + if(iRet == RS_RET_OK) { + if(pPoll != NULL) { + CHKiRet(nspoll.Ctl(pPoll, pNewSess->pStrm, 0, pNewSess, NSDPOLL_IN, NSDPOLL_ADD)); + } + DBGPRINTF("New session created with NSD %p.\n", pNewSess); + } else { + DBGPRINTF("tcpsrv: error %d during accept\n", iRet); + } + } else { + pNewSess = (tcps_sess_t*) pUsr; + doReceive(pThis, &pNewSess, pPoll); + if(pPoll == NULL && pNewSess == NULL) { + pThis->pSessions[idx] = NULL; + } + } + +finalize_it: + RETiRet; +} + + +/* worker to process incoming requests + */ +static void * +wrkr(void *myself) +{ + struct wrkrInfo_s *me = (struct wrkrInfo_s*) myself; + + pthread_mutex_lock(&wrkrMut); + while(1) { + while(me->pSrv == NULL && glbl.GetGlobalInputTermState() == 0) { + pthread_cond_wait(&me->run, &wrkrMut); + } + if(glbl.GetGlobalInputTermState() == 1) { + --wrkrRunning; + break; + } + pthread_mutex_unlock(&wrkrMut); + + ++me->numCalled; + processWorksetItem(me->pSrv, me->pPoll, me->idx, me->pUsr); + + pthread_mutex_lock(&wrkrMut); + me->pSrv = NULL; /* indicate we are free again */ + --wrkrRunning; + pthread_cond_signal(&wrkrIdle); + } + me->enabled = 0; /* indicate we are no longer available */ + pthread_mutex_unlock(&wrkrMut); + + return NULL; +} + + +/* Process a workset, that is handle io. We become activated + * from either select or epoll handler. We split the workload + * out to a pool of threads, but try to avoid context switches + * as much as possible. + */ +static rsRetVal +processWorkset(tcpsrv_t *pThis, nspoll_t *pPoll, int numEntries, nsd_epworkset_t workset[]) +{ + int i; + int origEntries = numEntries; + DEFiRet; + + DBGPRINTF("tcpsrv: ready to process %d event entries\n", numEntries); + + while(numEntries > 0) { + if(glbl.GetGlobalInputTermState() == 1) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + if(numEntries == 1) { + /* process self, save context switch */ + processWorksetItem(pThis, pPoll, workset[numEntries-1].id, workset[numEntries-1].pUsr); + } else { + pthread_mutex_lock(&wrkrMut); + /* check if there is a free worker */ + for(i = 0 ; (i < wrkrMax) && ((wrkrInfo[i].pSrv != NULL) || (wrkrInfo[i].enabled == 0)) ; ++i) + /*do search*/; + if(i < wrkrMax) { + /* worker free -> use it! */ + wrkrInfo[i].pSrv = pThis; + wrkrInfo[i].pPoll = pPoll; + wrkrInfo[i].idx = workset[numEntries -1].id; + wrkrInfo[i].pUsr = workset[numEntries -1].pUsr; + /* Note: we must increment wrkrRunning HERE and not inside the worker's + * code. This is because a worker may actually never start, and thus + * increment wrkrRunning, before we finish and check the running worker + * count. We can only avoid this by incrementing it here. + */ + ++wrkrRunning; + pthread_cond_signal(&wrkrInfo[i].run); + pthread_mutex_unlock(&wrkrMut); + } else { + pthread_mutex_unlock(&wrkrMut); + /* no free worker, so we process this one ourselfs */ + processWorksetItem(pThis, pPoll, workset[numEntries-1].id, + workset[numEntries-1].pUsr); + } + } + --numEntries; + } + + if(origEntries > 1) { + /* we now need to wait until all workers finish. This is because the + * rest of this module can not handle the concurrency introduced + * by workers running during the epoll call. + */ + pthread_mutex_lock(&wrkrMut); + while(wrkrRunning > 0) { + pthread_cond_wait(&wrkrIdle, &wrkrMut); + } + pthread_mutex_unlock(&wrkrMut); + } + +finalize_it: + RETiRet; +} + + +/* This function is called to gather input. + * This variant here is only used if we need to work with a netstream driver + * that does not support epoll(). + */ +#pragma GCC diagnostic ignored "-Wempty-body" +static inline rsRetVal +RunSelect(tcpsrv_t *pThis, nsd_epworkset_t workset[], size_t sizeWorkset) +{ + DEFiRet; + int nfds; + int i; + int iWorkset; + int iTCPSess; + int bIsReady; + nssel_t *pSel = NULL; + rsRetVal localRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* this is an endless loop - it is terminated by the framework canelling + * this thread. Thus, we also need to instantiate a cancel cleanup handler + * to prevent us from leaking anything. -- rgerhards, 20080-04-24 + */ + pthread_cleanup_push(RunCancelCleanup, (void*) &pSel); + while(1) { + CHKiRet(nssel.Construct(&pSel)); + // TODO: set driver + CHKiRet(nssel.ConstructFinalize(pSel)); + + /* Add the TCP listen sockets to the list of read descriptors. */ + for(i = 0 ; i < pThis->iLstnCurr ; ++i) { + CHKiRet(nssel.Add(pSel, pThis->ppLstn[i], NSDSEL_RD)); + } + + /* do the sessions */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(iTCPSess != -1) { + /* TODO: access to pNsd is NOT really CLEAN, use method... */ + CHKiRet(nssel.Add(pSel, pThis->pSessions[iTCPSess]->pStrm, NSDSEL_RD)); + /* now get next... */ + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + + /* wait for io to become ready */ + CHKiRet(nssel.Wait(pSel, &nfds)); + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + + iWorkset = 0; + for(i = 0 ; i < pThis->iLstnCurr ; ++i) { + if(glbl.GetGlobalInputTermState() == 1) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + CHKiRet(nssel.IsReady(pSel, pThis->ppLstn[i], NSDSEL_RD, &bIsReady, &nfds)); + if(bIsReady) { + workset[iWorkset].id = i; + workset[iWorkset].pUsr = (void*) pThis->ppLstn; /* this is a flag to indicate listen sock */ + ++iWorkset; + if(iWorkset >= (int) sizeWorkset) { + processWorkset(pThis, NULL, iWorkset, workset); + iWorkset = 0; + } + //DBGPRINTF("New connect on NSD %p.\n", pThis->ppLstn[i]); + //SessAccept(pThis, pThis->ppLstnPort[i], &pNewSess, pThis->ppLstn[i]); + --nfds; /* indicate we have processed one */ + } + } + + /* now check the sessions */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(nfds && iTCPSess != -1) { + if(glbl.GetGlobalInputTermState() == 1) + ABORT_FINALIZE(RS_RET_FORCE_TERM); + localRet = nssel.IsReady(pSel, pThis->pSessions[iTCPSess]->pStrm, NSDSEL_RD, &bIsReady, &nfds); + if(bIsReady || localRet != RS_RET_OK) { + workset[iWorkset].id = iTCPSess; + workset[iWorkset].pUsr = (void*) pThis->pSessions[iTCPSess]; + ++iWorkset; + if(iWorkset >= (int) sizeWorkset) { + processWorkset(pThis, NULL, iWorkset, workset); + iWorkset = 0; + } + --nfds; /* indicate we have processed one */ + } + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + + if(iWorkset > 0) + processWorkset(pThis, NULL, iWorkset, workset); + + /* we need to copy back close descriptors */ + CHKiRet(nssel.Destruct(&pSel)); +finalize_it: /* this is a very special case - this time only we do not exit the function, + * because that would not help us either. So we simply retry it. Let's see + * if that actually is a better idea. Exiting the loop wasn't we always + * crashed, which made sense (the rest of the engine was not prepared for + * that) -- rgerhards, 2008-05-19 + */ + if(pSel != NULL) { /* cleanup missing? happens during err exit! */ + nssel.Destruct(&pSel); + } + } + + /* note that this point is usually not reached */ + pthread_cleanup_pop(1); /* remove cleanup handler */ + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* This function is called to gather input. It tries doing that via the epoll() + * interface. If the driver does not support that, it falls back to calling its + * select() equivalent. + * rgerhards, 2009-11-18 + */ +static rsRetVal +Run(tcpsrv_t *pThis) +{ + DEFiRet; + int i; + nsd_epworkset_t workset[128]; /* 128 is currently fixed num of concurrent requests */ + int numEntries; + nspoll_t *pPoll = NULL; + rsRetVal localRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* check if we need to start the worker pool. Once it is running, all is + * well. Shutdown is done on modExit. + */ + d_pthread_mutex_lock(&wrkrMut); + if(!bWrkrRunning) { + bWrkrRunning = 1; + startWorkerPool(); + } + d_pthread_mutex_unlock(&wrkrMut); + + /* this is an endless loop - it is terminated by the framework canelling + * this thread. Thus, we also need to instantiate a cancel cleanup handler + * to prevent us from leaking anything. -- rgerhards, 20080-04-24 + */ + if((localRet = nspoll.Construct(&pPoll)) == RS_RET_OK) { + // TODO: set driver + localRet = nspoll.ConstructFinalize(pPoll); + } + if(localRet != RS_RET_OK) { + /* fall back to select */ + DBGPRINTF("tcpsrv could not use epoll() interface, iRet=%d, using select()\n", localRet); + iRet = RunSelect(pThis, workset, sizeof(workset)/sizeof(nsd_epworkset_t)); + FINALIZE; + } + + DBGPRINTF("tcpsrv uses epoll() interface, nsdpoll driver found\n"); + + /* flag that we are in epoll mode */ + pThis->bUsingEPoll = RSTRUE; + + /* Add the TCP listen sockets to the list of sockets to monitor */ + for(i = 0 ; i < pThis->iLstnCurr ; ++i) { + DBGPRINTF("Trying to add listener %d, pUsr=%p\n", i, pThis->ppLstn); + CHKiRet(nspoll.Ctl(pPoll, pThis->ppLstn[i], i, pThis->ppLstn, NSDPOLL_IN, NSDPOLL_ADD)); + DBGPRINTF("Added listener %d\n", i); + } + + while(1) { + numEntries = sizeof(workset)/sizeof(nsd_epworkset_t); + localRet = nspoll.Wait(pPoll, -1, &numEntries, workset); + if(glbl.GetGlobalInputTermState() == 1) + break; /* terminate input! */ + + /* check if we need to ignore the i/o ready state. We do this if we got an invalid + * return state. Validly, this can happen for RS_RET_EINTR, for other cases it may + * not be the right thing, but what is the right thing is really hard at this point... + */ + if(localRet != RS_RET_OK) + continue; + + processWorkset(pThis, pPoll, numEntries, workset); + } + + /* remove the tcp listen sockets from the epoll set */ + for(i = 0 ; i < pThis->iLstnCurr ; ++i) { + CHKiRet(nspoll.Ctl(pPoll, pThis->ppLstn[i], i, pThis->ppLstn, NSDPOLL_IN, NSDPOLL_DEL)); + } + +finalize_it: + if(pPoll != NULL) + nspoll.Destruct(&pPoll); + RETiRet; +} + + +/* Standard-Constructor */ +BEGINobjConstruct(tcpsrv) /* be sure to specify the object type also in END macro! */ + pThis->iSessMax = TCPSESS_MAX_DEFAULT; + pThis->iLstnMax = TCPLSTN_MAX_DEFAULT; + pThis->addtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER; + pThis->bDisableLFDelim = 0; + pThis->OnMsgReceive = NULL; + pThis->ratelimitInterval = 0; + pThis->ratelimitBurst = 10000; + pThis->bUseFlowControl = 1; +ENDobjConstruct(tcpsrv) + + +/* ConstructionFinalizer */ +static rsRetVal +tcpsrvConstructFinalize(tcpsrv_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* prepare network stream subsystem */ + CHKiRet(netstrms.Construct(&pThis->pNS)); + CHKiRet(netstrms.SetDrvrMode(pThis->pNS, pThis->iDrvrMode)); + if(pThis->pszDrvrAuthMode != NULL) + CHKiRet(netstrms.SetDrvrAuthMode(pThis->pNS, pThis->pszDrvrAuthMode)); + if(pThis->pPermPeers != NULL) + CHKiRet(netstrms.SetDrvrPermPeers(pThis->pNS, pThis->pPermPeers)); + // TODO: set driver! + CHKiRet(netstrms.ConstructFinalize(pThis->pNS)); + + /* set up listeners */ + CHKmalloc(pThis->ppLstn = calloc(pThis->iLstnMax, sizeof(netstrm_t*))); + CHKmalloc(pThis->ppLstnPort = calloc(pThis->iLstnMax, sizeof(tcpLstnPortList_t*))); + iRet = pThis->OpenLstnSocks(pThis); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pNS != NULL) + netstrms.Destruct(&pThis->pNS); + } + RETiRet; +} + + +/* destructor for the tcpsrv object */ +BEGINobjDestruct(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(tcpsrv) + if(pThis->OnDestruct != NULL) + pThis->OnDestruct(pThis->pUsr); + + deinit_tcp_listener(pThis); + + if(pThis->pNS != NULL) + netstrms.Destruct(&pThis->pNS); + free(pThis->pszDrvrAuthMode); + free(pThis->ppLstn); + free(pThis->ppLstnPort); + free(pThis->pszInputName); +ENDobjDestruct(tcpsrv) + + +/* debugprint for the tcpsrv object */ +BEGINobjDebugPrint(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(tcpsrv) +ENDobjDebugPrint(tcpsrv) + +/* set functions */ +static rsRetVal +SetCBIsPermittedHost(tcpsrv_t *pThis, int (*pCB)(struct sockaddr *addr, char *fromHostFQDN, void*, void*)) +{ + DEFiRet; + pThis->pIsPermittedHost = pCB; + RETiRet; +} + +static rsRetVal +SetCBRcvData(tcpsrv_t *pThis, rsRetVal (*pRcvData)(tcps_sess_t*, char*, size_t, ssize_t*)) +{ + DEFiRet; + pThis->pRcvData = pRcvData; + RETiRet; +} + +static rsRetVal +SetCBOnListenDeinit(tcpsrv_t *pThis, int (*pCB)(void*)) +{ + DEFiRet; + pThis->pOnListenDeinit = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessAccept(tcpsrv_t *pThis, rsRetVal (*pCB)(tcpsrv_t*, tcps_sess_t*)) +{ + DEFiRet; + pThis->pOnSessAccept = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnDestruct(tcpsrv_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->OnDestruct = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessConstructFinalize(tcpsrv_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->OnSessConstructFinalize = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessDestruct(tcpsrv_t *pThis, rsRetVal (*pCB)(void*)) +{ + DEFiRet; + pThis->pOnSessDestruct = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnRegularClose(tcpsrv_t *pThis, rsRetVal (*pCB)(tcps_sess_t*)) +{ + DEFiRet; + pThis->pOnRegularClose = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnErrClose(tcpsrv_t *pThis, rsRetVal (*pCB)(tcps_sess_t*)) +{ + DEFiRet; + pThis->pOnErrClose = pCB; + RETiRet; +} + +static rsRetVal +SetCBOpenLstnSocks(tcpsrv_t *pThis, rsRetVal (*pCB)(tcpsrv_t*)) +{ + DEFiRet; + pThis->OpenLstnSocks = pCB; + RETiRet; +} + +static rsRetVal +SetUsrP(tcpsrv_t *pThis, void *pUsr) +{ + DEFiRet; + pThis->pUsr = pUsr; + RETiRet; +} + +static rsRetVal +SetKeepAlive(tcpsrv_t *pThis, int iVal) +{ + DEFiRet; + DBGPRINTF("tcpsrv: keep-alive set to %d\n", iVal); + pThis->bUseKeepAlive = iVal; + RETiRet; +} + +static rsRetVal +SetOnMsgReceive(tcpsrv_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int)) +{ + DEFiRet; + assert(OnMsgReceive != NULL); + pThis->OnMsgReceive = OnMsgReceive; + RETiRet; +} + + +/* set enable/disable standard LF frame delimiter (use with care!) + * -- rgerhards, 2010-01-03 + */ +static rsRetVal +SetbDisableLFDelim(tcpsrv_t *pThis, int bVal) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->bDisableLFDelim = bVal; + RETiRet; +} + + +/* Set additional framing to use (if any) -- rgerhards, 2008-12-10 */ +static rsRetVal +SetAddtlFrameDelim(tcpsrv_t *pThis, int iDelim) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->addtlFrameDelim = iDelim; + RETiRet; +} + + +/* Set the input name to use -- rgerhards, 2008-12-10 */ +static rsRetVal +SetInputName(tcpsrv_t *pThis, uchar *name) +{ + uchar *pszName; + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + if(name == NULL) + pszName = NULL; + else + CHKmalloc(pszName = ustrdup(name)); + free(pThis->pszInputName); + pThis->pszInputName = pszName; +finalize_it: + RETiRet; +} + + +/* Set the linux-like ratelimiter settings */ +static rsRetVal +SetLinuxLikeRatelimiters(tcpsrv_t *pThis, int ratelimitInterval, int ratelimitBurst) +{ + DEFiRet; + pThis->ratelimitInterval = ratelimitInterval; + pThis->ratelimitBurst = ratelimitBurst; + RETiRet; +} + + +/* Set the ruleset (ptr) to use */ +static rsRetVal +SetRuleset(tcpsrv_t *pThis, ruleset_t *pRuleset) +{ + DEFiRet; + pThis->pRuleset = pRuleset; + RETiRet; +} + + +/* Set connection close notification */ +static rsRetVal +SetNotificationOnRemoteClose(tcpsrv_t *pThis, int bNewVal) +{ + DEFiRet; + pThis->bEmitMsgOnClose = bNewVal; + RETiRet; +} + + +/* here follows a number of methods that shuffle authentication settings down + * to the drivers. Drivers not supporting these settings may return an error + * state. + * -------------------------------------------------------------------------- */ + +/* set the driver mode -- rgerhards, 2008-04-30 */ +static rsRetVal +SetDrvrMode(tcpsrv_t *pThis, int iMode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->iDrvrMode = iMode; + RETiRet; +} + + +/* set the driver authentication mode -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrAuthMode(tcpsrv_t *pThis, uchar *mode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + CHKmalloc(pThis->pszDrvrAuthMode = ustrdup(mode)); +finalize_it: + RETiRet; +} + + +/* set the driver's permitted peers -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrPermPeers(tcpsrv_t *pThis, permittedPeers_t *pPermPeers) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->pPermPeers = pPermPeers; + RETiRet; +} + + +/* End of methods to shuffle autentication settings to the driver.; + + * -------------------------------------------------------------------------- */ + + +/* set max number of listeners + * this must be called before ConstructFinalize, or it will have no effect! + * rgerhards, 2009-08-17 + */ +static rsRetVal +SetLstnMax(tcpsrv_t *pThis, int iMax) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->iLstnMax = iMax; + RETiRet; +} + + +/* set if flow control shall be supported + */ +static rsRetVal +SetUseFlowControl(tcpsrv_t *pThis, int bUseFlowControl) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->bUseFlowControl = bUseFlowControl; + RETiRet; +} + + +/* set max number of sessions + * this must be called before ConstructFinalize, or it will have no effect! + * rgerhards, 2009-04-09 + */ +static rsRetVal +SetSessMax(tcpsrv_t *pThis, int iMax) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->iSessMax = iMax; + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(tcpsrv) +CODESTARTobjQueryInterface(tcpsrv) + if(pIf->ifVersion != tcpsrvCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->DebugPrint = tcpsrvDebugPrint; + pIf->Construct = tcpsrvConstruct; + pIf->ConstructFinalize = tcpsrvConstructFinalize; + pIf->Destruct = tcpsrvDestruct; + + pIf->configureTCPListen = configureTCPListen; + pIf->create_tcp_socket = create_tcp_socket; + pIf->Run = Run; + + pIf->SetKeepAlive = SetKeepAlive; + pIf->SetUsrP = SetUsrP; + pIf->SetInputName = SetInputName; + pIf->SetAddtlFrameDelim = SetAddtlFrameDelim; + pIf->SetbDisableLFDelim = SetbDisableLFDelim; + pIf->SetSessMax = SetSessMax; + pIf->SetUseFlowControl = SetUseFlowControl; + pIf->SetLstnMax = SetLstnMax; + pIf->SetDrvrMode = SetDrvrMode; + pIf->SetDrvrAuthMode = SetDrvrAuthMode; + pIf->SetDrvrPermPeers = SetDrvrPermPeers; + pIf->SetCBIsPermittedHost = SetCBIsPermittedHost; + pIf->SetCBOpenLstnSocks = SetCBOpenLstnSocks; + pIf->SetCBRcvData = SetCBRcvData; + pIf->SetCBOnListenDeinit = SetCBOnListenDeinit; + pIf->SetCBOnSessAccept = SetCBOnSessAccept; + pIf->SetCBOnSessConstructFinalize = SetCBOnSessConstructFinalize; + pIf->SetCBOnSessDestruct = SetCBOnSessDestruct; + pIf->SetCBOnDestruct = SetCBOnDestruct; + pIf->SetCBOnRegularClose = SetCBOnRegularClose; + pIf->SetCBOnErrClose = SetCBOnErrClose; + pIf->SetOnMsgReceive = SetOnMsgReceive; + pIf->SetRuleset = SetRuleset; + pIf->SetLinuxLikeRatelimiters = SetLinuxLikeRatelimiters; + pIf->SetNotificationOnRemoteClose = SetNotificationOnRemoteClose; + +finalize_it: +ENDobjQueryInterface(tcpsrv) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(tcpsrv, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(tcpsrv) + /* release objects we no longer need */ + objRelease(tcps_sess, DONT_LOAD_LIB); + objRelease(conf, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrms, DONT_LOAD_LIB); + objRelease(nssel, DONT_LOAD_LIB); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(net, LM_NET_FILENAME); +ENDObjClassExit(tcpsrv) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINObjClassInit(tcpsrv, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(netstrm, DONT_LOAD_LIB)); + CHKiRet(objUse(nssel, DONT_LOAD_LIB)); + CHKiRet(objUse(nspoll, DONT_LOAD_LIB)); + CHKiRet(objUse(tcps_sess, DONT_LOAD_LIB)); + CHKiRet(objUse(conf, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, tcpsrvDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcpsrvConstructFinalize); +ENDObjClassInit(tcpsrv) + + +/* start worker threads + * Important: if we fork, this MUST be done AFTER forking + */ +static void +startWorkerPool(void) +{ + int i; + int r; + pthread_attr_t sessThrdAttr; + + wrkrRunning = 0; + pthread_cond_init(&wrkrIdle, NULL); + pthread_attr_init(&sessThrdAttr); + pthread_attr_setstacksize(&sessThrdAttr, 4096*1024); + for(i = 0 ; i < wrkrMax ; ++i) { + /* init worker info structure! */ + pthread_cond_init(&wrkrInfo[i].run, NULL); + wrkrInfo[i].pSrv = NULL; + wrkrInfo[i].numCalled = 0; + r = pthread_create(&wrkrInfo[i].tid, &sessThrdAttr, wrkr, &(wrkrInfo[i])); + if(r == 0) { + wrkrInfo[i].enabled = 1; + } else { + char errStr[1024]; + wrkrInfo[i].enabled = 0; + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, NO_ERRCODE, "tcpsrv error creating thread %d: " + "%s", i, errStr); + } + } + pthread_attr_destroy(&sessThrdAttr); +} + +/* destroy worker pool structures and wait for workers to terminate + */ +static void +stopWorkerPool(void) +{ + int i; + for(i = 0 ; i < wrkrMax ; ++i) { + pthread_cond_signal(&wrkrInfo[i].run); /* awake wrkr if not running */ + pthread_join(wrkrInfo[i].tid, NULL); + DBGPRINTF("tcpsrv: info: worker %d was called %llu times\n", i, wrkrInfo[i].numCalled); + pthread_cond_destroy(&wrkrInfo[i].run); + } + pthread_cond_destroy(&wrkrIdle); + pthread_mutex_destroy(&wrkrMut); + +} + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + +BEGINmodExit +CODESTARTmodExit + stopWorkerPool(); + /* de-init in reverse order! */ + tcpsrvClassExit(); + tcps_sessClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + /* we just init the worker mutex, but do not start the workers themselves. This is deferred + * to the first call of Run(). Reasons for this: + * 1. depending on load order, tcpsrv gets loaded during rsyslog startup BEFORE + * it forks, in which case the workers would be running in the then-killed parent, + * leading to a defuncnt child (we actually had this bug). + * 2. depending on circumstances, Run() would possibly never be called, in which case + * the worker threads would be totally useless. + * Note that in order to guarantee a non-racy worker start, we need to guard the + * startup sequence by a mutex, which is why we init it here (no problem with fork() + * in this case as the mutex is a pure-memory structure). + * rgerhards, 2012-05-18 + */ + pthread_mutex_init(&wrkrMut, NULL); + bWrkrRunning = 0; + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(tcps_sessClassInit(pModInfo)); + CHKiRet(tcpsrvClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit + +/* vim:set ai: + */ diff --git a/tcpsrv.h b/tcpsrv.h new file mode 100644 index 00000000..93e472c9 --- /dev/null +++ b/tcpsrv.h @@ -0,0 +1,166 @@ +/* Definitions for tcpsrv class. + * + * Copyright 2008-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INCLUDED_TCPSRV_H +#define INCLUDED_TCPSRV_H + +#include "obj.h" +#include "prop.h" +#include "tcps_sess.h" +#include "statsobj.h" + +/* support for framing anomalies */ +typedef enum ETCPsyslogFramingAnomaly { + frame_normal = 0, + frame_NetScreen = 1, + frame_CiscoIOS = 2 +} eTCPsyslogFramingAnomaly; + + +/* list of tcp listen ports */ +struct tcpLstnPortList_s { + uchar *pszPort; /**< the ports the listener shall listen on */ + prop_t *pInputName; + tcpsrv_t *pSrv; /**< pointer to higher-level server instance */ + ruleset_t *pRuleset; /**< associated ruleset */ + statsobj_t *stats; /**< associated stats object */ + sbool bSuppOctetFram; /**< do we support octect-counted framing? (if no->legay only!)*/ + ratelimit_t *ratelimiter; + STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit) + tcpLstnPortList_t *pNext; /**< next port or NULL */ +}; + +#define TCPSRV_NO_ADDTL_DELIMITER -1 /* specifies that no additional delimiter is to be used in TCP framing */ + +/* the tcpsrv object */ +struct tcpsrv_s { + BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */ + int bUseKeepAlive; /**< use socket layer KEEPALIVE handling? */ + netstrms_t *pNS; /**< pointer to network stream subsystem */ + int iDrvrMode; /**< mode of the stream driver to use */ + uchar *pszDrvrAuthMode; /**< auth mode of the stream driver to use */ + uchar *pszInputName; /**< value to be used as input name */ + ruleset_t *pRuleset; /**< ruleset to bind to */ + permittedPeers_t *pPermPeers;/**< driver's permitted peers */ + sbool bEmitMsgOnClose; /**< emit an informational message when the remote peer closes connection */ + sbool bUsingEPoll; /**< are we in epoll mode (means we do not need to keep track of sessions!) */ + sbool bUseFlowControl; /**< use flow control (make light delayable) */ + int iLstnCurr; /**< max nbr of listeners currently supported */ + netstrm_t **ppLstn; /**< our netstream listners */ + tcpLstnPortList_t **ppLstnPort; /**< pointer to relevant listen port description */ + int iLstnMax; /**< max number of listners supported */ + int iSessMax; /**< max number of sessions supported */ + tcpLstnPortList_t *pLstnPorts; /**< head pointer for listen ports */ + + int addtlFrameDelim; /**< additional frame delimiter for plain TCP syslog framing (e.g. to handle NetScreen) */ + int bDisableLFDelim; /**< if 1, standard LF frame delimiter is disabled (*very dangerous*) */ + int ratelimitInterval; + int ratelimitBurst; + tcps_sess_t **pSessions;/**< array of all of our sessions */ + void *pUsr; /**< a user-settable pointer (provides extensibility for "derived classes")*/ + /* callbacks */ + int (*pIsPermittedHost)(struct sockaddr *addr, char *fromHostFQDN, void*pUsrSrv, void*pUsrSess); + rsRetVal (*pRcvData)(tcps_sess_t*, char*, size_t, ssize_t *); + rsRetVal (*OpenLstnSocks)(struct tcpsrv_s*); + rsRetVal (*pOnListenDeinit)(void*); + rsRetVal (*OnDestruct)(void*); + rsRetVal (*pOnRegularClose)(tcps_sess_t *pSess); + rsRetVal (*pOnErrClose)(tcps_sess_t *pSess); + /* session specific callbacks */ + rsRetVal (*pOnSessAccept)(tcpsrv_t *, tcps_sess_t*); + rsRetVal (*OnSessConstructFinalize)(void*); + rsRetVal (*pOnSessDestruct)(void*); + rsRetVal (*OnMsgReceive)(tcps_sess_t *, uchar *pszMsg, int iLenMsg); /* submit message callback */ +}; + + +/** + * The following structure is a set of descriptors that need to be processed. + * This set will be the result of the epoll or select call and be used + * in the actual request processing stage. It serves as a basis + * to run multiple request by concurrent threads. -- rgerhards, 2011-01-24 + */ +struct tcpsrv_workset_s { + int idx; /**< index into session table (or -1 if listener) */ + void *pUsr; +}; + + +/* interfaces */ +BEGINinterface(tcpsrv) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(tcpsrv); + rsRetVal (*Construct)(tcpsrv_t **ppThis); + rsRetVal (*ConstructFinalize)(tcpsrv_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(tcpsrv_t **ppThis); + rsRetVal (*configureTCPListen)(tcpsrv_t*, uchar *pszPort, int bSuppOctetFram); + //rsRetVal (*SessAccept)(tcpsrv_t *pThis, tcpLstnPortList_t*, tcps_sess_t **ppSess, netstrm_t *pStrm); + rsRetVal (*create_tcp_socket)(tcpsrv_t *pThis); + rsRetVal (*Run)(tcpsrv_t *pThis); + /* set methods */ + rsRetVal (*SetAddtlFrameDelim)(tcpsrv_t*, int); + rsRetVal (*SetInputName)(tcpsrv_t*, uchar*); + rsRetVal (*SetUsrP)(tcpsrv_t*, void*); + rsRetVal (*SetCBIsPermittedHost)(tcpsrv_t*, int (*) (struct sockaddr *addr, char*, void*, void*)); + rsRetVal (*SetCBOpenLstnSocks)(tcpsrv_t *, rsRetVal (*)(tcpsrv_t*)); + rsRetVal (*SetCBRcvData)(tcpsrv_t *pThis, rsRetVal (*pRcvData)(tcps_sess_t*, char*, size_t, ssize_t*)); + rsRetVal (*SetCBOnListenDeinit)(tcpsrv_t*, rsRetVal (*)(void*)); + rsRetVal (*SetCBOnDestruct)(tcpsrv_t*, rsRetVal (*) (void*)); + rsRetVal (*SetCBOnRegularClose)(tcpsrv_t*, rsRetVal (*) (tcps_sess_t*)); + rsRetVal (*SetCBOnErrClose)(tcpsrv_t*, rsRetVal (*) (tcps_sess_t*)); + rsRetVal (*SetDrvrMode)(tcpsrv_t *pThis, int iMode); + rsRetVal (*SetDrvrAuthMode)(tcpsrv_t *pThis, uchar *pszMode); + rsRetVal (*SetDrvrPermPeers)(tcpsrv_t *pThis, permittedPeers_t*); + /* session specifics */ + rsRetVal (*SetCBOnSessAccept)(tcpsrv_t*, rsRetVal (*) (tcpsrv_t*, tcps_sess_t*)); + rsRetVal (*SetCBOnSessDestruct)(tcpsrv_t*, rsRetVal (*) (void*)); + rsRetVal (*SetCBOnSessConstructFinalize)(tcpsrv_t*, rsRetVal (*) (void*)); + /* added v5 */ + rsRetVal (*SetSessMax)(tcpsrv_t *pThis, int iMaxSess); /* 2009-04-09 */ + /* added v6 */ + rsRetVal (*SetOnMsgReceive)(tcpsrv_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int)); /* 2009-05-24 */ + rsRetVal (*SetRuleset)(tcpsrv_t *pThis, ruleset_t*); /* 2009-06-12 */ + /* added v7 (accidently named v8!) */ + rsRetVal (*SetLstnMax)(tcpsrv_t *pThis, int iMaxLstn); /* 2009-08-17 */ + rsRetVal (*SetNotificationOnRemoteClose)(tcpsrv_t *pThis, int bNewVal); /* 2009-10-01 */ + /* added v9 -- rgerhards, 2010-03-01 */ + rsRetVal (*SetbDisableLFDelim)(tcpsrv_t*, int); + /* added v10 -- rgerhards, 2011-04-01 */ + rsRetVal (*SetUseFlowControl)(tcpsrv_t*, int); + /* added v11 -- rgerhards, 2011-05-09 */ + rsRetVal (*SetKeepAlive)(tcpsrv_t*, int); + /* added v13 -- rgerhards, 2012-10-15 */ + rsRetVal (*SetLinuxLikeRatelimiters)(tcpsrv_t *pThis, int interval, int burst); +ENDinterface(tcpsrv) +#define tcpsrvCURR_IF_VERSION 13 /* increment whenever you change the interface structure! */ +/* change for v4: + * - SetAddtlFrameDelim() added -- rgerhards, 2008-12-10 + * - SetInputName() added -- rgerhards, 2008-12-10 + * change for v5 and up: see above + * for v12: param bSuppOctetFram added to configureTCPListen + */ + + +/* prototypes */ +PROTOTYPEObj(tcpsrv); + +/* the name of our library binary */ +#define LM_TCPSRV_FILENAME "lmtcpsrv" + +#endif /* #ifndef INCLUDED_TCPSRV_H */ diff --git a/template.c b/template.c new file mode 100644 index 00000000..b6752551 --- /dev/null +++ b/template.c @@ -0,0 +1,2197 @@ +/* This is the template processing code of rsyslog. + * begun 2004-11-17 rgerhards + * + * Copyright 2004-2012 Rainer Gerhards and Adiscon + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Note: there is a tiny bit of code left where I could not get any response + * from the author if this code can be placed under ASL2.0. I have guarded this + * with #ifdef STRICT_GPLV3. Only if that macro is defined, the code will be + * compiled. Otherwise this feature is not present. The plan is to do a + * different implementation in the future to get rid of this problem. + * rgerhards, 2012-08-25 + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <json/json.h> +#include "stringbuf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "dirty.h" +#include "obj.h" +#include "errmsg.h" +#include "strgen.h" +#include "rsconf.h" +#include "msg.h" +#include "unicode-helper.h" + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(strgen) + +/* tables for interfacing with the v6 config system */ +static struct cnfparamdescr cnfparamdescr[] = { + { "name", eCmdHdlrString, 1 }, + { "type", eCmdHdlrString, 1 }, + { "string", eCmdHdlrString, 0 }, + { "plugin", eCmdHdlrString, 0 }, + { "subtree", eCmdHdlrString, 0 }, + { "option.stdsql", eCmdHdlrBinary, 0 }, + { "option.sql", eCmdHdlrBinary, 0 }, + { "option.json", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk pblk = + { CNFPARAMBLK_VERSION, + sizeof(cnfparamdescr)/sizeof(struct cnfparamdescr), + cnfparamdescr + }; + +static struct cnfparamdescr cnfparamdescrProperty[] = { + { "name", eCmdHdlrString, 1 }, + { "outname", eCmdHdlrString, 0 }, + { "dateformat", eCmdHdlrString, 0 }, + { "caseconversion", eCmdHdlrString, 0 }, + { "controlcharacters", eCmdHdlrString, 0 }, + { "securepath", eCmdHdlrString, 0 }, + { "format", eCmdHdlrString, 0 }, + { "position.from", eCmdHdlrInt, 0 }, + { "position.to", eCmdHdlrInt, 0 }, + { "position.relativetoend", eCmdHdlrBinary, 0 }, + { "field.number", eCmdHdlrInt, 0 }, + { "field.delimiter", eCmdHdlrInt, 0 }, + { "regex.expression", eCmdHdlrString, 0 }, + { "regex.type", eCmdHdlrString, 0 }, + { "regex.nomatchmode", eCmdHdlrString, 0 }, + { "regex.match", eCmdHdlrInt, 0 }, + { "regex.submatch", eCmdHdlrInt, 0 }, + { "droplastlf", eCmdHdlrBinary, 0 }, + { "mandatory", eCmdHdlrBinary, 0 }, + { "spifno1stsp", eCmdHdlrBinary, 0 } +}; +static struct cnfparamblk pblkProperty = + { CNFPARAMBLK_VERSION, + sizeof(cnfparamdescrProperty)/sizeof(struct cnfparamdescr), + cnfparamdescrProperty + }; + +static struct cnfparamdescr cnfparamdescrConstant[] = { + { "value", eCmdHdlrString, 1 }, + { "outname", eCmdHdlrString, 0 }, +}; +static struct cnfparamblk pblkConstant = + { CNFPARAMBLK_VERSION, + sizeof(cnfparamdescrConstant)/sizeof(struct cnfparamdescr), + cnfparamdescrConstant + }; + + +#ifdef FEATURE_REGEXP +DEFobjCurrIf(regexp) +static int bFirstRegexpErrmsg = 1; /**< did we already do a "can't load regexp" error message? */ +#endif + +/* helper to tplToString and strgen's, extends buffer */ +#define ALLOC_INC 128 +rsRetVal +ExtendBuf(uchar **pBuf, size_t *pLenBuf, size_t iMinSize) +{ + uchar *pNewBuf; + size_t iNewSize; + DEFiRet; + + iNewSize = (iMinSize / ALLOC_INC + 1) * ALLOC_INC; + CHKmalloc(pNewBuf = (uchar*) realloc(*pBuf, iNewSize)); + *pBuf = pNewBuf; + *pLenBuf = iNewSize; + +finalize_it: + RETiRet; +} + + +/* This functions converts a template into a string. + * + * The function takes a pointer to a template and a pointer to a msg object + * as well as a pointer to an output buffer and its size. Note that the output + * buffer pointer may be NULL, size 0, in which case a new one is allocated. + * The outpub buffer is grown as required. It is the caller's duty to free the + * buffer when it is done. Note that it is advisable to reuse memory, as this + * offers big performance improvements. + * rewritten 2009-06-19 rgerhards + */ +rsRetVal +tplToString(struct template *pTpl, msg_t *pMsg, uchar **ppBuf, size_t *pLenBuf, + struct syslogTime *ttNow) +{ + DEFiRet; + struct templateEntry *pTpe; + size_t iBuf; + unsigned short bMustBeFreed = 0; + uchar *pVal; + rs_size_t iLenVal = 0; + + assert(pTpl != NULL); + assert(pMsg != NULL); + assert(ppBuf != NULL); + assert(pLenBuf != NULL); + + if(pTpl->pStrgen != NULL) { + CHKiRet(pTpl->pStrgen(pMsg, ppBuf, pLenBuf)); + FINALIZE; + } + + if(pTpl->subtree != NULL) { + /* only a single CEE subtree must be provided */ + /* note: we could optimize the code below, however, this is + * not worth the effort, as this passing mode is not expected + * in subtree mode and so most probably only used for debug & test. + */ + getCEEPropVal(pMsg, pTpl->subtree, &pVal, &iLenVal, &bMustBeFreed); + if(iLenVal >= (rs_size_t)*pLenBuf) /* we reserve one char for the final \0! */ + CHKiRet(ExtendBuf(ppBuf, pLenBuf, iLenVal + 1)); + memcpy(*ppBuf, pVal, iLenVal+1); + if(bMustBeFreed) + free(pVal); + FINALIZE; + } + + /* we have a "regular" template with template entries */ + + /* loop through the template. We obtain one value + * and copy it over to our dynamic string buffer. Then, we + * free the obtained value (if requested). We continue this + * loop until we got hold of all values. + */ + pTpe = pTpl->pEntryRoot; + iBuf = 0; + while(pTpe != NULL) { + if(pTpe->eEntryType == CONSTANT) { + pVal = (uchar*) pTpe->data.constant.pConstant; + iLenVal = pTpe->data.constant.iLenConstant; + bMustBeFreed = 0; + } else if(pTpe->eEntryType == FIELD) { + pVal = (uchar*) MsgGetProp(pMsg, pTpe, pTpe->data.field.propid, + pTpe->data.field.propName, &iLenVal, + &bMustBeFreed, ttNow); + /* we now need to check if we should use SQL option. In this case, + * we must go over the generated string and escape '\'' characters. + * rgerhards, 2005-09-22: the option values below look somewhat misplaced, + * but they are handled in this way because of legacy (don't break any + * existing thing). + */ + if(pTpl->optFormatEscape == SQL_ESCAPE) + doEscape(&pVal, &iLenVal, &bMustBeFreed, SQL_ESCAPE); + else if(pTpl->optFormatEscape == JSON_ESCAPE) + doEscape(&pVal, &iLenVal, &bMustBeFreed, JSON_ESCAPE); + else if(pTpl->optFormatEscape == STDSQL_ESCAPE) + doEscape(&pVal, &iLenVal, &bMustBeFreed, STDSQL_ESCAPE); + } + /* got source, now copy over */ + if(iLenVal > 0) { /* may be zero depending on property */ + /* first, make sure buffer fits */ + if(iBuf + iLenVal >= *pLenBuf) /* we reserve one char for the final \0! */ + CHKiRet(ExtendBuf(ppBuf, pLenBuf, iBuf + iLenVal + 1)); + + memcpy(*ppBuf + iBuf, pVal, iLenVal); + iBuf += iLenVal; + } + + if(bMustBeFreed) + free(pVal); + + pTpe = pTpe->pNext; + } + + if(iBuf == *pLenBuf) { + /* in the weired case of an *empty* template, this can happen. + * it is debatable if we should really fix it here or simply + * forbid that case. However, performance toll is minimal, so + * I tend to permit it. -- 201011-05 rgerhards + */ + CHKiRet(ExtendBuf(ppBuf, pLenBuf, iBuf + 1)); + } + (*ppBuf)[iBuf] = '\0'; + +finalize_it: + RETiRet; +} + + +/* This functions converts a template into an array of strings. + * For further general details, see the very similar funtion + * tpltoString(). + * Instead of a string, an array of string pointers is returned by + * thus function. The caller is repsonsible for destroying that array as + * well as all of its elements. The array is of fixed size. It's end + * is indicated by a NULL pointer. + * rgerhards, 2009-04-03 + */ +rsRetVal +tplToArray(struct template *pTpl, msg_t *pMsg, uchar*** ppArr, struct syslogTime *ttNow) +{ + DEFiRet; + struct templateEntry *pTpe; + uchar **pArr; + int iArr; + rs_size_t propLen; + unsigned short bMustBeFreed; + uchar *pVal; + + assert(pTpl != NULL); + assert(pMsg != NULL); + assert(ppArr != NULL); + + if(pTpl->subtree) { + /* Note: this mode is untested, as there is no official plugin + * using array passing, so I simply could not test it. + */ + CHKmalloc(pArr = calloc(2, sizeof(uchar*))); + getCEEPropVal(pMsg, pTpl->subtree, &pVal, &propLen, &bMustBeFreed); + if(bMustBeFreed) { /* if it must be freed, it is our own private copy... */ + pArr[0] = pVal; /* ... so we can use it! */ + } else { + CHKmalloc(pArr[0] = (uchar*)strdup((char*) pVal)); + } + FINALIZE; + } + + /* loop through the template. We obtain one value, create a + * private copy (if necessary), add it to the string array + * and then on to the next until we have processed everything. + */ + CHKmalloc(pArr = calloc(pTpl->tpenElements + 1, sizeof(uchar*))); + iArr = 0; + + pTpe = pTpl->pEntryRoot; + while(pTpe != NULL) { + if(pTpe->eEntryType == CONSTANT) { + CHKmalloc(pArr[iArr] = (uchar*)strdup((char*) pTpe->data.constant.pConstant)); + } else if(pTpe->eEntryType == FIELD) { + pVal = (uchar*) MsgGetProp(pMsg, pTpe, pTpe->data.field.propid, + pTpe->data.field.propName, &propLen, + &bMustBeFreed, ttNow); + if(bMustBeFreed) { /* if it must be freed, it is our own private copy... */ + pArr[iArr] = pVal; /* ... so we can use it! */ + } else { + CHKmalloc(pArr[iArr] = (uchar*)strdup((char*) pVal)); + } + } + iArr++; + pTpe = pTpe->pNext; + } + +finalize_it: + *ppArr = (iRet == RS_RET_OK) ? pArr : NULL; + + RETiRet; +} + + +/* This functions converts a template into a json object. + * For further general details, see the very similar funtion + * tpltoString(). + * rgerhards, 2012-08-29 + */ +rsRetVal +tplToJSON(struct template *pTpl, msg_t *pMsg, struct json_object **pjson, struct syslogTime *ttNow) +{ + struct templateEntry *pTpe; + rs_size_t propLen; + unsigned short bMustBeFreed; + uchar *pVal; + struct json_object *json, *jsonf; + rsRetVal localRet; + DEFiRet; + + if(pTpl->subtree != NULL){ + localRet = jsonFind(pMsg, pTpl->subtree, pjson); + if(*pjson == NULL) { + /* we need to have a root object! */ + *pjson = json_object_new_object(); + } else { + json_object_get(*pjson); /* inc refcount */ + } + FINALIZE; + } + + json = json_object_new_object(); + for(pTpe = pTpl->pEntryRoot ; pTpe != NULL ; pTpe = pTpe->pNext) { + if(pTpe->eEntryType == CONSTANT) { + if(pTpe->fieldName == NULL) + continue; + jsonf = json_object_new_string((char*) pTpe->data.constant.pConstant); + json_object_object_add(json, (char*)pTpe->fieldName, jsonf); + } else if(pTpe->eEntryType == FIELD) { + if(pTpe->data.field.propid == PROP_CEE) { + localRet = msgGetCEEPropJSON(pMsg, pTpe->data.field.propName, &jsonf); + if(localRet == RS_RET_OK) { + json_object_object_add(json, (char*)pTpe->fieldName, json_object_get(jsonf)); + } else { + DBGPRINTF("tplToJSON: error %d looking up property\n", + localRet); + if(pTpe->data.field.options.bMandatory) { + json_object_object_add(json, (char*)pTpe->fieldName, NULL); + } + } + } else { + pVal = (uchar*) MsgGetProp(pMsg, pTpe, pTpe->data.field.propid, + pTpe->data.field.propName, &propLen, + &bMustBeFreed, ttNow); + if(pTpe->data.field.options.bMandatory || propLen > 0) { + jsonf = json_object_new_string_len((char*)pVal, propLen); + json_object_object_add(json, (char*)pTpe->fieldName, jsonf); + } + if(bMustBeFreed) { /* json-c makes its own private copy! */ + free(pVal); + } + } + } + } + *pjson = (iRet == RS_RET_OK) ? json : NULL; + +finalize_it: + RETiRet; +} + + +/* Helper to doEscape. This is called if doEscape + * runs out of memory allocating the escaped string. + * Then we are in trouble. We can + * NOT simply return the unmodified string because this + * may cause SQL injection. But we also can not simply + * abort the run, this would be a DoS. I think an appropriate + * measure is to remove the dangerous \' characters (SQL). We + * replace them by \", which will break the message and + * signatures eventually present - but this is the + * best thing we can do now (or does anybody + * have a better idea?). rgerhards 2004-11-23 + * added support for escape mode (see doEscape for details). + * if mode = SQL_ESCAPE, then backslashes are changed to slashes. + * rgerhards 2005-09-22 + */ +static void doEmergencyEscape(register uchar *p, int mode) +{ + while(*p) { + if((mode == SQL_ESCAPE||mode == STDSQL_ESCAPE) && *p == '\'') + *p = '"'; + else if((mode == JSON_ESCAPE) && *p == '"') + *p = '\''; + else if((mode == SQL_ESCAPE) && *p == '\\') + *p = '/'; + ++p; + } +} + + +/* SQL-Escape a string. Single quotes are found and + * replaced by two of them. A new buffer is allocated + * for the provided string and the provided buffer is + * freed. The length is updated. Parameter pbMustBeFreed + * is set to 1 if a new buffer is allocated. Otherwise, + * it is left untouched. + * -- + * We just discovered a security issue. MySQL is so + * "smart" to not only support the standard SQL mechanism + * for escaping quotes, but to also provide its own (using + * c-type syntax with backslashes). As such, it is actually + * possible to do sql injection via rsyslogd. The cure is now + * to escape backslashes, too. As we have found on the web, some + * other databases seem to be similar "smart" (why do we have standards + * at all if they are violated without any need???). Even better, MySQL's + * smartness depends on config settings. So we add a new option to this + * function that allows the caller to select if they want to standard or + * "smart" encoding ;) + * -- + * Parameter "mode" is STDSQL_ESCAPE, SQL_ESCAPE "smart" SQL engines, or + * JSON_ESCAPE for everyone requiring escaped JSON (e.g. ElasticSearch). + * 2005-09-22 rgerhards + */ +rsRetVal +doEscape(uchar **pp, rs_size_t *pLen, unsigned short *pbMustBeFreed, int mode) +{ + DEFiRet; + uchar *p = NULL; + int iLen; + cstr_t *pStrB = NULL; + uchar *pszGenerated; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pLen != NULL); + assert(pbMustBeFreed != NULL); + + /* first check if we need to do anything at all... */ + if(mode == STDSQL_ESCAPE) + for(p = *pp ; *p && *p != '\'' ; ++p) + ; + else if(mode == SQL_ESCAPE) + for(p = *pp ; *p && *p != '\'' && *p != '\\' ; ++p) + ; + else if(mode == JSON_ESCAPE) + for(p = *pp ; *p && *p != '"' ; ++p) + ; + /* when we get out of the loop, we are either at the + * string terminator or the first character to escape */ + if(p && *p == '\0') + FINALIZE; /* nothing to do in this case! */ + + p = *pp; + iLen = *pLen; + CHKiRet(cstrConstruct(&pStrB)); + + while(*p) { + if((mode == SQL_ESCAPE || mode == STDSQL_ESCAPE) && *p == '\'') { + CHKiRet(cstrAppendChar(pStrB, (mode == STDSQL_ESCAPE) ? '\'' : '\\')); + iLen++; /* reflect the extra character */ + } else if((mode == SQL_ESCAPE) && *p == '\\') { + CHKiRet(cstrAppendChar(pStrB, '\\')); + iLen++; /* reflect the extra character */ + } else if((mode == JSON_ESCAPE) && *p == '"') { + CHKiRet(cstrAppendChar(pStrB, '\\')); + iLen++; /* reflect the extra character */ + } + CHKiRet(cstrAppendChar(pStrB, *p)); + ++p; + } + CHKiRet(cstrFinalize(pStrB)); + CHKiRet(cstrConvSzStrAndDestruct(pStrB, &pszGenerated, 0)); + + if(*pbMustBeFreed) + free(*pp); /* discard previous value */ + + *pp = pszGenerated; + *pLen = iLen; + *pbMustBeFreed = 1; + +finalize_it: + if(iRet != RS_RET_OK) { + doEmergencyEscape(*pp, mode); + if(pStrB != NULL) + cstrDestruct(&pStrB); + } + + RETiRet; +} + + +/* Constructs a template entry object. Returns pointer to it + * or NULL (if it fails). Pointer to associated template list entry + * must be provided. + */ +struct templateEntry* tpeConstruct(struct template *pTpl) +{ + struct templateEntry *pTpe; + + assert(pTpl != NULL); + + if((pTpe = calloc(1, sizeof(struct templateEntry))) == NULL) + return NULL; + + /* basic initialization is done via calloc() - need to + * initialize only values != 0. */ + + if(pTpl->pEntryLast == NULL){ + /* we are the first element! */ + pTpl->pEntryRoot = pTpl->pEntryLast = pTpe; + } else { + pTpl->pEntryLast->pNext = pTpe; + pTpl->pEntryLast = pTpe; + } + pTpl->tpenElements++; + + return(pTpe); +} + + +/* Constructs a template list object. Returns pointer to it + * or NULL (if it fails). + */ +static struct template* +tplConstruct(rsconf_t *conf) +{ + struct template *pTpl; + if((pTpl = calloc(1, sizeof(struct template))) == NULL) + return NULL; + + /* basic initialisation is done via calloc() - need to + * initialize only values != 0. */ + + if(conf->templates.last == NULL) { + /* we are the first element! */ + conf->templates.root = conf->templates.last = pTpl; + } else { + conf->templates.last->pNext = pTpl; + conf->templates.last = pTpl; + } + + return(pTpl); +} + + +/* helper to tplAddLine. Parses a constant and generates + * the necessary structure. + * Paramter "bDoEscapes" is to support legacy vs. v6+ config system. In + * legacy, we must do escapes ourselves, whereas v6+ passes in already + * escaped strings (which we are NOT permitted to further escape, this would + * cause invalid result strings!). Note: if escapes are not permitted, + * quotes (") are just a regular character and do NOT terminate the constant! + */ +static rsRetVal +do_Constant(unsigned char **pp, struct template *pTpl, int bDoEscapes) +{ + register unsigned char *p; + cstr_t *pStrB; + struct templateEntry *pTpe; + int i; + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pTpl != NULL); + + p = *pp; + + CHKiRet(cstrConstruct(&pStrB)); + /* process the message and expand escapes + * (additional escapes can be added here if needed) + */ + while(*p && *p != '%' && !(bDoEscapes && *p == '\"')) { + if(bDoEscapes && *p == '\\') { + switch(*++p) { + case '\0': + /* the best we can do - it's invalid anyhow... */ + cstrAppendChar(pStrB, *p); + break; + case 'n': + cstrAppendChar(pStrB, '\n'); + ++p; + break; + case 'r': + cstrAppendChar(pStrB, '\r'); + ++p; + break; + case '\\': + cstrAppendChar(pStrB, '\\'); + ++p; + break; + case '%': + cstrAppendChar(pStrB, '%'); + ++p; + break; + case '0': /* numerical escape sequence */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + i = 0; + while(*p && isdigit((int)*p)) { + i = i * 10 + *p++ - '0'; + } + cstrAppendChar(pStrB, i); + break; + default: + cstrAppendChar(pStrB, *p++); + break; + } + } + else + cstrAppendChar(pStrB, *p++); + } + + if((pTpe = tpeConstruct(pTpl)) == NULL) { + rsCStrDestruct(&pStrB); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + pTpe->eEntryType = CONSTANT; + cstrFinalize(pStrB); + /* We obtain the length from the counted string object + * (before we delete it). Later we might take additional + * benefit from the counted string object. + * 2005-09-09 rgerhards + */ + pTpe->data.constant.iLenConstant = rsCStrLen(pStrB); + CHKiRet(cstrConvSzStrAndDestruct(pStrB, &pTpe->data.constant.pConstant, 0)); + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Helper to do_Parameter(). This parses the formatting options + * specified in a template variable. It returns the passed-in pointer + * updated to the next processed character. + */ +static void doOptions(unsigned char **pp, struct templateEntry *pTpe) +{ + register unsigned char *p; + unsigned char Buf[64]; + size_t i; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pTpe != NULL); + + p = *pp; + + while(*p && *p != '%' && *p != ':') { + /* outer loop - until end of options */ + i = 0; + while((i < sizeof(Buf) / sizeof(char)) && + *p && *p != '%' && *p != ':' && *p != ',') { + /* inner loop - until end of ONE option */ + Buf[i++] = tolower((int)*p); + ++p; + } + Buf[i] = '\0'; /* terminate */ + /* check if we need to skip oversize option */ + while(*p && *p != '%' && *p != ':' && *p != ',') + ++p; /* just skip */ + if(*p == ',') + ++p; /* eat ',' */ + /* OK, we got the option, so now lets look what + * it tells us... + */ + if(!strcmp((char*)Buf, "date-mysql")) { + pTpe->data.field.eDateFormat = tplFmtMySQLDate; + } else if(!strcmp((char*)Buf, "date-pgsql")) { + pTpe->data.field.eDateFormat = tplFmtPgSQLDate; + } else if(!strcmp((char*)Buf, "date-rfc3164")) { + pTpe->data.field.eDateFormat = tplFmtRFC3164Date; + } else if(!strcmp((char*)Buf, "date-rfc3164-buggyday")) { + pTpe->data.field.eDateFormat = tplFmtRFC3164BuggyDate; + } else if(!strcmp((char*)Buf, "date-rfc3339")) { + pTpe->data.field.eDateFormat = tplFmtRFC3339Date; + } else if(!strcmp((char*)Buf, "date-unixtimestamp")) { + pTpe->data.field.eDateFormat = tplFmtUnixDate; + } else if(!strcmp((char*)Buf, "date-subseconds")) { + pTpe->data.field.eDateFormat = tplFmtSecFrac; + } else if(!strcmp((char*)Buf, "lowercase")) { + pTpe->data.field.eCaseConv = tplCaseConvLower; + } else if(!strcmp((char*)Buf, "uppercase")) { + pTpe->data.field.eCaseConv = tplCaseConvUpper; + } else if(!strcmp((char*)Buf, "sp-if-no-1st-sp")) { + pTpe->data.field.options.bSPIffNo1stSP = 1; + } else if(!strcmp((char*)Buf, "escape-cc")) { + pTpe->data.field.options.bEscapeCC = 1; + } else if(!strcmp((char*)Buf, "drop-cc")) { + pTpe->data.field.options.bDropCC = 1; + } else if(!strcmp((char*)Buf, "space-cc")) { + pTpe->data.field.options.bSpaceCC = 1; + } else if(!strcmp((char*)Buf, "drop-last-lf")) { + pTpe->data.field.options.bDropLastLF = 1; + } else if(!strcmp((char*)Buf, "secpath-drop")) { + pTpe->data.field.options.bSecPathDrop = 1; + } else if(!strcmp((char*)Buf, "secpath-replace")) { + pTpe->data.field.options.bSecPathReplace = 1; + } else if(!strcmp((char*)Buf, "pos-end-relative")) { + pTpe->data.field.options.bFromPosEndRelative = 1; + } else if(!strcmp((char*)Buf, "csv")) { + if(pTpe->data.field.options.bJSON || pTpe->data.field.options.bJSONf) { + errmsg.LogError(0, NO_ERRCODE, "error: can only specify " + "one option out of (json, jsonf, csv) - csv ignored"); + } else { + pTpe->data.field.options.bCSV = 1; + } + } else if(!strcmp((char*)Buf, "json")) { + if(pTpe->data.field.options.bCSV || pTpe->data.field.options.bJSON) { + errmsg.LogError(0, NO_ERRCODE, "error: can only specify " + "one option out of (json, jsonf, csv) - json ignored"); + } else { + pTpe->data.field.options.bJSON = 1; + } + } else if(!strcmp((char*)Buf, "jsonf")) { + if(pTpe->data.field.options.bCSV || pTpe->data.field.options.bJSON) { + errmsg.LogError(0, NO_ERRCODE, "error: can only specify " + "one option out of (json, jsonf, csv) - jsonf ignored"); + } else { + pTpe->data.field.options.bJSONf = 1; + } + } else if(!strcmp((char*)Buf, "mandatory-field")) { + pTpe->data.field.options.bMandatory = 1; + } else { + errmsg.LogError(0, NO_ERRCODE, "template error: invalid field option '%s' " + "specified - ignored", Buf); + } + } + + *pp = p; +} + + +/* helper to tplAddLine. Parses a parameter and generates + * the necessary structure. + */ +static rsRetVal +do_Parameter(uchar **pp, struct template *pTpl) +{ + uchar *p; + cstr_t *pStrProp; + cstr_t *pStrField = NULL; + struct templateEntry *pTpe; + int iNum; /* to compute numbers */ +#ifdef FEATURE_REGEXP + /* APR: variables for regex */ + rsRetVal iRetLocal; + int longitud; + unsigned char *regex_char; + unsigned char *regex_end; +#endif + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pTpl != NULL); + + p = (uchar*) *pp; + CHKiRet(cstrConstruct(&pStrProp)); + CHKmalloc(pTpe = tpeConstruct(pTpl)); + pTpe->eEntryType = FIELD; + + while(*p && *p != '%' && *p != ':') { + cstrAppendChar(pStrProp, tolower(*p)); + ++p; /* do NOT do this in tolower()! */ + } + + /* got the name */ + cstrFinalize(pStrProp); + + if(propNameToID(pStrProp, &pTpe->data.field.propid) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_TPL_INVLD_PROP, "template '%s': invalid parameter '%s'", + pTpl->pszName, cstrGetSzStrNoNULL(pStrProp)); + cstrDestruct(&pStrProp); + ABORT_FINALIZE(RS_RET_TPL_INVLD_PROP); + } + if(pTpe->data.field.propid == PROP_CEE) { + /* in CEE case, we need to preserve the actual property name */ + if((pTpe->data.field.propName = es_newStrFromCStr((char*)cstrGetSzStrNoNULL(pStrProp)+1, cstrLen(pStrProp)-1)) == NULL) { + cstrDestruct(&pStrProp); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + } + + /* Check frompos, if it has an R, then topos should be a regex */ + if(*p == ':') { + pTpe->bComplexProcessing = 1; + ++p; /* eat ':' */ +#ifdef FEATURE_REGEXP + if(*p == 'R') { + /* APR: R found! regex alarm ! :) */ + ++p; /* eat ':' */ + + /* first come the regex type */ + if(*p == ',') { + ++p; /* eat ',' */ + if(p[0] == 'B' && p[1] == 'R' && p[2] == 'E' && (p[3] == ',' || p[3] == ':')) { + pTpe->data.field.typeRegex = TPL_REGEX_BRE; + p += 3; /* eat indicator sequence */ + } else if(p[0] == 'E' && p[1] == 'R' && p[2] == 'E' && (p[3] == ',' || p[3] == ':')) { + pTpe->data.field.typeRegex = TPL_REGEX_ERE; + p += 3; /* eat indicator sequence */ + } else { + errmsg.LogError(0, NO_ERRCODE, "error: invalid regular expression type, rest of line %s", + (char*) p); + } + } + + /* now check for submatch ID */ + pTpe->data.field.iSubMatchToUse = 0; + if(*p == ',') { + /* in this case a number follows, which indicates which match + * shall be used. This must be a single digit. + */ + ++p; /* eat ',' */ + if(isdigit((int) *p)) { + pTpe->data.field.iSubMatchToUse = *p - '0'; + ++p; /* eat digit */ + } + } + + /* now pull what to do if we do not find a match */ + if(*p == ',') { + ++p; /* eat ',' */ + if(p[0] == 'D' && p[1] == 'F' && p[2] == 'L' && p[3] == 'T' + && (p[4] == ',' || p[4] == ':')) { + pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_DFLTSTR; + p += 4; /* eat indicator sequence */ + } else if(p[0] == 'B' && p[1] == 'L' && p[2] == 'A' && p[3] == 'N' && p[4] == 'K' + && (p[5] == ',' || p[5] == ':')) { + pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_BLANK; + p += 5; /* eat indicator sequence */ + } else if(p[0] == 'F' && p[1] == 'I' && p[2] == 'E' && p[3] == 'L' && p[4] == 'D' + && (p[5] == ',' || p[5] == ':')) { + pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_WHOLE_FIELD; + p += 5; /* eat indicator sequence */ + } else if(p[0] == 'Z' && p[1] == 'E' && p[2] == 'R' && p[3] == 'O' + && (p[4] == ',' || p[4] == ':')) { + pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_ZERO; + p += 4; /* eat indicator sequence */ + } else if(p[0] == ',') { /* empty, use default */ + pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_DFLTSTR; + /* do NOT eat indicator sequence, as this was already eaten - the + * comma itself is already part of the next field. + */ + } else { + errmsg.LogError(0, NO_ERRCODE, "template %s error: invalid regular expression type, rest of line %s", + pTpl->pszName, (char*) p); + } + } + + /* now check for match ID */ + pTpe->data.field.iMatchToUse = 0; + if(*p == ',') { + /* in this case a number follows, which indicates which match + * shall be used. This must be a single digit. + */ + ++p; /* eat ',' */ + if(isdigit((int) *p)) { + pTpe->data.field.iMatchToUse = *p - '0'; + ++p; /* eat digit */ + } + } + + if(*p != ':') { + /* There is something more than an R , this is invalid ! */ + /* Complain on extra characters */ + errmsg.LogError(0, NO_ERRCODE, "error: invalid character in frompos after \"R\", property: '%%%s'", + (char*) *pp); + } else { + pTpe->data.field.has_regex = 1; + dbgprintf("we have a regexp and use match #%d, submatch #%d\n", + pTpe->data.field.iMatchToUse, pTpe->data.field.iSubMatchToUse); + } + } else { + /* now we fall through the "regular" FromPos code */ +#endif /* #ifdef FEATURE_REGEXP */ + if(*p == 'F') { +#ifdef STRICT_GPLV3 + pTpe->data.field.field_expand = 0; +#endif + /* we have a field counter, so indicate it in the template */ + ++p; /* eat 'F' */ + if (*p == ':') { + /* no delimiter specified, so use the default (HT) */ + pTpe->data.field.has_fields = 1; + pTpe->data.field.field_delim = 9; + } else if (*p == ',') { + ++p; /* eat ',' */ + /* configured delimiter follows, so we need to obtain + * it. Important: the following number must be the + * **DECIMAL** ASCII value of the delimiter character. + */ + pTpe->data.field.has_fields = 1; + if(!isdigit((int)*p)) { + /* complain and use default */ + errmsg.LogError(0, NO_ERRCODE, "error: invalid character in frompos after \"F,\", property: '%%%s' - using 9 (HT) as field delimiter", + (char*) *pp); + pTpe->data.field.field_delim = 9; + } else { + iNum = 0; + while(isdigit((int)*p)) + iNum = iNum * 10 + *p++ - '0'; + if(iNum < 0 || iNum > 255) { + errmsg.LogError(0, NO_ERRCODE, "error: non-USASCII delimiter character value %d in template - using 9 (HT) as substitute", iNum); + pTpe->data.field.field_delim = 9; + } else { + pTpe->data.field.field_delim = iNum; +# ifdef STRICT_GPLV3 + if (*p == '+') { + pTpe->data.field.field_expand = 1; + p ++; + } +# endif + if(*p == ',') { /* real fromPos? */ + ++p; + iNum = 0; + while(isdigit((int)*p)) + iNum = iNum * 10 + *p++ - '0'; + pTpe->data.field.iFromPos = iNum; + } + } + } + } else { + /* invalid character after F, so we need to reject + * this. + */ + errmsg.LogError(0, NO_ERRCODE, "error: invalid character in frompos after \"F\", property: '%%%s'", + (char*) *pp); + } + } else { + /* we now have a simple offset in frompos (the previously "normal" case) */ + iNum = 0; + while(isdigit((int)*p)) + iNum = iNum * 10 + *p++ - '0'; + pTpe->data.field.iFromPos = iNum; + /* skip to next known good */ + while(*p && *p != '%' && *p != ':') { + /* TODO: complain on extra characters */ + dbgprintf("error: extra character in frompos: '%s'\n", p); + ++p; + } + } +#ifdef FEATURE_REGEXP + } +#endif /* #ifdef FEATURE_REGEXP */ + } + /* check topos (holds an regex if FromPos is "R"*/ + if(*p == ':') { + ++p; /* eat ':' */ + +#ifdef FEATURE_REGEXP + if (pTpe->data.field.has_regex) { + dbgprintf("debug: has regex \n"); + /* APR 2005-09 I need the string that represent the regex */ + /* The regex end is: "--end" */ + /* TODO : this is hardcoded and cant be escaped, please change */ + regex_end = (unsigned char*) strstr((char*)p, "--end"); + if (regex_end == NULL) { + dbgprintf("error: can not find regex end in: '%s'\n", p); + pTpe->data.field.has_regex = 0; + } else { + /* We get here ONLY if the regex end was found */ + longitud = regex_end - p; + /* Malloc for the regex string */ + regex_char = (unsigned char *) MALLOC(longitud + 1); + if(regex_char == NULL) { + dbgprintf("Could not allocate memory for template parameter!\n"); + pTpe->data.field.has_regex = 0; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* Get the regex string for compiling later */ + memcpy(regex_char, p, longitud); + regex_char[longitud] = '\0'; + dbgprintf("debug: regex detected: '%s'\n", regex_char); + /* Now i compile the regex */ + /* Remember that the re is an attribute of the Template entry */ + if((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { + int iOptions; + iOptions = (pTpe->data.field.typeRegex == TPL_REGEX_ERE) ? REG_EXTENDED : 0; + if(regexp.regcomp(&(pTpe->data.field.re), (char*) regex_char, iOptions) != 0) { + dbgprintf("error: can not compile regex: '%s'\n", regex_char); + pTpe->data.field.has_regex = 2; + } + } else { + /* regexp object could not be loaded */ + dbgprintf("error %d trying to load regexp library - this may be desired and thus OK", + iRetLocal); + if(bFirstRegexpErrmsg) { /* prevent flood of messages, maybe even an endless loop! */ + bFirstRegexpErrmsg = 0; + errmsg.LogError(0, NO_ERRCODE, "regexp library could not be loaded (error %d), " + "regexp ignored", iRetLocal); + } + pTpe->data.field.has_regex = 2; + } + + /* Finally we move the pointer to the end of the regex + * so it aint parsed twice or something weird */ + p = regex_end + 5/*strlen("--end")*/; + free(regex_char); + } + } else if(*p == '$') { + /* shortcut for "end of message */ + p++; /* eat '$' */ + /* in this case, we do a quick, somewhat dirty but totally + * legitimate trick: we simply use a topos that is higher than + * potentially ever can happen. The code below checks that no copy + * will occur after the end of string, so this is perfectly legal. + * rgerhards, 2006-10-17 + */ + pTpe->data.field.iToPos = 9999999; + } else { + /* fallthrough to "regular" ToPos code */ +#endif /* #ifdef FEATURE_REGEXP */ + + if(pTpe->data.field.has_fields == 1) { + iNum = 0; + while(isdigit((int)*p)) + iNum = iNum * 10 + *p++ - '0'; + pTpe->data.field.iFieldNr = iNum; + if(*p == ',') { /* get real toPos? */ + ++p; + iNum = 0; + while(isdigit((int)*p)) + iNum = iNum * 10 + *p++ - '0'; + pTpe->data.field.iToPos = iNum; + } + } else { + iNum = 0; + while(isdigit((int)*p)) + iNum = iNum * 10 + *p++ - '0'; + pTpe->data.field.iToPos = iNum; + } + /* skip to next known good */ + while(*p && *p != '%' && *p != ':') { + /* TODO: complain on extra characters */ + dbgprintf("error: extra character in frompos: '%s'\n", p); + ++p; + } +#ifdef FEATURE_REGEXP + } +#endif /* #ifdef FEATURE_REGEXP */ + } + + /* check options */ + if(*p == ':') { + ++p; /* eat ':' */ + doOptions(&p, pTpe); + } + + if(pTpe->data.field.options.bFromPosEndRelative) { + if(pTpe->data.field.iToPos > pTpe->data.field.iFromPos) { + iNum = pTpe->data.field.iToPos; + pTpe->data.field.iToPos = pTpe->data.field.iFromPos; + pTpe->data.field.iFromPos = iNum; + } + } else { + if(pTpe->data.field.iToPos < pTpe->data.field.iFromPos) { + iNum = pTpe->data.field.iToPos; + pTpe->data.field.iToPos = pTpe->data.field.iFromPos; + pTpe->data.field.iFromPos = iNum; + } + } + + + /* check field name */ + if(*p == ':') { + ++p; /* eat ':' */ + CHKiRet(cstrConstruct(&pStrField)); + while(*p != ':' && *p != '%' && *p != '\0') { + cstrAppendChar(pStrField, *p); + ++p; + } + cstrFinalize(pStrField); + } + + /* save field name - if none was given, use the property name instead */ + if(pStrField == NULL) { + if(pTpe->data.field.propid == PROP_CEE) { + /* in CEE case, we remove "$!" from the fieldname - it's just our indicator */ + pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrProp)+2); + pTpe->lenFieldName = cstrLen(pStrProp)-2; + } else { + pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrProp)); + pTpe->lenFieldName = cstrLen(pStrProp); + } + } else { + pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrField)); + pTpe->lenFieldName = ustrlen(pTpe->fieldName); + cstrDestruct(&pStrField); + } + if(pTpe->fieldName == NULL) { + DBGPRINTF("template/do_Parameter: fieldName is NULL!\n"); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + cstrDestruct(&pStrProp); + if(*p) ++p; /* eat '%' */ + *pp = p; +finalize_it: + RETiRet; +} + + +/* Add a new entry for a template module. + * returns pointer to new object if it succeeds, NULL otherwise. + * rgerhards, 2010-05-31 + */ +static rsRetVal +tplAddTplMod(struct template *pTpl, uchar** ppRestOfConfLine) +{ + uchar *pSrc; + uchar szMod[2048]; + unsigned lenMod; + strgen_t *pStrgen; + DEFiRet; + + pSrc = *ppRestOfConfLine; + lenMod = 0; + while(*pSrc && !isspace(*pSrc) && lenMod < sizeof(szMod) - 1) { + szMod[lenMod] = *pSrc++; + lenMod++; + + } + szMod[lenMod] = '\0'; + *ppRestOfConfLine = pSrc; + CHKiRet(strgen.FindStrgen(&pStrgen, szMod)); + pTpl->pStrgen = pStrgen->pModule->mod.sm.strgen; + DBGPRINTF("template bound to strgen '%s'\n", szMod); + /* check if the name potentially contains some well-known options + * Note: we have opted to let the name contain all options. This sounds + * useful, because the strgen MUST actually implement a specific set + * of options. Doing this via the name looks to the enduser as if the + * regular syntax were used, and it make sure the strgen postively + * acknowledged implementing the option. -- rgerhards, 2011-03-21 + */ + if(lenMod > 6 && !strcasecmp((char*) szMod + lenMod - 7, ",stdsql")) { + pTpl->optFormatEscape = STDSQL_ESCAPE; + DBGPRINTF("strgen supports the stdsql option\n"); + } else if(lenMod > 3 && !strcasecmp((char*) szMod+ lenMod - 4, ",sql")) { + pTpl->optFormatEscape = SQL_ESCAPE; + DBGPRINTF("strgen supports the sql option\n"); + } else if(lenMod > 4 && !strcasecmp((char*) szMod+ lenMod - 4, ",json")) { + pTpl->optFormatEscape = JSON_ESCAPE; + DBGPRINTF("strgen supports the json option\n"); + } + +finalize_it: + RETiRet; +} + + +/* Add a new template line + * returns pointer to new object if it succeeds, NULL otherwise. + */ +struct template *tplAddLine(rsconf_t *conf, char* pName, uchar** ppRestOfConfLine) +{ + struct template *pTpl; + unsigned char *p; + int bDone; + char optBuf[128]; /* buffer for options - should be more than enough... */ + size_t i; + rsRetVal localRet; + + assert(pName != NULL); + assert(ppRestOfConfLine != NULL); + if((pTpl = tplConstruct(conf)) == NULL) + return NULL; + + DBGPRINTF("tplAddLine processing template '%s'\n", pName); + pTpl->iLenName = strlen(pName); + pTpl->pszName = (char*) MALLOC(sizeof(char) * (pTpl->iLenName + 1)); + if(pTpl->pszName == NULL) { + dbgprintf("tplAddLine could not alloc memory for template name!"); + pTpl->iLenName = 0; + return NULL; + /* I know - we create a memory leak here - but I deem + * it acceptable as it is a) a very small leak b) very + * unlikely to happen. rgerhards 2004-11-17 + */ + } + memcpy(pTpl->pszName, pName, pTpl->iLenName + 1); + + /* now actually parse the line */ + p = *ppRestOfConfLine; + assert(p != NULL); + + while(isspace((int)*p))/* skip whitespace */ + ++p; + + switch(*p) { + case '"': /* just continue */ + break; + case '=': + *ppRestOfConfLine = p + 1; + localRet = tplAddTplMod(pTpl, ppRestOfConfLine); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "Template '%s': error %d defining template via strgen module", + pTpl->pszName, localRet); + /* we simply make the template defunct in this case by setting + * its name to a zero-string. We do not free it, as this would + * require additional code and causes only a very small memory + * consumption. Memory is freed, however, in normal operation + * and most importantly by HUPing syslogd. + */ + *pTpl->pszName = '\0'; + } + return NULL; + default: + dbgprintf("Template '%s' invalid, does not start with '\"'!\n", pTpl->pszName); + /* we simply make the template defunct in this case by setting + * its name to a zero-string. We do not free it, as this would + * require additional code and causes only a very small memory + * consumption. + */ + *pTpl->pszName = '\0'; + return NULL; + } + ++p; + + /* we finally go to the actual template string - so let's have some fun... */ + bDone = *p ? 0 : 1; + while(!bDone) { + switch(*p) { + case '\0': + bDone = 1; + break; + case '%': /* parameter */ + ++p; /* eat '%' */ + if(do_Parameter(&p, pTpl) != RS_RET_OK) { + dbgprintf("tplAddLine error: parameter invalid"); + return NULL; + }; + break; + default: /* constant */ + do_Constant(&p, pTpl, 1); + break; + } + if(*p == '"') {/* end of template string? */ + ++p; /* eat it! */ + bDone = 1; + } + } + + /* we now have the template - let's look at the options (if any) + * we process options until we reach the end of the string or + * an error occurs - whichever is first. + */ + while(*p) { + while(isspace((int)*p))/* skip whitespace */ + ++p; + + if(*p != ',') + break; + ++p; /* eat ',' */ + + while(isspace((int)*p))/* skip whitespace */ + ++p; + + /* read option word */ + i = 0; + while(i < sizeof(optBuf) / sizeof(char) - 1 + && *p && *p != '=' && *p !=',' && *p != '\n') { + optBuf[i++] = tolower((int)*p); + ++p; + } + optBuf[i] = '\0'; + + if(*p == '\n') + ++p; + + /* as of now, the no form is nonsense... but I do include + * it anyhow... ;) rgerhards 2004-11-22 + */ + if(!strcmp(optBuf, "stdsql")) { + pTpl->optFormatEscape = STDSQL_ESCAPE; + } else if(!strcmp(optBuf, "json")) { + pTpl->optFormatEscape = JSON_ESCAPE; + } else if(!strcmp(optBuf, "sql")) { + pTpl->optFormatEscape = SQL_ESCAPE; + } else if(!strcmp(optBuf, "nosql")) { + pTpl->optFormatEscape = NO_ESCAPE; + } else { + dbgprintf("Invalid option '%s' ignored.\n", optBuf); + } + } + + *ppRestOfConfLine = p; + + return(pTpl); +} + +static rsRetVal +createConstantTpe(struct template *pTpl, struct cnfobj *o) +{ + struct templateEntry *pTpe; + es_str_t *value = NULL; /* init just to keep compiler happy - mandatory parameter */ + int i; + struct cnfparamvals *pvals = NULL; + uchar *outname = NULL; + DEFiRet; + + /* pull params */ + pvals = nvlstGetParams(o->nvlst, &pblkConstant, NULL); + cnfparamsPrint(&pblkConstant, pvals); + + for(i = 0 ; i < pblkConstant.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblkConstant.descr[i].name, "value")) { + value = pvals[i].val.d.estr; + } else if(!strcmp(pblkConstant.descr[i].name, "outname")) { + outname = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("template:constantTpe: program error, non-handled " + "param '%s'\n", pblkConstant.descr[i].name); + } + } + + /* sanity check */ + + /* apply */ + CHKmalloc(pTpe = tpeConstruct(pTpl)); + es_unescapeStr(value); + pTpe->eEntryType = CONSTANT; + pTpe->fieldName = outname; + if(outname != NULL) + pTpe->lenFieldName = ustrlen(outname); + pTpe->data.constant.iLenConstant = es_strlen(value); + pTpe->data.constant.pConstant = (uchar*)es_str2cstr(value, NULL); + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &pblkConstant); + RETiRet; +} + +static rsRetVal +createPropertyTpe(struct template *pTpl, struct cnfobj *o) +{ + struct templateEntry *pTpe; + cstr_t *name = NULL; + uchar *outname = NULL; + int i; + int droplastlf = 0; + int spifno1stsp = 0; + int mandatory = 0; + int frompos = -1; + int topos = -1; + int fieldnum = -1; + int fielddelim = 9; /* default is HT (USACSII 9) */ + int re_matchToUse = 0; + int re_submatchToUse = 0; + int bComplexProcessing = 0; + int bPosRelativeToEnd = 0; + char *re_expr = NULL; + struct cnfparamvals *pvals = NULL; + enum {F_NONE, F_CSV, F_JSON, F_JSONF} formatType = F_NONE; + enum {CC_NONE, CC_ESCAPE, CC_SPACE, CC_DROP} controlchr = CC_NONE; + enum {SP_NONE, SP_DROP, SP_REPLACE} secpath = SP_NONE; + enum tplFormatCaseConvTypes caseconv = tplCaseConvNo; + enum tplFormatTypes datefmt = tplFmtDefault; + enum tplRegexType re_type = TPL_REGEX_BRE; + enum tlpRegexNoMatchType re_nomatchType = TPL_REGEX_NOMATCH_USE_DFLTSTR; + DEFiRet; + + /* pull params */ + pvals = nvlstGetParams(o->nvlst, &pblkProperty, NULL); + cnfparamsPrint(&pblkProperty, pvals); + + for(i = 0 ; i < pblkProperty.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblkProperty.descr[i].name, "name")) { + uchar *tmpstr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + rsCStrConstructFromszStr(&name, tmpstr); + cstrFinalize(name); + free(tmpstr); + } else if(!strcmp(pblkProperty.descr[i].name, "droplastlf")) { + droplastlf = pvals[i].val.d.n; + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "mandatory")) { + mandatory = pvals[i].val.d.n; + } else if(!strcmp(pblkProperty.descr[i].name, "spifno1stsp")) { + spifno1stsp = pvals[i].val.d.n; + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "outname")) { + outname = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblkProperty.descr[i].name, "position.from")) { + frompos = pvals[i].val.d.n; + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "position.to")) { + topos = pvals[i].val.d.n; + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "position.relativetoend")) { + bPosRelativeToEnd = pvals[i].val.d.n; + } else if(!strcmp(pblkProperty.descr[i].name, "field.number")) { + fieldnum = pvals[i].val.d.n; + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "field.delimiter")) { + fielddelim = pvals[i].val.d.n; + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "regex.expression")) { + re_expr = es_str2cstr(pvals[i].val.d.estr, NULL); + bComplexProcessing = 1; + } else if(!strcmp(pblkProperty.descr[i].name, "regex.type")) { + bComplexProcessing = 1; + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"BRE", sizeof("BRE")-1)) { + re_type = TPL_REGEX_BRE; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"ERE", sizeof("ERE")-1)) { + re_type = TPL_REGEX_ERE; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid regex.type '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblkProperty.descr[i].name, "regex.nomatchmode")) { + bComplexProcessing = 1; + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"DFLT", sizeof("DFLT")-1)) { + re_nomatchType = TPL_REGEX_NOMATCH_USE_DFLTSTR; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"BLANK", sizeof("BLANK")-1)) { + re_nomatchType = TPL_REGEX_NOMATCH_USE_BLANK; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"FIELD", sizeof("FIELD")-1)) { + re_nomatchType = TPL_REGEX_NOMATCH_USE_WHOLE_FIELD; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"ZERO", sizeof("ZERO")-1)) { + re_nomatchType = TPL_REGEX_NOMATCH_USE_ZERO; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid format type '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblkProperty.descr[i].name, "regex.match")) { + bComplexProcessing = 1; + re_matchToUse = pvals[i].val.d.n; + } else if(!strcmp(pblkProperty.descr[i].name, "regex.submatch")) { + bComplexProcessing = 1; + re_submatchToUse = pvals[i].val.d.n; + } else if(!strcmp(pblkProperty.descr[i].name, "format")) { + bComplexProcessing = 1; + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"csv", sizeof("csv")-1)) { + formatType = F_CSV; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"json", sizeof("json")-1)) { + formatType = F_JSON; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"jsonf", sizeof("jsonf")-1)) { + formatType = F_JSONF; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid format type '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblkProperty.descr[i].name, "controlcharacters")) { + bComplexProcessing = 1; + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"escape", sizeof("escape")-1)) { + controlchr = CC_ESCAPE; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"space", sizeof("space")-1)) { + controlchr = CC_SPACE; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"drop", sizeof("drop")-1)) { + controlchr = CC_DROP; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid controlcharacter mode '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblkProperty.descr[i].name, "securepath")) { + bComplexProcessing = 1; + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"drop", sizeof("drop")-1)) { + secpath = SP_DROP; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"replace", sizeof("replace")-1)) { + secpath = SP_REPLACE; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid securepath mode '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblkProperty.descr[i].name, "caseconversion")) { + bComplexProcessing = 1; + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"lower", sizeof("lower")-1)) { + caseconv = tplCaseConvLower; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"upper", sizeof("upper")-1)) { + caseconv = tplCaseConvUpper; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid caseconversion type '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblkProperty.descr[i].name, "dateformat")) { + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"mysql", sizeof("mysql")-1)) { + datefmt = tplFmtMySQLDate; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"pgsql", sizeof("pgsql")-1)) { + datefmt = tplFmtPgSQLDate; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"rfc3164", sizeof("rfc3164")-1)) { + datefmt = tplFmtRFC3164Date; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"rfc3164-buggyday", sizeof("rfc3164-buggyday")-1)) { + datefmt = tplFmtRFC3164BuggyDate; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"rfc3339", sizeof("rfc3339")-1)) { + datefmt = tplFmtRFC3339Date; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"unixtimestamp", sizeof("unixtimestamp")-1)) { + datefmt = tplFmtUnixDate; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"subseconds", sizeof("subseconds")-1)) { + datefmt = tplFmtSecFrac; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid date format '%s' for property", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + dbgprintf("template:propertyTpe: program error, non-handled " + "param '%s'\n", pblkProperty.descr[i].name); + } + } + if(outname == NULL) { + uchar *psz = cstrGetSzStrNoNULL(name); + /* we need to drop "$!" prefix, if present */ + if(!strncmp((char*)psz, "$!", 2)) + outname = ustrdup(psz + 2); + else + outname = ustrdup(psz); + } + + /* sanity check */ + if(topos == -1 && frompos != -1) + topos = 2000000000; /* large enough ;) */ + if(frompos == -1 && topos != -1) + frompos = 0; + if(bPosRelativeToEnd) { + if(topos > frompos) { + errmsg.LogError(0, RS_RET_ERR, "position.to=%d is higher than postion.from=%d in 'relativeToEnd' mode\n", + topos, frompos); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + if(topos < frompos) { + errmsg.LogError(0, RS_RET_ERR, "position.to=%d is lower than postion.from=%d\n", + topos, frompos); + ABORT_FINALIZE(RS_RET_ERR); + } + } + if(fieldnum != -1 && re_expr != NULL) { + errmsg.LogError(0, RS_RET_ERR, "both field extraction and regex extraction " + "specified - this is not possible, remove one"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* apply */ + CHKmalloc(pTpe = tpeConstruct(pTpl)); + pTpe->eEntryType = FIELD; + CHKiRet(propNameToID(name, &pTpe->data.field.propid)); + if(pTpe->data.field.propid == PROP_CEE) { + /* in CEE case, we need to preserve the actual property name */ + pTpe->data.field.propName = es_newStrFromCStr((char*)cstrGetSzStrNoNULL(name)+1, + cstrLen(name)-1); + } + pTpe->data.field.options.bDropLastLF = droplastlf; + pTpe->data.field.options.bSPIffNo1stSP = spifno1stsp; + pTpe->data.field.options.bMandatory = mandatory; + pTpe->data.field.eCaseConv = caseconv; + switch(formatType) { + case F_NONE: + /* all set ;) */ + break; + case F_CSV: + pTpe->data.field.options.bCSV = 1; + break; + case F_JSON: + pTpe->data.field.options.bJSON = 1; + break; + case F_JSONF: + pTpe->data.field.options.bJSONf = 1; + break; + } + switch(controlchr) { + case CC_NONE: + /* all set ;) */ + break; + case CC_ESCAPE: + pTpe->data.field.options.bEscapeCC = 1; + break; + case CC_SPACE: + pTpe->data.field.options.bSpaceCC = 1; + break; + case CC_DROP: + pTpe->data.field.options.bDropCC = 1; + break; + } + switch(secpath) { + case SP_NONE: + /* all set ;) */ + break; + case SP_DROP: + pTpe->data.field.options.bSecPathDrop = 1; + break; + case SP_REPLACE: + pTpe->data.field.options.bSecPathReplace = 1; + break; + } + pTpe->fieldName = outname; + if(outname != NULL) + pTpe->lenFieldName = ustrlen(outname); + pTpe->bComplexProcessing = bComplexProcessing; + pTpe->data.field.eDateFormat = datefmt; + if(fieldnum != -1) { + pTpe->data.field.has_fields = 1; + pTpe->data.field.iFieldNr = fieldnum; + pTpe->data.field.field_delim = fielddelim; + } + if(frompos != -1) { + pTpe->data.field.iFromPos = frompos; + pTpe->data.field.iToPos = topos; + pTpe->data.field.options.bFromPosEndRelative = bPosRelativeToEnd; + } + if(re_expr != NULL) { + rsRetVal iRetLocal; + pTpe->data.field.typeRegex = re_type; + pTpe->data.field.nomatchAction = re_nomatchType; + pTpe->data.field.iMatchToUse = re_matchToUse; + pTpe->data.field.iSubMatchToUse = re_submatchToUse; + pTpe->data.field.has_regex = 1; + if((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { + int iOptions; + iOptions = (pTpe->data.field.typeRegex == TPL_REGEX_ERE) ? REG_EXTENDED : 0; + if(regexp.regcomp(&(pTpe->data.field.re), (char*) re_expr, iOptions) != 0) { + dbgprintf("error: can not compile regex: '%s'\n", re_expr); + errmsg.LogError(0, NO_ERRCODE, "error compiling regex '%s'", re_expr); + pTpe->data.field.has_regex = 2; + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + /* regexp object could not be loaded */ + if(bFirstRegexpErrmsg) { /* prevent flood of messages, maybe even an endless loop! */ + bFirstRegexpErrmsg = 0; + errmsg.LogError(0, NO_ERRCODE, "regexp library could not be loaded (error %d), " + "regexp ignored", iRetLocal); + } + pTpe->data.field.has_regex = 2; + ABORT_FINALIZE(RS_RET_ERR); + } + } + +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &pblkProperty); + if(name != NULL) + rsCStrDestruct(&name); + RETiRet; +} + +/* create a template in list mode, is build from sub-objects */ +static rsRetVal +createListTpl(struct template *pTpl, struct cnfobj *o) +{ + struct objlst *lst; + DEFiRet; + + dbgprintf("create template from subobjs\n"); + objlstPrint(o->subobjs); + + for(lst = o->subobjs ; lst != NULL ; lst = lst->next) { + switch(lst->obj->objType) { + case CNFOBJ_PROPERTY: + CHKiRet(createPropertyTpe(pTpl, lst->obj)); + break; + case CNFOBJ_CONSTANT: + CHKiRet(createConstantTpe(pTpl, lst->obj)); + break; + default:dbgprintf("program error: invalid object type %d " + "in createLstTpl\n", lst->obj->objType); + break; + } + nvlstChkUnused(lst->obj->nvlst); + } +finalize_it: + RETiRet; +} + +/* Add a new template via the v6 config system. */ +rsRetVal +tplProcessCnf(struct cnfobj *o) +{ + struct template *pTpl = NULL; + struct cnfparamvals *pvals = NULL; + int lenName = 0; /* init just to keep compiler happy: mandatory parameter */ + char *name = NULL; + uchar *tplStr = NULL; + uchar *plugin = NULL; + es_str_t *subtree = NULL; + uchar *p; + enum { T_STRING, T_PLUGIN, T_LIST, T_SUBTREE } + tplType = T_STRING; /* init just to keep compiler happy: mandatory parameter */ + int i; + int o_sql=0, o_stdsql=0, o_json=0; /* options */ + int numopts; + rsRetVal localRet; + DEFiRet; + + pvals = nvlstGetParams(o->nvlst, &pblk, NULL); + cnfparamsPrint(&pblk, pvals); + + for(i = 0 ; i < pblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(pblk.descr[i].name, "name")) { + lenName = es_strlen(pvals[i].val.d.estr); + name = es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblk.descr[i].name, "type")) { + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"string", sizeof("string")-1)) { + tplType = T_STRING; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"plugin", sizeof("plugin")-1)) { + tplType = T_PLUGIN; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"list", sizeof("list")-1)) { + tplType = T_LIST; + } else if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"subtree", sizeof("subtree")-1)) { + tplType = T_SUBTREE; + } else { + uchar *typeStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid template type '%s'", + typeStr); + free(typeStr); + ABORT_FINALIZE(RS_RET_ERR); + } + } else if(!strcmp(pblk.descr[i].name, "string")) { + tplStr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblk.descr[i].name, "subtree")) { + uchar *st_str = es_getBufAddr(pvals[i].val.d.estr); + if(st_str[0] != '$' || st_str[1] != '!') { + char *cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_ERR, "invalid subtree " + "parameter, variable must start with '$!' but " + "var name is '%s'", cstr); + free(cstr); + free(name); /* overall assigned */ + ABORT_FINALIZE(RS_RET_ERR); + } else { + /* TODO: unify strings! */ + char *cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + subtree = es_newStrFromBuf(cstr+1, es_strlen(pvals[i].val.d.estr)-1); + free(cstr); + } + } else if(!strcmp(pblk.descr[i].name, "plugin")) { + plugin = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(pblk.descr[i].name, "option.stdsql")) { + o_stdsql = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "option.sql")) { + o_sql = pvals[i].val.d.n; + } else if(!strcmp(pblk.descr[i].name, "option.json")) { + o_json = pvals[i].val.d.n; + } else { + dbgprintf("template: program error, non-handled " + "param '%s'\n", pblk.descr[i].name); + } + } + + /* do config sanity checks */ + if(tplStr == NULL) { + if(tplType == T_STRING) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' of type string needs " + "string parameter", name); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + if(tplType != T_STRING) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' is not a string " + "template but has a string specified - ignored", name); + } + } + + if(plugin == NULL) { + if(tplType == T_PLUGIN) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' of type plugin needs " + "plugin parameter", name); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + if(tplType != T_PLUGIN) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' is not a plugin " + "template but has a plugin specified - ignored", name); + } + } + + if(subtree == NULL) { + if(tplType == T_SUBTREE) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' of type subtree needs " + "subtree parameter", name); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + if(tplType != T_SUBTREE) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' is not a subtree " + "template but has a subtree specified - ignored", name); + } + } + + if(o->subobjs == NULL) { + if(tplType == T_LIST) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' of type list has " + "has no parameters specified", name); + ABORT_FINALIZE(RS_RET_ERR); + } + } else { + if(tplType != T_LIST) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' is not a list " + "template but has parameters specified - ignored", name); + } + } + + numopts = 0; + if(o_sql) ++numopts; + if(o_stdsql) ++numopts; + if(o_json) ++numopts; + if(numopts > 1) { + errmsg.LogError(0, RS_RET_ERR, "template '%s' has multiple incompatible " + "options of sql, stdsql or json specified", name); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* config ok */ + if((pTpl = tplConstruct(loadConf)) == NULL) { + DBGPRINTF("template.c: tplConstruct failed!\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + pTpl->pszName = name; + pTpl->iLenName = lenName; + + switch(tplType) { + case T_STRING: p = tplStr; + while(*p) { + switch(*p) { + case '%': /* parameter */ + ++p; /* eat '%' */ + CHKiRet(do_Parameter(&p, pTpl)); + break; + default: /* constant */ + do_Constant(&p, pTpl, 0); + break; + } + } + break; + case T_PLUGIN: p = plugin; + /* TODO: the use of tplAddTplMod() can be improved! */ + localRet = tplAddTplMod(pTpl, &p); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "template '%s': error %d " + "defining template via plugin (strgen) module", + pTpl->pszName, localRet); + ABORT_FINALIZE(localRet); + } + break; + case T_LIST: createListTpl(pTpl, o); + break; + case T_SUBTREE: pTpl->subtree = subtree; + break; + } + + pTpl->optFormatEscape = NO_ESCAPE; + if(o_stdsql) + pTpl->optFormatEscape = STDSQL_ESCAPE; + else if(o_sql) + pTpl->optFormatEscape = SQL_ESCAPE; + else if(o_json) + pTpl->optFormatEscape = JSON_ESCAPE; + +finalize_it: + free(tplStr); + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &pblk); + if(iRet != RS_RET_OK) { + if(pTpl != NULL) { + /* we simply make the template defunct in this case by setting + * its name to a zero-string. We do not free it, as this would + * require additional code and causes only a very small memory + * consumption. TODO: maybe in next iteration... + */ + *pTpl->pszName = '\0'; + } + } + + RETiRet; +} + + +/* Find a template object based on name. Search + * currently is case-senstive (should we change?). + * returns pointer to template object if found and + * NULL otherwise. + * rgerhards 2004-11-17 + */ +struct template *tplFind(rsconf_t *conf, char *pName, int iLenName) +{ + struct template *pTpl; + + assert(pName != NULL); + + pTpl = conf->templates.root; + while(pTpl != NULL && + !(pTpl->iLenName == iLenName && + !strcmp(pTpl->pszName, pName) + )) + { + pTpl = pTpl->pNext; + } + return(pTpl); +} + +/* Destroy the template structure. This is for de-initialization + * at program end. Everything is deleted. + * rgerhards 2005-02-22 + * I have commented out dbgprintfs, because they are not needed for + * "normal" debugging. Uncomment them, if they are needed. + * rgerhards, 2007-07-05 + */ +void tplDeleteAll(rsconf_t *conf) +{ + struct template *pTpl, *pTplDel; + struct templateEntry *pTpe, *pTpeDel; + BEGINfunc + + pTpl = conf->templates.root; + while(pTpl != NULL) { + /* dbgprintf("Delete Template: Name='%s'\n ", pTpl->pszName == NULL? "NULL" : pTpl->pszName);*/ + pTpe = pTpl->pEntryRoot; + while(pTpe != NULL) { + pTpeDel = pTpe; + pTpe = pTpe->pNext; + /*dbgprintf("\tDelete Entry(%x): type %d, ", (unsigned) pTpeDel, pTpeDel->eEntryType);*/ + switch(pTpeDel->eEntryType) { + case UNDEFINED: + /*dbgprintf("(UNDEFINED)");*/ + break; + case CONSTANT: + /*dbgprintf("(CONSTANT), value: '%s'", + pTpeDel->data.constant.pConstant);*/ + free(pTpeDel->data.constant.pConstant); + break; + case FIELD: + /* check if we have a regexp and, if so, delete it */ +#ifdef FEATURE_REGEXP + if(pTpeDel->data.field.has_regex != 0) { + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + regexp.regfree(&(pTpeDel->data.field.re)); + } + } + if(pTpeDel->data.field.propName != NULL) + es_deleteStr(pTpeDel->data.field.propName); +#endif + break; + } + free(pTpeDel->fieldName); + /*dbgprintf("\n");*/ + free(pTpeDel); + } + pTplDel = pTpl; + pTpl = pTpl->pNext; + free(pTplDel->pszName); + if(pTplDel->subtree != NULL) + es_deleteStr(pTplDel->subtree); + free(pTplDel); + } + ENDfunc +} + + +/* Destroy all templates obtained from conf file + * preserving hardcoded ones. This is called from init(). + */ +void tplDeleteNew(rsconf_t *conf) +{ + struct template *pTpl, *pTplDel; + struct templateEntry *pTpe, *pTpeDel; + + BEGINfunc + + if(conf->templates.root == NULL || conf->templates.lastStatic == NULL) + return; + + pTpl = conf->templates.lastStatic->pNext; + conf->templates.lastStatic->pNext = NULL; + conf->templates.last = conf->templates.lastStatic; + while(pTpl != NULL) { + /* dbgprintf("Delete Template: Name='%s'\n ", pTpl->pszName == NULL? "NULL" : pTpl->pszName);*/ + pTpe = pTpl->pEntryRoot; + while(pTpe != NULL) { + pTpeDel = pTpe; + pTpe = pTpe->pNext; + /*dbgprintf("\tDelete Entry(%x): type %d, ", (unsigned) pTpeDel, pTpeDel->eEntryType);*/ + switch(pTpeDel->eEntryType) { + case UNDEFINED: + /*dbgprintf("(UNDEFINED)");*/ + break; + case CONSTANT: + /*dbgprintf("(CONSTANT), value: '%s'", + pTpeDel->data.constant.pConstant);*/ + free(pTpeDel->data.constant.pConstant); + break; + case FIELD: +#ifdef FEATURE_REGEXP + /* check if we have a regexp and, if so, delete it */ + if(pTpeDel->data.field.has_regex != 0) { + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + regexp.regfree(&(pTpeDel->data.field.re)); + } + } + if(pTpeDel->data.field.propName != NULL) + es_deleteStr(pTpeDel->data.field.propName); +#endif + break; + } + /*dbgprintf("\n");*/ + free(pTpeDel); + } + pTplDel = pTpl; + pTpl = pTpl->pNext; + free(pTplDel->pszName); + if(pTplDel->subtree != NULL) + es_deleteStr(pTplDel->subtree); + free(pTplDel); + } + ENDfunc +} + +/* Store the pointer to the last hardcoded teplate */ +void tplLastStaticInit(rsconf_t *conf, struct template *tpl) +{ + conf->templates.lastStatic = tpl; +} + +/* Print the template structure. This is more or less a + * debug or test aid, but anyhow I think it's worth it... + */ +void tplPrintList(rsconf_t *conf) +{ + struct template *pTpl; + struct templateEntry *pTpe; + + pTpl = conf->templates.root; + while(pTpl != NULL) { + dbgprintf("Template: Name='%s' ", pTpl->pszName == NULL? "NULL" : pTpl->pszName); + if(pTpl->optFormatEscape == SQL_ESCAPE) + dbgprintf("[SQL-Format (MySQL)] "); + else if(pTpl->optFormatEscape == JSON_ESCAPE) + dbgprintf("[JSON-Escaped Format] "); + else if(pTpl->optFormatEscape == STDSQL_ESCAPE) + dbgprintf("[SQL-Format (standard SQL)] "); + dbgprintf("\n"); + pTpe = pTpl->pEntryRoot; + while(pTpe != NULL) { + dbgprintf("\tEntry(%lx): type %d, ", (unsigned long) pTpe, pTpe->eEntryType); + switch(pTpe->eEntryType) { + case UNDEFINED: + dbgprintf("(UNDEFINED)"); + break; + case CONSTANT: + dbgprintf("(CONSTANT), value: '%s'", + pTpe->data.constant.pConstant); + break; + case FIELD: + dbgprintf("(FIELD), value: '%d' ", pTpe->data.field.propid); + if(pTpe->data.field.propid == PROP_CEE) { + char *cstr = es_str2cstr(pTpe->data.field.propName, NULL); + dbgprintf("[EE-Property: '%s'] ", cstr); + free(cstr); + } + switch(pTpe->data.field.eDateFormat) { + case tplFmtDefault: + break; + case tplFmtMySQLDate: + dbgprintf("[Format as MySQL-Date] "); + break; + case tplFmtPgSQLDate: + dbgprintf("[Format as PgSQL-Date] "); + break; + case tplFmtRFC3164Date: + dbgprintf("[Format as RFC3164-Date] "); + break; + case tplFmtRFC3339Date: + dbgprintf("[Format as RFC3339-Date] "); + break; + case tplFmtUnixDate: + dbgprintf("[Format as Unix timestamp] "); + break; + case tplFmtSecFrac: + dbgprintf("[fractional seconds, only] "); + break; + case tplFmtRFC3164BuggyDate: + dbgprintf("[Format as buggy RFC3164-Date] "); + break; + default: + dbgprintf("[UNKNOWN eDateFormat %d] ", pTpe->data.field.eDateFormat); + } + switch(pTpe->data.field.eCaseConv) { + case tplCaseConvNo: + break; + case tplCaseConvLower: + dbgprintf("[Converted to Lower Case] "); + break; + case tplCaseConvUpper: + dbgprintf("[Converted to Upper Case] "); + break; + } + if(pTpe->data.field.options.bEscapeCC) { + dbgprintf("[escape control-characters] "); + } + if(pTpe->data.field.options.bDropCC) { + dbgprintf("[drop control-characters] "); + } + if(pTpe->data.field.options.bSpaceCC) { + dbgprintf("[replace control-characters with space] "); + } + if(pTpe->data.field.options.bSecPathDrop) { + dbgprintf("[slashes are dropped] "); + } + if(pTpe->data.field.options.bSecPathReplace) { + dbgprintf("[slashes are replaced by '_'] "); + } + if(pTpe->data.field.options.bSPIffNo1stSP) { + dbgprintf("[SP iff no first SP] "); + } + if(pTpe->data.field.options.bCSV) { + dbgprintf("[format as CSV (RFC4180)]"); + } + if(pTpe->data.field.options.bJSON) { + dbgprintf("[format as JSON] "); + } + if(pTpe->data.field.options.bJSONf) { + dbgprintf("[format as JSON field] "); + } + if(pTpe->data.field.options.bMandatory) { + dbgprintf("[mandatory field] "); + } + if(pTpe->data.field.options.bDropLastLF) { + dbgprintf("[drop last LF in msg] "); + } + if(pTpe->data.field.has_fields == 1) { + dbgprintf("[substring, field #%d only (delemiter %d)] ", + pTpe->data.field.iFieldNr, pTpe->data.field.field_delim); + } + if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { + dbgprintf("[substring, from character %d to %d] ", + pTpe->data.field.iFromPos, + pTpe->data.field.iToPos); + } + break; + } + if(pTpe->bComplexProcessing) + dbgprintf("[COMPLEX]"); + dbgprintf("\n"); + pTpe = pTpe->pNext; + } + pTpl = pTpl->pNext; /* done, go next */ + } +} + +int tplGetEntryCount(struct template *pTpl) +{ + assert(pTpl != NULL); + return(pTpl->tpenElements); +} + +rsRetVal templateInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(strgen, CORE_COMPONENT)); + +finalize_it: + RETiRet; +} diff --git a/template.h b/template.h new file mode 100644 index 00000000..318db6f8 --- /dev/null +++ b/template.h @@ -0,0 +1,163 @@ +/* This is the header for template processing code of rsyslog. + * begun 2004-11-17 rgerhards + * + * Copyright (C) 2004-2012 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Note: there is a tiny bit of code left where I could not get any response + * from the author if this code can be placed under ASL2.0. I have guarded this + * with #ifdef STRICT_GPLV3. Only if that macro is defined, the code will be + * compiled. Otherwise this feature is not present. The plan is to do a + * different implementation in the future to get rid of this problem. + * rgerhards, 2012-08-25 + */ + +#ifndef TEMPLATE_H_INCLUDED +#define TEMPLATE_H_INCLUDED 1 + +#include <json/json.h> +#include <libestr.h> +#include "regexp.h" +#include "stringbuf.h" + +struct template { + struct template *pNext; + char *pszName; + int iLenName; + rsRetVal (*pStrgen)(msg_t*, uchar**, size_t *); + es_str_t *subtree; /* subtree name for subtree-type templates */ + int tpenElements; /* number of elements in templateEntry list */ + struct templateEntry *pEntryRoot; + struct templateEntry *pEntryLast; + char optFormatEscape; /* in text fields, */ +# define NO_ESCAPE 0 /* 0 - do not escape, */ +# define SQL_ESCAPE 1 /* 1 - escape "the MySQL way" */ +# define STDSQL_ESCAPE 2 /* 2 - escape quotes by double quotes, */ +# define JSON_ESCAPE 3 /* 3 - escape double quotes for JSON. */ + /* following are options. All are 0/1 defined (either on or off). + * we use chars because they are faster than bit fields and smaller + * than short... + */ +}; + +enum EntryTypes { UNDEFINED = 0, CONSTANT = 1, FIELD = 2 }; +enum tplFormatTypes { tplFmtDefault = 0, tplFmtMySQLDate = 1, + tplFmtRFC3164Date = 2, tplFmtRFC3339Date = 3, tplFmtPgSQLDate = 4, + tplFmtSecFrac = 5, tplFmtRFC3164BuggyDate = 6, tplFmtUnixDate}; +enum tplFormatCaseConvTypes { tplCaseConvNo = 0, tplCaseConvUpper = 1, tplCaseConvLower = 2 }; +enum tplRegexType { TPL_REGEX_BRE = 0, /* posix BRE */ + TPL_REGEX_ERE = 1 /* posix ERE */ + }; + +#include "msg.h" + +/* a specific parse entry */ +struct templateEntry { + struct templateEntry *pNext; + enum EntryTypes eEntryType; + uchar *fieldName; /**< field name to be used for structured output */ + int lenFieldName; + sbool bComplexProcessing; /**< set if complex processing (options, etc) is required */ + union { + struct { + uchar *pConstant; /* pointer to constant value */ + int iLenConstant; /* its length */ + } constant; + struct { + propid_t propid; /* property to be used */ + unsigned iFromPos; /* for partial strings only chars from this position ... */ + unsigned iToPos; /* up to that one... */ + unsigned iFieldNr; /* for field extraction: field to extract */ +#ifdef FEATURE_REGEXP + regex_t re; /* APR: this is the regular expression */ + short has_regex; + short iMatchToUse;/* which match should be obtained (10 max) */ + short iSubMatchToUse;/* which submatch should be obtained (10 max) */ + enum tplRegexType typeRegex; + enum tlpRegexNoMatchType { + TPL_REGEX_NOMATCH_USE_DFLTSTR = 0, /* use the (old style) default "**NO MATCH**" string */ + TPL_REGEX_NOMATCH_USE_BLANK = 1, /* use a blank string */ + TPL_REGEX_NOMATCH_USE_WHOLE_FIELD = 2, /* use the full field contents that we were searching in*/ + TPL_REGEX_NOMATCH_USE_ZERO = 3 /* use 0 (useful for numerical values) */ + } nomatchAction; /**< what to do if we do not have a match? */ + +#endif + unsigned has_fields; /* support for field-counting: field to extract */ + unsigned char field_delim; /* support for field-counting: field delemiter char */ +#ifdef STRICT_GPLV3 + int field_expand; /* use multiple instances of the field delimiter as a single one? */ +#endif + + es_str_t *propName; /**< property name (currently being used for CEE only) */ + + enum tplFormatTypes eDateFormat; + enum tplFormatCaseConvTypes eCaseConv; + struct { /* bit fields! */ + unsigned bDropCC: 1; /* drop control characters? */ + unsigned bSpaceCC: 1; /* change control characters to spaceescape? */ + unsigned bEscapeCC: 1; /* escape control characters? */ + unsigned bDropLastLF: 1; /* drop last LF char in msg (PIX!) */ + unsigned bSecPathDrop: 1; /* drop slashes, replace dots, empty string */ + unsigned bSecPathReplace: 1; /* replace slashes, replace dots, empty string */ + unsigned bSPIffNo1stSP: 1; /* be a space if 1st pos if field is no space*/ + unsigned bCSV: 1; /* format field in CSV (RFC 4180) format */ + unsigned bJSON: 1; /* format field JSON escaped */ + unsigned bJSONf: 1; /* format field JSON *field* (n/v pair) */ + unsigned bMandatory: 1; /* mandatory field - emit even if empty */ + unsigned bFromPosEndRelative: 1;/* is From/To-Pos relative to end of string? */ + } options; /* options as bit fields */ + } field; + } data; +}; + + +/* interfaces */ +BEGINinterface(tpl) /* name must also be changed in ENDinterface macro! */ +ENDinterface(tpl) +#define tplCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(tpl); + + +//struct template* tplConstruct(void); +struct template *tplAddLine(rsconf_t *conf, char* pName, unsigned char** pRestOfConfLine); +struct template *tplFind(rsconf_t *conf, char *pName, int iLenName); +int tplGetEntryCount(struct template *pTpl); +void tplDeleteAll(rsconf_t *conf); +void tplDeleteNew(rsconf_t *conf); +void tplPrintList(rsconf_t *conf); +void tplLastStaticInit(rsconf_t *conf, struct template *tpl); +rsRetVal ExtendBuf(uchar **pBuf, size_t *pLenBuf, size_t iMinSize); +int tplRequiresDateCall(struct template *pTpl); +/* note: if a compiler warning for undefined type tells you to look at this + * code line below, the actual cause is that you currently MUST include template.h + * BEFORE msg.h, even if your code file does not actually need it. + * rgerhards, 2007-08-06 + */ +rsRetVal tplToArray(struct template *pTpl, msg_t *pMsg, uchar*** ppArr, struct syslogTime *ttNow); +rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz, size_t *, struct syslogTime *ttNow); +rsRetVal tplToJSON(struct template *pTpl, msg_t *pMsg, struct json_object **, struct syslogTime *ttNow); +rsRetVal doEscape(uchar **pp, rs_size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode); + +rsRetVal templateInit(); +rsRetVal tplProcessCnf(struct cnfobj *o); + +#endif /* #ifndef TEMPLATE_H_INCLUDED */ +/* vim:set ai: + */ diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..a8177ad4 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +rscript +rt_init +tmp diff --git a/tests/1.rstest b/tests/1.rstest new file mode 100644 index 00000000..4716e8b3 --- /dev/null +++ b/tests/1.rstest @@ -0,0 +1,26 @@ +# a simple RainerScript test +result: 0 +in: +'test 1' <> $var or /* some comment */($SEVERITY == -4 +5 -(3 * - 2) and $fromhost == '127.0.0.1') then +$$$ +out: +00000000: push_const test 1[cstr] +00000001: push_msgvar var[cstr] +00000002: cmp_!= +00000003: push_msgvar severity[cstr] +00000004: push_const 4[nbr] +00000005: unary_minus +00000006: push_const 5[nbr] +00000007: add +00000008: push_const 3[nbr] +00000009: push_const 2[nbr] +00000010: unary_minus +00000011: mul +00000012: sub +00000013: cmp_== +00000014: push_msgvar fromhost[cstr] +00000015: push_const 127.0.0.1[cstr] +00000016: cmp_== +00000017: and +00000018: or +$$$ diff --git a/tests/2.rstest b/tests/2.rstest new file mode 100644 index 00000000..f0e8205b --- /dev/null +++ b/tests/2.rstest @@ -0,0 +1,10 @@ +# a simple RainerScript test +result: 0 +in: +$msg contains 'test' then +$$$ +out: +00000000: push_msgvar msg[cstr] +00000001: push_const test[cstr] +00000002: contains +$$$ diff --git a/tests/3.rstest b/tests/3.rstest new file mode 100644 index 00000000..e75d9754 --- /dev/null +++ b/tests/3.rstest @@ -0,0 +1,21 @@ +# a simple RainerScript test +result: 0 +in: +strlen($msg & strlen('abc')) > 20 +30 + -40 then +$$$ +out: +00000000: push_msgvar msg[cstr] +00000001: push_const abc[cstr] +00000002: push_const 1[nbr] +00000003: func_call strlen +00000004: strconcat +00000005: push_const 1[nbr] +00000006: func_call strlen +00000007: push_const 20[nbr] +00000008: push_const 30[nbr] +00000009: add +00000010: push_const 40[nbr] +00000011: unary_minus +00000012: add +00000013: cmp_> +$$$ diff --git a/tests/DevNull.cfgtest b/tests/DevNull.cfgtest new file mode 100644 index 00000000..7822b6df --- /dev/null +++ b/tests/DevNull.cfgtest @@ -0,0 +1,2 @@ +rsyslogd: CONFIG ERROR: there are no active actions configured. Inputs will run, but no output whatsoever is created. [try http://www.rsyslog.com/e/2103 ] +rsyslogd: EMERGENCY CONFIGURATION ACTIVATED - fix rsyslog config file! diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 00000000..b339e797 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,571 @@ +if ENABLE_TESTBENCH +# TODO: reenable TESTRUNS = rt_init rscript +check_PROGRAMS = $(TESTRUNS) ourtail nettester tcpflood chkseq msleep randomgen diagtalker uxsockrcvr syslog_caller syslog_inject inputfilegen minitcpsrv +TESTS = $(TESTRUNS) +#TESTS = $(TESTRUNS) cfg.sh + +if ENABLE_IMDIAG +TESTS += \ + arrayqueue.sh \ + da-mainmsg-q.sh \ + validation-run.sh \ + imtcp-multiport.sh \ + daqueue-persist.sh \ + diskqueue.sh \ + diskqueue-fsync.sh \ + rulesetmultiqueue.sh \ + manytcp.sh \ + rsf_getenv.sh \ + imtcp_conndrop.sh \ + imtcp_addtlframedelim.sh \ + sndrcv.sh \ + sndrcv_failover.sh \ + sndrcv_gzip.sh \ + sndrcv_udp.sh \ + sndrcv_udp_nonstdpt.sh \ + asynwr_simple.sh \ + asynwr_timeout.sh \ + asynwr_small.sh \ + asynwr_tinybuf.sh \ + wr_large_async.sh \ + wr_large_sync.sh \ + asynwr_deadlock.sh \ + asynwr_deadlock2.sh \ + asynwr_deadlock4.sh \ + gzipwr_large.sh \ + gzipwr_large_dynfile.sh \ + dynfile_invld_async.sh \ + dynfile_invld_sync.sh \ + dynfile_invalid2.sh \ + complex1.sh \ + queue-persist.sh \ + pipeaction.sh \ + execonlyonce.sh \ + execonlywhenprevsuspended.sh \ + execonlywhenprevsuspended2.sh \ + execonlywhenprevsuspended3.sh \ + execonlywhenprevsuspended4.sh \ + pipe_noreader.sh \ + dircreate_dflt.sh \ + dircreate_off.sh \ + imuxsock_logger_root.sh \ + imuxsock_traillf_root.sh \ + imuxsock_ccmiddle_root.sh \ + udp-msgreduc-vg.sh \ + udp-msgreduc-orgmsg-vg.sh \ + queue-persist.sh + discard-rptdmsg.sh \ + discard-allmark.sh \ + discard.sh \ + failover-async.sh \ + failover-double.sh \ + failover-basic.sh \ + failover-rptd.sh \ + failover-no-rptd.sh \ + failover-no-basic.sh \ + rcvr_fail_restore.sh \ + rscript_contains.sh \ + rscript_field.sh \ + rscript_stop.sh \ + rscript_stop2.sh \ + rscript_prifilt.sh \ + rscript_optimizer1.sh \ + rscript_ruleset_call.sh \ + cee_simple.sh \ + cee_diskqueue.sh \ + incltest.sh \ + incltest_dir.sh \ + incltest_dir_wildcard.sh \ + incltest_dir_empty_wildcard.sh \ + linkedlistqueue.sh + +if HAVE_VALGRIND +TESTS += \ + discard-rptdmsg-vg.sh \ + discard-allmark-vg.sh \ + failover-basic-vg.sh \ + failover-rptd-vg.sh \ + failover-no-basic-vg.sh \ + failover-no-rptd-vg.sh \ + tcp-msgreduc-vg.sh +endif # HAVE_VALGRIND +endif # ENABLE_IMDIAG + + +if ENABLE_MYSQL_TESTS +TESTS += \ + mysql-basic.sh \ + mysql-basic-cnf6.sh \ + mysql-asyn.sh +if ENABLE_OMLIBDBI +TESTS += \ + libdbi-basic.sh \ + libdbi-asyn.sh +endif +if HAVE_VALGRIND +TESTS += \ + mysql-basic-vg.sh \ + mysql-asyn-vg.sh +endif +endif + +if ENABLE_IMPTCP +TESTS += \ + manyptcp.sh \ + imptcp_large.sh \ + imptcp_addtlframedelim.sh \ + imptcp_conndrop.sh +endif + +if ENABLE_GNUTLS +# TODO: re-enable in newer version +#TESTS += \ + #sndrcv_tls_anon.sh \ + #sndrcv_tls_anon_rebind.sh \ + #imtcp-tls-basic.sh +if HAVE_VALGRIND +TESTS += imtcp-tls-basic-vg.sh \ + imtcp_conndrop_tls-vg.sh + manytcp-too-few-tls-vg.sh +endif +endif + +if ENABLE_OMUXSOCK +TESTS += uxsock_simple.sh +endif + +if ENABLE_OMUDPSPOOF +TESTS += sndrcv_omudpspoof.sh \ + sndrcv_omudpspoof_nonstdpt.sh +endif + +if ENABLE_OMSTDOUT +TESTS += omod-if-array.sh \ + proprepltest.sh \ + parsertest.sh \ + timestamp.sh \ + inputname.sh \ + threadingmq.sh \ + threadingmqaq.sh \ + badqi.sh \ + tabescape_dflt.sh \ + tabescape_off.sh \ + fieldtest.sh +endif + +if ENABLE_OMRULESET +if ENABLE_IMDIAG +TESTS += omruleset.sh \ + omruleset-queue.sh +endif +endif + +if ENABLE_EXTENDED_TESTS +# random.sh is temporarily disabled as it needs some work +# to rsyslog core to complete in reasonable time +#TESTS += random.sh +endif + +if ENABLE_IMFILE +TESTS += imfile-basic.sh +if HAVE_VALGRIND +TESTS += imfile-basic-vg.sh +endif +endif + +endif # if ENABLE_TESTBENCH + +TESTS_ENVIRONMENT = RSYSLOG_MODDIR='$(abs_top_builddir)'/runtime/.libs/ +DISTCLEANFILES=rsyslog.pid +test_files = testbench.h runtime-dummy.c + +EXTRA_DIST= 1.rstest 2.rstest 3.rstest err1.rstest \ + validation-run.sh \ + tls-certs/ca-key.pem \ + tls-certs/ca.pem \ + tls-certs/cert.pem \ + tls-certs/key.pem \ + testsuites/x.509/ca.pem \ + testsuites/x.509/ca-key.pem \ + testsuites/x.509/client-cert.pem \ + testsuites/x.509/client-key.pem \ + testsuites/x.509/machine-cert.pem \ + testsuites/x.509/machine-key.pem \ + testsuites/invalid.conf \ + testsuites/valid.conf \ + cfg.sh \ + cfg1.cfgtest \ + cfg1.testin \ + cfg2.cfgtest \ + cfg2.testin \ + cfg3.cfgtest \ + cfg3.testin \ + cfg4.cfgtest \ + cfg4.testin \ + DevNull.cfgtest \ + err1.rstest \ + NoExistFile.cfgtest \ + timestamp.sh \ + testsuites/ts3164.conf \ + testsuites/mon1digit.ts3164 \ + testsuites/mon2digit.ts3164 \ + testsuites/Jan.ts3164 \ + testsuites/Feb.ts3164 \ + testsuites/Mar.ts3164 \ + testsuites/Apr.ts3164 \ + testsuites/May.ts3164 \ + testsuites/Jun.ts3164 \ + testsuites/Jul.ts3164 \ + testsuites/Aug.ts3164 \ + testsuites/Sep.ts3164 \ + testsuites/Oct.ts3164 \ + testsuites/Nov.ts3164 \ + testsuites/Dec.ts3164 \ + testsuites/ts3339.conf \ + testsuites/master.ts3339 \ + testsuites/tsmysql.conf \ + testsuites/master.tsmysql \ + testsuites/tspgsql.conf \ + testsuites/master.tspgsql \ + testsuites/subsecond.conf \ + testsuites/master.subsecond \ + testsuites/parse_8bit_escape.conf \ + testsuites/8bit.parse_8bit_escape \ + testsuites/parse1.conf \ + testsuites/field1.conf \ + testsuites/1.parse1 \ + testsuites/2.parse1 \ + testsuites/3.parse1 \ + testsuites/4.parse1 \ + testsuites/mark.parse1 \ + testsuites/8bit.parse1 \ + testsuites/empty.parse1 \ + testsuites/snare.parse1 \ + testsuites/oversizeTag-1.parse1 \ + testsuites/weird.parse1 \ + testsuites/date1.parse1 \ + testsuites/date2.parse1 \ + testsuites/date3.parse1 \ + testsuites/date4.parse1 \ + testsuites/date5.parse1 \ + testsuites/rfc3164.parse1 \ + testsuites/rfc5424-1.parse1 \ + testsuites/rfc5424-2.parse1 \ + testsuites/rfc5424-3.parse1 \ + testsuites/rfc5424-4.parse1 \ + testsuites/malformed1.parse1 \ + testsuites/reallife.parse1 \ + testsuites/parse2.conf \ + testsuites/reallife.parse2 \ + testsuites/parse3.conf \ + testsuites/reallife.parse3 \ + testsuites/parse-nodate.conf \ + testsuites/samples.parse-nodate \ + testsuites/parse_invld_regex.conf \ + testsuites/samples.parse_invld_regex \ + testsuites/parse-3164-buggyday.conf \ + testsuites/samples.parse-3164-buggyday \ + testsuites/snare_ccoff_udp.conf \ + testsuites/samples.snare_ccoff_udp \ + testsuites/snare_ccoff_udp2.conf \ + testsuites/samples.snare_ccoff_udp2 \ + testsuites/omod-if-array.conf \ + testsuites/1.omod-if-array \ + testsuites/1.field1 \ + killrsyslog.sh \ + parsertest.sh \ + fieldtest.sh \ + rsf_getenv.sh \ + testsuites/rsf_getenv.conf \ + diskqueue.sh \ + testsuites/diskqueue.conf \ + arrayqueue.sh \ + testsuites/arrayqueue.conf \ + rscript_contains.sh \ + testsuites/rscript_contains.conf \ + rscript_field.sh \ + testsuites/rscript_field.conf \ + rscript_stop.sh \ + testsuites/rscript_stop.conf \ + rscript_stop2.sh \ + testsuites/rscript_stop2.conf \ + rscript_prifilt.sh \ + testsuites/rscript_prifilt.conf \ + rscript_optimizer1.sh \ + testsuites/rscript_optimizer1.conf \ + rscript_ruleset_call.sh \ + testsuites/rscript_ruleset_call.conf \ + cee_simple.sh \ + testsuites/cee_simple.conf \ + cee_diskqueue.sh \ + testsuites/cee_diskqueue.conf \ + incltest.sh \ + testsuites/incltest.conf \ + incltest_dir.sh \ + testsuites/incltest_dir.conf \ + incltest_dir_empty_wildcard.sh \ + testsuites/incltest_dir_empty_wildcard.conf \ + incltest_dir_wildcard.sh \ + testsuites/incltest_dir_wildcard.conf \ + testsuites/incltest.d/include.conf \ + linkedlistqueue.sh \ + testsuites/linkedlistqueue.conf \ + da-mainmsg-q.sh \ + testsuites/da-mainmsg-q.conf \ + diskqueue-fsync.sh \ + testsuites/diskqueue-fsync.conf \ + imtcp-tls-basic.sh \ + imtcp-tls-basic-vg.sh \ + testsuites/imtcp-tls-basic.conf \ + imtcp-multiport.sh \ + testsuites/imtcp-multiport.conf \ + udp-msgreduc-orgmsg-vg.sh \ + testsuites/udp-msgreduc-orgmsg-vg.conf \ + udp-msgreduc-vg.sh \ + testsuites/udp-msgreduc-vg.conf \ + manytcp-too-few-tls.sh \ + testsuites/manytcp-too-few-tls.conf \ + manytcp.sh \ + testsuites/manytcp.conf \ + manyptcp.sh \ + testsuites/manyptcp.conf \ + imptcp_large.sh \ + testsuites/imptcp_large.conf \ + imptcp_addtlframedelim.sh \ + testsuites/imptcp_addtlframedelim.conf \ + imptcp_conndrop.sh \ + testsuites/imptcp_conndrop.conf \ + imtcp_conndrop.sh \ + testsuites/imtcp_conndrop.conf \ + imtcp_conndrop_tls.sh \ + imtcp_conndrop_tls-vg.sh \ + testsuites/imtcp_conndrop.conf \ + imtcp_addtlframedelim.sh \ + testsuites/imtcp_addtlframedelim.conf \ + tcp-msgreduc-vg.sh \ + testsuites/./tcp-msgreduc-vg.conf \ + inputname.sh \ + testsuites/inputname_imtcp.conf \ + testsuites/1.inputname_imtcp_12514 \ + testsuites/1.inputname_imtcp_12515 \ + testsuites/1.inputname_imtcp_12516 \ + omod-if-array.sh \ + discard.sh \ + testsuites/discard.conf \ + failover-no-rptd.sh \ + failover-no-rptd-vg.sh \ + testsuites/failover-no-rptd.conf \ + failover-no-basic.sh \ + failover-no-basic-vg.sh \ + testsuites/failover-no-basic.conf \ + failover-rptd.sh \ + failover-rptd-vg.sh \ + testsuites/failover-rptd.conf \ + failover-basic.sh \ + failover-basic-vg.sh \ + testsuites/failover-basic.conf \ + failover-async.sh \ + testsuites/failover-async.conf \ + failover-double.sh \ + testsuites/failover-double.conf \ + discard-rptdmsg.sh \ + discard-rptdmsg-vg.sh \ + testsuites/discard-rptdmsg.conf \ + discard-allmark.sh \ + discard-allmark-vg.sh \ + testsuites/discard-allmark.conf \ + diag.sh \ + testsuites/diag-common.conf \ + testsuites/diag-common2.conf \ + rcvr_fail_restore.sh \ + testsuites/rcvr_fail_restore_rcvr.conf \ + testsuites/rcvr_fail_restore_sender.conf \ + daqueue-persist.sh \ + daqueue-persist-drvr.sh \ + queue-persist.sh \ + queue-persist-drvr.sh \ + testsuites/queue-persist.conf \ + threadingmq.sh \ + testsuites/threadingmq.conf \ + threadingmqaq.sh \ + testsuites/threadingmqaq.conf \ + sndrcv_drvr.sh \ + sndrcv_drvr_noexit.sh \ + sndrcv_failover.sh \ + testsuites/sndrcv_failover_sender.conf \ + testsuites/sndrcv_failover_rcvr.conf \ + sndrcv.sh \ + testsuites/sndrcv_sender.conf \ + testsuites/sndrcv_rcvr.conf \ + sndrcv_udp.sh \ + testsuites/sndrcv_udp_sender.conf \ + testsuites/sndrcv_udp_rcvr.conf \ + sndrcv_udp_nonstdpt.sh \ + testsuites/sndrcv_udp_nonstdpt_sender.conf \ + testsuites/sndrcv_udp_nonstdpt_rcvr.conf \ + sndrcv_omudpspoof.sh \ + testsuites/sndrcv_omudpspoof_sender.conf \ + testsuites/sndrcv_omudpspoof_rcvr.conf \ + sndrcv_omudpspoof_nonstdpt.sh \ + testsuites/sndrcv_omudpspoof_nonstdpt_sender.conf \ + testsuites/sndrcv_omudpspoof_nonstdpt_rcvr.conf \ + sndrcv_gzip.sh \ + testsuites/sndrcv_gzip_sender.conf \ + testsuites/sndrcv_gzip_rcvr.conf \ + pipeaction.sh \ + testsuites/pipeaction.conf \ + pipe_noreader.sh \ + testsuites/pipe_noreader.conf \ + uxsock_simple.sh \ + testsuites/uxsock_simple.conf \ + asynwr_simple.sh \ + testsuites/asynwr_simple.conf \ + asynwr_timeout.sh \ + testsuites/asynwr_timeout.conf \ + asynwr_small.sh \ + testsuites/asynwr_small.conf \ + asynwr_tinybuf.sh \ + testsuites/asynwr_tinybuf.conf \ + wr_large_async.sh \ + wr_large_sync.sh \ + wr_large.sh \ + testsuites/wr_large.conf \ + asynwr_deadlock.sh \ + testsuites/asynwr_deadlock.conf \ + asynwr_deadlock2.sh \ + testsuites/asynwr_deadlock2.conf \ + asynwr_deadlock4.sh \ + testsuites/asynwr_deadlock4.conf \ + gzipwr_large.sh \ + testsuites/gzipwr_large.conf \ + gzipwr_large_dynfile.sh \ + testsuites/gzipwr_large_dynfile.conf \ + complex1.sh \ + testsuites/complex1.conf \ + random.sh \ + testsuites/random.conf \ + imfile-basic.sh \ + imfile-basic-vg.sh \ + testsuites/imfile-basic.conf \ + dynfile_invld_async.sh \ + dynfile_invld_sync.sh \ + dynfile_cachemiss.sh \ + testsuites/dynfile_cachemiss.conf \ + dynfile_invalid2.sh \ + testsuites/dynfile_invalid2.conf \ + proprepltest.sh \ + testsuites/rfctag.conf \ + testsuites/master.rfctag \ + testsuites/nolimittag.conf \ + testsuites/master.nolimittag \ + rulesetmultiqueue.sh \ + testsuites/rulesetmultiqueue.conf \ + omruleset.sh \ + testsuites/omruleset.conf \ + omruleset-queue.sh \ + testsuites/omruleset-queue.conf \ + badqi.sh \ + testsuites/badqi.conf \ + bad_qi/dbq.qi \ + execonlyonce.sh \ + testsuites/execonlyonce.conf \ + testsuites/execonlyonce.data \ + execonlywhenprevsuspended.sh \ + testsuites/execonlywhenprevsuspended.conf \ + execonlywhenprevsuspended2.sh \ + testsuites/execonlywhenprevsuspended2.conf \ + execonlywhenprevsuspended3.sh \ + testsuites/execonlywhenprevsuspended3.conf \ + execonlywhenprevsuspended4.sh \ + testsuites/execonlywhenprevsuspended4.conf \ + tabescape_dflt.sh \ + testsuites/tabescape_dflt.conf \ + testsuites/1.tabescape_dflt \ + tabescape_off.sh \ + testsuites/tabescape_off.conf \ + testsuites/1.tabescape_off \ + dircreate_dflt.sh \ + testsuites/dircreate_dflt.conf \ + dircreate_off.sh \ + testsuites/dircreate_off.conf \ + imuxsock_logger_root.sh \ + testsuites/imuxsock_logger_root.conf \ + resultdata/imuxsock_logger.log \ + imuxsock_traillf_root.sh \ + testsuites/imuxsock_traillf_root.conf \ + resultdata/imuxsock_traillf.log \ + imuxsock_ccmiddle_root.sh \ + testsuites/imuxsock_ccmiddle_root.conf \ + resultdata/imuxsock_ccmiddle.log \ + testsuites/mysql-truncate.sql \ + testsuites/mysql-select-msg.sql \ + libdbi-basic.sh \ + testsuites/libdbi-basic.conf \ + libdbi-asyn.sh \ + testsuites/libdbi-asyn.conf \ + mysql-basic.sh \ + mysql-basic-cnf6.sh \ + mysql-basic-vg.sh \ + testsuites/mysql-basic.conf \ + testsuites/mysql-basic-cnf6.conf \ + mysql-asyn.sh \ + mysql-asyn-vg.sh \ + testsuites/mysql-asyn.conf \ + cfg.sh + +# TODO: re-enable +#sndrcv_tls_anon_rebind.sh \ +#testsuites/sndrcv_tls_anon_rebind_sender.conf \ +#testsuites/sndrcv_tls_anon_rebind_rcvr.conf \ +#sndrcv_tls_anon.sh \ +#testsuites/sndrcv_tls_anon_sender.conf \ +#testsuites/sndrcv_tls_anon_rcvr.conf \ +# + +ourtail_SOURCES = ourtail.c +msleep_SOURCES = msleep.c +chkseq_SOURCES = chkseq.c + +uxsockrcvr_SOURCES = uxsockrcvr.c +uxsockrcvr_LDADD = $(SOL_LIBS) + +tcpflood_SOURCES = tcpflood.c +tcpflood_CPPFLAGS = $(PTHREADS_CFLAGS) $(GNUTLS_CFLAGS) +tcpflood_LDADD = $(SOL_LIBS) $(PTHREADS_LIBS) $(GNUTLS_LIBS) +if ENABLE_GNUTLS +tcpflood_LDADD += -lgcrypt +endif + +minitcpsrv_SOURCES = minitcpsrvr.c +minitcpsrv_LDADD = $(SOL_LIBS) + +syslog_caller_SOURCES = syslog_caller.c +syslog_caller_LDADD = $(SOL_LIBS) + +syslog_inject_SOURCES = syslog_inject.c +syslog_inject_LDADD = $(SOL_LIBS) + +diagtalker_SOURCES = diagtalker.c +diagtalker_LDADD = $(SOL_LIBS) + +randomgen_SOURCES = randomgen.c +randomgen_LDADD = $(SOL_LIBS) + +inputfilegen_SOURCES = inputfilegen.c +inputfilegen_LDADD = $(SOL_LIBS) + +nettester_SOURCES = nettester.c getline.c +nettester_LDADD = $(SOL_LIBS) + +# rtinit tests disabled for the moment - also questionable if they +# really provide value (after all, everything fails if rtinit fails...) +#rt_init_SOURCES = rt-init.c $(test_files) +#rt_init_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +#rt_init_LDADD = $(RSRT_LIBS) $(ZLIB_LIBS) $(PTHREADS_LIBS) $(SOL_LIBS) +#rt_init_LDFLAGS = -export-dynamic + +# same for basic rscript tests +#rscript_SOURCES = rscript.c getline.c $(test_files) +#rscript_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +#rscript_LDADD = $(RSRT_LIBS) $(ZLIB_LIBS) $(PTHREADS_LIBS) $(SOL_LIBS) +#rscript_LDFLAGS = -export-dynamic diff --git a/tests/NoExistFile.cfgtest b/tests/NoExistFile.cfgtest new file mode 100644 index 00000000..88d3123f --- /dev/null +++ b/tests/NoExistFile.cfgtest @@ -0,0 +1,2 @@ +rsyslogd: CONFIG ERROR: could not interpret master config file '/This/does/not/exist'. [try http://www.rsyslog.com/e/2013 ] +rsyslogd: EMERGENCY CONFIGURATION ACTIVATED - fix rsyslog config file! diff --git a/tests/README b/tests/README new file mode 100644 index 00000000..0ce79f63 --- /dev/null +++ b/tests/README @@ -0,0 +1,9 @@ +This directory contains the rsyslog testbench. It is slowly +evolving. New tests are always welcome. So far, most tests check +out the functionality of a single module. More complex tests are +welcome. + +For a simple sample, see rtinit.c, which does a simple +init/deinit check of the runtime system. + +rgerhards, 2008-06-13 diff --git a/tests/arrayqueue.sh b/tests/arrayqueue.sh new file mode 100755 index 00000000..71e1cc21 --- /dev/null +++ b/tests/arrayqueue.sh @@ -0,0 +1,18 @@ +# Test for fixedArray queue mode +# added 2009-05-20 by rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[arrayqueue.sh\]: testing queue fixedArray queue mode +source $srcdir/diag.sh init +source $srcdir/diag.sh startup arrayqueue.conf + +# 40000 messages should be enough +source $srcdir/diag.sh injectmsg 0 40000 + +# terminate *now* (don't wait for queue to drain!) +kill `cat rsyslog.pid` + +# now wait until rsyslog.pid is gone (and the process finished) +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 39999 +source $srcdir/diag.sh exit diff --git a/tests/asynwr_deadlock.sh b/tests/asynwr_deadlock.sh new file mode 100755 index 00000000..dc08355e --- /dev/null +++ b/tests/asynwr_deadlock.sh @@ -0,0 +1,23 @@ +# This is test case from practice, with the version we introduced it, it +# caused a deadlock on shutdown. I have added it to the test suite to automatically +# detect such things in the future. +# +# added 2010-03-17 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo ================================================================================ +echo TEST: \[asynwr_deadlock.sh\]: a case known to have caused a deadlock in the past +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_deadlock.conf +# just send one message +source $srcdir/diag.sh tcpflood -m1 +# sleep is important! need to make sure the instance is inactive +sleep 1 +# now try shutdown. The actual test is if the process does hang here! +echo "processing must continue soon" +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 0 +source $srcdir/diag.sh exit diff --git a/tests/asynwr_deadlock2.sh b/tests/asynwr_deadlock2.sh new file mode 100755 index 00000000..1190f67a --- /dev/null +++ b/tests/asynwr_deadlock2.sh @@ -0,0 +1,69 @@ +# This is test case from practice, with the version we introduced it, it +# caused a deadlock during processing (when the a stream was purged from the +# dynafile cache). +# We added this as a standard test in the hopes that iw will help +# detect such things in the future. +# +# The problem that originally caused this test to fail was: +# We write to files asynchronously (with the async writer thread). There is +# no signaling done when the file stream is closed. That can lead to the writer +# process hanging in memory, that in turn leads to the main thread waiting on a +# condition that never occurs (because it would need to be signalled by the +# async writer). Even worse, in that case, the async writer was signalled invalid +# in such a way that when it received a wakeup, it thought it shall not terminate, +# but received a spurios wakeup due to timeout and no data to write. In that case +# it (correctly) concluded that it would not need to timeout until a new buffer write +# was done (in which case it would receive a wakeup). As such, it went into an eternal +# wait. However, the invalid signaling did not take into account that it did not +# signal the async writer to shut down. So the main thread went into a condition +# wait - and thus we had a deadlock. That situation occured only under very specific +# cirumstances. As far as the analysis goes, the following need to happen: +# 1. buffers on that file are being flushed +# 2. no new data arrives +# 3. the inactivity timeout has not yet expired +# 4. *then* (and only then) the stream is closed or destructed +# In that, 1 to 4 are prequisites for the deadlock which will happen in 4. However, +# for it to happen, we also need the right "timing". There is a race between the +# main thread and the async writer thread. The deadlock will only happen under +# the "right" circumstances, which basically means it will not happen always. +# In order to create this case as reliable as possible, I have used +# the "$OMFileFlushOnTXEnd on" directive +# inside my test case. It makes sure that #1 above happens. The test uses a dynafile +# cache size of 4, and the load generator generates data for 5 different dynafiles. +# So over time, we will hit a spot where 4 dynafiles are open and the 5th file name +# is generated. As such, one file needs to be discarded. Thanks to FlushOnTXEnd, we +# now likely have #2 in place and thanks to the load pattern generated, we most +# probably have #3 in place. During the dynafile cache displacement of the oldest +# entry, #4 is generated. At this point, we have the deadlock we are testing for. +# Note that this deadlock does not necessarily lead to a total lockup of rsyslogd. +# Parts of it continue to operate. But in our test setup, this means data is +# received and placed into the main queue. Once it's high water mark is hit, data +# is still being enqueued, but at a slow rate. So if one is patient enough, the load +# generator will be able to finish. However, rsyslogd will never process the data +# it received because it is locked in the deadlock caused by #4 above. +# Note that "$OMFileFlushOnTXEnd on" is not causing this behaviour. We just use it +# to (quite) reliably cause the failure condition. The failure described above +# (in version 4.6.1) was also present when the setting was set to "off", but its +# occurence was very much less probable - because the perquisites are then much +# harder to hit. without it, the test may need to run for several hours before +# we hit all failure conditions. +# +# added 2010-03-17 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo ================================================================================= +echo TEST: \[asynwr_deadlock2.sh\]: a case known to have caused a deadlock in the past +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_deadlock2.conf +# send 20000 messages, each close to 2K (non-randomized!), so that we can fill +# the buffers and hopefully run into the "deadlock". +source $srcdir/diag.sh tcpflood -m20000 -d1800 -P129 -i1 -f5 +# the sleep below is needed to prevent too-early termination of the tcp listener +sleep 1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +cat rsyslog.out.*.log > rsyslog.out.log +source $srcdir/diag.sh seq-check 1 20000 -E +source $srcdir/diag.sh exit diff --git a/tests/asynwr_deadlock4.sh b/tests/asynwr_deadlock4.sh new file mode 100755 index 00000000..a3452f5b --- /dev/null +++ b/tests/asynwr_deadlock4.sh @@ -0,0 +1,25 @@ +# This is test case from practice, with the version we introduced it, it +# caused a deadlock during processing. +# We added this as a standard test in the hopes that iw will help +# detect such things in the future. +# +# This is a test that is constructed similar to asynwr_deadlock2.sh, but +# can produce problems in a simpler way. +# +# added 2010-03-18 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo ================================================================================= +echo TEST: \[asynwr_deadlock4.sh\]: a case known to have caused a deadlock in the past +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_deadlock4.conf +# send 20000 messages, each close to 2K (non-randomized!), so that we can fill +# the buffers and hopefully run into the "deadlock". +source $srcdir/diag.sh tcpflood -m20000 -d18 -P129 -i1 -f5 +# sleep is important! need to make sure the instance is inactive +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 1 20000 -E +source $srcdir/diag.sh exit diff --git a/tests/asynwr_simple.sh b/tests/asynwr_simple.sh new file mode 100755 index 00000000..eb87443c --- /dev/null +++ b/tests/asynwr_simple.sh @@ -0,0 +1,18 @@ +# This is test driver for testing asynchronous file output. +# +# added 2010-03-09 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[asynwr_simple.sh\]: simple test for async file writing +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_simple.conf +# send 35555 messages, make sure file size is not a multiple of +# 10K, the buffer size! +source $srcdir/diag.sh tcpflood -m35555 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 35554 +source $srcdir/diag.sh exit diff --git a/tests/asynwr_small.sh b/tests/asynwr_small.sh new file mode 100755 index 00000000..97818f6e --- /dev/null +++ b/tests/asynwr_small.sh @@ -0,0 +1,26 @@ +# This tests async writing with only a small set of data. That +# shall result in data staying in buffers until shutdown, what +# then will trigger some somewhat complex logic in the stream +# writer (open, write, close all during the stream close +# opertion). It is vital that only few messages be sent. +# +# The main effort of this test is not (only) to see if we +# receive the data, but rather to see if we get into an abort +# condition. +# +# added 2010-03-09 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[asynwr_small.sh\]: test for async file writing for few messages +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_small.conf +# send 4000 messages +source $srcdir/diag.sh tcpflood -m2 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 1 +source $srcdir/diag.sh exit diff --git a/tests/asynwr_timeout.sh b/tests/asynwr_timeout.sh new file mode 100755 index 00000000..b0bc5c28 --- /dev/null +++ b/tests/asynwr_timeout.sh @@ -0,0 +1,21 @@ +# This test writes to the output buffers, let's the output +# write timeout (and write data) and then continue. The conf file +# has a 2 second timeout, so we wait 4 seconds to be on the save side. +# +# added 2010-03-09 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[asynwr_timeout.sh\]: test async file writing timeout writes +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_timeout.conf +# send 35555 messages, make sure file size is not a multiple of +# 10K, the buffer size! +source $srcdir/diag.sh tcpflood -m 35555 +sleep 4 # wait for output writer to write and empty buffer +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 35554 +source $srcdir/diag.sh exit diff --git a/tests/asynwr_tinybuf.sh b/tests/asynwr_tinybuf.sh new file mode 100755 index 00000000..8eae1e26 --- /dev/null +++ b/tests/asynwr_tinybuf.sh @@ -0,0 +1,19 @@ +# This tests async writing with a very small output buffer (1 byte!), +# so it stresses output buffer handling. This also means operations will +# be somewhat slow, so we send only a small amounts of data. +# +# added 2010-03-09 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[asynwr_tinybuf.sh\]: test async file writing with 1-byte buffer +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup asynwr_tinybuf.conf +# send 1000 messages, fairly enough to trigger problems +source $srcdir/diag.sh tcpflood -m1000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh seq-check 0 999 +source $srcdir/diag.sh exit diff --git a/tests/bad_qi/dbq.qi b/tests/bad_qi/dbq.qi new file mode 100644 index 00000000..5f56e41a --- /dev/null +++ b/tests/bad_qi/dbq.qi @@ -0,0 +1,29 @@ +!OPB:1:queue:1: ++iQueueSize:2:2:84: ++iUngottenObjs:2:1:1: ++tVars.disk.sizeOnDisk:2:5:57906: ++tVars.disk.bytesRead:2:4:1010: +>End +. +<Obj:1:strm:1: ++iCurrFNum:2:1:1: ++pszFName:1:3:dbq: ++iMaxFiles:2:8:10000000: ++bDeleteOnClose:2:1:0: ++sType:2:1:1: ++tOperationsMode:2:1:2: ++tOpenMode:2:3:384: ++iCurrOffs:2:5:57906: +>End +. +<Obj:1:strm:1: ++iCurrFNum:2:1:1: ++pszFName:1:3:dbq: ++iMaxFiles:2:8:10000000: ++bDeleteOnClose:2:1:1: ++sType:2:1:1: ++tOperationsMode:2:1:1: ++tOpenMode:2:3:384: ++iCurrOffs:2:4:1010: +>End +. diff --git a/tests/badqi.sh b/tests/badqi.sh new file mode 100755 index 00000000..28f76229 --- /dev/null +++ b/tests/badqi.sh @@ -0,0 +1,16 @@ +# Test for a startup with a bad qi file. This tests simply tests +# if the rsyslog engine survives (we had segfaults in this situation +# in the past). +# added 2009-10-21 by RGerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo =============================================================================== +echo \[badqi.sh\]: test startup with invalid .qi file +source $srcdir/diag.sh init +source $srcdir/diag.sh startup badqi.conf +# we just inject a handful of messages so that we have something to wait for... +source $srcdir/diag.sh tcpflood -m20 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # wait for process to terminate +source $srcdir/diag.sh seq-check 0 19 +source $srcdir/diag.sh exit diff --git a/tests/cee_diskqueue.sh b/tests/cee_diskqueue.sh new file mode 100755 index 00000000..4e19855b --- /dev/null +++ b/tests/cee_diskqueue.sh @@ -0,0 +1,14 @@ +# check if CEE properties are properly saved & restored to/from disk queue +# added 2012-09-19 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[cee_diskqueue.sh\]: CEE and diskqueue test +source $srcdir/diag.sh init +source $srcdir/diag.sh startup cee_diskqueue.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/cee_simple.sh b/tests/cee_simple.sh new file mode 100755 index 00000000..32f56393 --- /dev/null +++ b/tests/cee_simple.sh @@ -0,0 +1,13 @@ +# added 2012-09-19 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[cee_simple.sh\]: basic CEE property test +source $srcdir/diag.sh init +source $srcdir/diag.sh startup cee_simple.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/cfg.sh b/tests/cfg.sh new file mode 100755 index 00000000..7610407a --- /dev/null +++ b/tests/cfg.sh @@ -0,0 +1,140 @@ +# /bin/bash +# This is a simple shell script that carries out some checks against +# configurations we expect from some provided config files. We use +# rsyslogd's verifcation function. Note that modifications to the +# config elements, or even simple text changes, cause these checks to +# fail. However, it should be fairly easy to adapt them to the changed +# environment. And while nothing changed, they permit is to make sure +# that everything works well and is not broken by interim changes. +# Note that we always compare starting with the second output line. +# This is because the first line contains the rsyslog version ;) +# rgerhards, 2008-07-29 +# +# Part of the testbench for rsyslog. +# +# Copyright 2008 Rainer Gerhards and Adiscon GmbH. +# +# This file is part of rsyslog. +# +# Rsyslog is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. +# +# A copy of the GPL can be found in the file "COPYING" in this distribution. +#set -x +echo \[cfg.sh\]: +rm -f tmp +echo "local directory" +# +# check empty config file +# +../tools/rsyslogd -c4 -N1 -f/dev/null -M../runtime/.libs:../.libs 2>&1 |./ourtail |head -2 > tmp +cmp tmp $srcdir/DevNull.cfgtest +if [ ! $? -eq 0 ]; then +echo "DevNull.cfgtest failed" +echo "Expected:" +cat $srcdir/DevNull.cfgtest +echo "Received:" +cat tmp +exit 1 +else +echo "DevNull.cfgtest succeeded" +fi; +# +# check missing config file +# +../tools/rsyslogd -c4 -N1 -M../runtime/.libs:../.libs -f/This/does/not/exist 2>&1 |./ourtail |head -2 > tmp +cmp tmp $srcdir/NoExistFile.cfgtest +if [ ! $? -eq 0 ]; then +echo "NoExistFile.cfgtest failed" +echo "Expected:" +cat $srcdir/NoExistFile.cfgtest +echo "Received:" +cat tmp +exit 1 +else +echo "NoExistFile.cfgtest succeeded" +fi; + + +# TODO: re-enable the following checks. They need to have support in +# rsyslogd so that the log file name is NOT contained in the error +# messages - this prevents proper comparison in make distcheck +rm -f tmp +exit 0 + +# +# check config with invalid directive +# +../tools/rsyslogd -c4 -u2 -N1 -f$srcdir/cfg1.testin 2>&1 |./ourtail > tmp +cmp tmp $srcdir/cfg1.cfgtest +if [ ! $? -eq 0 ]; then +echo "cfg1.cfgtest failed" +echo "Expected:" +cat $srcdir/cfg1.cfgtest +echo "Received:" +cat tmp +exit 1 +else +echo "cfg1.cfgtest succeeded" +fi; +# +# now check for included config file. We use a sample similar to +# the one with the invalid config directive, so that we may see +# an effect of the included config ;) +# +../tools/rsyslogd -c4 -u2 -N1 -f$srcdir/cfg2.testin 2>&1 |./ourtail > tmp +cmp tmp $srcdir/cfg2.cfgtest +if [ ! $? -eq 0 ]; then +echo "cfg2.cfgtest failed" +echo "Expected:" +cat $srcdir/cfg2.cfgtest +echo "Received:" +cat tmp +exit 1 +else +echo "cfg2.cfgtest succeeded" +fi; +# +# check included config file, where included file does not exist +# +../tools/rsyslogd -c4 -u2 -N1 -f$srcdir/cfg3.testin 2>&1 |./ourtail > tmp +cmp tmp $srcdir/cfg3.cfgtest +if [ ! $? -eq 0 ]; then +echo "cfg3.cfgtest failed" +echo "Expected:" +cat $srcdir/cfg3.cfgtest +echo "Received:" +cat tmp +exit 1 +else +echo "cfg3.cfgtest succeeded" +fi; +# +# check a reasonable complex, but correct, log file +# +../tools/rsyslogd -c4 -u2 -N1 -f$srcdir/cfg4.testin 2>&1 |./ourtail > tmp +cmp tmp $srcdir/cfg4.cfgtest +if [ ! $? -eq 0 ]; then +echo "cfg4.cfgtest failed" +echo "Expected:" +cat $srcdir/cfg4.cfgtest +echo "Received:" +cat tmp +exit 1 +else +echo "cfg4.cfgtest succeeded" +fi; +# +# done, some cleanup +# +rm -f tmp diff --git a/tests/cfg1.cfgtest b/tests/cfg1.cfgtest new file mode 100644 index 00000000..099ba929 --- /dev/null +++ b/tests/cfg1.cfgtest @@ -0,0 +1,3 @@ +rsyslogd: invalid or yet-unknown config file command - have you forgotten to load a module? [try http://www.rsyslog.com/e/3003 ] +rsyslogd: the last error occured in ./cfg1.testin, line 2 +rsyslogd: End of config validation run. Bye. diff --git a/tests/cfg1.testin b/tests/cfg1.testin new file mode 100644 index 00000000..7d7b594c --- /dev/null +++ b/tests/cfg1.testin @@ -0,0 +1,2 @@ +*.* * +$invaliddirective test diff --git a/tests/cfg2.cfgtest b/tests/cfg2.cfgtest new file mode 100644 index 00000000..b44a487e --- /dev/null +++ b/tests/cfg2.cfgtest @@ -0,0 +1,3 @@ +rsyslogd: invalid or yet-unknown config file command - have you forgotten to load a module? [try http://www.rsyslog.com/e/3003 ] +rsyslogd: the last error occured in cfg1.testin, line 2 +rsyslogd: End of config validation run. Bye. diff --git a/tests/cfg2.testin b/tests/cfg2.testin new file mode 100644 index 00000000..b6d98c8f --- /dev/null +++ b/tests/cfg2.testin @@ -0,0 +1 @@ +$includeconfig cfg1.testin diff --git a/tests/cfg3.cfgtest b/tests/cfg3.cfgtest new file mode 100644 index 00000000..68bc17d4 --- /dev/null +++ b/tests/cfg3.cfgtest @@ -0,0 +1,5 @@ +rsyslogd: error accessing config file or directory 'file-does-not-exist': No such file or directory [try http://www.rsyslog.com/e/2040 ] +rsyslogd: the last error occured in ./cfg3.testin, line 1 +rsyslogd: CONFIG ERROR: there are no active actions configured. Inputs will run, but no output whatsoever is created. [try http://www.rsyslog.com/e/2103 ] +rsyslogd: EMERGENCY CONFIGURATION ACTIVATED - fix rsyslog config file! +rsyslogd: End of config validation run. Bye. diff --git a/tests/cfg3.testin b/tests/cfg3.testin new file mode 100644 index 00000000..9789d939 --- /dev/null +++ b/tests/cfg3.testin @@ -0,0 +1 @@ +$includeconfig file-does-not-exist diff --git a/tests/cfg4.cfgtest b/tests/cfg4.cfgtest new file mode 100644 index 00000000..04acf84f --- /dev/null +++ b/tests/cfg4.cfgtest @@ -0,0 +1 @@ +rsyslogd: End of config validation run. Bye. diff --git a/tests/cfg4.testin b/tests/cfg4.testin new file mode 100644 index 00000000..2dc0e830 --- /dev/null +++ b/tests/cfg4.testin @@ -0,0 +1,31 @@ +# This is more or less the sample config, but without imklog being +# active. imklog must not always be present and as such may spoil +# our testing result. The core point at this test is that a valid +# config file should not lead to any error messages. +# It may be a good idea to update this file from time to time, so that +# it contains a reasonable complex config sample. + +# if you experience problems, check +# http://www.rsyslog.com/troubleshoot for assistance + +# rsyslog v3: load input modules +# If you do not load inputs, nothing happens! +# You may need to set the module load path if modules are not found. + + + +# ######### Receiving Messages from Remote Hosts ########## +# TCP Syslog Server: +# provides TCP syslog reception and GSS-API (if compiled to support it) +#$ModLoad imtcp.so # load module +#$InputTCPServerRun 514 # start up TCP listener at port 514 + +# UDP Syslog Server: +$ModLoad imudp.so # provides UDP syslog reception +$ModLoad omoracle.so +$UDPServerRun 514 # start a UDP syslog server at standard port 514 + +$IncludeConfig /home/munoz/logging/rsyslog/20*conf +$IncludeConfig /home/munoz/logging/rsyslog/30*conf + +#*.* ~ diff --git a/tests/chkseq.c b/tests/chkseq.c new file mode 100644 index 00000000..b22c8992 --- /dev/null +++ b/tests/chkseq.c @@ -0,0 +1,143 @@ +/* Checks if a file consists of line of strictly monotonically + * increasing numbers. An expected start and end number may + * be set. + * + * Params + * -f<filename> MUST be given! + * -s<starting number> -e<ending number> + * default for s is 0. -e should be given (else it is also 0) + * -d may be specified, in which case duplicate messages are permitted. + * + * Part of the testbench for rsyslog. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> + +int main(int argc, char *argv[]) +{ + FILE *fp; + int val; + int i; + int ret = 0; + int scanfOK; + int verbose = 0; + int bHaveExtraData = 0; + int dupsPermitted = 0; + int start = 0, end = 0; + int opt; + int nDups = 0; + int edLen; /* length of extra data */ + static char edBuf[500*1024]; /* buffer for extra data (pretty large to be on the save side...) */ + char *file = NULL; + + while((opt = getopt(argc, argv, "e:f:ds:vE")) != EOF) { + switch((char)opt) { + case 'f': + file = optarg; + break; + case 'd': + dupsPermitted = 1; + break; + case 'e': + end = atoi(optarg); + break; + case 's': + start = atoi(optarg); + break; + case 'v': + ++verbose; + break; + case 'E': + bHaveExtraData = 1; + break; + default:printf("Invalid call of chkseq, optchar='%c'\n", opt); + printf("Usage: chkseq file -sstart -eend -d -E\n"); + exit(1); + } + } + + if(file == NULL) { + printf("file must be given!\n"); + exit(1); + } + + if(start > end) { + printf("start must be less than or equal end!\n"); + exit(1); + } + + if(verbose) { + printf("chkseq: start %d, end %d\n", start, end); + } + + /* read file */ + fp = fopen(file, "r"); + if(fp == NULL) { + printf("error opening file '%s'\n", file); + perror(file); + exit(1); + } + + for(i = start ; i < end+1 ; ++i) { + if(bHaveExtraData) { + scanfOK = fscanf(fp, "%d,%d,%s\n", &val, &edLen, edBuf) == 3 ? 1 : 0; + if(edLen != (int) strlen(edBuf)) { + printf("extra data length specified %d, but actually is %ld in record %d\n", + edLen, (long) strlen(edBuf), i); + exit(1); + } + } else { + scanfOK = fscanf(fp, "%d\n", &val) == 1 ? 1 : 0; + } + if(!scanfOK) { + printf("scanf error in index i=%d\n", i); + exit(1); + } + if(val != i) { + if(val == i - 1 && dupsPermitted) { + --i; + ++nDups; + } else { + printf("read value %d, but expected value %d\n", val, i); + exit(1); + } + } + } + + if(nDups != 0) + printf("info: had %d duplicates (this is no error)\n", nDups); + + if(i - 1 != end) { + printf("only %d records in file, expected %d\n", i - 1, end); + exit(1); + } + + if(!feof(fp)) { + printf("end of processing, but NOT end of file!\n"); + exit(1); + } + + exit(ret); +} diff --git a/tests/complex1.sh b/tests/complex1.sh new file mode 100755 index 00000000..e138bff5 --- /dev/null +++ b/tests/complex1.sh @@ -0,0 +1,22 @@ +# This is a rather complex test that runs a number of features together. +# +# added 2010-03-16 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[complex1.sh\]: complex test with gzip and multiple action queues +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup complex1.conf +# send 40,000 messages of 400 bytes plus header max, via three dest ports +source $srcdir/diag.sh tcpflood -m40000 -rd400 -P129 -f5 -n3 -c15 -i1 +sleep 4 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +ls rsyslog.out.*.log +source $srcdir/diag.sh setzcat # find out which zcat to use +$ZCAT rsyslog.out.*.log > rsyslog.out.log +source $srcdir/diag.sh seq-check 1 40000 -E +source $srcdir/diag.sh exit diff --git a/tests/da-mainmsg-q.sh b/tests/da-mainmsg-q.sh new file mode 100755 index 00000000..d9cc0d4d --- /dev/null +++ b/tests/da-mainmsg-q.sh @@ -0,0 +1,33 @@ +# Test for DA mode on the main message queue +# This test checks if DA mode operates correctly. To do so, +# it uses a small in-memory queue size, so that DA mode is initiated +# rather soon, and disk spooling used. There is some uncertainty (based +# on machine speeds), but in general the test should work rather well. +# We add a few messages after the initial run, just so that we can +# check everything recovers from DA mode correctly. +# added 2009-04-22 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo "[da-mainmsg-q.sh]: testing main message queue in DA mode (going to disk)" +source $srcdir/diag.sh init +source $srcdir/diag.sh startup da-mainmsg-q.conf + +# part1: send first 50 messages (in memory, only) +#source $srcdir/diag.sh tcpflood 127.0.0.1 13514 1 50 +source $srcdir/diag.sh injectmsg 0 50 +source $srcdir/diag.sh wait-queueempty # let queue drain for this test case + +# part 2: send bunch of messages. This should trigger DA mode +#source $srcdir/diag.sh injectmsg 50 20000 +source $srcdir/diag.sh injectmsg 50 2000 +ls -l test-spool # for manual review + +# send another handful +source $srcdir/diag.sh injectmsg 2050 50 +#sleep 1 # we need this so that rsyslogd can receive all outstanding messages + +# clean up and check test result +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 2099 +source $srcdir/diag.sh exit diff --git a/tests/daqueue-persist-drvr.sh b/tests/daqueue-persist-drvr.sh new file mode 100755 index 00000000..7934eb2b --- /dev/null +++ b/tests/daqueue-persist-drvr.sh @@ -0,0 +1,36 @@ +# Test for queue data persisting at shutdown. The +# plan is to start an instance, emit some data, do a relatively +# fast shutdown and then re-start the engine to process the +# remaining data. +# added 2009-05-27 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo \[daqueue-persist-drvr.sh\]: testing memory daqueue persisting to disk, mode $1 +source $srcdir/diag.sh init + +#export RSYSLOG_DEBUG="debug nologfuncflow nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" + +# prepare config +echo \$MainMsgQueueType $1 > work-queuemode.conf +echo "*.* :omtesting:sleep 0 1000" > work-delay.conf + +# inject 10000 msgs, so that DO hit the high watermark +source $srcdir/diag.sh startup queue-persist.conf +source $srcdir/diag.sh injectmsg 0 10000 +$srcdir/diag.sh shutdown-immediate +$srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh check-mainq-spool + +echo "Enter phase 2, rsyslogd restart" + +exit + +# restart engine and have rest processed +#remove delay +echo "#" > work-delay.conf +source $srcdir/diag.sh startup queue-persist.conf +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +$srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99999 +source $srcdir/diag.sh exit diff --git a/tests/daqueue-persist.sh b/tests/daqueue-persist.sh new file mode 100755 index 00000000..feb2a347 --- /dev/null +++ b/tests/daqueue-persist.sh @@ -0,0 +1,12 @@ +# Test for queue data persisting at shutdown. We use the actual driver +# to carry out multiple tests with different queue modes +# added 2009-05-27 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo \[daqueue-persist.sh\]: test data persisting at shutdown +source $srcdir/daqueue-persist-drvr.sh LinkedList +source $srcdir/daqueue-persist-drvr.sh FixedArray +# the disk test should not fail, however, the config is extreme and using +# it more or less is a config error +source $srcdir/daqueue-persist-drvr.sh Disk +# we do not test Direct mode because this absolute can not work in direct mode +# (maybe we should do a fail-type of test?) diff --git a/tests/diag.sh b/tests/diag.sh new file mode 100755 index 00000000..2fdcbfb9 --- /dev/null +++ b/tests/diag.sh @@ -0,0 +1,194 @@ +# this shell script provides commands to the common diag system. It enables +# test scripts to wait for certain conditions and initiate certain actions. +# needs support in config file. +# NOTE: this file should be included with "source diag.sh", as it otherwise is +# not always able to convey back states to the upper-level test driver +# begun 2009-05-27 by rgerhards +# This file is part of the rsyslog project, released under GPLv3 +#valgrind="valgrind --malloc-fill=ff --free-fill=fe --log-fd=1" +#valgrind="valgrind --tool=drd --log-fd=1" +#valgrind="valgrind --tool=helgrind --log-fd=1" +#valgrind="valgrind --tool=exp-ptrcheck --log-fd=1" +#set -o xtrace +#export RSYSLOG_DEBUG="debug nologfuncflow noprintmutexaction nostdout" +#export RSYSLOG_DEBUGLOG="log" +case $1 in + 'init') $srcdir/killrsyslog.sh # kill rsyslogd if it runs for some reason + cp $srcdir/testsuites/diag-common.conf diag-common.conf + cp $srcdir/testsuites/diag-common2.conf diag-common2.conf + rm -f rsyslogd.started work-*.conf rsyslog.random.data + rm -f rsyslogd2.started work-*.conf + rm -f work rsyslog.out.log rsyslog2.out.log rsyslog.out.log.save # common work files + rm -rf test-spool test-logdir stat-file1 + rm -f rsyslog.out.*.log work-presort rsyslog.pipe + rm -f rsyslog.input rsyslog.empty + rm -f core.* vgcore.* + # Note: rsyslog.action.*.include must NOT be deleted, as it + # is used to setup some parameters BEFORE calling init. This + # happens in chained test scripts. Delete on exit is fine, + # though. + mkdir test-spool + ;; + 'exit') rm -f rsyslogd.started work-*.conf diag-common.conf + rm -f rsyslogd2.started diag-common2.conf rsyslog.action.*.include + rm -f work rsyslog.out.log rsyslog2.out.log rsyslog.out.log.save # common work files + rm -rf test-spool test-logdir stat-file1 + rm -f rsyslog.out.*.log rsyslog.random.data work-presort rsyslog.pipe + rm -f rsyslog.input rsyslog.conf.tlscert stat-file1 rsyslog.empty + echo ------------------------------------------------------------------------------- + ;; + 'startup') # start rsyslogd with default params. $2 is the config file name to use + # returns only after successful startup, $3 is the instance (blank or 2!) + $valgrind ../tools/rsyslogd -u2 -n -irsyslog$3.pid -M../runtime/.libs:../.libs -f$srcdir/testsuites/$2 & + $srcdir/diag.sh wait-startup $3 + ;; + 'startup-vg') # start rsyslogd with default params under valgrind control. $2 is the config file name to use + # returns only after successful startup, $3 is the instance (blank or 2!) + valgrind --log-fd=1 --error-exitcode=10 --malloc-fill=ff --free-fill=fe --leak-check=full ../tools/rsyslogd -u2 -n -irsyslog$3.pid -M../runtime/.libs:../.libs -f$srcdir/testsuites/$2 & + $srcdir/diag.sh wait-startup $3 + ;; + 'wait-startup') # wait for rsyslogd startup ($2 is the instance) + while test ! -f rsyslog$2.pid; do + ./msleep 100 # wait 100 milliseconds + done + while test ! -f rsyslogd$2.started; do + ./msleep 100 # wait 100 milliseconds + done + echo "rsyslogd$2 started with pid " `cat rsyslog$2.pid` + ;; + 'wait-shutdown') # actually, we wait for rsyslog.pid to be deleted. $2 is the + # instance + while test -f rsyslog$2.pid; do + ./msleep 100 # wait 100 milliseconds + done + if [ -e core.* ] + then + echo "ABORT! core file exists, starting interactive shell" + bash + exit 1 + fi + ;; + 'wait-shutdown-vg') # actually, we wait for rsyslog.pid to be deleted. $2 is the + # instance + wait `cat rsyslog.pid` + export RSYSLOGD_EXIT=$? + echo rsyslogd run exited with $RSYSLOGD_EXIT + if [ -e core.* ] + then + echo "ABORT! core file exists, starting interactive shell" + bash + exit 1 + fi + ;; + 'check-exit-vg') # wait for main message queue to be empty. $2 is the instance. + if [ "$RSYSLOGD_EXIT" -eq "10" ] + then + echo "valgrind run FAILED with exceptions - terminating" + exit 1 + fi + ;; + 'get-mainqueuesize') # show the current main queue size + if [ "$2" == "2" ] + then + echo getmainmsgqueuesize | ./diagtalker -p13501 + else + echo getmainmsgqueuesize | ./diagtalker + fi + ;; + 'wait-queueempty') # wait for main message queue to be empty. $2 is the instance. + if [ "$2" == "2" ] + then + echo WaitMainQueueEmpty | ./diagtalker -p13501 + else + echo WaitMainQueueEmpty | ./diagtalker + fi + ;; + 'shutdown-when-empty') # shut rsyslogd down when main queue is empty. $2 is the instance. + if [ "$2" == "2" ] + then + echo Shutting down instance 2 + fi + $srcdir/diag.sh wait-queueempty $2 + kill `cat rsyslog$2.pid` + # note: we do not wait for the actual termination! + ;; + 'shutdown-immediate') # shut rsyslogd down without emptying the queue. $2 is the instance. + kill `cat rsyslog.pid` + # note: we do not wait for the actual termination! + ;; + 'tcpflood') # do a tcpflood run and check if it worked params are passed to tcpflood + ./tcpflood $2 $3 $4 $5 $6 $7 $8 $9 + if [ "$?" -ne "0" ]; then + echo "error during tcpflood! see rsyslog.out.log.save for what was written" + cp rsyslog.out.log rsyslog.out.log.save + exit 1 + fi + ;; + 'injectmsg') # inject messages via our inject interface (imdiag) + echo injecting $3 messages + echo injectmsg $2 $3 $4 $5 | ./diagtalker + # TODO: some return state checking? (does it really make sense here?) + ;; + 'check-mainq-spool') # check if mainqueue spool files exist, if not abort (we just check .qi). + echo There must exist some files now: + ls -l test-spool + if test ! -f test-spool/mainq.qi; then + echo "error: mainq.qi does not exist where expected to do so!" + ls -l test-spool + exit 1 + fi + ;; + 'seq-check') # do the usual sequence check to see if everything was properly received. $2 is the instance. + rm -f work + cp rsyslog.out.log work-presort + sort < rsyslog.out.log > work + # $4... are just to have the abilit to pass in more options... + # add -v to chkseq if you need more verbose output + ./chkseq -fwork -s$2 -e$3 $4 $5 $6 $7 + if [ "$?" -ne "0" ]; then + echo "sequence error detected" + exit 1 + fi + ;; + 'seq-check2') # do the usual sequence check to see if everything was properly received. This is + # a duplicateof seq-check, but we could not change its calling conventions without + # breaking a lot of exitings test cases, so we preferred to duplicate the code here. + rm -f work2 + sort < rsyslog2.out.log > work2 + # $4... are just to have the abilit to pass in more options... + # add -v to chkseq if you need more verbose output + ./chkseq -fwork2 -s$2 -e$3 $4 $5 $6 $7 + if [ "$?" -ne "0" ]; then + echo "sequence error detected" + exit 1 + fi + rm -f work2 + ;; + 'gzip-seq-check') # do the usual sequence check, but for gzip files + rm -f work + ls -l rsyslog.out.log + gunzip < rsyslog.out.log | sort > work + ls -l work + # $4... are just to have the abilit to pass in more options... + ./chkseq -fwork -v -s$2 -e$3 $4 $5 $6 $7 + if [ "$?" -ne "0" ]; then + echo "sequence error detected" + exit 1 + fi + ;; + 'nettester') # perform nettester-based tests + # use -v for verbose output! + ./nettester -t$2 -i$3 $4 + if [ "$?" -ne "0" ]; then + exit 1 + fi + ;; + 'setzcat') # find out name of zcat tool + if [ `uname` == SunOS ]; then + ZCAT=gzcat + else + ZCAT=zcat + fi + ;; + *) echo "invalid argument" $1 +esac diff --git a/tests/diagtalker.c b/tests/diagtalker.c new file mode 100644 index 00000000..f61270a8 --- /dev/null +++ b/tests/diagtalker.c @@ -0,0 +1,160 @@ +/* A yet very simple tool to talk to imdiag (this replaces the + * previous Java implementation in order to get fewer dependencies). + * + * Copyright 2010,2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> + + +static char *targetIP = "127.0.0.1"; +static int targetPort = 13500; + + +/* open a single tcp connection + */ +int openConn(int *fd) +{ + int sock; + struct sockaddr_in addr; + int port; + int retries = 0; + + if((sock=socket(AF_INET, SOCK_STREAM, 0))==-1) { + perror("socket()"); + exit(1); + } + + port = targetPort; + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if(inet_aton(targetIP, &addr.sin_addr)==0) { + fprintf(stderr, "inet_aton() failed\n"); + exit(1); + } + while(1) { /* loop broken inside */ + if(connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + break; + } else { + if(retries++ == 50) { + perror("connect()"); + fprintf(stderr, "connect() failed\n"); + exit(1); + } else { + fprintf(stderr, "connect failed, retrying...\n"); + usleep(100000); /* ms = 1000 us! */ + } + } + } + if(retries > 0) { + fprintf(stderr, "connection established.\n"); + } + + *fd = sock; + return 0; +} + + +/* send a string + */ +static void +sendCmd(int fd, char *buf, int len) +{ + int lenSend; + + lenSend = send(fd, buf, len, 0); + if(lenSend != len) { + perror("sending string"); + exit(1); + } +} + + +/* wait for a response from remote system + */ +static void +waitRsp(int fd, char *buf, int len) +{ + int ret; + + ret = recv(fd, buf, len - 1, 0); + if(ret < 0) { + perror("receiving response"); + exit(1); + } + /* we assume the message was complete, it may be better to wait + * for a LF... + */ + buf[ret] = '\0'; +} + + +/* do the actual processing + */ +static void +doProcessing() +{ + int fd; + int len; + char line[2048]; + + openConn(&fd); + while(!feof(stdin)) { + if(fgets(line, sizeof(line) - 1, stdin) == NULL) + break; + len = strlen(line); + sendCmd(fd, line, len); + waitRsp(fd, line, sizeof(line)); + printf("imdiag[%d]: %s", targetPort, line); + } +} + + +/* Run the test. + * rgerhards, 2009-04-03 + */ +int main(int argc, char *argv[]) +{ + int ret = 0; + int opt; + + while((opt = getopt(argc, argv, "t:p:")) != -1) { + switch (opt) { + case 't': targetIP = optarg; + break; + case 'p': targetPort = atoi(optarg); + break; + default: printf("invalid option '%c' or value missing - terminating...\n", opt); + exit (1); + break; + } + } + + doProcessing(); + + exit(ret); +} diff --git a/tests/dircreate_dflt.sh b/tests/dircreate_dflt.sh new file mode 100755 index 00000000..71a671f3 --- /dev/null +++ b/tests/dircreate_dflt.sh @@ -0,0 +1,20 @@ +# Test for automatic creation of dynafile directories +# note that we use the "test-spool" directory, because it is handled by diag.sh +# in any case, so we do not need to add any extra new test dir. +# added 2009-11-30 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo =================================================================================== +echo \[dircreate_dflt_dflt.sh\]: testing automatic directory creation for dynafiles - default +source $srcdir/diag.sh init +source $srcdir/diag.sh startup dircreate_dflt.conf +source $srcdir/diag.sh injectmsg 0 1 # a single message is sufficient +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +if [ ! -e test-logdir/rsyslog.out.log ] +then + echo "test-logdir or logfile not created!" + exit 1 +fi +exit +source $srcdir/diag.sh exit diff --git a/tests/dircreate_off.sh b/tests/dircreate_off.sh new file mode 100755 index 00000000..92fdee01 --- /dev/null +++ b/tests/dircreate_off.sh @@ -0,0 +1,20 @@ +# Test for automatic creation of dynafile directories +# note that we use the "test-spool" directory, because it is handled by diag.sh +# in any case, so we do not need to add any extra new test dir. +# added 2009-11-30 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo =================================================================================== +echo \[dircreate_off_off.sh\]: testing automatic directory creation for dynafiles - default +source $srcdir/diag.sh init +source $srcdir/diag.sh startup dircreate_off.conf +source $srcdir/diag.sh injectmsg 0 1 # a single message is sufficient +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +if [ -e test-logdir/rsyslog.out.log ] +then + echo "test-logdir or logfile WAS created where not permitted to!" + exit 1 +fi +exit +source $srcdir/diag.sh exit diff --git a/tests/discard-allmark-vg.sh b/tests/discard-allmark-vg.sh new file mode 100755 index 00000000..57ce8e3e --- /dev/null +++ b/tests/discard-allmark-vg.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[discard-allmark.sh\]: testing discard-allmark functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg discard-allmark.conf +source $srcdir/diag.sh tcpflood -m10 -i1 +# we need to give rsyslog a little time to settle the receiver +./msleep 1500 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 2 10 +source $srcdir/diag.sh exit diff --git a/tests/discard-allmark.sh b/tests/discard-allmark.sh new file mode 100755 index 00000000..eb46ae70 --- /dev/null +++ b/tests/discard-allmark.sh @@ -0,0 +1,10 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[discard-allmark.sh\]: testing discard-allmark functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup discard-allmark.conf +source $srcdir/diag.sh tcpflood -m10 -i1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 2 10 +source $srcdir/diag.sh exit diff --git a/tests/discard-rptdmsg-vg.sh b/tests/discard-rptdmsg-vg.sh new file mode 100755 index 00000000..f56ac597 --- /dev/null +++ b/tests/discard-rptdmsg-vg.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[discard-rptdmsg.sh\]: testing discard-rptdmsg functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg discard-rptdmsg.conf +source $srcdir/diag.sh tcpflood -m10 -i1 +# we need to give rsyslog a little time to settle the receiver +./msleep 1500 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 2 10 +source $srcdir/diag.sh exit diff --git a/tests/discard-rptdmsg.sh b/tests/discard-rptdmsg.sh new file mode 100755 index 00000000..a8be110c --- /dev/null +++ b/tests/discard-rptdmsg.sh @@ -0,0 +1,10 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[discard-rptdmsg.sh\]: testing discard-rptdmsg functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup discard-rptdmsg.conf +source $srcdir/diag.sh tcpflood -m10 -i1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 2 10 +source $srcdir/diag.sh exit diff --git a/tests/discard.sh b/tests/discard.sh new file mode 100755 index 00000000..96006f01 --- /dev/null +++ b/tests/discard.sh @@ -0,0 +1,17 @@ +# Test for discard functionality +# This test checks if discard works. It is not a perfect test but +# will find at least segfaults and obviously not discarded messages. +# added 2009-07-30 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo =============================================================================== +echo \[discard.sh\]: testing discard functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup discard.conf +# 20000 messages should be enough - the disk test is slow enough ;) +sleep 4 +source $srcdir/diag.sh tcpflood -m10 -i1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 2 10 +source $srcdir/diag.sh exit diff --git a/tests/diskqueue-fsync.sh b/tests/diskqueue-fsync.sh new file mode 100755 index 00000000..fe923c22 --- /dev/null +++ b/tests/diskqueue-fsync.sh @@ -0,0 +1,16 @@ +# Test for disk-only queue mode (with fsync for queue files) +# This test checks if queue files can be correctly written +# and read back, but it does not test the transition from +# memory to disk mode for DA queues. +# added 2009-06-09 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo \[diskqueue-fsync.sh\]: testing queue disk-only mode, fsync case +source $srcdir/diag.sh init +source $srcdir/diag.sh startup diskqueue-fsync.conf +# 1000 messages should be enough - the disk fsync test is very slow! +source $srcdir/diag.sh injectmsg 0 1000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 999 +source $srcdir/diag.sh exit diff --git a/tests/diskqueue.sh b/tests/diskqueue.sh new file mode 100755 index 00000000..b871e9eb --- /dev/null +++ b/tests/diskqueue.sh @@ -0,0 +1,20 @@ +# Test for disk-only queue mode +# This test checks if queue files can be correctly written +# and read back, but it does not test the transition from +# memory to disk mode for DA queues. +# added 2009-04-17 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo \[diskqueue.sh\]: testing queue disk-only mode +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh init +source $srcdir/diag.sh startup diskqueue.conf +# 20000 messages should be enough - the disk test is slow enough ;) +sleep 4 +source $srcdir/diag.sh tcpflood -m20000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 19999 +source $srcdir/diag.sh exit diff --git a/tests/dynfile_cachemiss.sh b/tests/dynfile_cachemiss.sh new file mode 100755 index 00000000..6e2d9cca --- /dev/null +++ b/tests/dynfile_cachemiss.sh @@ -0,0 +1,34 @@ +# This test checks if omfile segfaults when a file open() in dynacache mode fails. +# The test is mimiced after a real-life scenario (which, of course, was much more +# complex). +# +# added 2010-03-09 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[dynfile_cachemiss.sh\]: test open fail for dynafiles with `cat rsyslog.action.1.include` +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup dynfile_cachemiss.conf +# we send handcrafted message. We have a dynafile cache of 4, and now send one message +# each to fill up the cache. +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.0.log:0" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.1.log:1" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.2.log:2" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.3.log:3" +# the next one has caused a segfault in practice +# note that /proc/rsyslog.error.file must not be creatable +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:/proc/rsyslog.error.file:boom" +# some more writes +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.0.log:4" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.1.log:5" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.2.log:6" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.3.log:7" +# done message generation +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +cat rsyslog.out.*.log > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 7 +source $srcdir/diag.sh exit diff --git a/tests/dynfile_invalid2.sh b/tests/dynfile_invalid2.sh new file mode 100755 index 00000000..cb3ef51e --- /dev/null +++ b/tests/dynfile_invalid2.sh @@ -0,0 +1,34 @@ +# This test checks if omfile segfaults when a file open() in dynacache mode fails. +# The test is mimiced after a real-life scenario (which, of course, was much more +# complex). +# +# added 2010-03-22 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[dynfile_invalid2.sh\]: test open fail for dynafiles +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup dynfile_invalid2.conf +# we send handcrafted message. We have a dynafile cache of 4, and now send one message +# each to fill up the cache. +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.0.log:0" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.1.log:1" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.2.log:2" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.3.log:3" +# the next one has caused a segfault in practice +# note that /proc/rsyslog.error.file must not be creatable +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:/proc/rsyslog.error.file:boom" +# some more writes +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.0.log:4" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.1.log:5" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.2.log:6" +./tcpflood -m1 -M "<129>Mar 10 01:00:00 172.20.245.8 tag msg:rsyslog.out.3.log:7" +# done message generation +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +cat rsyslog.out.*.log > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 7 +source $srcdir/diag.sh exit diff --git a/tests/dynfile_invld_async.sh b/tests/dynfile_invld_async.sh new file mode 100755 index 00000000..3c9b2045 --- /dev/null +++ b/tests/dynfile_invld_async.sh @@ -0,0 +1,2 @@ +echo "\$OMFileAsyncWriting on" > rsyslog.action.1.include +source $srcdir/dynfile_cachemiss.sh diff --git a/tests/dynfile_invld_sync.sh b/tests/dynfile_invld_sync.sh new file mode 100755 index 00000000..cc6b6451 --- /dev/null +++ b/tests/dynfile_invld_sync.sh @@ -0,0 +1,2 @@ +echo "\$OMFileAsyncWriting off" > rsyslog.action.1.include +source $srcdir/dynfile_cachemiss.sh diff --git a/tests/err1.rstest b/tests/err1.rstest new file mode 100644 index 00000000..8c56887e --- /dev/null +++ b/tests/err1.rstest @@ -0,0 +1,7 @@ +# This test case check for an error condition +result: -2051 +in: +'test 1' <> == $hostname +$$$ +out: +$$$ diff --git a/tests/execonlyonce.sh b/tests/execonlyonce.sh new file mode 100755 index 00000000..8e184070 --- /dev/null +++ b/tests/execonlyonce.sh @@ -0,0 +1,29 @@ +# Test for the $ActionExecOnlyOnceEveryInterval directive. +# We inject a couple of messages quickly during the interval, +# then wait until the interval expires, then quickly inject +# another set. After that, it is checked if exactly two messages +# have arrived. +# The once interval must be set to 3 seconds in the config file. +# added 2009-11-12 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[execonlyonce.sh\]: test for the $ActionExecOnlyOnceEveryInterval directive +source $srcdir/diag.sh init +source $srcdir/diag.sh startup execonlyonce.conf +source $srcdir/diag.sh tcpflood -m10 -i1 +# now wait until the interval definitely expires +sleep 4 # one more than the once inerval! +# and inject another couple of messages +source $srcdir/diag.sh tcpflood -m10 -i100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown + +# now we need your custom logic to see if the result is equal to the +# expected result +cmp rsyslog.out.log testsuites/execonlyonce.data +if [ $? -eq 1 ] +then + echo "ERROR, output not as expected" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/execonlywhenprevsuspended.sh b/tests/execonlywhenprevsuspended.sh new file mode 100755 index 00000000..624f64af --- /dev/null +++ b/tests/execonlywhenprevsuspended.sh @@ -0,0 +1,13 @@ +# we test the execonly if previous is suspended directive. This is the +# most basic test which soley tests a singel case but no dependencies within +# the ruleset. +# rgerhards, 2010-06-23 +echo ===================================================================================== +echo \[execonlywhenprevsuspended.sh\]: test execonly...suspended functionality simple case +source $srcdir/diag.sh init +source $srcdir/diag.sh startup execonlywhenprevsuspended.conf +source $srcdir/diag.sh injectmsg 0 1000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 1 999 +source $srcdir/diag.sh exit diff --git a/tests/execonlywhenprevsuspended2.sh b/tests/execonlywhenprevsuspended2.sh new file mode 100755 index 00000000..8af1b4d8 --- /dev/null +++ b/tests/execonlywhenprevsuspended2.sh @@ -0,0 +1,17 @@ +# we test the execonly if previous is suspended directive. For this, +# we have an action that is suspended for all messages but the second. +# we write two files: one only if the output is suspended and the other one +# in all cases. This should thouroughly check the logic involved. +# rgerhards, 2010-06-23 +echo =============================================================================== +echo \[execonlywhenprevsuspended2.sh\]: test execonly...suspended functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup execonlywhenprevsuspended2.conf +source $srcdir/diag.sh injectmsg 0 1000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +echo check file 1 +source $srcdir/diag.sh seq-check 1 999 +echo check file 2 +source $srcdir/diag.sh seq-check2 0 999 +source $srcdir/diag.sh exit diff --git a/tests/execonlywhenprevsuspended3.sh b/tests/execonlywhenprevsuspended3.sh new file mode 100755 index 00000000..408aeba4 --- /dev/null +++ b/tests/execonlywhenprevsuspended3.sh @@ -0,0 +1,17 @@ +# we test the execonly if previous is suspended directive. +# This test checks if, within the same rule, one action can be set +# to emit only if the previous was suspended while the next action +# always sends data. +# rgerhards, 2010-06-24 +echo =============================================================================== +echo \[execonlywhenprevsuspended3.sh\]: test execonly...suspended functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup execonlywhenprevsuspended3.conf +source $srcdir/diag.sh injectmsg 0 1000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +echo check file 1 +source $srcdir/diag.sh seq-check 1 999 +echo check file 2 +source $srcdir/diag.sh seq-check2 0 999 +source $srcdir/diag.sh exit diff --git a/tests/execonlywhenprevsuspended4.sh b/tests/execonlywhenprevsuspended4.sh new file mode 100755 index 00000000..87008b33 --- /dev/null +++ b/tests/execonlywhenprevsuspended4.sh @@ -0,0 +1,16 @@ +# we test the execonly if previous is suspended directive. +# This test checks if multiple backup actions can be defined. +# rgerhards, 2010-06-24 +echo =============================================================================== +echo \[execonlywhenprevsuspended4.sh\]: test execonly..suspended multi backup action +source $srcdir/diag.sh init +source $srcdir/diag.sh startup execonlywhenprevsuspended4.conf +source $srcdir/diag.sh injectmsg 0 1000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 1 999 +if [[ -s rsyslog2.out.log ]] ; then + echo failure: second output file has data where it should be empty + exit 1 +fi ; +source $srcdir/diag.sh exit diff --git a/tests/failover-async.sh b/tests/failover-async.sh new file mode 100755 index 00000000..5fc393de --- /dev/null +++ b/tests/failover-async.sh @@ -0,0 +1,12 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-async.sh\]: async test for failover functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup failover-async.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/failover-basic-vg.sh b/tests/failover-basic-vg.sh new file mode 100755 index 00000000..0eb77caa --- /dev/null +++ b/tests/failover-basic-vg.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-basic.sh\]: basic test for failover functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg failover-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/failover-basic.sh b/tests/failover-basic.sh new file mode 100755 index 00000000..031ea25b --- /dev/null +++ b/tests/failover-basic.sh @@ -0,0 +1,12 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-basic.sh\]: basic test for failover functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup failover-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/failover-double.sh b/tests/failover-double.sh new file mode 100755 index 00000000..172b91de --- /dev/null +++ b/tests/failover-double.sh @@ -0,0 +1,12 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-double.sh\]: test for double failover functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup failover-double.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/failover-no-basic-vg.sh b/tests/failover-no-basic-vg.sh new file mode 100755 index 00000000..266163d4 --- /dev/null +++ b/tests/failover-no-basic-vg.sh @@ -0,0 +1,20 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-no-basic.sh\]: basic test for failover functionality - no failover +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg failover-no-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +# now we need our custom logic to see if the result file is empty +# (what it should be!) +cmp rsyslog.out.log /dev/null +if [ $? -eq 1 ] +then + echo "ERROR, output file not empty" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/failover-no-basic.sh b/tests/failover-no-basic.sh new file mode 100755 index 00000000..6177e10d --- /dev/null +++ b/tests/failover-no-basic.sh @@ -0,0 +1,19 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-no-basic.sh\]: basic test for failover functionality - no failover +source $srcdir/diag.sh init +source $srcdir/diag.sh startup failover-no-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +# now we need our custom logic to see if the result file is empty +# (what it should be!) +cmp rsyslog.out.log /dev/null +if [ $? -eq 1 ] +then + echo "ERROR, output file not empty" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/failover-no-rptd-vg.sh b/tests/failover-no-rptd-vg.sh new file mode 100755 index 00000000..3b77a0a4 --- /dev/null +++ b/tests/failover-no-rptd-vg.sh @@ -0,0 +1,20 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-no-rptd.sh\]: rptd test for failover functionality - no failover +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg failover-no-rptd.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +# now we need our custom logic to see if the result file is empty +# (what it should be!) +cmp rsyslog.out.log /dev/null +if [ $? -eq 1 ] +then + echo "ERROR, output file not empty" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/failover-no-rptd.sh b/tests/failover-no-rptd.sh new file mode 100755 index 00000000..6abeba44 --- /dev/null +++ b/tests/failover-no-rptd.sh @@ -0,0 +1,19 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-no-rptd.sh\]: rptd test for failover functionality - no failover +source $srcdir/diag.sh init +source $srcdir/diag.sh startup failover-no-rptd.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +# now we need our custom logic to see if the result file is empty +# (what it should be!) +cmp rsyslog.out.log /dev/null +if [ $? -eq 1 ] +then + echo "ERROR, output file not empty" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/failover-rptd-vg.sh b/tests/failover-rptd-vg.sh new file mode 100755 index 00000000..d208003b --- /dev/null +++ b/tests/failover-rptd-vg.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-rptd.sh\]: rptd test for failover functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg failover-rptd.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/failover-rptd.sh b/tests/failover-rptd.sh new file mode 100755 index 00000000..8a313e9e --- /dev/null +++ b/tests/failover-rptd.sh @@ -0,0 +1,12 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[failover-rptd.sh\]: rptd test for failover functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup failover-rptd.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/fieldtest.sh b/tests/fieldtest.sh new file mode 100755 index 00000000..9875fda6 --- /dev/null +++ b/tests/fieldtest.sh @@ -0,0 +1,13 @@ +echo \[fieldtest.sh\]: test fieldtest via udp +$srcdir/killrsyslog.sh # kill rsyslogd if it runs for some reason + +./nettester -tfield1 -iudp +if [ "$?" -ne "0" ]; then + exit 1 +fi + +echo test fieldtest via tcp +./nettester -tfield1 -itcp +if [ "$?" -ne "0" ]; then + exit 1 +fi diff --git a/tests/filewriter.c b/tests/filewriter.c new file mode 100644 index 00000000..07991b1d --- /dev/null +++ b/tests/filewriter.c @@ -0,0 +1,158 @@ +/* This program expands the input file several times. This + * is done in order to obtain large (and maybe huge) files for + * testing. Note that the input file is stored in memory. It's + * last line must properly be terminated. + * Max input line size is 10K. + * + * command line options: + * -i file to be used for input (else stdin) + * -o file to be used for output (else stdout) + * -c number of times the file is to be copied + * -n add line numbers (default: off) + * -w wait nbr of microsecs between batches + * -W number of file lines to generate in a batch + * This is useful only if -w is specified as well, + * default is 1000. + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> + +/* input file is stored in a single-linked list */ +struct line { + struct line *next; + char *ln; +} *root, *tail; + +static FILE *fpIn; +static FILE *fpOut; +static long long nCopies = 1; +static int linenbrs = 0; +static int waitusecs = 0; +static int batchsize = 1000; + + +/* read the input file and create in-memory representation + */ +static inline void +readFile() +{ + char *r; + char lnBuf[10240]; + struct line *node; + + root = tail = NULL; + r = fgets(lnBuf, sizeof(lnBuf), fpIn); + while(r != NULL) { + node = malloc(sizeof(struct line)); + if(node == NULL) { + perror("malloc node"); + exit(1); + } + node->next = NULL; + node->ln = strdup(lnBuf); + if(node->ln == NULL) { + perror("malloc node"); + exit(1); + } + if(tail == NULL) { + tail = root = node; + } else { + tail->next = node; + tail = node; + } + r = fgets(lnBuf, sizeof(lnBuf), fpIn); + } + if(!feof(fpIn)) { + perror("fgets"); + fprintf(stderr, "end of read loop, but not end of file!"); + exit(1); + } +} + + +static void +genCopies() +{ + long long i; + long long unsigned lnnbr; + struct line *node; + + lnnbr = 1; + for(i = 0 ; i < nCopies ; ++i) { + if(i % 10000 == 0) + fprintf(stderr, "copyrun %d\n", i); + if(waitusecs && (i % batchsize == 0)) { + usleep(waitusecs); + } + for(node = root ; node != NULL ; node = node->next) { + if(linenbrs) + fprintf(fpOut, "%12.12llu:%s", lnnbr, node->ln); + else + fprintf(fpOut, "%s", node->ln); + ++lnnbr; + } + } +} + +void main(int argc, char *argv[]) +{ + int opt; + fpIn = stdin; + fpOut = stdout; + + while((opt = getopt(argc, argv, "i:o:c:nw:W:")) != -1) { + switch (opt) { + case 'i': /* input file */ + if((fpIn = fopen(optarg, "r")) == NULL) { + perror(optarg); + exit(1); + } + break; + case 'o': /* output file */ + if((fpOut = fopen(optarg, "w")) == NULL) { + perror(optarg); + exit(1); + } + break; + case 'c': + nCopies = atoll(optarg); + break; + case 'n': + linenbrs = 1; + break; + case 'w': + waitusecs = atoi(optarg); + break; + case 'W': + batchsize = atoi(optarg); + break; + default: printf("invalid option '%c' or value missing - terminating...\n", opt); + exit (1); + break; + } + } + + readFile(); + genCopies(); +} diff --git a/tests/getline.c b/tests/getline.c new file mode 100644 index 00000000..617d1b0e --- /dev/null +++ b/tests/getline.c @@ -0,0 +1,57 @@ +/* getline() replacement for platforms that do not have it. + * + * Part of the testbench for rsyslog. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <sys/types.h> +#include <stdlib.h> + +/* we emulate getline (the dirty way) if we do not have it + * We do not try very hard, as this is just a test driver. + * rgerhards, 2009-03-31 + */ +#ifndef HAVE_GETLINE +ssize_t getline(char **lineptr, size_t *n, FILE *fp) +{ + int c; + int len = 0; + + if(*lineptr == NULL) + *lineptr = malloc(4096); /* quick and dirty! */ + + c = fgetc(fp); + while(c != EOF && c != '\n') { + (*lineptr)[len++] = c; + c = fgetc(fp); + } + if(c != EOF) /* need to add NL? */ + (*lineptr)[len++] = c; + + (*lineptr)[len] = '\0'; + + *n = len; + //printf("getline returns: '%s'\n", *lineptr); + + return (len > 0) ? len : -1; +} +#endif /* #ifndef HAVE_GETLINE */ diff --git a/tests/gzipwr_large.sh b/tests/gzipwr_large.sh new file mode 100755 index 00000000..ffce06f6 --- /dev/null +++ b/tests/gzipwr_large.sh @@ -0,0 +1,20 @@ +# This tests writing large data records in gzip mode. We use up to 10K +# record size. +# +# added 2010-03-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[gzipwr_large.sh\]: test for gzip file writing for large message sets +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup gzipwr_large.conf +# send 4000 messages of 10.000bytes plus header max, randomized +source $srcdir/diag.sh tcpflood -m4000 -r -d10000 -P129 +sleep 1 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh gzip-seq-check 0 3999 -E +source $srcdir/diag.sh exit diff --git a/tests/gzipwr_large_dynfile.sh b/tests/gzipwr_large_dynfile.sh new file mode 100755 index 00000000..73d44796 --- /dev/null +++ b/tests/gzipwr_large_dynfile.sh @@ -0,0 +1,36 @@ +# This tests writing large data records in gzip mode. We also write it to +# 5 different dynafiles, with a dynafile cache size set to 4. So this stresses +# both the input side, as well as zip writing, async writing and the dynafile +# cache logic. +# +# This test is a bit timing-dependent on the tcp reception side, so if it fails +# one may look into the timing first. The main issue is that the testbench +# currently has no good way to know if the tcp receiver is finished. This is NOT +# a problem in rsyslogd, but only of the testbench. +# +# Note that we do not yet have sufficient support for dynafiles in diag.sh, +# so we mangle some files here manually. +# +# added 2010-03-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[gzipwr_large_dynfile.sh\]: test for gzip file writing for large message sets +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup gzipwr_large_dynfile.conf +# send 4000 messages of 10.000bytes plus header max, randomized +source $srcdir/diag.sh tcpflood -m4000 -r -d10000 -P129 -f5 +sleep 2 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +gunzip < rsyslog.out.0.log > rsyslog.out.log +gunzip < rsyslog.out.1.log >> rsyslog.out.log +gunzip < rsyslog.out.2.log >> rsyslog.out.log +gunzip < rsyslog.out.3.log >> rsyslog.out.log +gunzip < rsyslog.out.4.log >> rsyslog.out.log +#cat rsyslog.out.* > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 3999 -E +source $srcdir/diag.sh exit diff --git a/tests/historical/DiagTalker.java b/tests/historical/DiagTalker.java new file mode 100644 index 00000000..147cec34 --- /dev/null +++ b/tests/historical/DiagTalker.java @@ -0,0 +1,74 @@ +This tool has been replaced by ./tests/diagtalker.c in release 4.7.1 +/* A yet very simple tool to talk to imdiag. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +//package com.rsyslog.diag; +import java.io.*; +import java.net.*; + +public class DiagTalker { + public static void main(String[] args) throws IOException { + + Socket diagSocket = null; + PrintWriter out = null; + BufferedReader in = null; + final String host = "127.0.0.1"; + int port = 13500; + if(args.length > 1) { + port = Integer.parseInt(args[1]); + } + + try { + diagSocket = new Socket(host, port); + diagSocket.setSoTimeout(0); /* wait for lenghty operations */ + out = new PrintWriter(diagSocket.getOutputStream(), true); + in = new BufferedReader(new InputStreamReader( + diagSocket.getInputStream())); + } catch (UnknownHostException e) { + System.err.println("can not resolve " + host + "!"); + System.exit(1); + } catch (IOException e) { + System.err.println("Couldn't get I/O for " + + "the connection to: " + host + "."); + System.exit(1); + } + + BufferedReader stdIn = new BufferedReader( + new InputStreamReader(System.in)); + String userInput; + + try { + while ((userInput = stdIn.readLine()) != null) { + out.println(userInput); + System.out.println("imdiag returns: " + in.readLine()); + } + } catch (SocketException e) { + System.err.println("We had a socket exception and consider this to be OK: " + + e.getMessage()); + } + + out.close(); + in.close(); + stdIn.close(); + diagSocket.close(); + } +} + diff --git a/tests/historical/README b/tests/historical/README new file mode 100644 index 00000000..5f10ecef --- /dev/null +++ b/tests/historical/README @@ -0,0 +1,2 @@ +This directory contains tools that are currently not being used, but are +kept because they may be used again in the future. diff --git a/tests/imfile-basic-vg.sh b/tests/imfile-basic-vg.sh new file mode 100755 index 00000000..92cfc7f6 --- /dev/null +++ b/tests/imfile-basic-vg.sh @@ -0,0 +1,15 @@ +# This is part of the rsyslog testbench, licensed under GPLv3 +echo [imfile-basic.sh] +source $srcdir/diag.sh init +# generate input file first. Note that rsyslog processes it as +# soon as it start up (so the file should exist at that point). +./inputfilegen 50000 > rsyslog.input +ls -l rsyslog.input +source $srcdir/diag.sh startup-vg imfile-basic.conf +# sleep a little to give rsyslog a chance to begin processing +sleep 1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/imfile-basic.sh b/tests/imfile-basic.sh new file mode 100755 index 00000000..ca6a5d3a --- /dev/null +++ b/tests/imfile-basic.sh @@ -0,0 +1,14 @@ +# This is part of the rsyslog testbench, licensed under GPLv3 +echo [imfile-basic.sh] +source $srcdir/diag.sh init +# generate input file first. Note that rsyslog processes it as +# soon as it start up (so the file should exist at that point). +./inputfilegen 50000 > rsyslog.input +ls -l rsyslog.input +source $srcdir/diag.sh startup imfile-basic.conf +# sleep a little to give rsyslog a chance to begin processing +sleep 1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # we need to wait until rsyslogd is finished! +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/imptcp_addtlframedelim.sh b/tests/imptcp_addtlframedelim.sh new file mode 100755 index 00000000..00276ab3 --- /dev/null +++ b/tests/imptcp_addtlframedelim.sh @@ -0,0 +1,13 @@ +# added 2010-08-11 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imptcp_addtlframedelim.sh\]: test imptcp additional frame delimiter +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imptcp_addtlframedelim.conf +source $srcdir/diag.sh tcpflood -m20000 -F0 -P129 +#sleep 2 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 19999 +source $srcdir/diag.sh exit diff --git a/tests/imptcp_conndrop.sh b/tests/imptcp_conndrop.sh new file mode 100755 index 00000000..0cf0ba5e --- /dev/null +++ b/tests/imptcp_conndrop.sh @@ -0,0 +1,15 @@ +# Test imptcp with many dropping connections +# added 2010-08-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imptcp_conndrop.sh\]: test imptcp with random connection drops +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imptcp_conndrop.conf +# 100 byte messages to gain more practical data use +source $srcdir/diag.sh tcpflood -c20 -m50000 -r -d100 -P129 -D +sleep 4 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 49999 -E +source $srcdir/diag.sh exit diff --git a/tests/imptcp_large.sh b/tests/imptcp_large.sh new file mode 100755 index 00000000..43027069 --- /dev/null +++ b/tests/imptcp_large.sh @@ -0,0 +1,15 @@ +# Test imptcp with large messages +# added 2010-08-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imptcp_large.sh\]: test imptcp with large-size messages +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imptcp_large.conf +# send 4000 messages of 10.000bytes plus header max, randomized +source $srcdir/diag.sh tcpflood -c5 -m20000 -r -d10000 -P129 +sleep 2 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 19999 -E +source $srcdir/diag.sh exit diff --git a/tests/imtcp-multiport.sh b/tests/imtcp-multiport.sh new file mode 100755 index 00000000..ad2b44f8 --- /dev/null +++ b/tests/imtcp-multiport.sh @@ -0,0 +1,42 @@ +# Test for multiple ports in imtcp +# This test checks if multiple tcp listener ports are correctly +# handled by imtcp +# +# NOTE: this test must (and can) be enhanced when we merge in the +# upgraded tcpflood program +# +# added 2009-05-22 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[imtcp-multiport.sh\]: testing imtcp multiple listeners +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imtcp-multiport.conf +source $srcdir/diag.sh tcpflood -p13514 -m10000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 9999 +source $srcdir/diag.sh exit +# +# +# ### now complete new cycle with other port ### +# +# +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imtcp-multiport.conf +source $srcdir/diag.sh tcpflood -p13515 -m10000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 9999 +source $srcdir/diag.sh exit +# +# +# ### now complete new cycle with other port ### +# +# +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imtcp-multiport.conf +source $srcdir/diag.sh tcpflood -p13516 -m10000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 9999 +source $srcdir/diag.sh exit diff --git a/tests/imtcp-tls-basic-vg.sh b/tests/imtcp-tls-basic-vg.sh new file mode 100755 index 00000000..960a14d1 --- /dev/null +++ b/tests/imtcp-tls-basic-vg.sh @@ -0,0 +1,15 @@ +# added 2011-02-28 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[imtcp-tls-basic-vg.sh\]: testing imtcp in TLS mode - basic test +source $srcdir/diag.sh init +echo \$DefaultNetstreamDriverCAFile $srcdir/tls-certs/ca.pem >rsyslog.conf.tlscert +echo \$DefaultNetstreamDriverCertFile $srcdir/tls-certs/cert.pem >>rsyslog.conf.tlscert +echo \$DefaultNetstreamDriverKeyFile $srcdir/tls-certs/key.pem >>rsyslog.conf.tlscert +source $srcdir/diag.sh startup-vg imtcp-tls-basic.conf +source $srcdir/diag.sh tcpflood -p13514 -m50000 -Ttls -Z$srcdir/tls-certs/cert.pem -z$srcdir/tls-certs/key.pem +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/imtcp-tls-basic.sh b/tests/imtcp-tls-basic.sh new file mode 100755 index 00000000..bfe24880 --- /dev/null +++ b/tests/imtcp-tls-basic.sh @@ -0,0 +1,14 @@ +# added 2011-02-28 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[imtcp-tls-basic.sh\]: testing imtcp in TLS mode - basic test +source $srcdir/diag.sh init +echo \$DefaultNetstreamDriverCAFile $srcdir/tls-certs/ca.pem >rsyslog.conf.tlscert +echo \$DefaultNetstreamDriverCertFile $srcdir/tls-certs/cert.pem >>rsyslog.conf.tlscert +echo \$DefaultNetstreamDriverKeyFile $srcdir/tls-certs/key.pem >>rsyslog.conf.tlscert +source $srcdir/diag.sh startup imtcp-tls-basic.conf +source $srcdir/diag.sh tcpflood -p13514 -m50000 -Ttls -Z$srcdir/tls-certs/cert.pem -z$srcdir/tls-certs/key.pem +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/imtcp_addtlframedelim.sh b/tests/imtcp_addtlframedelim.sh new file mode 100755 index 00000000..4c1fd9cb --- /dev/null +++ b/tests/imtcp_addtlframedelim.sh @@ -0,0 +1,13 @@ +# added 2010-08-11 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imtcp_addtlframedelim.sh\]: test imtcp additional frame delimiter +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imtcp_addtlframedelim.conf +source $srcdir/diag.sh tcpflood -m20000 -F0 -P129 +#sleep 2 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 19999 +source $srcdir/diag.sh exit diff --git a/tests/imtcp_conndrop.sh b/tests/imtcp_conndrop.sh new file mode 100755 index 00000000..c5073924 --- /dev/null +++ b/tests/imtcp_conndrop.sh @@ -0,0 +1,15 @@ +# Test imtcp with many dropping connections +# added 2010-08-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imtcp_conndrop.sh\]: test imtcp with random connection drops +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imtcp_conndrop.conf +# 100 byte messages to gain more practical data use +source $srcdir/diag.sh tcpflood -c20 -m50000 -r -d100 -P129 -D +sleep 10 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 49999 -E +source $srcdir/diag.sh exit diff --git a/tests/imtcp_conndrop_tls-vg.sh b/tests/imtcp_conndrop_tls-vg.sh new file mode 100755 index 00000000..21f6876c --- /dev/null +++ b/tests/imtcp_conndrop_tls-vg.sh @@ -0,0 +1,17 @@ +# Test imtcp/TLS with many dropping connections +# added 2011-06-09 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imtcp_conndrop_tls-vg.sh\]: test imtcp/tls with random connection drops +cat rsyslog.action.1.include +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg imtcp_conndrop.conf +# 100 byte messages to gain more practical data use +source $srcdir/diag.sh tcpflood -c20 -m50000 -r -d100 -P129 -D +sleep 10 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg # and wait for it to terminate +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh seq-check 0 49999 -E +source $srcdir/diag.sh exit diff --git a/tests/imtcp_conndrop_tls.sh b/tests/imtcp_conndrop_tls.sh new file mode 100755 index 00000000..31a3477a --- /dev/null +++ b/tests/imtcp_conndrop_tls.sh @@ -0,0 +1,16 @@ +# Test imtcp/TLS with many dropping connections +# added 2011-06-09 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo ==================================================================================== +echo TEST: \[imtcp_conndrop_tls.sh\]: test imtcp/tls with random connection drops +cat rsyslog.action.1.include +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imtcp_conndrop.conf +# 100 byte messages to gain more practical data use +source $srcdir/diag.sh tcpflood -c20 -m50000 -r -d100 -P129 -D +sleep 10 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 49999 -E +source $srcdir/diag.sh exit diff --git a/tests/imuxsock_ccmiddle_root.sh b/tests/imuxsock_ccmiddle_root.sh new file mode 100755 index 00000000..7f255bd0 --- /dev/null +++ b/tests/imuxsock_ccmiddle_root.sh @@ -0,0 +1,21 @@ +# note: we must be root and no other syslogd running in order to +# carry out this test +echo \[imuxsock_ccmiddle_root.sh\]: test trailing LF handling in imuxsock +echo This test must be run as root with no other active syslogd +if [ "$EUID" -ne 0 ]; then + exit 77 # Not root, skip this test +fi +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imuxsock_ccmiddle_root.conf +# send a message with trailing LF +./syslog_inject -c +# the sleep below is needed to prevent too-early termination of rsyslogd +./msleep 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # we need to wait until rsyslogd is finished! +cmp rsyslog.out.log $srcdir/resultdata/imuxsock_ccmiddle.log +if [ ! $? -eq 0 ]; then +echo "imuxsock_ccmiddle_root.sh failed" +exit 1 +fi; +source $srcdir/diag.sh exit diff --git a/tests/imuxsock_logger_root.sh b/tests/imuxsock_logger_root.sh new file mode 100755 index 00000000..0902d797 --- /dev/null +++ b/tests/imuxsock_logger_root.sh @@ -0,0 +1,21 @@ +# note: we must be root and no other syslogd running in order to +# carry out this test. +echo \[imuxsock_logger_root.sh\]: test trailing LF handling in imuxsock +echo This test must be run as root with no other active syslogd +if [ "$EUID" -ne 0 ]; then + exit 77 # Not root, skip this test +fi +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imuxsock_logger_root.conf +# send a message with trailing LF +logger test +# the sleep below is needed to prevent too-early termination of rsyslogd +./msleep 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # we need to wait until rsyslogd is finished! +cmp rsyslog.out.log $srcdir/resultdata/imuxsock_logger.log +if [ ! $? -eq 0 ]; then +echo "imuxsock_logger.sh failed" +exit 1 +fi; +source $srcdir/diag.sh exit diff --git a/tests/imuxsock_traillf_root.sh b/tests/imuxsock_traillf_root.sh new file mode 100755 index 00000000..0141a626 --- /dev/null +++ b/tests/imuxsock_traillf_root.sh @@ -0,0 +1,21 @@ +# note: we must be root and no other syslogd running in order to +# carry out this test +echo \[imuxsock_traillf_root.sh\]: test trailing LF handling in imuxsock +echo This test must be run as root with no other active syslogd +if [ "$EUID" -ne 0 ]; then + exit 77 # Not root, skip this test +fi +source $srcdir/diag.sh init +source $srcdir/diag.sh startup imuxsock_traillf_root.conf +# send a message with trailing LF +./syslog_inject -l +# the sleep below is needed to prevent too-early termination of rsyslogd +./msleep 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # we need to wait until rsyslogd is finished! +cmp rsyslog.out.log $srcdir/resultdata/imuxsock_traillf.log +if [ ! $? -eq 0 ]; then +echo "imuxsock_traillf_root.sh failed" +exit 1 +fi; +source $srcdir/diag.sh exit diff --git a/tests/incltest.sh b/tests/incltest.sh new file mode 100755 index 00000000..8e3fe454 --- /dev/null +++ b/tests/incltest.sh @@ -0,0 +1,11 @@ +echo =============================================================================== +echo \[incltest.sh\]: test $IncludeConfig for specific file + +source $srcdir/diag.sh init +source $srcdir/diag.sh startup incltest.conf +# 100 messages are enough - the question is if the include is read ;) +source $srcdir/diag.sh injectmsg 0 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99 +source $srcdir/diag.sh exit diff --git a/tests/incltest_dir.sh b/tests/incltest_dir.sh new file mode 100755 index 00000000..3716a695 --- /dev/null +++ b/tests/incltest_dir.sh @@ -0,0 +1,11 @@ +echo =============================================================================== +echo \[incltest_dir.sh\]: test $IncludeConfig for directories + +source $srcdir/diag.sh init +source $srcdir/diag.sh startup incltest_dir.conf +# 100 messages are enough - the question is if the include is read ;) +source $srcdir/diag.sh injectmsg 0 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99 +source $srcdir/diag.sh exit diff --git a/tests/incltest_dir_empty_wildcard.sh b/tests/incltest_dir_empty_wildcard.sh new file mode 100755 index 00000000..6cdb3b21 --- /dev/null +++ b/tests/incltest_dir_empty_wildcard.sh @@ -0,0 +1,13 @@ +# This test checks if an empty includeConfig directory causes problems. It +# should not, as this is a valid situation that by default exists on many +# distros. +echo =============================================================================== +echo \[incltest_dir_empty_wildcard.sh\]: test $IncludeConfig for \"empty\" wildcard +source $srcdir/diag.sh init +source $srcdir/diag.sh startup incltest_dir_empty_wildcard.conf +# 100 messages are enough - the question is if the include is read ;) +source $srcdir/diag.sh injectmsg 0 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99 +source $srcdir/diag.sh exit diff --git a/tests/incltest_dir_wildcard.sh b/tests/incltest_dir_wildcard.sh new file mode 100755 index 00000000..0dcad34d --- /dev/null +++ b/tests/incltest_dir_wildcard.sh @@ -0,0 +1,11 @@ +echo =============================================================================== +echo \[incltest_dir_wildcard.sh\]: test $IncludeConfig for directories with wildcards + +source $srcdir/diag.sh init +source $srcdir/diag.sh startup incltest_dir_wildcard.conf +# 100 messages are enough - the question is if the include is read ;) +source $srcdir/diag.sh injectmsg 0 100 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99 +source $srcdir/diag.sh exit diff --git a/tests/inputfilegen.c b/tests/inputfilegen.c new file mode 100644 index 00000000..0ff8d049 --- /dev/null +++ b/tests/inputfilegen.c @@ -0,0 +1,24 @@ +/* generate an input file suitable for use by the testbench + * Copyright (C) 2011 by Rainer Gerhards and Adiscon GmbH. + * usage: ./inputfilegen num-lines > file + * Part of rsyslog, licensed under GPLv3 + */ +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, char* argv[]) +{ + int nmsgs; + int i; + + if(argc != 2) { + fprintf(stderr, "usage: inputfilegen num-messages\n"); + return 1; + } + + nmsgs = atoi(argv[1]); + for(i = 0 ; i < nmsgs ; ++i) { + printf("msgnum:%8.8d:\n", i); + } + return 0; +} diff --git a/tests/inputname.sh b/tests/inputname.sh new file mode 100755 index 00000000..71f11c1e --- /dev/null +++ b/tests/inputname.sh @@ -0,0 +1,20 @@ +echo \[inputname.sh\]: testing $InputTCPServerInputName directive +$srcdir/killrsyslog.sh # kill rsyslogd if it runs for some reason + +echo port 12514 +./nettester -tinputname_imtcp_12514 -cinputname_imtcp -itcp -p12514 +if [ "$?" -ne "0" ]; then + exit 1 +fi + +echo port 12515 +./nettester -tinputname_imtcp_12515 -cinputname_imtcp -itcp -p12515 +if [ "$?" -ne "0" ]; then + exit 1 +fi + +echo port 12516 +./nettester -tinputname_imtcp_12516 -cinputname_imtcp -itcp -p12516 +if [ "$?" -ne "0" ]; then + exit 1 +fi diff --git a/tests/killrsyslog.sh b/tests/killrsyslog.sh new file mode 100755 index 00000000..aac24909 --- /dev/null +++ b/tests/killrsyslog.sh @@ -0,0 +1,13 @@ +#check if rsyslog instance exists and, if so, kill it +if [ -e "rsyslog.pid" ] +then + echo rsyslog.pid exists, trying to shut down rsyslogd process `cat rsyslog.pid`. + kill -9 `cat rsyslog.pid` + sleep 1 +fi +if [ -e "rsyslog2.pid" ] +then + echo rsyslog2.pid exists, trying to shut down rsyslogd process `cat rsyslog2.pid`. + kill -9 `cat rsyslog2.pid` + sleep 1 +fi diff --git a/tests/libdbi-asyn.sh b/tests/libdbi-asyn.sh new file mode 100755 index 00000000..0076da9c --- /dev/null +++ b/tests/libdbi-asyn.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[libdbi-asyn.sh\]: asyn test for libdbi functionality +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup libdbi-asyn.conf +source $srcdir/diag.sh injectmsg 0 50000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/libdbi-basic-vg.sh b/tests/libdbi-basic-vg.sh new file mode 100755 index 00000000..75d12857 --- /dev/null +++ b/tests/libdbi-basic-vg.sh @@ -0,0 +1,16 @@ +# This file is part of the rsyslog project, released under GPLv3 +# this test is currently not included in the testbench as libdbi +# itself seems to have a memory leak +echo =============================================================================== +echo \[libdbi-basic.sh\]: basic test for libdbi-basic functionality via mysql +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup-vg libdbi-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/libdbi-basic.sh b/tests/libdbi-basic.sh new file mode 100755 index 00000000..a854a565 --- /dev/null +++ b/tests/libdbi-basic.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[libdbi-basic.sh\]: basic test for libdbi-basic functionality via mysql +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup libdbi-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/linkedlistqueue.sh b/tests/linkedlistqueue.sh new file mode 100755 index 00000000..e6d48a68 --- /dev/null +++ b/tests/linkedlistqueue.sh @@ -0,0 +1,17 @@ +# Test for Linkedlist queue mode +# added 2009-05-20 by rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo \[linkedlistqueue.sh\]: testing queue Linkedlist queue mode +source $srcdir/diag.sh init +source $srcdir/diag.sh startup linkedlistqueue.conf + +# 40000 messages should be enough +source $srcdir/diag.sh injectmsg 0 40000 + +# terminate *now* (don't wait for queue to drain) +kill `cat rsyslog.pid` + +# now wait until rsyslog.pid is gone (and the process finished) +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 39999 +source $srcdir/diag.sh exit diff --git a/tests/longrun.sh b/tests/longrun.sh new file mode 100755 index 00000000..407e9fa9 --- /dev/null +++ b/tests/longrun.sh @@ -0,0 +1,30 @@ +# This is a test-aid script to try running some tests through many iterations. +# It is not yet used in the automated testbench, but I keep this file so that +# I can use it whenever there is need to. As such, it currently does not have +# parameters but is expected to be edited as needed. -- rgerhards, 2010-03-10 +# +# use: ./longrun.sh testname.sh +# +# where testname.sh is the test to be run +# to change other params, you need to edit the settings here below: +MAXRUNS=10 +DISPLAYALIVE=100 +LOGFILE=runlog + +echo "logfile is $LOGFILE" +echo "executing test $1" + +date > $LOGFILE + +for (( i=0; $i < 10000; i++ )) + do + if [ $(( i % DISPLAYALIVE )) -eq 0 ]; then + echo "$i iterations done" + fi + $1 >> $LOGFILE + if [ "$?" -ne "0" ]; then + echo "Test failed in iteration $i, review $LOGFILE for details!" + exit 1 + fi + done +echo "No failure in $i iterations." diff --git a/tests/manyptcp.sh b/tests/manyptcp.sh new file mode 100755 index 00000000..3ed5493b --- /dev/null +++ b/tests/manyptcp.sh @@ -0,0 +1,13 @@ +# test many concurrent tcp connections +echo ==================================================================================== +echo TEST: \[manyptcp.sh\]: test imptcp with large connection count +source $srcdir/diag.sh init +source $srcdir/diag.sh startup manyptcp.conf +# the config file specifies exactly 1100 connections +source $srcdir/diag.sh tcpflood -c1000 -m40000 +# the sleep below is needed to prevent too-early termination of the tcp listener +sleep 1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # we need to wait until rsyslogd is finished! +source $srcdir/diag.sh seq-check 0 39999 +source $srcdir/diag.sh exit diff --git a/tests/manytcp-too-few-tls.sh b/tests/manytcp-too-few-tls.sh new file mode 100755 index 00000000..8d401f48 --- /dev/null +++ b/tests/manytcp-too-few-tls.sh @@ -0,0 +1,17 @@ +# test many concurrent tcp connections +echo \[manytcp-too-few-tls.sh\]: test concurrent tcp connections +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg manytcp-too-few-tls.conf +# the config file specifies exactly 1100 connections +source $srcdir/diag.sh tcpflood -c1000 -m40000 +# the sleep below is needed to prevent too-early termination of the tcp listener +sleep 1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg # we need to wait until rsyslogd is finished! +source $srcdir/diag.sh check-exit-vg +# we do not do a seq check, as of the design of this test some messages +# will be lost. So there is no point in checking if all were received. The +# point is that we look at the valgrind result, to make sure we do not +# have a mem leak in those error cases (we had in the past, thus the test +# to prevent that in the future). +source $srcdir/diag.sh exit diff --git a/tests/manytcp.sh b/tests/manytcp.sh new file mode 100755 index 00000000..ec8f2453 --- /dev/null +++ b/tests/manytcp.sh @@ -0,0 +1,12 @@ +# test many concurrent tcp connections +echo \[manytcp.sh\]: test concurrent tcp connections +source $srcdir/diag.sh init +source $srcdir/diag.sh startup manytcp.conf +# the config file specifies exactly 1100 connections +source $srcdir/diag.sh tcpflood -c1000 -m40000 +# the sleep below is needed to prevent too-early termination of the tcp listener +sleep 1 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # we need to wait until rsyslogd is finished! +source $srcdir/diag.sh seq-check 0 39999 +source $srcdir/diag.sh exit diff --git a/tests/minitcpsrvr.c b/tests/minitcpsrvr.c new file mode 100644 index 00000000..8ac59f25 --- /dev/null +++ b/tests/minitcpsrvr.c @@ -0,0 +1,62 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <unistd.h> +#include <arpa/inet.h> + +static void +errout(char *reason) +{ + perror(reason); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int fds; + int fdc; + int fdf; + struct sockaddr_in srvAddr; + struct sockaddr_in cliAddr; + unsigned int srvAddrLen; + unsigned int cliAddrLen; + char wrkBuf[4096]; + ssize_t nRead; + + if(argc != 4) { + fprintf(stderr, "usage: minitcpsrvr ip-addr port outfile\n"); + exit(1); + } + + if(!strcmp(argv[3], "-")) { + fdf = 1; + } else { + fdf = open(argv[3], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR|S_IWUSR); + if(fdf == -1) errout(argv[3]); + } + + fds = socket(AF_INET, SOCK_STREAM, 0); + srvAddr.sin_family = AF_INET; + srvAddr.sin_addr.s_addr = inet_addr(argv[1]); + srvAddr.sin_port = htons(atoi(argv[2])); + srvAddrLen = sizeof(srvAddr); + if(bind(fds, (struct sockaddr *)&srvAddr, srvAddrLen) != 0) + errout("bind"); + if(listen(fds, 20) != 0) errout("listen"); + cliAddrLen = sizeof(cliAddr); + + fdc = accept(fds, (struct sockaddr *)&cliAddr, &cliAddrLen); + while(1) { + nRead = read(fdc, wrkBuf, sizeof(wrkBuf)); + if(nRead == 0) break; + if(write(fdf, wrkBuf, nRead) != nRead) + errout("write"); + } + /* let the OS do the cleanup */ + return 0; +} diff --git a/tests/msleep.c b/tests/msleep.c new file mode 100644 index 00000000..36fa01b5 --- /dev/null +++ b/tests/msleep.c @@ -0,0 +1,51 @@ +/* sleeps for the specified number of MILLIseconds. + * Primarily meant as a portable tool available everywhere for the + * testbench (sleep 0.1 does not work on all platforms). + * + * Part of the testbench for rsyslog. + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +int main(int argc, char *argv[]) +{ + struct timeval tvSelectTimeout; + long sleepTime; + + if(argc != 2) { + fprintf(stderr, "usage: msleep <milliseconds>\n"); + exit(1); + } + + sleepTime = atoi(argv[1]); + tvSelectTimeout.tv_sec = sleepTime / 1000; + tvSelectTimeout.tv_usec = (sleepTime % 1000) * 1000; /* micro seconds */ + if(select(0, NULL, NULL, NULL, &tvSelectTimeout) == -1) { + perror("select"); + exit(1); + } + + return 0; +} + diff --git a/tests/mysql-asyn-vg.sh b/tests/mysql-asyn-vg.sh new file mode 100755 index 00000000..dfe68665 --- /dev/null +++ b/tests/mysql-asyn-vg.sh @@ -0,0 +1,14 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[mysql-asyn.sh\]: asyn test for mysql functionality +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup-vg mysql-asyn.conf +source $srcdir/diag.sh injectmsg 0 50000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/mysql-asyn.sh b/tests/mysql-asyn.sh new file mode 100755 index 00000000..de6d6fde --- /dev/null +++ b/tests/mysql-asyn.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[mysql-asyn.sh\]: asyn test for mysql functionality +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup mysql-asyn.conf +source $srcdir/diag.sh injectmsg 0 50000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 49999 +source $srcdir/diag.sh exit diff --git a/tests/mysql-basic-cnf6.sh b/tests/mysql-basic-cnf6.sh new file mode 100755 index 00000000..8990ba35 --- /dev/null +++ b/tests/mysql-basic-cnf6.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[mysql-basic.sh\]: basic test for mysql-basic functionality +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup mysql-basic-cnf6.conf +source $srcdir/diag.sh injectmsg 0 5000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/mysql-basic-vg.sh b/tests/mysql-basic-vg.sh new file mode 100755 index 00000000..215f41f0 --- /dev/null +++ b/tests/mysql-basic-vg.sh @@ -0,0 +1,14 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[mysql-basic-vg.sh\]: basic test for mysql-basic functionality/valgrind +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup-vg mysql-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/mysql-basic.sh b/tests/mysql-basic.sh new file mode 100755 index 00000000..ba9d00f2 --- /dev/null +++ b/tests/mysql-basic.sh @@ -0,0 +1,13 @@ +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[mysql-basic.sh\]: basic test for mysql-basic functionality +source $srcdir/diag.sh init +mysql --user=rsyslog --password=testbench < testsuites/mysql-truncate.sql +source $srcdir/diag.sh startup mysql-basic.conf +source $srcdir/diag.sh injectmsg 0 5000 +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown +# note "-s" is requried to suppress the select "field header" +mysql -s --user=rsyslog --password=testbench < testsuites/mysql-select-msg.sql > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/nettester.c b/tests/nettester.c new file mode 100644 index 00000000..4e4fe55a --- /dev/null +++ b/tests/nettester.c @@ -0,0 +1,592 @@ +/* Runs a test suite on the rsyslog (and later potentially + * other things). + * + * The name of the test suite must be given as argv[1]. In this config, + * rsyslogd is loaded with config ./testsuites/<name>.conf and then + * test cases ./testsuites/ *.<name> are executed on it. This test driver is + * suitable for testing cases where a message sent (via UDP) results in + * exactly one response. It can not be used in cases where no response + * is expected (that would result in a hang of the test driver). + * Note: each test suite can contain many tests, but they all need to work + * with the same rsyslog configuration. + * + * Part of the testbench for rsyslog. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <assert.h> +#include <unistd.h> +#include <string.h> +#include <glob.h> +#include <signal.h> +#include <netinet/in.h> +#include <getopt.h> +#include <errno.h> +#include <ctype.h> + +#define EXIT_FAILURE 1 +#define INVALID_SOCKET -1 +/* Name of input file, must match $IncludeConfig in test suite .conf files */ +#define NETTEST_INPUT_CONF_FILE "nettest.input.conf" /* name of input file, must match $IncludeConfig in .conf files */ + +typedef enum { inputUDP, inputTCP } inputMode_t; +inputMode_t inputMode = inputTCP; /* input for which tests are to be run */ +static pid_t rsyslogdPid = 0; /* pid of rsyslog instance being tested */ +static char *srcdir; /* global $srcdir, set so that we can run outside of "make check" */ +static char *testSuite = NULL; /* name of current test suite */ +static int iPort = 12514; /* port which shall be used for sending data */ +static char* pszCustomConf = NULL; /* custom config file, use -c conf to specify */ +static int verbose = 0; /* verbose output? -v option */ +static int IPv4Only = 0; /* use only IPv4 in rsyslogd call? */ +static char **ourEnvp; + +/* these two are quick hacks... */ +int iFailed = 0; +int iTests = 0; + +/* provide user-friednly name of input mode + */ +static char *inputMode2Str(inputMode_t mode) +{ + char *pszMode; + + if(mode == inputUDP) + pszMode = "udp"; + else + pszMode = "tcp"; + + return pszMode; +} + + +void readLine(int fd, char *ln) +{ + char *orig = ln; + char c; + int lenRead; + + if(verbose) + fprintf(stderr, "begin readLine\n"); + lenRead = read(fd, &c, 1); + + while(lenRead == 1 && c != '\n') { + if(c == '\0') { + *ln = c; + fprintf(stderr, "Warning: there was a '\\0'-Byte in the read response " + "right after this string: '%s'\n", orig); + c = '?'; + } + *ln++ = c; + lenRead = read(fd, &c, 1); + } + *ln = '\0'; + + if(lenRead < 0) { + printf("read from rsyslogd returned with error '%s' - aborting test\n", strerror(errno)); + exit(1); + } + + if(verbose) + fprintf(stderr, "end readLine, val read '%s'\n", orig); +} + + +/* send a message via TCP + * We open the connection on the initial send, and never close it + * (let the OS do that). If a conneciton breaks, we do NOT try to + * recover, so all test after that one will fail (and the test + * driver probably hang. returns 0 if ok, something else otherwise. + * We use traditional framing '\n' at EOR for this tester. It may be + * worth considering additional framing modes. + * rgerhards, 2009-04-08 + * Note: we re-create the socket within the retry loop, because this + * seems to be needed under Solaris. If we do not do that, we run + * into troubles (maybe something wrongly initialized then?) + * -- rgerhards, 2010-04-12 + */ +int +tcpSend(char *buf, int lenBuf) +{ + static int sock = INVALID_SOCKET; + struct sockaddr_in addr; + int retries; + int ret; + int iRet = 0; /* 0 OK, anything else error */ + + if(sock == INVALID_SOCKET) { + /* first time, need to connect to target */ + retries = 0; + while(1) { /* loop broken inside */ + /* first time, need to connect to target */ + if((sock=socket(AF_INET, SOCK_STREAM, 0))==-1) { + perror("socket()"); + iRet = 1; + goto finalize_it; + } + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(iPort); + if(inet_aton("127.0.0.1", &addr.sin_addr)==0) { + fprintf(stderr, "inet_aton() failed\n"); + iRet = 1; + goto finalize_it; + } + if((ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr))) == 0) { + break; + } else { + if(retries++ == 50) { + fprintf(stderr, "connect() failed\n"); + iRet = 1; + goto finalize_it; + } else { + usleep(100000); /* ms = 1000 us! */ + } + } + } + } + + /* send test data */ + if((ret = send(sock, buf, lenBuf, 0)) != lenBuf) { + perror("send test data"); + fprintf(stderr, "send() failed, sock=%d, ret=%d\n", sock, ret); + iRet = 1; + goto finalize_it; + } + + /* send record terminator */ + if(send(sock, "\n", 1, 0) != 1) { + perror("send record terminator"); + fprintf(stderr, "send() failed\n"); + iRet = 1; + goto finalize_it; + } + +finalize_it: + if(iRet != 0) { + /* need to do some (common) cleanup */ + if(sock != INVALID_SOCKET) { + close(sock); + sock = INVALID_SOCKET; + } + ++iFailed; + } + + return iRet; +} + + +/* send a message via UDP + * returns 0 if ok, something else otherwise. + */ +int +udpSend(char *buf, int lenBuf) +{ + struct sockaddr_in si_other; + int s, slen=sizeof(si_other); + + if((s=socket(AF_INET, SOCK_DGRAM, 0))==-1) { + perror("socket()"); + return(1); + } + + memset((char *) &si_other, 0, sizeof(si_other)); + si_other.sin_family = AF_INET; + si_other.sin_port = htons(iPort); + if(inet_aton("127.0.0.1", &si_other.sin_addr)==0) { + fprintf(stderr, "inet_aton() failed\n"); + return(1); + } + + if(sendto(s, buf, lenBuf, 0, (struct sockaddr*) &si_other, slen)==-1) { + perror("sendto"); + fprintf(stderr, "sendto() failed\n"); + return(1); + } + + close(s); + return 0; +} + + +/* open pipe to test candidate - so far, this is + * always rsyslogd and with a fixed config. Later, we may + * change this. Returns 0 if ok, something else otherwise. + * rgerhards, 2009-03-31 + */ +int openPipe(char *configFile, pid_t *pid, int *pfd) +{ + int pipefd[2]; + pid_t cpid; + char *newargv[] = {"../tools/rsyslogd", "dummy", "-u2", "-n", "-irsyslog.pid", + "-M../runtime/.libs:../.libs", NULL, NULL}; + char confFile[1024]; + + sprintf(confFile, "-f%s/testsuites/%s.conf", srcdir, + (pszCustomConf == NULL) ? configFile : pszCustomConf); + newargv[1] = confFile; + + if(IPv4Only) + newargv[(sizeof(newargv)/sizeof(char*)) - 2] = "-4"; + + if (pipe(pipefd) == -1) { + perror("pipe"); + exit(EXIT_FAILURE); + } + + cpid = fork(); + if (cpid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + + if(cpid == 0) { /* Child reads from pipe */ + fclose(stdout); + dup(pipefd[1]); + close(pipefd[1]); + close(pipefd[0]); + fclose(stdin); + execve("../tools/rsyslogd", newargv, ourEnvp); + } else { + usleep(10000); + close(pipefd[1]); + *pid = cpid; + *pfd = pipefd[0]; + } + + return(0); +} + + +/* This function unescapes a string of testdata. That it, escape sequences + * are converted into their one-character equivalent. While doing so, it applies + * C-like semantics. This was made necessary for easy integration of control + * characters inside test cases. -- rgerhards, 2009-03-11 + * Currently supported: + * \\ single backslash + * \n, \t, \r as in C + * \nnn where nnn is a 1 to 3 character octal sequence + * Note that when a problem occurs, the end result is undefined. After all, this + * is for a testsuite generatort, it must not be 100% bullet proof (so do not + * copy this code into something that must be!). Also note that we do in-memory + * unescaping and assume that the string gets shorter but NEVER longer! + */ +void unescapeTestdata(char *testdata) +{ + char *pDst; + char *pSrc; + int i; + int c; + + pDst = pSrc = testdata; + while(*pSrc) { + if(*pSrc == '\\') { + switch(*++pSrc) { + case '\\': *pDst++ = *pSrc++; + break; + case 'n': *pDst++ = '\n'; + ++pSrc; + break; + case 'r': *pDst++ = '\r'; + ++pSrc; + break; + case 't': *pDst++ = '\t'; + ++pSrc; + break; + case '0': + case '1': + case '2': + case '3': c = *pSrc++ - '0'; + i = 1; /* we already processed one digit! */ + while(i < 3 && isdigit(*pSrc)) { + c = c * 8 + *pSrc++ - '0'; + ++i; + } + *pDst++ = c; + break; + default: break; + } + } else { + *pDst++ = *pSrc++; + } + } + *pDst = '\0'; +} + + +/* Process a specific test case. File name is provided. + * Needs to return 0 if all is OK, something else otherwise. + */ +int +processTestFile(int fd, char *pszFileName) +{ + FILE *fp; + char *testdata = NULL; + char *expected = NULL; + int ret = 0; + size_t lenLn; + char buf[4096]; + + if((fp = fopen((char*)pszFileName, "r")) == NULL) { + perror((char*)pszFileName); + return(2); + } + + /* skip comments at start of file */ + + while(!feof(fp)) { + getline(&testdata, &lenLn, fp); + while(!feof(fp)) { + if(*testdata == '#') + getline(&testdata, &lenLn, fp); + else + break; /* first non-comment */ + } + + /* this is not perfect, but works ;) */ + if(feof(fp)) + break; + + ++iTests; /* increment test count, we now do one! */ + + testdata[strlen(testdata)-1] = '\0'; /* remove \n */ + /* now we have the test data to send (we could use function pointers here...) */ + unescapeTestdata(testdata); + if(inputMode == inputUDP) { + if(udpSend(testdata, strlen(testdata)) != 0) + return(2); + } else { + if(tcpSend(testdata, strlen(testdata)) != 0) + return(2); + } + + /* next line is expected output + * we do not care about EOF here, this will lead to a failure and thus + * draw enough attention. -- rgerhards, 2009-03-31 + */ + getline(&expected, &lenLn, fp); + expected[strlen(expected)-1] = '\0'; /* remove \n */ + + /* pull response from server and then check if it meets our expectation */ +//printf("try pull pipe...\n"); + readLine(fd, buf); + if(strlen(buf) == 0) { + printf("something went wrong - read a zero-length string from rsyslogd\n"); + exit(1); + } + if(strcmp(expected, buf)) { + ++iFailed; + printf("\nFile %s:\nExpected Response:\n'%s'\nActual Response:\n'%s'\n", + pszFileName, expected, buf); + ret = 1; + } + + /* we need to free buffers, as we have potentially modified them! */ + free(testdata); + testdata = NULL; + free(expected); + expected = NULL; + } + + fclose(fp); + return(ret); +} + + +/* carry out all tests. Tests are specified via a file name + * wildcard. Each of the files is read and the test carried + * out. + * Returns the number of tests that failed. Zero means all + * success. + */ +int +doTests(int fd, char *files) +{ + int ret; + char *testFile; + glob_t testFiles; + size_t i = 0; + struct stat fileInfo; + + glob(files, GLOB_MARK, NULL, &testFiles); + + for(i = 0; i < testFiles.gl_pathc; i++) { + testFile = testFiles.gl_pathv[i]; + + if(stat((char*) testFile, &fileInfo) != 0) + continue; /* continue with the next file if we can't stat() the file */ + + /* all regular files are run through the test logic. Symlinks don't work. */ + if(S_ISREG(fileInfo.st_mode)) { /* config file */ + if(verbose) printf("processing test case '%s' ... ", testFile); + ret = processTestFile(fd, testFile); + if(ret == 0) { + if(verbose) printf("successfully completed\n"); + } else { + if(!verbose) + printf("test '%s' ", testFile); + printf("failed!\n"); + } + } + } + globfree(&testFiles); + + if(iTests == 0) { + printf("Error: no test cases found, no tests executed.\n"); + iFailed = 1; + } else { + printf("Number of tests run: %3d, number of failures: %d, test: %s/%s\n", + iTests, iFailed, testSuite, inputMode2Str(inputMode)); + } + + return(iFailed); +} + + +/* indicate that our child has died (where it is not permitted to!). + */ +void childDied(__attribute__((unused)) int sig) +{ + printf("ERROR: child died unexpectedly (maybe a segfault?)!\n"); + exit(1); +} + + +/* cleanup */ +void doAtExit(void) +{ + int status; + + /* disarm died-child handler */ + signal(SIGCHLD, SIG_IGN); + + if(rsyslogdPid != 0) { + kill(rsyslogdPid, SIGTERM); + waitpid(rsyslogdPid, &status, 0); /* wait until instance terminates */ + } + + unlink(NETTEST_INPUT_CONF_FILE); +} + +/* Run the test suite. This must be called with exactly one parameter, the + * name of the test suite. For details, see file header comment at the top + * of this file. + * rgerhards, 2009-04-03 + */ +int main(int argc, char *argv[], char *envp[]) +{ + int fd; + int opt; + int ret = 0; + FILE *fp; + char buf[4096]; + char testcases[4096]; + + ourEnvp = envp; + while((opt = getopt(argc, argv, "4c:i:p:t:v")) != EOF) { + switch((char)opt) { + case '4': + IPv4Only = 1; + break; + case 'c': + pszCustomConf = optarg; + break; + case 'i': + if(!strcmp(optarg, "udp")) + inputMode = inputUDP; + else if(!strcmp(optarg, "tcp")) + inputMode = inputTCP; + else { + printf("error: unsupported input mode '%s'\n", optarg); + exit(1); + } + break; + case 'p': + iPort = atoi(optarg); + break; + case 't': + testSuite = optarg; + break; + case 'v': + verbose = 1; + break; + default:printf("Invalid call of nettester, invalid option '%c'.\n", opt); + printf("Usage: nettester -d -ttestsuite-name -iudp|tcp [-pport] [-ccustomConfFile] \n"); + exit(1); + } + } + + if(testSuite == NULL) { + printf("error: no testsuite given, need to specify -t testsuite!\n"); + exit(1); + } + + atexit(doAtExit); + + if((srcdir = getenv("srcdir")) == NULL) + srcdir = "."; + + if(verbose) printf("Start of nettester run ($srcdir=%s, testsuite=%s, input=%s/%d)\n", + srcdir, testSuite, inputMode2Str(inputMode), iPort); + + /* create input config file */ + if((fp = fopen(NETTEST_INPUT_CONF_FILE, "w")) == NULL) { + perror(NETTEST_INPUT_CONF_FILE); + printf("error opening input configuration file\n"); + exit(1); + } + if(inputMode == inputUDP) { + fputs("$ModLoad ../plugins/imudp/.libs/imudp\n", fp); + fprintf(fp, "$UDPServerRun %d\n", iPort); + } else { + fputs("$ModLoad ../plugins/imtcp/.libs/imtcp\n", fp); + fprintf(fp, "$InputTCPServerRun %d\n", iPort); + } + fclose(fp); + + /* arm died-child handler */ + signal(SIGCHLD, childDied); + + /* make sure we do not abort if there is an issue with pipes. + * our code does the necessary error handling. + */ + sigset(SIGPIPE, SIG_IGN); + + /* start to be tested rsyslogd */ + openPipe(testSuite, &rsyslogdPid, &fd); + readLine(fd, buf); + + /* generate filename */ + sprintf(testcases, "%s/testsuites/*.%s", srcdir, testSuite); + if(doTests(fd, testcases) != 0) + ret = 1; + + if(verbose) printf("End of nettester run (%d).\n", ret); + + exit(ret); +} diff --git a/tests/omod-if-array.sh b/tests/omod-if-array.sh new file mode 100755 index 00000000..4e916f1e --- /dev/null +++ b/tests/omod-if-array.sh @@ -0,0 +1,14 @@ +echo \[omod-if-array.sh\]: test omod-if-array via udp +$srcdir/killrsyslog.sh # kill rsyslogd if it runs for some reason + +./nettester -tomod-if-array -iudp -p4711 +if [ "$?" -ne "0" ]; then + exit 1 +fi + +echo test omod-if-array via tcp +./nettester -tomod-if-array -itcp +if [ "$?" -ne "0" ]; then + exit 1 +fi + diff --git a/tests/omruleset-queue.sh b/tests/omruleset-queue.sh new file mode 100755 index 00000000..cfb80c62 --- /dev/null +++ b/tests/omruleset-queue.sh @@ -0,0 +1,19 @@ +# test for omruleset. What we do is have the main queue forward +# all messages to a secondary ruleset via omruleset, which then does +# the actual file write. We check if all messages arrive at the file, +# what implies that omruleset works. No filters or special queue modes +# are used, but the ruleset uses its own queue. So we can also inject +# more messages without running into troubles. +# added 2009-11-02 by rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[omruleset-queue.sh\]: test for omruleset functionality with a ruleset queue +source $srcdir/diag.sh init +source $srcdir/diag.sh startup omruleset-queue.conf +source $srcdir/diag.sh injectmsg 0 20000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 19999 +source $srcdir/diag.sh exit diff --git a/tests/omruleset.sh b/tests/omruleset.sh new file mode 100755 index 00000000..dbc5cb31 --- /dev/null +++ b/tests/omruleset.sh @@ -0,0 +1,22 @@ +# Basic test for omruleset. What we do is have the main queue forward +# all messages to a secondary ruleset via omruleset, which then does +# the actual file write. We check if all messages arrive at the file, +# what implies that omruleset works. No filters or special queue modes +# are used, so the message is re-enqueued into the main message queue. +# We inject just 5,000 message because we may otherwise run into +# queue full conditions (as we use the same queue) and that +# would result in longer execution time. In any case, 5000 messages +# are well enough to test what we want to test. +# added 2009-11-02 by rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[omruleset.sh\]: basic test for omruleset functionality +source $srcdir/diag.sh init +source $srcdir/diag.sh startup omruleset.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/ourtail.c b/tests/ourtail.c new file mode 100644 index 00000000..c31babb9 --- /dev/null +++ b/tests/ourtail.c @@ -0,0 +1,46 @@ +/* This is a quick and dirty "tail implementation", one which always + * skips the first line, but nothing else. I have done this to prevent + * the various incompatible options of tail come into my way. One could + * probably work around this by using autoconf magic, but for me it + * was much quicker writing this small C program, which really should + * be portable across all platforms. + * + * Part of the testbench for rsyslog. + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> + +int main(int __attribute__((unused)) argc, char __attribute__((unused)) *argv[]) +{ + int c; + + for(c = getchar() ; c != EOF && c != '\n' ; c = getchar()) + /*skip to newline*/; + + if(c == '\n') + c = getchar(); + + for( ; c != EOF ; c = getchar()) + putchar(c); + + return 0; +} diff --git a/tests/parsertest.sh b/tests/parsertest.sh new file mode 100755 index 00000000..9f9c2f74 --- /dev/null +++ b/tests/parsertest.sh @@ -0,0 +1,41 @@ +echo TEST: \[parsertest.sh\]: various parser tests +source $srcdir/diag.sh init +source $srcdir/diag.sh nettester parse1 udp +source $srcdir/diag.sh nettester parse1 tcp +source $srcdir/diag.sh nettester parse2 udp +source $srcdir/diag.sh nettester parse2 tcp +source $srcdir/diag.sh nettester parse_8bit_escape udp +source $srcdir/diag.sh nettester parse_8bit_escape tcp +source $srcdir/diag.sh nettester parse3 udp +source $srcdir/diag.sh nettester parse3 tcp +source $srcdir/diag.sh nettester parse_invld_regex udp +source $srcdir/diag.sh nettester parse_invld_regex tcp +source $srcdir/diag.sh nettester parse-3164-buggyday udp +source $srcdir/diag.sh nettester parse-3164-buggyday tcp +source $srcdir/diag.sh nettester parse-nodate udp +source $srcdir/diag.sh nettester parse-nodate tcp +# the following samples can only be run over UDP as they are so +# malformed they break traditional syslog/tcp framing... +source $srcdir/diag.sh nettester snare_ccoff_udp udp +source $srcdir/diag.sh nettester snare_ccoff_udp2 udp + +echo \[parsertest.sh]: redoing tests in IPv4-only mode +source $srcdir/diag.sh nettester parse1 udp -4 +source $srcdir/diag.sh nettester parse1 tcp -4 +source $srcdir/diag.sh nettester parse2 udp -4 +source $srcdir/diag.sh nettester parse2 tcp -4 +source $srcdir/diag.sh nettester parse_8bit_escape udp -4 +source $srcdir/diag.sh nettester parse_8bit_escape tcp -4 +source $srcdir/diag.sh nettester parse3 udp -4 +source $srcdir/diag.sh nettester parse3 tcp -4 +source $srcdir/diag.sh nettester parse_invld_regex udp -4 +source $srcdir/diag.sh nettester parse_invld_regex tcp -4 +source $srcdir/diag.sh nettester parse-3164-buggyday udp -4 +source $srcdir/diag.sh nettester parse-3164-buggyday tcp -4 +source $srcdir/diag.sh nettester parse-nodate udp -4 +source $srcdir/diag.sh nettester parse-nodate tcp -4 +# UDP-only tests +source $srcdir/diag.sh nettester snare_ccoff_udp udp -4 +source $srcdir/diag.sh nettester snare_ccoff_udp2 udp -4 + +source $srcdir/diag.sh exit diff --git a/tests/pipe_noreader.sh b/tests/pipe_noreader.sh new file mode 100755 index 00000000..b2c46581 --- /dev/null +++ b/tests/pipe_noreader.sh @@ -0,0 +1,29 @@ +# This is test driver for a pipe that has no reader. This mimics a usual +# real-world scenario, the /dev/xconsole pipe. Some versions of rsyslog +# were known to hang or loop on this pipe, thus we added this scenario +# as a permanent testcase. For some details, please see bug tracker +# http://bugzilla.adiscon.com/show_bug.cgi?id=186 +# +# IMPORTANT: we do NOT check any result message set. The whole point in +# this test is to verify that we do NOT run into an eternal loop. As such, +# the test is "PASS", if rsyslogd terminates. If it does not terminate, we +# obviously do not cause "FAIL", but processing will hang, which should be +# a good-enough indication of failure. +# +# added 2010-04-26 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[pipe_noreader.sh\]: test for pipe writing without reader +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh init +mkfifo ./rsyslog.pipe +source $srcdir/diag.sh startup pipe_noreader.conf +# we need to emit ~ 128K of data according to bug report +source $srcdir/diag.sh tcpflood -m1000 -d500 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +# NO need to check seqno -- see header comment +echo we did not loop, so the test is sucessfull +source $srcdir/diag.sh exit diff --git a/tests/pipeaction.sh b/tests/pipeaction.sh new file mode 100755 index 00000000..c2201011 --- /dev/null +++ b/tests/pipeaction.sh @@ -0,0 +1,33 @@ +# Test for the pipe output action. +# will create a fifo in the current directory, write to it and +# then do the usual sequence checks. +# added 2009-11-05 by RGerhards +echo =============================================================================== +echo \[pipeaction.sh\]: testing pipe output action + +# create the pipe and start a background process that copies data from +# it to the "regular" work file +source $srcdir/diag.sh init +rm -f rsyslog-testbench-fifo +mkfifo rsyslog-testbench-fifo +cp rsyslog-testbench-fifo rsyslog.out.log & +CPPROCESS=$! +echo background cp process id is $CPPROCESS + +# now do the usual run +source $srcdir/diag.sh startup pipeaction.conf +# 20000 messages should be enough +#source $srcdir/diag.sh tcpflood -m20000 +source $srcdir/diag.sh injectmsg 0 20000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown + +# wait for the cp process to finish, do pipe-specific cleanup +echo waiting for background cp to terminate... +wait $CPPROCESS +rm -f rsyslog-testbench-fifo +echo background cp has terminated, continue test... + +# and continue the usual checks +source $srcdir/diag.sh seq-check 0 19999 +source $srcdir/diag.sh exit diff --git a/tests/pmlastmsg.sh b/tests/pmlastmsg.sh new file mode 100755 index 00000000..9a14ce21 --- /dev/null +++ b/tests/pmlastmsg.sh @@ -0,0 +1,6 @@ +echo ============================================================================== +echo \[pmlastmsg.sh\]: tests for pmlastmsg +source $srcdir/diag.sh init +source $srcdir/diag.sh nettester pmlastmsg udp +source $srcdir/diag.sh nettester pmlastmsg tcp +source $srcdir/diag.sh exit diff --git a/tests/proprepltest.sh b/tests/proprepltest.sh new file mode 100755 index 00000000..2e59a31d --- /dev/null +++ b/tests/proprepltest.sh @@ -0,0 +1,7 @@ +echo \[proprepltest.sh\]: various tests for the property replacer +source $srcdir/diag.sh init +source $srcdir/diag.sh nettester rfctag udp +source $srcdir/diag.sh nettester rfctag tcp +source $srcdir/diag.sh nettester nolimittag udp +source $srcdir/diag.sh nettester nolimittag tcp +source $srcdir/diag.sh init diff --git a/tests/queue-persist-drvr.sh b/tests/queue-persist-drvr.sh new file mode 100755 index 00000000..823fed6c --- /dev/null +++ b/tests/queue-persist-drvr.sh @@ -0,0 +1,35 @@ +# Test for queue data persisting at shutdown. The +# plan is to start an instance, emit some data, do a relatively +# fast shutdown and then re-start the engine to process the +# remaining data. +# added 2009-05-27 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo testing memory queue persisting to disk, mode $1 +source $srcdir/diag.sh init + +# prepare config +echo \$MainMsgQueueType $1 > work-queuemode.conf +echo "*.* :omtesting:sleep 0 1000" > work-delay.conf + +# inject 5000 msgs, so that we do not hit the high watermark +source $srcdir/diag.sh startup queue-persist.conf +source $srcdir/diag.sh injectmsg 0 5000 +$srcdir/diag.sh shutdown-immediate +$srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh check-mainq-spool + +# restart engine and have rest processed +#remove delay +echo "#" > work-delay.conf +source $srcdir/diag.sh startup queue-persist.conf +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +./msleep 500 +$srcdir/diag.sh wait-shutdown +# note: we need to permit duplicate messages, as due to the forced +# shutdown some messages may be flagged as "unprocessed" while they +# actually were processed. This is inline with rsyslog's philosophy +# to better duplicate than loose messages. Duplicate messages are +# permitted by the -d seq-check option. +source $srcdir/diag.sh seq-check 0 4999 -d +source $srcdir/diag.sh exit diff --git a/tests/queue-persist.sh b/tests/queue-persist.sh new file mode 100755 index 00000000..ff1842bb --- /dev/null +++ b/tests/queue-persist.sh @@ -0,0 +1,12 @@ +# Test for queue data persisting at shutdown. We use the actual driver +# to carry out multiple tests with different queue modes +# added 2009-05-27 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo \[queue-persist.sh\]: +source $srcdir/queue-persist-drvr.sh LinkedList +source $srcdir/queue-persist-drvr.sh FixedArray +# the disk test should not fail, however, the config is extreme and using +# it more or less is a config error +source $srcdir/queue-persist-drvr.sh Disk +# we do not test Direct mode because this absolute can not work in direct mode +# (maybe we should do a fail-type of test?) diff --git a/tests/random.sh b/tests/random.sh new file mode 100755 index 00000000..969d720c --- /dev/null +++ b/tests/random.sh @@ -0,0 +1,18 @@ +# Test if rsyslog survives sending truely random data to it... +# +# added 2010-04-01 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[random.sh\]: testing random data +source $srcdir/diag.sh init +source $srcdir/diag.sh startup random.conf +# generate random data +./randomgen -f rsyslog.random.data -s 100000 +ls -l rsyslog.random.data +source $srcdir/diag.sh tcpflood -B -I rsyslog.random.data -c5 -C10 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +# we do not check anything yet, the point is if rsyslog survived ;) +# TODO: check for exit message, but we'll notice an abort anyhow, so not that important +rm -f random.data +source $srcdir/diag.sh exit diff --git a/tests/randomgen.c b/tests/randomgen.c new file mode 100644 index 00000000..9ba56954 --- /dev/null +++ b/tests/randomgen.c @@ -0,0 +1,130 @@ +/* generates random data for later use in test cases. Of course, + * we could generate random data during the testcase itself, but + * the core idea is that we record the random data so that we have + * a chance to reproduce a problem should it occur. IMHO this + * provides the best compromise, by a) having randomness but + * b) knowing what was used during the test. + * + * Params + * -f output file name (stdout if not given) + * -s size of test data, plain number is size in k, 1MB default + * -u uses /dev/urandom instead of libc random number generator + * (when available). Note that this is usually much slower. + * + * Part of the testbench for rsyslog. + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <assert.h> +#include <unistd.h> +#include <string.h> +#include <netinet/in.h> + +#define EXIT_FAILURE 1 + +static char *fileName = NULL; /* name of output file */ +static int tryUseURandom = 0; /* try to use /dev/urandom? */ +static long long fileSize = 1024*1024; /* file size in K, 1MB default */ + + +/* generate the random file. This code really can be improved (e.g. read /dev/urandom + * when available) + */ +static inline void +genFile() +{ + long i; + FILE *fp; + FILE *rfp = NULL; + + if(fileName == NULL) { + fp = stdout; + } else { + if((fp = fopen(fileName, "w")) == NULL) { + perror(fileName); + } + } + + /* try to use /dev/urandom, if available */ + if(tryUseURandom) + rfp = fopen("/dev/urandom", "r"); + + if(rfp == NULL) { + /* fallback, use libc random number generator */ + for(i = 0 ; i < fileSize ; ++i) { + if(fputc((char) rand(), fp) == EOF) { + perror(fileName); + exit(1); + } + } + } else { + /* use /dev/urandom */ + printf("using /dev/urandom"); + for(i = 0 ; i < fileSize ; ++i) { + if(fputc(fgetc(rfp), fp) == EOF) { + perror(fileName); + exit(1); + } + } + } + + if(fileName != NULL) + fclose(fp); +} + + +/* Run the test. + * rgerhards, 2009-04-03 + */ +int main(int argc, char *argv[]) +{ + int ret = 0; + int opt; + + srand(time(NULL)); /* seed is good enough for our needs */ + + while((opt = getopt(argc, argv, "f:s:u")) != -1) { + switch (opt) { + case 'f': fileName = optarg; + break; + case 's': fileSize = atol(optarg) * 1024; + break; + case 'u': tryUseURandom = 1; + break; + default: printf("invalid option '%c' or value missing - terminating...\n", opt); + exit (1); + break; + } + } + + printf("generating random data file '%s' of %ldkb - may take a short while...\n", + fileName, (long) (fileSize / 1024)); + genFile(); + + exit(ret); +} diff --git a/tests/rcvr_fail_restore.sh b/tests/rcvr_fail_restore.sh new file mode 100755 index 00000000..79486f10 --- /dev/null +++ b/tests/rcvr_fail_restore.sh @@ -0,0 +1,122 @@ +# Copyright (C) 2011 by Rainer Gerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[rcvr_fail_restore.sh\]: test failed receiver restore case +source $srcdir/diag.sh init +# +# STEP1: start both instances and send 1000 messages. +# Note: receiver is instance 2, sender instance 1. +# +# start up the instances. Note that the envrionment settings can be changed to +# set instance-specific debugging parameters! +#export RSYSLOG_DEBUG="debug nostdout" +#export RSYSLOG_DEBUGLOG="log2" +source $srcdir/diag.sh startup rcvr_fail_restore_rcvr.conf 2 +#export RSYSLOG_DEBUGLOG="log" +#valgrind="valgrind" +source $srcdir/diag.sh startup rcvr_fail_restore_sender.conf +# re-set params so that new instances do not thrash it... +#unset RSYSLOG_DEBUG +#unset RSYSLOG_DEBUGLOG + +# now inject the messages into instance 2. It will connect to instance 1, +# and that instance will record the data. +source $srcdir/diag.sh injectmsg 1 1000 +source $srcdir/diag.sh wait-queueempty +./msleep 1000 # let things settle down a bit + +# +# Step 2: shutdown receiver, then send some more data, which then +# needs to go into the queue. +# + +source $srcdir/diag.sh shutdown-when-empty 2 +source $srcdir/diag.sh wait-shutdown 2 + +source $srcdir/diag.sh injectmsg 1001 10000 +./msleep 3000 # make sure some retries happen (retry interval is set to 3 second) +source $srcdir/diag.sh get-mainqueuesize +ls -l test-spool + +# +# Step 3: restart receiver, wait that the sender drains its queue +# +#export RSYSLOG_DEBUGLOG="log2" +source $srcdir/diag.sh startup rcvr_fail_restore_rcvr.conf 2 +echo waiting for sender to drain queue [may need a short while] +source $srcdir/diag.sh wait-queueempty +ls -l test-spool +OLDFILESIZE=$(stat -c%s test-spool/mainq.00000001) +echo file size to expect is $OLDFILESIZE + + +# +# Step 4: send new data. Queue files are not permitted to grow now +# (but one file continous to exist). +# +source $srcdir/diag.sh injectmsg 11001 10 +source $srcdir/diag.sh wait-queueempty + +# at this point, the queue file shall not have grown. Note +# that we MUST NOT shut down the instance right now, because it +# would clean up the queue files! So we need to do our checks +# first (here!). +ls -l test-spool +NEWFILESIZE=$(stat -c%s test-spool/mainq.00000001) +if [ $NEWFILESIZE != $OLDFILESIZE ] +then + echo file sizes do not match, expected $OLDFILESIZE, actual $NEWFILESIZE + echo this means that data has been written to the queue file where it + echo no longer should be written. + # abort will happen below, because we must ensure proper system shutdown + # HOWEVER, during actual testing it may be useful to do an exit here (so + # that e.g. the debug log is pointed right at the correct spot). + # exit 1 +fi + +# +# We now do an extra test (so this is two in one ;)) to see if the DA +# queue can be reactivated after its initial shutdown. In essence, we +# redo steps 2 and 3. +# +# Step 5: stop receiver again, then send some more data, which then +# needs to go into the queue. +# +echo "*** done primary test *** now checking if DA can be restarted" +source $srcdir/diag.sh shutdown-when-empty 2 +source $srcdir/diag.sh wait-shutdown 2 + +source $srcdir/diag.sh injectmsg 11011 10000 +sleep 1 # we need to wait, otherwise we may be so fast that the receiver +# comes up before we have finally suspended the action +source $srcdir/diag.sh get-mainqueuesize +ls -l test-spool + +# +# Step 6: restart receiver, wait that the sender drains its queue +# +source $srcdir/diag.sh startup rcvr_fail_restore_rcvr.conf 2 +echo waiting for sender to drain queue [may need a short while] +source $srcdir/diag.sh wait-queueempty +ls -l test-spool + +# +# Queue file size checks done. Now it is time to terminate the system +# and see if everything could be received (the usual check, done here +# for completeness, more or less as a bonus). +# +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown + +# now it is time to stop the receiver as well +source $srcdir/diag.sh shutdown-when-empty 2 +source $srcdir/diag.sh wait-shutdown 2 + +# now abort test if we need to (due to filesize predicate) +if [ $NEWFILESIZE != $OLDFILESIZE ] +then + exit 1 +fi +# do the final check +source $srcdir/diag.sh seq-check 1 21010 +source $srcdir/diag.sh exit diff --git a/tests/resultdata/imuxsock_ccmiddle.log b/tests/resultdata/imuxsock_ccmiddle.log new file mode 100644 index 00000000..d2531f9d --- /dev/null +++ b/tests/resultdata/imuxsock_ccmiddle.log @@ -0,0 +1 @@ + test 1#0112 diff --git a/tests/resultdata/imuxsock_logger.log b/tests/resultdata/imuxsock_logger.log new file mode 100644 index 00000000..883dabdf --- /dev/null +++ b/tests/resultdata/imuxsock_logger.log @@ -0,0 +1 @@ + test diff --git a/tests/resultdata/imuxsock_traillf.log b/tests/resultdata/imuxsock_traillf.log new file mode 100644 index 00000000..883dabdf --- /dev/null +++ b/tests/resultdata/imuxsock_traillf.log @@ -0,0 +1 @@ + test diff --git a/tests/rscript.c b/tests/rscript.c new file mode 100644 index 00000000..5baf74cc --- /dev/null +++ b/tests/rscript.c @@ -0,0 +1,264 @@ +/* This test checks runtime initialization and exit. Other than that, it + * also serves as the most simplistic sample of how a test can be coded. + * + * Part of the testbench for rsyslog. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <glob.h> +#include <sys/stat.h> + +#include "rsyslog.h" +#include "testbench.h" +#include "ctok.h" +#include "expr.h" + +rsconf_t *ourConf; +MODULE_TYPE_TESTBENCH +/* define addtional objects we need for our tests */ +DEFobjCurrIf(expr) +DEFobjCurrIf(ctok) +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(vmprg) + + +BEGINInit +CODESTARTInit + pErrObj = "expr"; CHKiRet(objUse(expr, CORE_COMPONENT)); + pErrObj = "ctok"; CHKiRet(objUse(ctok, CORE_COMPONENT)); + pErrObj = "ctok_token"; CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + pErrObj = "vmprg"; CHKiRet(objUse(vmprg, CORE_COMPONENT)); +ENDInit + +BEGINExit +CODESTARTExit +ENDExit + + +/* perform a single test. This involves compiling the test script, + * checking the result of the compilation (iRet) and a check of the + * generated program (via a simple strcmp). The resulting program + * check is only done if the test should not detect a syntax error + * (for obvious reasons, there is no point in checking the result of + * a failed compilation). + * rgerhards, 2008-07--07 + */ +static rsRetVal +PerformTest(cstr_t *pstrIn, rsRetVal iRetExpected, cstr_t *pstrOut) +{ + cstr_t *pstrPrg = NULL; + ctok_t *tok = NULL; + ctok_token_t *pToken = NULL; + expr_t *pExpr; + rsRetVal localRet; + DEFiRet; + + /* we first need a tokenizer... */ + CHKiRet(ctok.Construct(&tok)); + CHKiRet(ctok.Setpp(tok, rsCStrGetSzStr(pstrIn))); + CHKiRet(ctok.ConstructFinalize(tok)); + + /* now construct our expression */ + CHKiRet(expr.Construct(&pExpr)); + CHKiRet(expr.ConstructFinalize(pExpr)); + + /* ready to go... */ + localRet = expr.Parse(pExpr, tok); + + /* check if we expected an error */ + if(localRet != iRetExpected) { + printf("Error in compile return code. Expected %d, received %d\n", + iRetExpected, localRet); + CHKiRet(rsCStrConstruct(&pstrPrg)); + CHKiRet(vmprg.Obj2Str(pExpr->pVmprg, pstrPrg)); + printf("generated vmprg:\n%s\n", rsCStrGetSzStr(pstrPrg)); + ABORT_FINALIZE(iRetExpected == RS_RET_OK ? localRet : RS_RET_ERR); + } + + if(iRetExpected != RS_RET_OK) + FINALIZE; /* if we tested an error case, we are done */ + + /* OK, we got a compiled program, so now let's compare that */ + + CHKiRet(rsCStrConstruct(&pstrPrg)); + CHKiRet(vmprg.Obj2Str(pExpr->pVmprg, pstrPrg)); + + if(strcmp((char*)rsCStrGetSzStr(pstrPrg), (char*)rsCStrGetSzStr(pstrOut))) { + printf("error: compiled program different from expected result!\n"); + printf("generated vmprg (%d bytes):\n%s\n", (int)strlen((char*)rsCStrGetSzStr(pstrPrg)), rsCStrGetSzStr(pstrPrg)); + printf("expected (%d bytes):\n%s\n", (int)strlen((char*)rsCStrGetSzStr(pstrOut)), rsCStrGetSzStr(pstrOut)); + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + /* we are done, so we now need to restore things */ + if(pToken != NULL) + ctok_token.Destruct(&pToken); /* no longer needed */ + if(pstrPrg != NULL) + rsCStrDestruct(&pstrPrg); + if(tok != NULL) + ctok.Destruct(&tok); + RETiRet; +} + + +/* a helper macro to generate some often-used code... */ +#define CHKEOF \ + if(feof(fp)) { \ + printf("error: unexpected end of control file %s\n", pszFileName); \ + ABORT_FINALIZE(RS_RET_ERR); \ + } +/* process a single test file + * Note that we do not do a real parser here. The effort is not + * justified by what we need to do. So it is a quick shot. + * rgerhards, 2008-07-07 + */ +static rsRetVal +ProcessTestFile(uchar *pszFileName) +{ + FILE *fp; + char *lnptr = NULL; + size_t lenLn; + cstr_t *pstrIn = NULL; + cstr_t *pstrOut = NULL; + int iParse; + rsRetVal iRetExpected; + DEFiRet; + + if((fp = fopen((char*)pszFileName, "r")) == NULL) { + perror((char*)pszFileName); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } + + /* skip comments at start of file */ + + getline(&lnptr, &lenLn, fp); + while(!feof(fp)) { + if(*lnptr == '#') + getline(&lnptr, &lenLn, fp); + else + break; /* first non-comment */ + } + CHKEOF; + + /* once we had a comment, the next line MUST be "result: <nbr>". Anything + * after nbr is simply ignored. + */ + if(sscanf(lnptr, "result: %d", &iParse) != 1) { + printf("error in result line, scanf failed, line: '%s'\n", lnptr); + ABORT_FINALIZE(RS_RET_ERR); + } + iRetExpected = iParse; + getline(&lnptr, &lenLn, fp); CHKEOF; + + /* and now we look for "in:" (and again ignore the rest...) */ + if(strncmp(lnptr, "in:", 3)) { + printf("error: expected 'in:'-line, but got: '%s'\n", lnptr); + ABORT_FINALIZE(RS_RET_ERR); + } + /* if we reach this point, we need to read in the input script. It is + * terminated by a line with three sole $ ($$$\n) + */ + CHKiRet(rsCStrConstruct(&pstrIn)); + getline(&lnptr, &lenLn, fp); CHKEOF; + while(strncmp(lnptr, "$$$\n", 4)) { + CHKiRet(rsCStrAppendStr(pstrIn, (uchar*)lnptr)); + getline(&lnptr, &lenLn, fp); CHKEOF; + } + getline(&lnptr, &lenLn, fp); CHKEOF; /* skip $$$-line */ + + /* and now we look for "out:" (and again ignore the rest...) */ + if(strncmp(lnptr, "out:", 4)) { + printf("error: expected 'out:'-line, but got: '%s'\n", lnptr); + ABORT_FINALIZE(RS_RET_ERR); + } + /* if we reach this point, we need to read in the expected program code. It is + * terminated by a line with three sole $ ($$$\n) + */ + CHKiRet(rsCStrConstruct(&pstrOut)); + getline(&lnptr, &lenLn, fp); CHKEOF; + while(strncmp(lnptr, "$$$\n", 4)) { + CHKiRet(rsCStrAppendStr(pstrOut, (uchar*)lnptr)); + getline(&lnptr, &lenLn, fp); CHKEOF; + } + + /* un-comment for testing: + * printf("iRet: %d, script: %s\n, out: %s\n", iRetExpected, rsCStrGetSzStr(pstrIn),rsCStrGetSzStr(pstrOut)); + */ + if(rsCStrGetSzStr(pstrIn) == NULL) { + printf("error: input script is empty!\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + if(rsCStrGetSzStr(pstrOut) == NULL && iRetExpected == RS_RET_OK) { + printf("error: output script is empty!\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + + CHKiRet(PerformTest(pstrIn, iRetExpected, pstrOut)); + +finalize_it: + if(pstrIn != NULL) + rsCStrDestruct(&pstrIn); + if(pstrOut != NULL) + rsCStrDestruct(&pstrOut); + RETiRet; +} + + +/* This test is parameterized. It search for test control files and + * loads all that it finds. To add tests, simply create new .rstest + * files. + * rgerhards, 2008-07-07 + */ +BEGINTest + uchar *testFile; + glob_t testFiles; + size_t i = 0; + struct stat fileInfo; +CODESTARTTest + glob("*.rstest", GLOB_MARK, NULL, &testFiles); + + for(i = 0; i < testFiles.gl_pathc; i++) { + testFile = (uchar*) testFiles.gl_pathv[i]; + + if(stat((char*) testFile, &fileInfo) != 0) + continue; /* continue with the next file if we can't stat() the file */ + + /* all regular files are run through the test logic. Symlinks don't work. */ + if(S_ISREG(fileInfo.st_mode)) { /* config file */ + printf("processing RainerScript test file '%s'...\n", testFile); + iRet = ProcessTestFile((uchar*) testFile); + if(iRet != RS_RET_OK) { + /* in this case, re-run with debugging on */ + printf("processing test case failed with %d, re-running with debug messages:\n", + iRet); + Debug = 1; /* these two are dirty, but we need them today... */ + debugging_on = 1; + CHKiRet(ProcessTestFile((uchar*) testFile)); + } + } + } + globfree(&testFiles); + +finalize_it: +ENDTest diff --git a/tests/rscript_contains.sh b/tests/rscript_contains.sh new file mode 100755 index 00000000..fd5c3354 --- /dev/null +++ b/tests/rscript_contains.sh @@ -0,0 +1,13 @@ +# added 2012-09-14 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_contains.sh\]: test for contains script-filter +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_contains.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rscript_field.sh b/tests/rscript_field.sh new file mode 100755 index 00000000..e989e666 --- /dev/null +++ b/tests/rscript_field.sh @@ -0,0 +1,13 @@ +# added 2012-09-20 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_field.sh\]: testing rainerscript field\(\) function +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_field.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rscript_optimizer1.sh b/tests/rscript_optimizer1.sh new file mode 100755 index 00000000..1d2dcf87 --- /dev/null +++ b/tests/rscript_optimizer1.sh @@ -0,0 +1,13 @@ +# added 2012-09-20 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_optimizer1.sh\]: testing rainerscript optimizer +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_optimizer1.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rscript_prifilt.sh b/tests/rscript_prifilt.sh new file mode 100755 index 00000000..815492ab --- /dev/null +++ b/tests/rscript_prifilt.sh @@ -0,0 +1,13 @@ +# added 2012-09-20 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_prifilt.sh\]: testing rainerscript prifield\(\) function +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_prifilt.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rscript_ruleset_call.sh b/tests/rscript_ruleset_call.sh new file mode 100755 index 00000000..e29f21da --- /dev/null +++ b/tests/rscript_ruleset_call.sh @@ -0,0 +1,13 @@ +# added 2012-10-29 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_ruleset_call.sh\]: testing rainerscript ruleset\(\) and call statement +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_ruleset_call.conf +source $srcdir/diag.sh injectmsg 0 5000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rscript_stop.sh b/tests/rscript_stop.sh new file mode 100755 index 00000000..e532a522 --- /dev/null +++ b/tests/rscript_stop.sh @@ -0,0 +1,13 @@ +# added 2012-09-20 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_stop.sh\]: testing rainerscript STOP statement +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_stop.conf +source $srcdir/diag.sh injectmsg 0 8000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rscript_stop2.sh b/tests/rscript_stop2.sh new file mode 100755 index 00000000..eae36cce --- /dev/null +++ b/tests/rscript_stop2.sh @@ -0,0 +1,13 @@ +# added 2012-09-20 by rgerhards +# This file is part of the rsyslog project, released under ASL 2.0 +echo =============================================================================== +echo \[rscript_stop2.sh\]: testing rainerscript STOP statement, alternate method +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rscript_stop2.conf +source $srcdir/diag.sh injectmsg 0 8000 +echo doing shutdown +source $srcdir/diag.sh shutdown-when-empty +echo wait on shutdown +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 4999 +source $srcdir/diag.sh exit diff --git a/tests/rsf_getenv.sh b/tests/rsf_getenv.sh new file mode 100755 index 00000000..fd083bce --- /dev/null +++ b/tests/rsf_getenv.sh @@ -0,0 +1,17 @@ +# Test for the getenv() rainerscript function +# this is a quick test, but it gurantees that the code path is +# at least progressed (but we do not check for unset envvars!) +# added 2009-11-03 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +echo =============================================================================== +echo \[rsf_getenv.sh\]: testing RainerScript getenv\(\) function +export MSGNUM="msgnum:" +source $srcdir/diag.sh init +source $srcdir/diag.sh startup rsf_getenv.conf +source $srcdir/diag.sh tcpflood -m10000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 9999 +unset MSGNUM +source $srcdir/diag.sh exit diff --git a/tests/rt-init.c b/tests/rt-init.c new file mode 100644 index 00000000..d3cf4698 --- /dev/null +++ b/tests/rt-init.c @@ -0,0 +1,44 @@ +/* This test checks runtime initialization and exit. Other than that, it + * also serves as the most simplistic sample of how a test can be coded. + * + * Part of the testbench for rsyslog. + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include "testbench.h" +#include <stdio.h> /* must be last, else we get a zlib compile error on some platforms */ + +rsconf_t *ourConf; +MODULE_TYPE_TESTBENCH + +BEGINInit +CODESTARTInit +ENDInit + +BEGINExit +CODESTARTExit +ENDExit + +BEGINTest +CODESTARTTest +/*finalize_it:*/ + /* room for custom error reporter, leave blank if not needed */ +ENDTest diff --git a/tests/rulesetmultiqueue.sh b/tests/rulesetmultiqueue.sh new file mode 100755 index 00000000..71ed9dce --- /dev/null +++ b/tests/rulesetmultiqueue.sh @@ -0,0 +1,33 @@ +# Test for disk-only queue mode +# This tests defines three rulesets, each one with its own queue. Then, it +# sends data to them and checks the outcome. Note that we do need to +# use some custom code as the test driver framework does not (yet?) +# support multi-output-file operations. +# added 2009-10-30 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[rulesetmultiqueu.sh\]: testing multiple queues via rulesets +source $srcdir/diag.sh init +rm -f rsyslog.out1.log rsyslog.out2.log rsyslog.out3.log +source $srcdir/diag.sh startup rulesetmultiqueue.conf +source $srcdir/diag.sh wait-startup +# now fill the three files (a bit sequentially, but they should +# still get their share of concurrency - to increase the chance +# we use three connections per set). +source $srcdir/diag.sh tcpflood -c3 -p13514 -m20000 -i0 +source $srcdir/diag.sh tcpflood -c3 -p13515 -m20000 -i20000 +source $srcdir/diag.sh tcpflood -c3 -p13516 -m20000 -i40000 + +# in this version of the imdiag, we do not have the capability to poll +# all queues for emptyness. So we do a sleep in the hopes that this will +# sufficiently drain the queues. This is race, but the best we currently +# can do... - rgerhards, 2009-11-05 +sleep 2 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +# now consolidate all logs into a single one so that we can use the +# regular check logic +cat rsyslog.out1.log rsyslog.out2.log rsyslog.out3.log > rsyslog.out.log +source $srcdir/diag.sh seq-check 0 59999 +rm -f rsyslog.out1.log rsyslog.out2.log rsyslog.out3.log +source $srcdir/diag.sh exit diff --git a/tests/runtime-dummy.c b/tests/runtime-dummy.c new file mode 100644 index 00000000..f6f2d07f --- /dev/null +++ b/tests/runtime-dummy.c @@ -0,0 +1,45 @@ +/* Testbench for rsyslog + * + * This are dummy calls for "runtime" routines which are not yet properly + * abstracted and part of the actual runtime libraries. This module tries + * to make the linker happy. Please note that it does NOT provide anything + * more but the symbols. If a test requires these functions (or functions + * that depend on them), this dummy can not be used. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include "rsyslog.h" + +int bReduceRepeatMsgs = 0; +int bActExecWhenPrevSusp = 0; +int iActExecOnceInterval = 1; +int MarkInterval = 30; +void *pMsgQueue = NULL; + +void cflineClassic(void) {}; +void selectorAddList(void) {}; +void selectorConstruct(void) {}; +void selectorDestruct(void) {}; +rsRetVal createMainQueue(void) { return RS_RET_ERR; } + +ruleset_t *pCurrRuleset; +/* these are required by some dynamically loaded modules */ diff --git a/tests/sndrcv.sh b/tests/sndrcv.sh new file mode 100755 index 00000000..2fc3bd82 --- /dev/null +++ b/tests/sndrcv.sh @@ -0,0 +1,9 @@ +# This tests two rsyslog instances. Instance +# TWO sends data to instance ONE. A number of messages is injected into +# the instance 2 and we finally check if all those messages +# arrived at instance 1. +# added 2009-11-11 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv.sh\]: testing sending and receiving via tcp +source $srcdir/sndrcv_drvr.sh sndrcv 50000 diff --git a/tests/sndrcv_drvr.sh b/tests/sndrcv_drvr.sh new file mode 100755 index 00000000..1f3b9113 --- /dev/null +++ b/tests/sndrcv_drvr.sh @@ -0,0 +1 @@ +source $srcdir/sndrcv_drvr_noexit.sh $1 $2 diff --git a/tests/sndrcv_drvr_noexit.sh b/tests/sndrcv_drvr_noexit.sh new file mode 100755 index 00000000..899eace3 --- /dev/null +++ b/tests/sndrcv_drvr_noexit.sh @@ -0,0 +1,49 @@ +# This is test driver for testing two rsyslog instances. It can be +# utilized by any test that just needs two instances with different +# config files, where messages are injected in instance TWO and +# (with whatever rsyslog mechanism) being relayed over to instance ONE, +# where they are written to the log file. After the run, the completeness +# of that log file is checked. +# The code is almost the same, but the config files differ (probably greatly) +# for different test cases. As such, this driver needs to be called with the +# config file name ($2). From that name, the sender and receiver config file +# names are automatically generated. +# So: $1 config file name, $2 number of messages +# +# A note on TLS testing: the current testsuite (in git!) already contains +# TLS test cases. However, getting these test cases correct is not simple. +# That's not a problem with the code itself, but rater a problem with +# synchronization in the test environment. So I have deciced to keep the +# TLS tests in, but not yet actually utilize them. This is most probably +# left as an excercise for future (devel) releases. -- rgerhards, 2009-11-11 +# +# added 2009-11-11 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +# uncomment for debugging support: +source $srcdir/diag.sh init +# start up the instances +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +source $srcdir/diag.sh startup $1_rcvr.conf +source $srcdir/diag.sh wait-startup +#export RSYSLOG_DEBUGLOG="log2" +#valgrind="valgrind" +source $srcdir/diag.sh startup $1_sender.conf 2 +source $srcdir/diag.sh wait-startup 2 +# may be needed by TLS (once we do it): sleep 30 + +# now inject the messages into instance 2. It will connect to instance 1, +# and that instance will record the data. +source $srcdir/diag.sh tcpflood -m$2 -i1 +sleep 2 # make sure all data is received in input buffers +# shut down sender when everything is sent, receiver continues to run concurrently +# may be needed by TLS (once we do it): sleep 60 +source $srcdir/diag.sh shutdown-when-empty 2 +source $srcdir/diag.sh wait-shutdown 2 +# now it is time to stop the receiver as well +source $srcdir/diag.sh shutdown-when-empty +source $srcdir/diag.sh wait-shutdown + +# may be needed by TLS (once we do it): sleep 60 +# do the final check +source $srcdir/diag.sh seq-check 1 $2 diff --git a/tests/sndrcv_failover.sh b/tests/sndrcv_failover.sh new file mode 100755 index 00000000..4c5e1831 --- /dev/null +++ b/tests/sndrcv_failover.sh @@ -0,0 +1,21 @@ +# This tests failover capabilities. Data is sent to local port 13516, where +# no process shall listen. Then it fails over to a second instance, then to +# a file. The second instance is started. So all data should be received +# there and none be logged to the file. +# This builds on the basic sndrcv.sh test, but adds a first, failing, +# location to the conf file. +# added 2011-06-20 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_failover.sh\]: testing failover capabilities for tcp sending +source $srcdir/sndrcv_drvr_noexit.sh sndrcv_failover 50000 +ls -l rsyslog.empty +if [[ -s rsyslog.empty ]] ; then + echo "FAIL: rsyslog.empty has data. Failover handling failed. Data is written" + echo " even though the previous action (in a failover chain!) properly" + echo " worked." + exit 1 +else + echo "rsyslog.empty is empty - OK" +fi ; +source $srcdir/diag.sh exit diff --git a/tests/sndrcv_gzip.sh b/tests/sndrcv_gzip.sh new file mode 100755 index 00000000..4931f3d0 --- /dev/null +++ b/tests/sndrcv_gzip.sh @@ -0,0 +1,7 @@ +# This test is similar to tcpsndrcv, but it forwards messages in +# zlib-compressed format (our own syslog extension). +# rgerhards, 2009-11-11 +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_gzip.sh\]: testing sending and receiving via tcp in zlib mode +source $srcdir/sndrcv_drvr.sh sndrcv_gzip 50000 diff --git a/tests/sndrcv_omudpspoof.sh b/tests/sndrcv_omudpspoof.sh new file mode 100755 index 00000000..cb1c2497 --- /dev/null +++ b/tests/sndrcv_omudpspoof.sh @@ -0,0 +1,14 @@ +# This runs sends and receives messages via UDP to the standard +# ports. Note that with UDP we can always have message loss. While this is +# less likely in a local environment, we strongly limit the amount of data +# we send in the hope to not lose any messages. However, failure of this +# test does not necessarily mean that the code is wrong (but it is very likely!) +# added 2009-11-11 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_omudpspoof.sh\]: testing sending and receiving via omudp +echo This test must be run as root [raw socket access required] +if [ "$EUID" -ne 0 ]; then + exit 77 # Not root, skip this test +fi +source $srcdir/sndrcv_drvr.sh sndrcv_omudpspoof 50 diff --git a/tests/sndrcv_omudpspoof_nonstdpt.sh b/tests/sndrcv_omudpspoof_nonstdpt.sh new file mode 100755 index 00000000..ddd3eb7e --- /dev/null +++ b/tests/sndrcv_omudpspoof_nonstdpt.sh @@ -0,0 +1,14 @@ +# This runs sends and receives messages via UDP to the standard +# ports. Note that with UDP we can always have message loss. While this is +# less likely in a local environment, we strongly limit the amount of data +# we send in the hope to not lose any messages. However, failure of this +# test does not necessarily mean that the code is wrong (but it is very likely!) +# added 2009-11-11 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_omudpspoof_nonstdpt.sh\]: testing sending and receiving via omudp +echo This test must be run as root [raw socket access required] +if [ "$EUID" -ne 0 ]; then + exit 77 # Not root, skip this test +fi +source $srcdir/sndrcv_drvr.sh sndrcv_omudpspoof_nonstdpt 50 diff --git a/tests/sndrcv_tls_anon_rebind.sh b/tests/sndrcv_tls_anon_rebind.sh new file mode 100755 index 00000000..55b96d04 --- /dev/null +++ b/tests/sndrcv_tls_anon_rebind.sh @@ -0,0 +1,5 @@ +# rgerhards, 2011-04-04 +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_tls_anon_rebind.sh\]: testing sending and receiving via TLS with anon auth and rebind +source $srcdir/sndrcv_drvr.sh sndrcv_tls_anon_rebind 25000 diff --git a/tests/sndrcv_udp.sh b/tests/sndrcv_udp.sh new file mode 100755 index 00000000..df37782c --- /dev/null +++ b/tests/sndrcv_udp.sh @@ -0,0 +1,13 @@ +# This runs sends and receives messages via UDP to the standard +# ports. Note that with UDP we can always have message loss. While this is +# less likely in a local environment, we strongly limit the amount of data +# we send in the hope to not lose any messages. However, failure of this +# test does not necessarily mean that the code is wrong (but it is very likely!) +# added 2009-11-11 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_udp.sh\]: testing sending and receiving via udp +if [ "$EUID" -ne 0 ]; then + exit 77 # Not root, skip this test +fi +source $srcdir/sndrcv_drvr.sh sndrcv_udp 50 diff --git a/tests/sndrcv_udp_nonstdpt.sh b/tests/sndrcv_udp_nonstdpt.sh new file mode 100755 index 00000000..2ad2906f --- /dev/null +++ b/tests/sndrcv_udp_nonstdpt.sh @@ -0,0 +1,10 @@ +# This runs sends and receives messages via UDP to the non-standard port 2514 +# Note that with UDP we can always have message loss. While this is +# less likely in a local environment, we strongly limit the amount of data +# we send in the hope to not lose any messages. However, failure of this +# test does not necessarily mean that the code is wrong (but it is very likely!) +# added 2009-11-11 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[sndrcv_udp_nonstdpt.sh\]: testing sending and receiving via udp +source $srcdir/sndrcv_drvr.sh sndrcv_udp_nonstdpt 50 diff --git a/tests/syslog_caller.c b/tests/syslog_caller.c new file mode 100644 index 00000000..3f2702a6 --- /dev/null +++ b/tests/syslog_caller.c @@ -0,0 +1,75 @@ +/* A very primitive testing tool that just emits a number of + * messages to the system log socket. Currently sufficient, but + * obviously room for improvement. + * + * It is highly suggested NOT to "base" any derivative work + * on this tool ;) + * + * Options + * + * -s severity (0..7 accoding to syslog spec, r "rolling", default 6) + * -m number of messages to generate (default 500) + * + * Part of the testbench for rsyslog. + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <syslog.h> + +static void usage(void) +{ + fprintf(stderr, "usage: syslog_caller num-messages\n"); + exit(1); +} + + +int main(int argc, char *argv[]) +{ + int i; + int opt; + int bRollingSev = 0; + int sev = 6; + int msgs = 500; + + while((opt = getopt(argc, argv, "m:s:")) != -1) { + switch (opt) { + case 's': if(*optarg == 'r') { + bRollingSev = 1; + sev = 0; + } else + sev = atoi(optarg); + break; + case 'm': msgs = atoi(optarg); + break; + default: usage(); + break; + } + } + + for(i = 0 ; i < msgs ; ++i) { + syslog(sev % 8, "test message nbr %d, severity=%d", i, sev % 8); + if(bRollingSev) + sev++; + } + return(0); +} diff --git a/tests/syslog_inject.c b/tests/syslog_inject.c new file mode 100644 index 00000000..a5d77b1a --- /dev/null +++ b/tests/syslog_inject.c @@ -0,0 +1,28 @@ +/* This tool deliberately logs a message with the a trailing LF */ +/* Options: + * -l: inject linefeed at end of message + * -c: inject control character in middle of message + */ +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <string.h> + +static inline void usage(void) { + fprintf(stderr, "Usage: syslog_inject [-l] [-c]\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + if(argc != 2) + usage(); + if(!strcmp(argv[1], "-l")) + syslog(LOG_NOTICE, "test\n"); + else if(!strcmp(argv[1], "-c")) + syslog(LOG_NOTICE, "test 1\t2"); + else + usage(); + + return 0; +} diff --git a/tests/tabescape_dflt.sh b/tests/tabescape_dflt.sh new file mode 100755 index 00000000..d0e13ec9 --- /dev/null +++ b/tests/tabescape_dflt.sh @@ -0,0 +1,14 @@ +echo =============================================================================== +echo \[tabescape_dflt.sh\]: test for default tab escaping +$srcdir/killrsyslog.sh # kill rsyslogd if it runs for some reason + +./nettester -ttabescape_dflt -iudp +if [ "$?" -ne "0" ]; then + exit 1 +fi + +echo test via tcp +./nettester -ttabescape_dflt -itcp +if [ "$?" -ne "0" ]; then + exit 1 +fi diff --git a/tests/tabescape_off.sh b/tests/tabescape_off.sh new file mode 100755 index 00000000..71ede7c0 --- /dev/null +++ b/tests/tabescape_off.sh @@ -0,0 +1,14 @@ +echo =============================================================================== +echo \[tabescape_off.sh\]: test for tab escaping off +$srcdir/killrsyslog.sh # kill rsyslogd if it runs for some reason + +./nettester -ttabescape_off -iudp +if [ "$?" -ne "0" ]; then + exit 1 +fi + +echo test via tcp +./nettester -ttabescape_off -itcp +if [ "$?" -ne "0" ]; then + exit 1 +fi diff --git a/tests/tcp-msgreduc-vg.sh b/tests/tcp-msgreduc-vg.sh new file mode 100755 index 00000000..cd8534e4 --- /dev/null +++ b/tests/tcp-msgreduc-vg.sh @@ -0,0 +1,16 @@ +# check if valgrind violations occur. Correct output is not checked. +# added 2011-03-01 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[tcp-msgreduc-vg.sh\]: testing msg reduction via UDP +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg tcp-msgreduc-vg.conf +source $srcdir/diag.sh wait-startup +./tcpflood -t 127.0.0.1 -m 4 -r -M "<133>2011-03-01T11:22:12Z host tag msgh ..." +./tcpflood -t 127.0.0.1 -m 1 -r -M "<133>2011-03-01T11:22:12Z host tag msgh ...x" +# we need to give rsyslog a little time to settle the receiver +./msleep 1500 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +source $srcdir/diag.sh check-exit-vg +source $srcdir/diag.sh exit diff --git a/tests/tcp_forwarding_tpl.sh b/tests/tcp_forwarding_tpl.sh new file mode 100755 index 00000000..61114507 --- /dev/null +++ b/tests/tcp_forwarding_tpl.sh @@ -0,0 +1,30 @@ +# This test tests tcp forwarding with assigned template. To do so, a simple +# tcp listener service is started. +# added 2012-10-30 by Rgerhards. Released under GNU GPLv3+ +echo =============================================================================== +echo \[tcp_forwarding_tpl.sh\]: test for tcp forwarding with assigned template + +# create the pipe and start a background process that copies data from +# it to the "regular" work file +source $srcdir/diag.sh init +./minitcpsrvr 127.0.0.1 13514 rsyslog.out.log & +BGPROCESS=$! +echo background minitcpsrvr process id is $BGPROCESS + +# now do the usual run +source $srcdir/diag.sh startup tcp_forwarding_tpl.conf +# 10000 messages should be enough +source $srcdir/diag.sh injectmsg 0 10000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown + +# note: minitcpsrvr shuts down automatically if the connection is closed! +# (we still leave the code here in in case we need it later) +#echo shutting down minitcpsrv... +#kill $BGPROCESS +#wait $BGPROCESS +#echo background process has terminated, continue test... + +# and continue the usual checks +source $srcdir/diag.sh seq-check 0 9999 +source $srcdir/diag.sh exit diff --git a/tests/tcpflood.c b/tests/tcpflood.c new file mode 100644 index 00000000..b3cef2e0 --- /dev/null +++ b/tests/tcpflood.c @@ -0,0 +1,975 @@ +/* Opens a large number of tcp connections and sends + * messages over them. This is used for stress-testing. + * + * Params + * -t target address (default 127.0.0.1) + * -p target port (default 13514) + * -n number of target ports (targets are in range -p..(-p+-n-1) + * Note -c must also be set to at LEAST the number of -n! + * -c number of connections (default 1) + * -m number of messages to send (connection is random) + * -i initial message number (optional) + * -P PRI to be used for generated messages (default is 167). + * Specify the plain number without leading zeros + * -d amount of extra data to add to message. If present, the + * number itself will be added as third field, and the data + * bytes as forth. Add -r to randomize the amount of extra + * data included in the range 1..(value of -d). + * -r randomize amount of extra data added (-d must be > 0) + * -s (silent) do not show progress indicator (never done on non-tty) + * -f support for testing dynafiles. If given, include a dynafile ID + * in the range 0..(f-1) as the SECOND field, shifting all field values + * one field to the right. Zero (default) disables this functionality. + * -M the message to be sent. Disables all message format options, as + * only that exact same message is sent. + * -I read specified input file, do NOT generate own test data. The test + * completes when eof is reached. + * -B The specified file (-I) is binary. No data processing is done by + * tcpflood. If multiple connections are specified, data is read in + * chunks and spread across the connections without taking any record + * delemiters into account. + * -C when input from a file is read, this file is transmitted -C times + * (C like cycle, running out of meaningful option switches ;)) + * -D randomly drop and re-establish connections. Useful for stress-testing + * the TCP receiver. + * -F USASCII value for frame delimiter (in octet-stuffing mode), default LF + * -R number of times the test shall be run (very useful for gathering performance + * data and other repetitive things). Default: 1 + * -S number of seconds to sleep between different runs (-R) Default: 30 + * -X generate sTats data records. Default: off + * -e encode output in CSV (not yet everywhere supported) + * for performance data: + * each inidividual line has the runtime of one test + * the last line has 0 in field 1, followed by numberRuns,TotalRuntime, + * Average,min,max + * -T transport to use. Currently supported: "udp", "tcp" (default) + * Note: UDP supports a single target port, only + * -W wait time between sending batches of messages, in microseconds (Default: 0) + * -b number of messages within a batch (default: 100,000,000 millions) + * -Y use multiple threads, one per connection (which means 1 if one only connection + * is configured!) + * -z private key file for TLS mode + * -Z cert (public key) file for TLS mode + * -L loglevel to use for GnuTLS troubleshooting (0-off to 10-all, 0 default) + * + * Part of the testbench for rsyslog. + * + * Copyright 2009, 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <assert.h> +#include <unistd.h> +#include <string.h> +#include <netinet/in.h> +#include <pthread.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <errno.h> +#ifdef ENABLE_GNUTLS +# include <gnutls/gnutls.h> +# if GNUTLS_VERSION_NUMBER <= 0x020b00 +# include <gcrypt.h> + GCRY_THREAD_OPTION_PTHREAD_IMPL; +# endif +#endif + +#define EXIT_FAILURE 1 +#define INVALID_SOCKET -1 +/* Name of input file, must match $IncludeConfig in test suite .conf files */ +#define NETTEST_INPUT_CONF_FILE "nettest.input.conf" /* name of input file, must match $IncludeConfig in .conf files */ + +#define MAX_EXTRADATA_LEN 100*1024 +#define MAX_SENDBUF 2 * MAX_EXTRADATA_LEN + +static char *targetIP = "127.0.0.1"; +static char *msgPRI = "167"; +static int targetPort = 13514; +static int numTargetPorts = 1; +static int dynFileIDs = 0; +static int extraDataLen = 0; /* amount of extra data to add to message */ +static int bRandomizeExtraData = 0; /* randomize amount of extra data added */ +static int numMsgsToSend; /* number of messages to send */ +static unsigned numConnections = 1; /* number of connections to create */ +static int *sockArray; /* array of sockets to use */ +static int msgNum = 0; /* initial message number to start with */ +static int bShowProgress = 1; /* show progress messages */ +static int bSilent = 0; /* completely silent operation */ +static int bRandConnDrop = 0; /* randomly drop connections? */ +static char *MsgToSend = NULL; /* if non-null, this is the actual message to send */ +static int bBinaryFile = 0; /* is -I file binary */ +static char *dataFile = NULL; /* name of data file, if NULL, generate own data */ +static int numFileIterations = 1;/* how often is file data to be sent? */ +static char frameDelim = '\n'; /* default frame delimiter */ +FILE *dataFP = NULL; /* file pointer for data file, if used */ +static long nConnDrops = 0; /* counter: number of time connection was dropped (-D option) */ +static int numRuns = 1; /* number of times the test shall be run */ +static int sleepBetweenRuns = 30; /* number of seconds to sleep between runs */ +static int bStatsRecords = 0; /* generate stats records */ +static int bCSVoutput = 0; /* generate output in CSV (where applicable) */ +static long long batchsize = 100000000ll; +static int waittime = 0; +static int runMultithreaded = 0; /* run tests in multithreaded mode */ +static int numThrds = 1; /* number of threads to use */ +static char *tlsCertFile = NULL; +static char *tlsKeyFile = NULL; +static int tlsLogLevel = 0; + +#ifdef ENABLE_GNUTLS +static gnutls_session_t *sessArray; /* array of TLS sessions to use */ +static gnutls_certificate_credentials tlscred; +#endif + +/* variables for managing multi-threaded operations */ +int runningThreads; /* number of threads currently running */ +int doRun; /* shall sender thread begin to run? */ +pthread_mutex_t thrdMgmt; /* mutex for controling startup/shutdown */ +pthread_cond_t condStarted; +pthread_cond_t condDoRun; + +/* the following struct provides information for a generator instance (thread) */ +struct instdata { + /* lower and upper bounds for the thread in question */ + unsigned long long lower; + unsigned long long numMsgs; /* number of messages to send */ + unsigned long long numSent; /* number of messages already sent */ + unsigned idx; /**< index of fd to be used for sending */ + pthread_t thread; /**< thread processing this instance */ +} *instarray = NULL; + +/* the following structure is used to gather performance data */ +struct runstats { + unsigned long long totalRuntime; + unsigned long minRuntime; + unsigned long maxRuntime; + int numRuns; +}; + +static int udpsock; /* socket for sending in UDP mode */ +static struct sockaddr_in udpRcvr; /* remote receiver in UDP mode */ + +static enum { TP_UDP, TP_TCP, TP_TLS } transport = TP_TCP; + +/* forward definitions */ +static void initTLSSess(int); +static int sendTLS(int i, char *buf, int lenBuf); +static void closeTLSSess(int __attribute__((unused)) i); + +/* prepare send subsystem for UDP send */ +static inline int +setupUDP(void) +{ + if((udpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return 1; + + memset((char *) &udpRcvr, 0, sizeof(udpRcvr)); + udpRcvr.sin_family = AF_INET; + udpRcvr.sin_port = htons(targetPort); + if(inet_aton(targetIP, &udpRcvr.sin_addr)==0) { + fprintf(stderr, "inet_aton() failed\n"); + return(1); + } + + return 0; +} + + +/* open a single tcp connection + */ +int openConn(int *fd) +{ + int sock; + struct sockaddr_in addr; + int port; + int retries = 0; + int rnd; + + if((sock=socket(AF_INET, SOCK_STREAM, 0))==-1) { + perror("socket()"); + return(1); + } + + /* randomize port if required */ + if(numTargetPorts > 1) { + rnd = rand(); /* easier if we need value for debug messages ;) */ + port = targetPort + (rnd % numTargetPorts); + } else { + port = targetPort; + } + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if(inet_aton(targetIP, &addr.sin_addr)==0) { + fprintf(stderr, "inet_aton() failed\n"); + return(1); + } + while(1) { /* loop broken inside */ + if(connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + break; + } else { + if(retries++ == 50) { + perror("connect()"); + fprintf(stderr, "connect() failed\n"); + return(1); + } else { + usleep(100000); /* ms = 1000 us! */ + } + } + } + + *fd = sock; + return 0; +} + + +/* open all requested tcp connections + * this includes allocating the connection array + */ +int openConnections(void) +{ + unsigned i; + char msgBuf[128]; + size_t lenMsg; + + if(transport == TP_UDP) + return setupUDP(); + + if(bShowProgress) + if(write(1, " open connections", sizeof(" open connections")-1)){} +# ifdef ENABLE_GNUTLS + sessArray = calloc(numConnections, sizeof(gnutls_session_t)); +# endif + sockArray = calloc(numConnections, sizeof(int)); + for(i = 0 ; i < numConnections ; ++i) { + if(i % 10 == 0) { + if(bShowProgress) + printf("\r%5.5d", i); + } + if(openConn(&(sockArray[i])) != 0) { + printf("error in trying to open connection i=%d\n", i); + return 1; + } + if(transport == TP_TLS) { + initTLSSess(i); + } + } + if(bShowProgress) { + lenMsg = sprintf(msgBuf, "\r%5.5d open connections\n", i); + if(write(1, msgBuf, lenMsg)) {} + } + + return 0; +} + + +/* we also close all connections because otherwise we may get very bad + * timing for the syslogd - it may not be able to process all incoming + * messages fast enough if we immediately shut down. + * TODO: it may be an interesting excercise to handle that situation + * at the syslogd level, too + * rgerhards, 2009-04-14 + */ +void closeConnections(void) +{ + unsigned i; + size_t lenMsg; + struct linger ling; + char msgBuf[128]; + + if(transport == TP_UDP) + return; + + if(bShowProgress) + if(write(1, " close connections", sizeof(" close connections")-1)){} + for(i = 0 ; i < numConnections ; ++i) { + if(i % 10 == 0) { + if(bShowProgress) { + lenMsg = sprintf(msgBuf, "\r%5.5d", i); + if(write(1, msgBuf, lenMsg)){} + } + } + if(sockArray[i] != -1) { + /* we try to not overrun the receiver by trying to flush buffers + * *during* close(). -- rgerhards, 2010-08-10 + */ + ling.l_onoff = 1; + ling.l_linger = 1; + setsockopt(sockArray[i], SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); + if(transport == TP_TLS) + closeTLSSess(i); + close(sockArray[i]); + } + } + if(bShowProgress) { + lenMsg = sprintf(msgBuf, "\r%5.5d close connections\n", i); + if(write(1, msgBuf, lenMsg)){} + } + +} + + +/* generate the message to be sent according to program command line parameters. + * this has been moved to its own function as we now have various different ways + * of constructing test messages. -- rgerhards, 2010-03-31 + */ +static inline void +genMsg(char *buf, size_t maxBuf, int *pLenBuf, struct instdata *inst) +{ + int edLen; /* actual extra data length to use */ + char extraData[MAX_EXTRADATA_LEN + 1]; + char dynFileIDBuf[128] = ""; + int done; + + if(dataFP != NULL) { + /* get message from file */ + do { + done = 1; + *pLenBuf = fread(buf, 1, MAX_EXTRADATA_LEN + 1024, dataFP); + if(*pLenBuf == 0) { + if(--numFileIterations > 0) { + rewind(dataFP); + done = 0; /* need new iteration */ + } else { + *pLenBuf = 0; + goto finalize_it; + } + } + } while(!done); /* Attention: do..while()! */ + } else if(MsgToSend == NULL) { + if(dynFileIDs > 0) { + snprintf(dynFileIDBuf, sizeof(dynFileIDBuf), "%d:", rand() % dynFileIDs); + } + if(extraDataLen == 0) { + *pLenBuf = snprintf(buf, maxBuf, "<%s>Mar 1 01:00:00 172.20.245.8 tag msgnum:%s%8.8d:%c", + msgPRI, dynFileIDBuf, msgNum, frameDelim); + } else { + if(bRandomizeExtraData) + edLen = ((long) rand() + extraDataLen) % extraDataLen + 1; + else + edLen = extraDataLen; + memset(extraData, 'X', edLen); + extraData[edLen] = '\0'; + *pLenBuf = snprintf(buf, maxBuf, "<%s>Mar 1 01:00:00 172.20.245.8 tag msgnum:%s%8.8d:%d:%s%c", + msgPRI, dynFileIDBuf, msgNum, edLen, extraData, frameDelim); + } + } else { + /* use fixed message format from command line */ + *pLenBuf = snprintf(buf, maxBuf, "%s\n", MsgToSend); + } + ++inst->numSent; + +finalize_it: /*EMPTY to keep the compiler happy */; +} + +/* send messages to the tcp connections we keep open. We use + * a very basic format that helps identify the message + * (via msgnum:<number>: e.g. msgnum:00000001:). This format is suitable + * for extracton to field-based properties. + * The first numConnection messages are sent sequentially, as are the + * last. All messages in between are sent over random connections. + * Note that message numbers start at 0. + */ +int sendMessages(struct instdata *inst) +{ + unsigned i = 0; + int socknum; + int lenBuf; + int lenSend = 0; + char *statusText = ""; + char buf[MAX_EXTRADATA_LEN + 1024]; + char sendBuf[MAX_SENDBUF]; + int offsSendBuf = 0; + + if(!bSilent) { + if(dataFile == NULL) { + printf("Sending %llu messages.\n", inst->numMsgs); + statusText = "messages"; + } else { + printf("Sending file '%s' %d times.\n", dataFile, + numFileIterations); + statusText = "kb"; + } + } + if(bShowProgress) + printf("\r%8.8d %s sent", 0, statusText); + while(i < inst->numMsgs) { + if(runMultithreaded) { + socknum = inst->idx; + } else { + if(i < numConnections) + socknum = i; + else if(i >= inst->numMsgs - numConnections) { + socknum = i - (inst->numMsgs - numConnections); + } else { + int rnd = rand(); + socknum = rnd % numConnections; + } + } + genMsg(buf, sizeof(buf), &lenBuf, inst); /* generate the message to send according to params */ + if(lenBuf == 0) + break; /* terminate when no message could be generated */ + if(transport == TP_TCP) { + if(sockArray[socknum] == -1) { + /* connection was dropped, need to re-establish */ + if(openConn(&(sockArray[socknum])) != 0) { + printf("error in trying to re-open connection %d\n", socknum); + exit(1); + } + } + lenSend = send(sockArray[socknum], buf, lenBuf, 0); + } else if(transport == TP_UDP) { + lenSend = sendto(udpsock, buf, lenBuf, 0, &udpRcvr, sizeof(udpRcvr)); + } else if(transport == TP_TLS) { + if(offsSendBuf + lenBuf < MAX_SENDBUF) { + memcpy(sendBuf+offsSendBuf, buf, lenBuf); + offsSendBuf += lenBuf; + lenSend = lenBuf; /* simulate "good" call */ + } else { + lenSend = sendTLS(socknum, sendBuf, offsSendBuf); + lenSend = (lenSend == offsSendBuf) ? lenBuf : -1; + memcpy(sendBuf, buf, lenBuf); + offsSendBuf = lenBuf; + } + } + if(lenSend != lenBuf) { + printf("\r%5.5d\n", i); + fflush(stdout); + perror("send test data"); + printf("send() failed at socket %d, index %d, msgNum %lld\n", + sockArray[socknum], i, inst->numSent); + fflush(stderr); + return(1); + } + if(i % 100 == 0) { + if(bShowProgress) + printf("\r%8.8d", i); + } + if(!runMultithreaded && bRandConnDrop) { + /* if we need to randomly drop connections, see if we + * are a victim + */ + if(rand() > (int) (RAND_MAX * 0.95)) { + ++nConnDrops; + close(sockArray[socknum]); + sockArray[socknum] = -1; + } + } + if(inst->numSent % batchsize == 0) { + usleep(waittime); + } + ++msgNum; + ++i; + } + if(transport == TP_TLS && offsSendBuf != 0) { + /* send remaining buffer */ + lenSend = sendTLS(socknum, sendBuf, offsSendBuf); + } + if(!bSilent) + printf("\r%8.8d %s sent\n", i, statusText); + + return 0; +} + + +/* this is the thread that starts a generator + */ +static void * +thrdStarter(void *arg) +{ + struct instdata *inst = (struct instdata*) arg; + pthread_mutex_lock(&thrdMgmt); + runningThreads++; + pthread_cond_signal(&condStarted); + while(doRun == 0) { + pthread_cond_wait(&condDoRun, &thrdMgmt); + } + pthread_mutex_unlock(&thrdMgmt); + if(sendMessages(inst) != 0) { + printf("error sending messages\n"); + } + return NULL; +} + + +/* This function initializes the actual traffic generators. The function sets up all required + * parameter blocks and starts threads. It returns when all threads are ready to run + * and the main task must just enable them. + */ +static inline void +prepareGenerators() +{ + int i; + long long msgsThrd; + long long starting = 0; + + if(runMultithreaded) { + bSilent = 1; + numThrds = numConnections; + } else { + numThrds = 1; + } + + runningThreads = 0; + doRun = 0; + pthread_mutex_init(&thrdMgmt, NULL); + pthread_cond_init(&condStarted, NULL); + pthread_cond_init(&condDoRun, NULL); + + if(instarray != NULL) { + free(instarray); + } + instarray = calloc(numThrds, sizeof(struct instdata)); + msgsThrd = numMsgsToSend / numThrds; + + for(i = 0 ; i < numThrds ; ++i) { + instarray[i].lower = starting; + instarray[i].numMsgs = msgsThrd; + instarray[i].numSent = 0; + instarray[i].idx = i; + pthread_create(&(instarray[i].thread), NULL, thrdStarter, instarray + i); + /*printf("started thread %x\n", (unsigned) instarray[i].thread);*/ + starting += msgsThrd; + } +} + +/* Let all generators run. Threads must have been started. Here we wait until + * all threads are initialized and then broadcast that they can begin to run. + */ +static inline void +runGenerators() +{ + pthread_mutex_lock(&thrdMgmt); + while(runningThreads != numThrds){ + pthread_cond_wait(&condStarted, &thrdMgmt); + } + doRun = 1; + pthread_cond_broadcast(&condDoRun); + pthread_mutex_unlock(&thrdMgmt); +} + + +/* Wait for all traffic generators to stop. + */ +static inline void +waitGenerators() +{ + int i; + for(i = 0 ; i < numThrds ; ++i) { + pthread_join(instarray[i].thread, NULL); + /*printf("thread %x stopped\n", (unsigned) instarray[i].thread);*/ + } + pthread_mutex_destroy(&thrdMgmt); + pthread_cond_destroy(&condStarted); + pthread_cond_destroy(&condDoRun); +} + +/* functions related to computing statistics on the runtime of a test. This is + * a separate function primarily not to mess up the test driver. + * rgerhards, 2010-12-08 + */ +static inline void +endTiming(struct timeval *tvStart, struct runstats *stats) +{ + long sec, usec; + unsigned long runtime; + struct timeval tvEnd; + + gettimeofday(&tvEnd, NULL); + if(tvStart->tv_usec > tvEnd.tv_usec) { + tvEnd.tv_sec--; + tvEnd.tv_usec += 1000000; + } + + sec = tvEnd.tv_sec - tvStart->tv_sec; + usec = tvEnd.tv_usec - tvStart->tv_usec; + + runtime = sec * 1000 + (usec / 1000); + stats->totalRuntime += runtime; + if(runtime < stats->minRuntime) + stats->minRuntime = runtime; + if(runtime > stats->maxRuntime) + stats->maxRuntime = runtime; + + if(!bSilent || bStatsRecords) { + if(bCSVoutput) { + printf("%ld.%3.3ld\n", runtime / 1000, runtime % 1000); + } else { + printf("runtime: %ld.%3.3ld\n", runtime / 1000, runtime % 1000); + } + } +} + + +/* generate stats summary record at end of run + */ +static inline void +genStats(struct runstats *stats) +{ + long unsigned avg; + avg = stats->totalRuntime / stats->numRuns; + + if(bCSVoutput) { + printf("#numRuns,TotalRuntime,AvgRuntime,MinRuntime,MaxRuntime\n"); + printf("%d,%llu.%3.3d,%lu.%3.3lu,%lu.%3.3lu,%lu.%3.3lu\n", + stats->numRuns, + stats->totalRuntime / 1000, (int) stats->totalRuntime % 1000, + avg / 1000, avg % 1000, + stats->minRuntime / 1000, stats->minRuntime % 1000, + stats->maxRuntime / 1000, stats->maxRuntime % 1000); + } else { + printf("Runs: %d\n", stats->numRuns); + printf("Runtime:\n"); + printf(" total: %llu.%3.3d\n", stats->totalRuntime / 1000, + (int) stats->totalRuntime % 1000); + printf(" avg: %lu.%3.3lu\n", avg / 1000, avg % 1000); + printf(" min: %lu.%3.3lu\n", stats->minRuntime / 1000, stats->minRuntime % 1000); + printf(" max: %lu.%3.3lu\n", stats->maxRuntime / 1000, stats->maxRuntime % 1000); + printf("All times are wallclock time.\n"); + } +} + + +/* Run the actual test. This function handles various meta-parameters, like + * a specified number of iterations, performance measurement and so on... + * rgerhards, 2010-12-08 + */ +static int +runTests(void) +{ + struct timeval tvStart; + struct runstats stats; + int run; + + stats.totalRuntime = 0; + stats.minRuntime = 0xffffffffllu; + stats.maxRuntime = 0; + stats.numRuns = numRuns; + run = 1; + while(1) { /* loop broken inside */ + if(!bSilent) + printf("starting run %d\n", run); + prepareGenerators(); + gettimeofday(&tvStart, NULL); + runGenerators(); + waitGenerators(); + endTiming(&tvStart, &stats); + if(run == numRuns) + break; + if(!bSilent) + printf("sleeping %d seconds before next run\n", sleepBetweenRuns); + sleep(sleepBetweenRuns); + ++run; + } + + if(bStatsRecords) { + genStats(&stats); + } + + return 0; +} + +# if defined(ENABLE_GNUTLS) +/* This defines a log function to be provided to GnuTLS. It hopefully + * helps us track down hard to find problems. + * rgerhards, 2008-06-20 + */ +static void tlsLogFunction(int level, const char *msg) +{ + printf("GnuTLS (level %d): %s", level, msg); + +} + + +/* global init GnuTLS + */ +static void +initTLS(void) +{ + int r; + + /* order of gcry_control and gnutls_global_init matters! */ + #if GNUTLS_VERSION_NUMBER <= 0x020b00 + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + #endif + gnutls_global_init(); + /* set debug mode, if so required by the options */ + if(tlsLogLevel > 0) { + gnutls_global_set_log_function(tlsLogFunction); + gnutls_global_set_log_level(tlsLogLevel); + } + + r = gnutls_certificate_allocate_credentials(&tlscred); + if(r != GNUTLS_E_SUCCESS) { + printf("error allocating credentials\n"); + gnutls_perror(r); + exit(1); + } + r = gnutls_certificate_set_x509_key_file(tlscred, tlsCertFile, tlsKeyFile, GNUTLS_X509_FMT_PEM); + if(r != GNUTLS_E_SUCCESS) { + printf("error setting certificate files -- have you mixed up key and certificate?\n"); + printf("If in doubt, try swapping the files in -z/-Z\n"); + printf("Certifcate is: '%s'\n", tlsCertFile); + printf("Key is: '%s'\n", tlsKeyFile); + gnutls_perror(r); + r = gnutls_certificate_set_x509_key_file(tlscred, tlsKeyFile, tlsCertFile, + GNUTLS_X509_FMT_PEM); + if(r == GNUTLS_E_SUCCESS) { + printf("Tried swapping files, this seems to work " + "(but results may be unpredictable!)\n"); + } else { + exit(1); + } + } +} + + +static void +initTLSSess(int i) +{ + int r; + gnutls_init(sessArray + i, GNUTLS_CLIENT); + + /* Use default priorities */ + gnutls_set_default_priority(sessArray[i]); + + /* put our credentials to the current session */ + r = gnutls_credentials_set(sessArray[i], GNUTLS_CRD_CERTIFICATE, tlscred); + if(r != GNUTLS_E_SUCCESS) { + fprintf (stderr, "Setting credentials failed\n"); + gnutls_perror(r); + exit(1); + } + + /* NOTE: the following statement generates a cast warning, but there seems to + * be no way around it with current GnuTLS. Do NOT try to "fix" the situation! + */ + gnutls_transport_set_ptr(sessArray[i], (gnutls_transport_ptr_t) sockArray[i]); + + /* Perform the TLS handshake */ + r = gnutls_handshake(sessArray[i]); + if(r < 0) { + fprintf (stderr, "TLS Handshake failed\n"); + gnutls_perror(r); + exit(1); + } +} + +static int +sendTLS(int i, char *buf, int lenBuf) +{ + int lenSent; + int r; + + lenSent = 0; + while(lenSent != lenBuf) { + r = gnutls_record_send(sessArray[i], buf + lenSent, lenBuf - lenSent); + if(r < 0) + break; + lenSent += r; + } + + return lenSent; +} + +static void +closeTLSSess(int i) +{ + gnutls_bye(sessArray[i], GNUTLS_SHUT_RDWR); + gnutls_deinit(sessArray[i]); +} +# else /* NO TLS available */ +static void initTLS(void) {} +static void initTLSSess(int __attribute__((unused)) i) {} +static int sendTLS(int __attribute__((unused)) i, char __attribute__((unused)) *buf, int __attribute__((unused)) lenBuf) { return 0; } +static void closeTLSSess(int __attribute__((unused)) i) {} +# endif + +/* Run the test. + * rgerhards, 2009-04-03 + */ +int main(int argc, char *argv[]) +{ + int ret = 0; + int opt; + struct sigaction sigAct; + struct rlimit maxFiles; + static char buf[1024]; + + srand(time(NULL)); /* seed is good enough for our needs */ + + /* on Solaris, we do not HAVE MSG_NOSIGNAL, so for this reason + * we block SIGPIPE (not an issue for this program) + */ + memset(&sigAct, 0, sizeof(sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sigAct, NULL); + + setvbuf(stdout, buf, _IONBF, 48); + + while((opt = getopt(argc, argv, "b:ef:F:t:p:c:C:m:i:I:P:d:Dn:L:M:rsBR:S:T:XW:Yz:Z:")) != -1) { + switch (opt) { + case 'b': batchsize = atoll(optarg); + break; + case 't': targetIP = optarg; + break; + case 'p': targetPort = atoi(optarg); + break; + case 'n': numTargetPorts = atoi(optarg); + break; + case 'c': numConnections = (unsigned) atoi(optarg); + break; + case 'C': numFileIterations = atoi(optarg); + break; + case 'm': numMsgsToSend = atoi(optarg); + break; + case 'i': msgNum = atoi(optarg); + break; + case 'P': msgPRI = optarg; + break; + case 'd': extraDataLen = atoi(optarg); + if(extraDataLen > MAX_EXTRADATA_LEN) { + fprintf(stderr, "-d max is %d!\n", + MAX_EXTRADATA_LEN); + exit(1); + } + break; + case 'D': bRandConnDrop = 1; + break; + case 'r': bRandomizeExtraData = 1; + break; + case 'f': dynFileIDs = atoi(optarg); + break; + case 'F': frameDelim = atoi(optarg); + break; + case 'L': tlsLogLevel = atoi(optarg); + break; + case 'M': MsgToSend = optarg; + break; + case 'I': dataFile = optarg; + /* in this mode, we do not know the num messages to send, so + * we set a (high) number to keep the code happy. + */ + numMsgsToSend = 1000000; + break; + case 's': bSilent = 1; + break; + case 'B': bBinaryFile = 1; + break; + case 'R': numRuns = atoi(optarg); + break; + case 'S': sleepBetweenRuns = atoi(optarg); + break; + case 'X': bStatsRecords = 1; + break; + case 'e': bCSVoutput = 1; + break; + case 'T': if(!strcmp(optarg, "udp")) { + transport = TP_UDP; + } else if(!strcmp(optarg, "tcp")) { + transport = TP_TCP; + } else if(!strcmp(optarg, "tls")) { +# if defined(ENABLE_GNUTLS) + transport = TP_TLS; +# else + fprintf(stderr, "compiled without TLS support: " + "\"-Ttls\" not supported!\n"); + exit(1); +# endif + } else { + fprintf(stderr, "unknown transport '%s'\n", optarg); + exit(1); + } + break; + case 'W': waittime = atoi(optarg); + break; + case 'Y': runMultithreaded = 1; + break; + case 'z': tlsKeyFile = optarg; + break; + case 'Z': tlsCertFile = optarg; + break; + default: printf("invalid option '%c' or value missing - terminating...\n", opt); + exit (1); + break; + } + } + + if(bStatsRecords && waittime) { + fprintf(stderr, "warning: generating performance stats and using a waittime " + "is somewhat contradictory!\n"); + } + + if(!isatty(1) || bSilent) + bShowProgress = 0; + + if(numConnections > 20) { + /* if we use many (whatever this means, 20 is randomly picked) + * connections, we need to make sure we have a high enough + * limit. -- rgerhards, 2010-03-25 + */ + maxFiles.rlim_cur = numConnections + 20; + maxFiles.rlim_max = numConnections + 20; + if(setrlimit(RLIMIT_NOFILE, &maxFiles) < 0) { + perror("setrlimit to increase file handles failed"); + fprintf(stderr, + "could net set sufficiently large number of " + "open files for required connection count!\n"); + exit(1); + } + } + + if(dataFile != NULL) { + if((dataFP = fopen(dataFile, "r")) == NULL) { + perror(dataFile); + exit(1); + } + } + + if(transport == TP_TLS) { + initTLS(); + } + + if(openConnections() != 0) { + printf("error opening connections\n"); + exit(1); + } + + if(runTests() != 0) { + printf("error running tests\n"); + exit(1); + } + + closeConnections(); /* this is important so that we do not finish too early! */ + + if(nConnDrops > 0 && !bSilent) + printf("-D option initiated %ld connection closures\n", nConnDrops); + + if(!bSilent) + printf("End of tcpflood Run\n"); + + exit(ret); +} diff --git a/tests/testbench.h b/tests/testbench.h new file mode 100644 index 00000000..12687743 --- /dev/null +++ b/tests/testbench.h @@ -0,0 +1,103 @@ +/* Defines for a rsyslog standard testbench application. + * + * Work begun 2008-06-13 by Rainer Gerhards (written from scratch) + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include <stdlib.h> + +/* everything we need to begin a testbench */ +#define MODULE_TYPE_TESTBENCH \ +/* definitions for objects we access */ \ +DEFobjCurrIf(obj) \ +\ +static rsRetVal doInit(void); \ +static rsRetVal doTest(void); \ +static rsRetVal doExit(void); \ +\ +/* Below is the driver, which is always the same */ \ +int main(int __attribute__((unused)) argc, char __attribute__((unused)) *argv[]) \ +{ \ + DEFiRet; \ + CHKiRet(doInit()); \ + CHKiRet(doTest()); \ + CHKiRet(doExit()); \ +finalize_it: \ + if(iRet != RS_RET_OK) \ + printf("test returns iRet %d\n", iRet); \ + RETiRet; \ +} + + +/* Initialize everything (most importantly the runtime objects) for the test. The framework + * initializes the global runtime, user must add those objects that it needs additionally. + */ +#define BEGINInit \ +static rsRetVal doInit(void) \ +{ \ + DEFiRet; \ + char *pErrObj; /* tells us which object failed if that happens */ \ + putenv("RSYSLOG_MODDIR=../runtime/.libs/"); /* this is a bit hackish... */ \ + \ + dbgClassInit(); \ + /* Intialize the runtime system */ \ + pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */ \ + CHKiRet(rsrtInit(&pErrObj, &obj)); \ + +#define CODESTARTInit + +#define ENDInit \ +finalize_it: \ + if(iRet != RS_RET_OK) { \ + printf("failure occured during init of object '%s'\n", pErrObj); \ + } \ + \ + RETiRet; \ +} + + + +/* Carry out the actual test... + */ +#define BEGINTest \ +rsRetVal doTest(void) \ +{ \ + DEFiRet; + +#define CODESTARTTest + +#define ENDTest \ + RETiRet; \ +} + + +/* De-init everything (most importantly the runtime objects) for the test. */ +#define BEGINExit \ +rsRetVal doExit(void) \ +{ \ + DEFiRet; \ + CHKiRet(rsrtExit()); + +#define CODESTARTExit + +#define ENDExit \ +finalize_it: \ + RETiRet; \ +} diff --git a/tests/testconfgen.c b/tests/testconfgen.c new file mode 100644 index 00000000..9f191cc7 --- /dev/null +++ b/tests/testconfgen.c @@ -0,0 +1,72 @@ +/* a testcase generator + * THis program reads stdin, which must consist of (name,stmt) tupels + * where name is a part of the config name (small!) and stmt is an actual + * config statement. These tupels must be encoded as + * name<SP>stmt<LF> + * on stdin. After all tupels are read, the power set of all possible + * configurations is generated. + * Copyright (C) 2011 by Rainer Gerhards and Adiscon GmbH + * Released under the GPLv3 as part of the rsyslog project. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int arr[128]; +static char *name[128]; +static char *stmt[128]; + +void output(int n) +{ + int i; + + printf("name:"); + for(i = 0 ; i < n ; ++i) { + if(arr[i]) { + printf("-%s", name[i]); + } + } + printf("\n"); +} + +void pows(int n, int i) +{ + if(i == 0) { + output(n); + } else { + --i; + arr[i] = 0; + pows(n, i); + arr[i] = 1; + pows(n, i); + } +} + + +int main(int argc, char *argv[]) +{ + int n; + char iname[512]; + char istmt[2048]; + int nscanned; + + n = 0; + while(!feof(stdin)) { + nscanned = scanf("%s %[^\n]s\n", iname, istmt); + if(nscanned == EOF) + break; + else if(nscanned != 2) { + fprintf(stderr, "problem scanning entry %d, scanned %d\n", + n, nscanned); + exit(1); + } + name[n] = strdup(iname); + stmt[n] = strdup(istmt); + n++; + printf("name: %s, stmt: %s\n", iname, istmt); + } + /* n is on to high for an index, but just right as the actual number! */ + + printf("read %d entries\n", n); + pows(n, n); +} diff --git a/tests/testsuites/1.field1 b/tests/testsuites/1.field1 new file mode 100644 index 00000000..54751171 --- /dev/null +++ b/tests/testsuites/1.field1 @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: DROP_url_www.sina.com.cn:IN=eth1 OUT=eth0 SRC=192.168.10.78 DST=61.172.201.194 LEN=1182 TOS=0x00 PREC=0x00 TTL=63 ID=14368 DF PROTO=TCP SPT=33343 DPT=80 WINDOW=92 RES=0x00 ACK PSH URGP=0 +DROP_url_www.sina.com.cn:IN=eth1 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/1.inputname_imtcp_12514 b/tests/testsuites/1.inputname_imtcp_12514 new file mode 100644 index 00000000..178b1724 --- /dev/null +++ b/tests/testsuites/1.inputname_imtcp_12514 @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: MSG +12514 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/1.inputname_imtcp_12515 b/tests/testsuites/1.inputname_imtcp_12515 new file mode 100644 index 00000000..d616098b --- /dev/null +++ b/tests/testsuites/1.inputname_imtcp_12515 @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: MSG +12515 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/1.inputname_imtcp_12516 b/tests/testsuites/1.inputname_imtcp_12516 new file mode 100644 index 00000000..8e6997ce --- /dev/null +++ b/tests/testsuites/1.inputname_imtcp_12516 @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: MSG +12516 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/1.omod-if-array b/tests/testsuites/1.omod-if-array new file mode 100644 index 00000000..c464b19c --- /dev/null +++ b/tests/testsuites/1.omod-if-array @@ -0,0 +1,2 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: UDP request discarded from SERVER1/2741 to test_app:255.255.255.255/61601 +167,Mar 6 16:57:54,172.20.245.8,%PIX-7-710005,%PIX-7-710005:, diff --git a/tests/testsuites/1.parse1 b/tests/testsuites/1.parse1 new file mode 100644 index 00000000..5ae655e6 --- /dev/null +++ b/tests/testsuites/1.parse1 @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: UDP request discarded from SERVER1/2741 to test_app:255.255.255.255/61601 +167,local4,debug,Mar 6 16:57:54,172.20.245.8,%PIX-7-710005,%PIX-7-710005:, UDP request discarded from SERVER1/2741 to test_app:255.255.255.255/61601 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/1.retry.conf b/tests/testsuites/1.retry.conf new file mode 100644 index 00000000..c464b19c --- /dev/null +++ b/tests/testsuites/1.retry.conf @@ -0,0 +1,2 @@ +<167>Mar 6 16:57:54 172.20.245.8 %PIX-7-710005: UDP request discarded from SERVER1/2741 to test_app:255.255.255.255/61601 +167,Mar 6 16:57:54,172.20.245.8,%PIX-7-710005,%PIX-7-710005:, diff --git a/tests/testsuites/1.tabescape_dflt b/tests/testsuites/1.tabescape_dflt new file mode 100644 index 00000000..91444bd3 --- /dev/null +++ b/tests/testsuites/1.tabescape_dflt @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 test: before HT after HT (do NOT remove TAB!) + before HT#011after HT (do NOT remove TAB!) +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/1.tabescape_off b/tests/testsuites/1.tabescape_off new file mode 100644 index 00000000..6a331c35 --- /dev/null +++ b/tests/testsuites/1.tabescape_off @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 test: before HT after HT (do NOT remove TAB!) + before HT after HT (do NOT remove TAB!) +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/2.parse1 b/tests/testsuites/2.parse1 new file mode 100644 index 00000000..628e06df --- /dev/null +++ b/tests/testsuites/2.parse1 @@ -0,0 +1,3 @@ +<38>Mar 27 19:06:53 source_server sshd(pam_unix)[12750]: session opened for user foo by (uid=0) +38,auth,info,Mar 27 19:06:53,source_server,sshd(pam_unix),sshd(pam_unix)[12750]:, session opened for user foo by (uid=0) +# yet another real-life sample where we had some issues with diff --git a/tests/testsuites/3.parse1 b/tests/testsuites/3.parse1 new file mode 100644 index 00000000..a6b4e884 --- /dev/null +++ b/tests/testsuites/3.parse1 @@ -0,0 +1,3 @@ +<38>Apr 6 15:07:10 lxcvs07 sshd(pam_unix)[31738]: session closed for user cvsadmin +38,auth,info,Apr 6 15:07:10,lxcvs07,sshd(pam_unix),sshd(pam_unix)[31738]:, session closed for user cvsadmin +# yet another real-life sample where we had some issues with diff --git a/tests/testsuites/4.parse1 b/tests/testsuites/4.parse1 new file mode 100644 index 00000000..07e2445a --- /dev/null +++ b/tests/testsuites/4.parse1 @@ -0,0 +1,4 @@ +<29>Jul 31 21:39:21 example-b example-gw[10538]: disconnect host=/192.0.2.1 destination=192.0.2.2/11282 in=3274 out=1448 duration=0 +29,daemon,notice,Jul 31 21:39:21,example-b,example-gw,example-gw[10538]:, disconnect host=/192.0.2.1 destination=192.0.2.2/11282 in=3274 out=1448 duration=0 +# yet another real-life sample where we had some issues with - the important +# part is the dash inside the hostname! diff --git a/tests/testsuites/8bit.parse1 b/tests/testsuites/8bit.parse1 new file mode 100644 index 00000000..90db6352 --- /dev/null +++ b/tests/testsuites/8bit.parse1 @@ -0,0 +1,2 @@ +<6>AUG 10 22:18:24 host tag This msg contains 8-bit European chars: äöü +6,kern,info,Aug 10 22:18:24,host,tag,tag, This msg contains 8-bit European chars: äöü diff --git a/tests/testsuites/8bit.parse_8bit_escape b/tests/testsuites/8bit.parse_8bit_escape new file mode 100644 index 00000000..b2f6335c --- /dev/null +++ b/tests/testsuites/8bit.parse_8bit_escape @@ -0,0 +1,2 @@ +<6>AUG 10 22:18:24 host tag This msg contains 8-bit European chars: äöü +6,kern,info,Aug 10 22:18:24,host,tag,tag, This msg contains 8-bit European chars: #303#244#303#266#303#274 diff --git a/tests/testsuites/Apr.ts3164 b/tests/testsuites/Apr.ts3164 new file mode 100644 index 00000000..3134f224 --- /dev/null +++ b/tests/testsuites/Apr.ts3164 @@ -0,0 +1,3 @@ +<167>Apr 6 16:57:54 172.20.245.8 TAG: MSG +Apr 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Aug.ts3164 b/tests/testsuites/Aug.ts3164 new file mode 100644 index 00000000..d9a721eb --- /dev/null +++ b/tests/testsuites/Aug.ts3164 @@ -0,0 +1,3 @@ +<167>Aug 6 16:57:54 172.20.245.8 TAG: MSG +Aug 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Dec.ts3164 b/tests/testsuites/Dec.ts3164 new file mode 100644 index 00000000..080ba401 --- /dev/null +++ b/tests/testsuites/Dec.ts3164 @@ -0,0 +1,3 @@ +<167>Dec 6 16:57:54 172.20.245.8 TAG: MSG +Dec 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Feb.ts3164 b/tests/testsuites/Feb.ts3164 new file mode 100644 index 00000000..d1eaaa33 --- /dev/null +++ b/tests/testsuites/Feb.ts3164 @@ -0,0 +1,3 @@ +<167>Feb 6 16:57:54 172.20.245.8 TAG: MSG +Feb 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Jan.ts3164 b/tests/testsuites/Jan.ts3164 new file mode 100644 index 00000000..0cb1c8e2 --- /dev/null +++ b/tests/testsuites/Jan.ts3164 @@ -0,0 +1,3 @@ +<167>Jan 6 16:57:54 172.20.245.8 TAG: MSG +Jan 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Jul.ts3164 b/tests/testsuites/Jul.ts3164 new file mode 100644 index 00000000..562e1ec4 --- /dev/null +++ b/tests/testsuites/Jul.ts3164 @@ -0,0 +1,3 @@ +<167>Jul 6 16:57:54 172.20.245.8 TAG: MSG +Jul 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Jun.ts3164 b/tests/testsuites/Jun.ts3164 new file mode 100644 index 00000000..ede27e0e --- /dev/null +++ b/tests/testsuites/Jun.ts3164 @@ -0,0 +1,3 @@ +<167>Jun 6 16:57:54 172.20.245.8 TAG: MSG +Jun 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Mar.ts3164 b/tests/testsuites/Mar.ts3164 new file mode 100644 index 00000000..55dd5bc2 --- /dev/null +++ b/tests/testsuites/Mar.ts3164 @@ -0,0 +1,3 @@ +<167>Mar 6 16:57:54 172.20.245.8 TAG: MSG +Mar 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/May.ts3164 b/tests/testsuites/May.ts3164 new file mode 100644 index 00000000..72a5a301 --- /dev/null +++ b/tests/testsuites/May.ts3164 @@ -0,0 +1,3 @@ +<167>May 6 16:57:54 172.20.245.8 TAG: MSG +May 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Nov.ts3164 b/tests/testsuites/Nov.ts3164 new file mode 100644 index 00000000..e8f00e01 --- /dev/null +++ b/tests/testsuites/Nov.ts3164 @@ -0,0 +1,3 @@ +<167>Nov 6 16:57:54 172.20.245.8 TAG: MSG +Nov 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Oct.ts3164 b/tests/testsuites/Oct.ts3164 new file mode 100644 index 00000000..01423fef --- /dev/null +++ b/tests/testsuites/Oct.ts3164 @@ -0,0 +1,3 @@ +<167>Oct 6 16:57:54 172.20.245.8 TAG: MSG +Oct 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/Sep.ts3164 b/tests/testsuites/Sep.ts3164 new file mode 100644 index 00000000..6c9e48e0 --- /dev/null +++ b/tests/testsuites/Sep.ts3164 @@ -0,0 +1,3 @@ +<167>Sep 6 16:57:54 172.20.245.8 TAG: MSG +Sep 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/arrayqueue.conf b/tests/testsuites/arrayqueue.conf new file mode 100644 index 00000000..c5874a83 --- /dev/null +++ b/tests/testsuites/arrayqueue.conf @@ -0,0 +1,14 @@ +# Test for queue fixedArray mode (see .sh file for details) +# rgerhards, 2009-04-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +# set spool locations and switch queue to disk-only mode +$MainMsgQueueType FixedArray + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/asynwr_deadlock.conf b/tests/testsuites/asynwr_deadlock.conf new file mode 100644 index 00000000..dc4045b0 --- /dev/null +++ b/tests/testsuites/asynwr_deadlock.conf @@ -0,0 +1,14 @@ +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" + +$OMFileFlushOnTXEnd on +$OMFileFlushInterval 10 +$OMFileFlushIOBufferSize 10k +$OMFileAsyncWriting on +:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/asynwr_deadlock2.conf b/tests/testsuites/asynwr_deadlock2.conf new file mode 100644 index 00000000..07811613 --- /dev/null +++ b/tests/testsuites/asynwr_deadlock2.conf @@ -0,0 +1,16 @@ +# rgerhards, 2010-03-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:3%,%msg:F,58:4%,%msg:F,58:5%\n" +$template dynfile,"rsyslog.out.%msg:F,58:2%.log" # use multiple dynafiles + +$OMFileFlushOnTXEnd on +$OMFileFlushInterval 10 +$OMFileIOBufferSize 10k +$OMFileAsyncWriting on +$DynaFileCacheSize 4 +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/asynwr_deadlock4.conf b/tests/testsuites/asynwr_deadlock4.conf new file mode 100644 index 00000000..f4308ff1 --- /dev/null +++ b/tests/testsuites/asynwr_deadlock4.conf @@ -0,0 +1,16 @@ +# rgerhards, 2010-03-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:3%,%msg:F,58:4%,%msg:F,58:5%\n" +$template dynfile,"rsyslog.out.log" # use multiple dynafiles + +$OMFileFlushOnTXEnd on +$OMFileFlushInterval 10 +$OMFileIOBufferSize 10k +$OMFileAsyncWriting on +$DynaFileCacheSize 4 +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/asynwr_simple.conf b/tests/testsuites/asynwr_simple.conf new file mode 100644 index 00000000..44b03f2b --- /dev/null +++ b/tests/testsuites/asynwr_simple.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileFlushIOBufferSize 10k +$OMFileAsyncWriting on +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/asynwr_small.conf b/tests/testsuites/asynwr_small.conf new file mode 100644 index 00000000..f04ce962 --- /dev/null +++ b/tests/testsuites/asynwr_small.conf @@ -0,0 +1,14 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileAsyncWriting on +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/asynwr_timeout.conf b/tests/testsuites/asynwr_timeout.conf new file mode 100644 index 00000000..44b03f2b --- /dev/null +++ b/tests/testsuites/asynwr_timeout.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileFlushIOBufferSize 10k +$OMFileAsyncWriting on +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/asynwr_tinybuf.conf b/tests/testsuites/asynwr_tinybuf.conf new file mode 100644 index 00000000..01dec4d8 --- /dev/null +++ b/tests/testsuites/asynwr_tinybuf.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 1 +$OMFileAsyncWriting on +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/badqi.conf b/tests/testsuites/badqi.conf new file mode 100644 index 00000000..0ab059df --- /dev/null +++ b/tests/testsuites/badqi.conf @@ -0,0 +1,15 @@ +# Test for bad .qi file (see .sh file for details) +# rgerhards, 2009-10-21 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +# instruct to use bad .qi file +$WorkDirectory bad_qi +$ActionQueueType LinkedList +$ActionQueueFileName dbq +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/cee_diskqueue.conf b/tests/testsuites/cee_diskqueue.conf new file mode 100644 index 00000000..a9b98e80 --- /dev/null +++ b/tests/testsuites/cee_diskqueue.conf @@ -0,0 +1,9 @@ +$IncludeConfig diag-common.conf + +global(workDirectory="/tmp") +template(name="outfmt" type="string" string="%$!usr!msg:F,58:2%\n") + +set $!usr!msg = $msg; +if $msg contains 'msgnum' then + action(type="omfile" file="./rsyslog.out.log" template="outfmt" + queue.type="disk" queue.filename="rsyslog-act1") diff --git a/tests/testsuites/cee_simple.conf b/tests/testsuites/cee_simple.conf new file mode 100644 index 00000000..1bcf83c1 --- /dev/null +++ b/tests/testsuites/cee_simple.conf @@ -0,0 +1,6 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="string" string="%$!usr!msg:F,58:2%\n") +set $!usr!msg = $msg; +if $msg contains 'msgnum' then + action(type="omfile" file="./rsyslog.out.log" template="outfmt") diff --git a/tests/testsuites/complex1.conf b/tests/testsuites/complex1.conf new file mode 100644 index 00000000..9b6a9f35 --- /dev/null +++ b/tests/testsuites/complex1.conf @@ -0,0 +1,86 @@ +# complex test case with multiple actions in gzip mode +# rgerhards, 2009-05-22 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$MainMsgQueueTimeoutEnqueue 5000 + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 + +$template outfmt,"%msg:F,58:3%,%msg:F,58:4%,%msg:F,58:5%\n" +$template dynfile,"rsyslog.out.%inputname%.%msg:F,58:2%.log" + +## RULESET with listener +$Ruleset R13514 +# queue params: +$ActionQueueTimeoutShutdown 60000 +$ActionQueueTimeoutEnqueue 5000 +$ActionQueueSize 5000 +$ActionQueueSaveOnShutdown on +$ActionQueueHighWaterMark 4900 +$ActionQueueLowWaterMark 3500 +$ActionQueueType FixedArray +$ActionQueueWorkerThreads 1 +# action params: +$OMFileFlushOnTXEnd off +$OMFileZipLevel 6 +#$OMFileIOBufferSize 256k +$DynaFileCacheSize 4 +$omfileFlushInterval 1 +*.* ?dynfile;outfmt +# listener +$InputTCPServerInputName 13514 +$InputTCPServerBindRuleset R13514 +$InputTCPServerRun 13514 + + +## RULESET with listener +$Ruleset R13515 +# queue params: +$ActionQueueTimeoutShutdown 60000 +$ActionQueueTimeoutEnqueue 5000 +$ActionQueueSize 5000 +$ActionQueueSaveOnShutdown on +$ActionQueueHighWaterMark 4900 +$ActionQueueLowWaterMark 3500 +$ActionQueueType FixedArray +$ActionQueueWorkerThreads 1 +# action params: +$OMFileFlushOnTXEnd off +$OMFileZipLevel 6 +$OMFileIOBufferSize 256k +$DynaFileCacheSize 4 +$omfileFlushInterval 1 +*.* ?dynfile;outfmt +# listener +$InputTCPServerInputName 13515 +$InputTCPServerBindRuleset R13515 +$InputTCPServerRun 13515 + + + +## RULESET with listener +$Ruleset R13516 +# queue params: +$ActionQueueTimeoutShutdown 60000 +$ActionQueueTimeoutEnqueue 5000 +$ActionQueueSize 5000 +$ActionQueueSaveOnShutdown on +$ActionQueueHighWaterMark 4900 +$ActionQueueLowWaterMark 3500 +$ActionQueueType FixedArray +$ActionQueueWorkerThreads 1 +# action params: +$OMFileFlushOnTXEnd off +$OMFileZipLevel 6 +$OMFileIOBufferSize 256k +$DynaFileCacheSize 4 +$omfileFlushInterval 1 +*.* ?dynfile;outfmt +# listener +$InputTCPServerInputName 13516 +$InputTCPServerBindRuleset R13516 +$InputTCPServerRun 13516 + + diff --git a/tests/testsuites/da-mainmsg-q.conf b/tests/testsuites/da-mainmsg-q.conf new file mode 100644 index 00000000..843a3e4f --- /dev/null +++ b/tests/testsuites/da-mainmsg-q.conf @@ -0,0 +1,21 @@ +# Test for DA mode in main message queue (see .sh file for details) +# rgerhards, 2009-04-22 +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$IncludeConfig diag-common.conf + +# set spool locations and switch queue to disk assisted mode +$WorkDirectory test-spool +$MainMsgQueueSize 200 # this *should* trigger moving on to DA mode... +# note: we must set QueueSize sufficiently high, so that 70% (light delay mark) +# is high enough above HighWatermark! +$MainMsgQueueHighWatermark 80 +$MainMsgQueueLowWatermark 40 +$MainMsgQueueFilename mainq +$MainMsgQueueType linkedlist + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/date1.parse1 b/tests/testsuites/date1.parse1 new file mode 100644 index 00000000..ffc7c373 --- /dev/null +++ b/tests/testsuites/date1.parse1 @@ -0,0 +1,3 @@ +<38> Mar 7 19:06:53 example tag: testmessage (only date actually tested) +38,auth,info,Mar 7 19:06:53,example,tag,tag:, testmessage (only date actually tested) +# one space in front of the date diff --git a/tests/testsuites/date2.parse1 b/tests/testsuites/date2.parse1 new file mode 100644 index 00000000..8d587d9d --- /dev/null +++ b/tests/testsuites/date2.parse1 @@ -0,0 +1,3 @@ +<38>Mar 7 19:06:53 example tag: testmessage (only date actually tested) +38,auth,info,Mar 7 19:06:53,example,tag,tag:, testmessage (only date actually tested) +# only one space between "Mar" and "7" diff --git a/tests/testsuites/date3.parse1 b/tests/testsuites/date3.parse1 new file mode 100644 index 00000000..940d261e --- /dev/null +++ b/tests/testsuites/date3.parse1 @@ -0,0 +1,3 @@ +<38>Mar 7 2008 19:06:53: example tag: testmessage (only date actually tested) +38,auth,info,Mar 7 19:06:53,example,tag,tag:, testmessage (only date actually tested) +# the year should not be there, nor the colon after the date, but we accept it... diff --git a/tests/testsuites/date4.parse1 b/tests/testsuites/date4.parse1 new file mode 100644 index 00000000..eee5fb09 --- /dev/null +++ b/tests/testsuites/date4.parse1 @@ -0,0 +1,3 @@ +<38>Mar 7 2008 19:06:53 example tag: testmessage (only date actually tested) +38,auth,info,Mar 7 19:06:53,example,tag,tag:, testmessage (only date actually tested) +# the year should not be there, but we accept it... diff --git a/tests/testsuites/date5.parse1 b/tests/testsuites/date5.parse1 new file mode 100644 index 00000000..be32e605 --- /dev/null +++ b/tests/testsuites/date5.parse1 @@ -0,0 +1,3 @@ +<38>Mar 7 19:06:53: example tag: testmessage (only date actually tested) +38,auth,info,Mar 7 19:06:53,example,tag,tag:, testmessage (only date actually tested) +# colon after timestamp is strictly not ok, but we accept it diff --git a/tests/testsuites/diag-common.conf b/tests/testsuites/diag-common.conf new file mode 100644 index 00000000..d76e2d15 --- /dev/null +++ b/tests/testsuites/diag-common.conf @@ -0,0 +1,19 @@ +# This is a config include file. It sets up rsyslog so that the +# diag system can successfully be used. Also, it generates a file +# "rsyslogd.started" after rsyslogd is initialized. This config file +# should be included in all tests that intend to use common code for +# controlling the daemon. +# NOTE: we assume that rsyslogd's current working directory is +# ./tests (or the distcheck equivalent), in particlular that this +# config file resides in the testsuites subdirectory. +# rgerhards, 2009-05-27 +$ModLoad ../plugins/imdiag/.libs/imdiag +$IMDiagServerRun 13500 + +$template startupfile,"rsyslogd.started" # trick to use relative path names! +:syslogtag, contains, "rsyslogd" ?startupfile + +# I have disabled the directive below, so that we see errors in testcase +# creation. I am not sure why it was present in the first place, so for +# now I just leave it commented out -- rgerhards, 2011-03-30 +#$ErrorMessagesToStderr off diff --git a/tests/testsuites/diag-common2.conf b/tests/testsuites/diag-common2.conf new file mode 100644 index 00000000..94f7e87f --- /dev/null +++ b/tests/testsuites/diag-common2.conf @@ -0,0 +1,16 @@ +# This is a config include file. It sets up rsyslog so that the +# diag system can successfully be used. Also, it generates a file +# "rsyslogd.started" after rsyslogd is initialized. This config file +# should be included in all tests that intend to use common code for +# controlling the daemon. +# NOTE: we assume that rsyslogd's current working directory is +# ./tests (or the distcheck equivalent), in particlular that this +# config file resides in the testsuites subdirectory. +# rgerhards, 2009-05-27 +$ModLoad ../plugins/imdiag/.libs/imdiag +$IMDiagServerRun 13501 + +$template startupfile,"rsyslogd2.started" # trick to use relative path names! +:syslogtag, contains, "rsyslogd" ?startupfile + +$ErrorMessagesToStderr off diff --git a/tests/testsuites/dircreate_dflt.conf b/tests/testsuites/dircreate_dflt.conf new file mode 100644 index 00000000..9b9aadb8 --- /dev/null +++ b/tests/testsuites/dircreate_dflt.conf @@ -0,0 +1,11 @@ +# see .sh file for description +# rgerhards, 2009-11-30 +$IncludeConfig diag-common.conf + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueFilename mainq +$MainMsgQueueType disk + +$template dynfile,"test-logdir/rsyslog.out.log" # trick to use relative path names! +*.* ?dynfile diff --git a/tests/testsuites/dircreate_off.conf b/tests/testsuites/dircreate_off.conf new file mode 100644 index 00000000..28ccbd8c --- /dev/null +++ b/tests/testsuites/dircreate_off.conf @@ -0,0 +1,12 @@ +# see .sh file for description +# rgerhards, 2009-11-30 +$IncludeConfig diag-common.conf + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueFilename mainq +$MainMsgQueueType disk + +$CreateDirs off +$template dynfile,"test-logdir/rsyslog.out.log" # trick to use relative path names! +*.* ?dynfile diff --git a/tests/testsuites/discard-allmark.conf b/tests/testsuites/discard-allmark.conf new file mode 100644 index 00000000..8a4983c1 --- /dev/null +++ b/tests/testsuites/discard-allmark.conf @@ -0,0 +1,15 @@ +# Test for discard functionality +# rgerhards, 2009-07-30 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$ActionWriteAllMarkMessages on + +:msg, contains, "00000001" ~ + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/discard-rptdmsg.conf b/tests/testsuites/discard-rptdmsg.conf new file mode 100644 index 00000000..74060e31 --- /dev/null +++ b/tests/testsuites/discard-rptdmsg.conf @@ -0,0 +1,15 @@ +# Test for discard functionality +# rgerhards, 2009-07-30 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$RepeatedMsgReduction on + +:msg, contains, "00000001" ~ + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/discard.conf b/tests/testsuites/discard.conf new file mode 100644 index 00000000..bbe2fe77 --- /dev/null +++ b/tests/testsuites/discard.conf @@ -0,0 +1,13 @@ +# Test for discard functionality +# rgerhards, 2009-07-30 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +:msg, contains, "00000001" ~ + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/diskqueue-fsync.conf b/tests/testsuites/diskqueue-fsync.conf new file mode 100644 index 00000000..0a02c6ce --- /dev/null +++ b/tests/testsuites/diskqueue-fsync.conf @@ -0,0 +1,17 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-04-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueSyncQueueFiles on +$MainMsgQueueTimeoutShutdown 10000 +$MainMsgQueueFilename mainq +$MainMsgQueueType disk + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/diskqueue.conf b/tests/testsuites/diskqueue.conf new file mode 100644 index 00000000..a992c5a5 --- /dev/null +++ b/tests/testsuites/diskqueue.conf @@ -0,0 +1,16 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-04-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueFilename mainq +$MainMsgQueueType disk + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/dynfile_cachemiss.conf b/tests/testsuites/dynfile_cachemiss.conf new file mode 100644 index 00000000..273ff176 --- /dev/null +++ b/tests/testsuites/dynfile_cachemiss.conf @@ -0,0 +1,14 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:3%\n" +$template dynfile,"%msg:F,58:2%.log" # complete name is in message +$OMFileFlushOnTXEnd on +$DynaFileCacheSize 4 +$IncludeConfig rsyslog.action.1.include +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/dynfile_invalid2.conf b/tests/testsuites/dynfile_invalid2.conf new file mode 100644 index 00000000..6d94c40d --- /dev/null +++ b/tests/testsuites/dynfile_invalid2.conf @@ -0,0 +1,14 @@ +# simple async writing test +# rgerhards, 2010-03-22 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:3%\n" +$template dynfile,"%msg:F,58:2%.log" # complete name is in message +$OMFileFlushOnTXEnd off +$DynaFileCacheSize 4 +$omfileFlushInterval 1 +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/empty.parse1 b/tests/testsuites/empty.parse1 new file mode 100644 index 00000000..86a86986 --- /dev/null +++ b/tests/testsuites/empty.parse1 @@ -0,0 +1,3 @@ +<14>Jan 6 2009 15:22:26 localhost +14,user,info,Jan 6 15:22:26,localhost,,, +#Note: there is one space after localhost, but then \n! diff --git a/tests/testsuites/execonlyonce.conf b/tests/testsuites/execonlyonce.conf new file mode 100644 index 00000000..085b970e --- /dev/null +++ b/tests/testsuites/execonlyonce.conf @@ -0,0 +1,12 @@ +# see the equally-named .sh file for details +# rgerhards, 2009-11-12 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$ActionExecOnlyOnceEveryInterval 3 +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/execonlyonce.data b/tests/testsuites/execonlyonce.data new file mode 100644 index 00000000..3c54f3d4 --- /dev/null +++ b/tests/testsuites/execonlyonce.data @@ -0,0 +1,2 @@ +00000001 +00000100 diff --git a/tests/testsuites/execonlywhenprevsuspended.conf b/tests/testsuites/execonlywhenprevsuspended.conf new file mode 100644 index 00000000..04dc6b59 --- /dev/null +++ b/tests/testsuites/execonlywhenprevsuspended.conf @@ -0,0 +1,13 @@ +# See main .sh file for info +# rgerhards, 2010-06-23 +$IncludeConfig diag-common.conf + +# omtesting provides the ability to cause "SUSPENDED" action state +$ModLoad ../plugins/omtesting/.libs/omtesting + +$MainMsgQueueTimeoutShutdown 100000 +$template outfmt,"%msg:F,58:2%\n" + +:msg, contains, "msgnum:" :omtesting:fail 2 0 +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/execonlywhenprevsuspended2.conf b/tests/testsuites/execonlywhenprevsuspended2.conf new file mode 100644 index 00000000..86aa8832 --- /dev/null +++ b/tests/testsuites/execonlywhenprevsuspended2.conf @@ -0,0 +1,19 @@ +# See main .sh file for info +# rgerhards, 2010-06-23 +$IncludeConfig diag-common.conf + +# omtesting provides the ability to cause "SUSPENDED" action state +$ModLoad ../plugins/omtesting/.libs/omtesting + +$MainMsgQueueTimeoutShutdown 100000 +$template outfmt,"%msg:F,58:2%\n" + +:msg, contains, "msgnum:" :omtesting:fail 2 0 +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log;outfmt +# note that we MUST re-set PrevSusp, else it will remain active +# for all other actions as well (this tells us how bad the current +# config language is...). -- rgerhards, 2010-06-24 +$ActionExecOnlyWhenPreviousIsSuspended off + +:msg, contains, "msgnum:" ./rsyslog2.out.log;outfmt diff --git a/tests/testsuites/execonlywhenprevsuspended3.conf b/tests/testsuites/execonlywhenprevsuspended3.conf new file mode 100644 index 00000000..d2750e9a --- /dev/null +++ b/tests/testsuites/execonlywhenprevsuspended3.conf @@ -0,0 +1,18 @@ +# See main .sh file for info +# rgerhards, 2010-06-23 +$IncludeConfig diag-common.conf + +# omtesting provides the ability to cause "SUSPENDED" action state +$ModLoad ../plugins/omtesting/.libs/omtesting + +$MainMsgQueueTimeoutShutdown 100000 +$template outfmt,"%msg:F,58:2%\n" + +:msg, contains, "msgnum:" :omtesting:fail 2 0 +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log;outfmt +# note that we MUST re-set PrevSusp, else it will remain active +# for all other actions as well (this tells us how bad the current +# config language is...). -- rgerhards, 2010-06-24 +$ActionExecOnlyWhenPreviousIsSuspended off +& ./rsyslog2.out.log;outfmt diff --git a/tests/testsuites/execonlywhenprevsuspended4.conf b/tests/testsuites/execonlywhenprevsuspended4.conf new file mode 100644 index 00000000..04bc3805 --- /dev/null +++ b/tests/testsuites/execonlywhenprevsuspended4.conf @@ -0,0 +1,15 @@ +# See main .sh file for info +# rgerhards, 2010-06-23 +$IncludeConfig diag-common.conf + +# omtesting provides the ability to cause "SUSPENDED" action state +$ModLoad ../plugins/omtesting/.libs/omtesting + +$MainMsgQueueTimeoutShutdown 100000 +$template outfmt,"%msg:F,58:2%\n" + +:msg, contains, "msgnum:" :omtesting:fail 2 0 +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log;outfmt +# note that $ActionExecOnlyWhenPreviousIsSuspended on is still active! +& ./rsyslog2.out.log;outfmt diff --git a/tests/testsuites/failover-async.conf b/tests/testsuites/failover-async.conf new file mode 100644 index 00000000..76445de3 --- /dev/null +++ b/tests/testsuites/failover-async.conf @@ -0,0 +1,9 @@ +# see the equally-named .sh file for details +$IncludeConfig diag-common.conf + +$template outfmt,"%msg:F,58:2%\n" +# note: the target server shall not be available! + +$ActionQueueType LinkedList +:msg, contains, "msgnum:" @@127.0.0.1:13514 +& ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/failover-basic.conf b/tests/testsuites/failover-basic.conf new file mode 100644 index 00000000..a858769c --- /dev/null +++ b/tests/testsuites/failover-basic.conf @@ -0,0 +1,8 @@ +# see the equally-named .sh file for details +$IncludeConfig diag-common.conf + +$template outfmt,"%msg:F,58:2%\n" +# note: the target server shall not be available! +:msg, contains, "msgnum:" @@127.0.0.1:13514 +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/failover-double.conf b/tests/testsuites/failover-double.conf new file mode 100644 index 00000000..a9991321 --- /dev/null +++ b/tests/testsuites/failover-double.conf @@ -0,0 +1,9 @@ +$IncludeConfig diag-common.conf + +$template outfmt,"%msg:F,58:2%\n" + +:msg, contains, "msgnum:" @@127.0.0.1:13516 +$ActionExecOnlyWhenPreviousIsSuspended on +& @@127.0.0.1:1234 +& ./rsyslog.out.log;outfmt +$ActionExecOnlyWhenPreviousIsSuspended off diff --git a/tests/testsuites/failover-no-basic.conf b/tests/testsuites/failover-no-basic.conf new file mode 100644 index 00000000..b40ef7d7 --- /dev/null +++ b/tests/testsuites/failover-no-basic.conf @@ -0,0 +1,9 @@ +# see the equally-named .sh file for details +$IncludeConfig diag-common.conf + +$RepeatedMsgReduction off + +# second action should never execute +:msg, contains, "msgnum:" /dev/null +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log diff --git a/tests/testsuites/failover-no-rptd.conf b/tests/testsuites/failover-no-rptd.conf new file mode 100644 index 00000000..a46ce116 --- /dev/null +++ b/tests/testsuites/failover-no-rptd.conf @@ -0,0 +1,9 @@ +# see the equally-named .sh file for details +$IncludeConfig diag-common.conf + +$RepeatedMsgReduction on + +# second action should never execute +:msg, contains, "msgnum:" /dev/null +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log diff --git a/tests/testsuites/failover-rptd.conf b/tests/testsuites/failover-rptd.conf new file mode 100644 index 00000000..d3553dbb --- /dev/null +++ b/tests/testsuites/failover-rptd.conf @@ -0,0 +1,10 @@ +# see the equally-named .sh file for details +$IncludeConfig diag-common.conf + +$RepeatedMsgReduction on + +$template outfmt,"%msg:F,58:2%\n" +# note: the target server shall not be available! +:msg, contains, "msgnum:" @@127.0.0.1:13514 +$ActionExecOnlyWhenPreviousIsSuspended on +& ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/field1.conf b/tests/testsuites/field1.conf new file mode 100644 index 00000000..1ff833dd --- /dev/null +++ b/tests/testsuites/field1.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$template fmt,"%msg:F,32:2%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/gzipwr_large.conf b/tests/testsuites/gzipwr_large.conf new file mode 100644 index 00000000..54ad3bb3 --- /dev/null +++ b/tests/testsuites/gzipwr_large.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%,%msg:F,58:3%,%msg:F,58:4%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileZipLevel 6 +$OMFileIOBufferSize 256k +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/gzipwr_large_dynfile.conf b/tests/testsuites/gzipwr_large_dynfile.conf new file mode 100644 index 00000000..3a1b255a --- /dev/null +++ b/tests/testsuites/gzipwr_large_dynfile.conf @@ -0,0 +1,17 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:3%,%msg:F,58:4%,%msg:F,58:5%\n" +$template dynfile,"rsyslog.out.%msg:F,58:2%.log" # use multiple dynafiles +$OMFileFlushOnTXEnd off +$OMFileZipLevel 6 +$OMFileIOBufferSize 256k +$DynaFileCacheSize 4 +$omfileFlushInterval 1 +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/imfile-basic.conf b/tests/testsuites/imfile-basic.conf new file mode 100644 index 00000000..59b109a6 --- /dev/null +++ b/tests/testsuites/imfile-basic.conf @@ -0,0 +1,13 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imfile/.libs/imfile +$InputFileName ./rsyslog.input +$InputFileTag file: +$InputFileStateFile stat-file1 +$InputFileSeverity error +$InputFileFacility local7 +$InputFileMaxLinesAtOnce 100000 +$InputRunFileMonitor + +$template outfmt,"%msg:F,58:2%\n" +:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/imptcp_addtlframedelim.conf b/tests/testsuites/imptcp_addtlframedelim.conf new file mode 100644 index 00000000..bf302fb4 --- /dev/null +++ b/tests/testsuites/imptcp_addtlframedelim.conf @@ -0,0 +1,12 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imptcp/.libs/imptcp +$MainMsgQueueTimeoutShutdown 10000 +$InputPTCPServerAddtlFrameDelimiter 0 +$InputPTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 256k +local0.* ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/imptcp_conndrop.conf b/tests/testsuites/imptcp_conndrop.conf new file mode 100644 index 00000000..77a5d79a --- /dev/null +++ b/tests/testsuites/imptcp_conndrop.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imptcp/.libs/imptcp +$MainMsgQueueTimeoutShutdown 10000 +$InputPTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%,%msg:F,58:3%,%msg:F,58:4%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 256k +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/imptcp_large.conf b/tests/testsuites/imptcp_large.conf new file mode 100644 index 00000000..77a5d79a --- /dev/null +++ b/tests/testsuites/imptcp_large.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imptcp/.libs/imptcp +$MainMsgQueueTimeoutShutdown 10000 +$InputPTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%,%msg:F,58:3%,%msg:F,58:4%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 256k +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/imtcp-multiport.conf b/tests/testsuites/imtcp-multiport.conf new file mode 100644 index 00000000..ccdc15fb --- /dev/null +++ b/tests/testsuites/imtcp-multiport.conf @@ -0,0 +1,13 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-05-22 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 +$InputTCPServerRun 13515 +$InputTCPServerRun 13516 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/imtcp-tls-basic.conf b/tests/testsuites/imtcp-tls-basic.conf new file mode 100644 index 00000000..a94a00ef --- /dev/null +++ b/tests/testsuites/imtcp-tls-basic.conf @@ -0,0 +1,21 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-05-22 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 + +$DefaultNetstreamDriver gtls + +# certificate files - just CA for a client +$IncludeConfig rsyslog.conf.tlscert +$InputTCPServerStreamDriverMode 1 +$InputTCPServerStreamDriverAuthMode anon +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileAsyncWriting on +$OMFileIOBufferSize 16k +:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/imtcp_addtlframedelim.conf b/tests/testsuites/imtcp_addtlframedelim.conf new file mode 100644 index 00000000..6558c519 --- /dev/null +++ b/tests/testsuites/imtcp_addtlframedelim.conf @@ -0,0 +1,12 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerAddtlFrameDelimiter 0 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 256k +local0.* ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/imtcp_conndrop.conf b/tests/testsuites/imtcp_conndrop.conf new file mode 100644 index 00000000..de41bc43 --- /dev/null +++ b/tests/testsuites/imtcp_conndrop.conf @@ -0,0 +1,15 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%,%msg:F,58:3%,%msg:F,58:4%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 256k +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/imuxsock_ccmiddle_root.conf b/tests/testsuites/imuxsock_ccmiddle_root.conf new file mode 100644 index 00000000..8336ecfa --- /dev/null +++ b/tests/testsuites/imuxsock_ccmiddle_root.conf @@ -0,0 +1,7 @@ +# rgerhards, 2011-02-21 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imuxsock/.libs/imuxsock + +$template outfmt,"%msg:%\n" +*.notice ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/imuxsock_logger_root.conf b/tests/testsuites/imuxsock_logger_root.conf new file mode 100644 index 00000000..8336ecfa --- /dev/null +++ b/tests/testsuites/imuxsock_logger_root.conf @@ -0,0 +1,7 @@ +# rgerhards, 2011-02-21 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imuxsock/.libs/imuxsock + +$template outfmt,"%msg:%\n" +*.notice ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/imuxsock_traillf_root.conf b/tests/testsuites/imuxsock_traillf_root.conf new file mode 100644 index 00000000..8336ecfa --- /dev/null +++ b/tests/testsuites/imuxsock_traillf_root.conf @@ -0,0 +1,7 @@ +# rgerhards, 2011-02-21 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imuxsock/.libs/imuxsock + +$template outfmt,"%msg:%\n" +*.notice ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/incltest.conf b/tests/testsuites/incltest.conf new file mode 100644 index 00000000..737018cd --- /dev/null +++ b/tests/testsuites/incltest.conf @@ -0,0 +1,5 @@ +# see .sh file for description +# rgerhards, 2009-11-30 +$IncludeConfig diag-common.conf + +$IncludeConfig testsuites/incltest.d/include.conf diff --git a/tests/testsuites/incltest.d/include.conf b/tests/testsuites/incltest.d/include.conf new file mode 100644 index 00000000..39a2ea70 --- /dev/null +++ b/tests/testsuites/incltest.d/include.conf @@ -0,0 +1,2 @@ +$template outfmt,"%msg:F,58:2%\n" +:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/incltest_dir.conf b/tests/testsuites/incltest_dir.conf new file mode 100644 index 00000000..421349d2 --- /dev/null +++ b/tests/testsuites/incltest_dir.conf @@ -0,0 +1,5 @@ +# see .sh file for description +# rgerhards, 2009-11-30 +$IncludeConfig diag-common.conf + +$IncludeConfig testsuites/incltest.d/ diff --git a/tests/testsuites/incltest_dir_empty_wildcard.conf b/tests/testsuites/incltest_dir_empty_wildcard.conf new file mode 100644 index 00000000..5e750c5b --- /dev/null +++ b/tests/testsuites/incltest_dir_empty_wildcard.conf @@ -0,0 +1,11 @@ +# see .sh file for description +# rgerhards, 2009-11-30 +$IncludeConfig diag-common.conf + +# the following include leads to no files actually being included +# but MUST NOT abort rsyslog's startup sequence. No files matching +# the wildcard is valid (as long as the path exists)! +$IncludeConfig testsuites/incltest.d/*.conf-not-there + +$template outfmt,"%msg:F,58:2%\n" +:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/incltest_dir_wildcard.conf b/tests/testsuites/incltest_dir_wildcard.conf new file mode 100644 index 00000000..0d7e6782 --- /dev/null +++ b/tests/testsuites/incltest_dir_wildcard.conf @@ -0,0 +1,5 @@ +# see .sh file for description +# rgerhards, 2009-11-30 +$IncludeConfig diag-common.conf + +$IncludeConfig testsuites/incltest.d/*.conf diff --git a/tests/testsuites/inputname_imtcp.conf b/tests/testsuites/inputname_imtcp.conf new file mode 100644 index 00000000..a25eab37 --- /dev/null +++ b/tests/testsuites/inputname_imtcp.conf @@ -0,0 +1,19 @@ +# This is a special case, thus we define the inputs ourselfs +$ModLoad ../plugins/omstdout/.libs/omstdout + +$ModLoad ../plugins/imtcp/.libs/imtcp + +$InputTCPServerInputname 12514 +$InputTCPServerRun 12514 + +$InputTCPServerInputname 12515 +$InputTCPServerRun 12515 + +$InputTCPServerInputname 12516 +$InputTCPServerRun 12516 + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$template fmt,"%inputname%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/invalid.conf b/tests/testsuites/invalid.conf new file mode 100644 index 00000000..8a865ba5 --- /dev/null +++ b/tests/testsuites/invalid.conf @@ -0,0 +1,3 @@ +# This is an invalid config file that shall trigger an exit code +# with the config verification run +$invalid diff --git a/tests/testsuites/libdbi-asyn.conf b/tests/testsuites/libdbi-asyn.conf new file mode 100644 index 00000000..39b01fbc --- /dev/null +++ b/tests/testsuites/libdbi-asyn.conf @@ -0,0 +1,12 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/omlibdbi/.libs/omlibdbi + +$ActionQueueType LinkedList + +$ActionLibdbiDriver mysql +$ActionLibdbiHost 127.0.0.1 +$ActionLibdbiUserName root +$ActionLibdbiPassword pass +$ActionLibdbiDBName Syslog +:msg, contains, "msgnum:" :omlibdbi: diff --git a/tests/testsuites/libdbi-basic.conf b/tests/testsuites/libdbi-basic.conf new file mode 100644 index 00000000..212225cc --- /dev/null +++ b/tests/testsuites/libdbi-basic.conf @@ -0,0 +1,9 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/omlibdbi/.libs/omlibdbi +$ActionLibdbiDriver mysql +$ActionLibdbiHost 127.0.0.1 +$ActionLibdbiUserName root +$ActionLibdbiPassword pass +$ActionLibdbiDBName Syslog +:msg, contains, "msgnum:" :omlibdbi: diff --git a/tests/testsuites/linkedlistqueue.conf b/tests/testsuites/linkedlistqueue.conf new file mode 100644 index 00000000..92a9649c --- /dev/null +++ b/tests/testsuites/linkedlistqueue.conf @@ -0,0 +1,16 @@ +# Test for queue LinkedList mode (see .sh file for details) +# rgerhards, 2009-04-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$ErrorMessagesToStderr off + +# set spool locations and switch queue to disk-only mode +$MainMsgQueueType LinkedList + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/malformed1.parse1 b/tests/testsuites/malformed1.parse1 new file mode 100644 index 00000000..a8825fe8 --- /dev/null +++ b/tests/testsuites/malformed1.parse1 @@ -0,0 +1,6 @@ +<131>Oct 8 23:05:06 10.321.1.123 05",result_code=200,b +131,local0,err,Oct 8 23:05:06,10.321.1.123,05",result_code=200,b,05",result_code=200,b, +# a somewhat mangeld-with real-life sample of a malformed message +# the key here is not what is being parsed, but that we do not abort! +# NOTE: if a parser enhancement breaks the format, this is probably OK +# also note that the above message does NOT contain a MSG part diff --git a/tests/testsuites/manyptcp.conf b/tests/testsuites/manyptcp.conf new file mode 100644 index 00000000..4069f977 --- /dev/null +++ b/tests/testsuites/manyptcp.conf @@ -0,0 +1,12 @@ +# Test for tcp "flood" testing +# rgerhards, 2009-04-08 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imptcp/.libs/imptcp +$MainMsgQueueTimeoutShutdown 10000 +$MaxOpenFiles 2000 +$InputPTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/manytcp-too-few-tls.conf b/tests/testsuites/manytcp-too-few-tls.conf new file mode 100644 index 00000000..5269e73b --- /dev/null +++ b/tests/testsuites/manytcp-too-few-tls.conf @@ -0,0 +1,22 @@ +# Test for tcp "flood" testing +# rgerhards, 2009-04-08 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$MaxOpenFiles 200 +$InputTCPMaxSessions 1100 +# certificates +$DefaultNetstreamDriverCAFile testsuites/x.509/ca.pem +$DefaultNetstreamDriverCertFile testsuites/x.509/client-cert.pem +$DefaultNetstreamDriverKeyFile testsuites/x.509/client-key.pem + +$DefaultNetstreamDriver gtls # use gtls netstream driver + +$InputTCPServerStreamDriverMode 1 +$InputTCPServerStreamDriverAuthMode anon +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/manytcp.conf b/tests/testsuites/manytcp.conf new file mode 100644 index 00000000..eb9db257 --- /dev/null +++ b/tests/testsuites/manytcp.conf @@ -0,0 +1,13 @@ +# Test for tcp "flood" testing +# rgerhards, 2009-04-08 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$MaxOpenFiles 2000 +$InputTCPMaxSessions 1100 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/mark.parse1 b/tests/testsuites/mark.parse1 new file mode 100644 index 00000000..fff9ae6d --- /dev/null +++ b/tests/testsuites/mark.parse1 @@ -0,0 +1,7 @@ +#This is a malformed message, but one from real life. At least, +#it should be parsed as can be seen here. +<6>Feb 18 16:01:59 serverX -- MARK -- +6,kern,info,Feb 18 16:01:59,serverX,--,--, MARK -- +# and the next one as an extreme case (note the absence of PRI) +Feb 18 16:01:59 serverX -- MARK -- +13,user,notice,Feb 18 16:01:59,serverX,--,--, MARK -- diff --git a/tests/testsuites/master.nolimittag b/tests/testsuites/master.nolimittag new file mode 100644 index 00000000..502d9d5d --- /dev/null +++ b/tests/testsuites/master.nolimittag @@ -0,0 +1,11 @@ +<167>Mar 6 16:57:54 172.20.245.8 TAG: Rest of message... ++TAG:+ +# now one char, no colon +<167>Mar 6 16:57:54 172.20.245.8 0 Rest of message... ++0+ +# Now exactly with 32 characters +<167>Mar 6 16:57:54 172.20.245.8 01234567890123456789012345678901 Rest of message... ++01234567890123456789012345678901+ +# Now oversize, should be completely output with this config +<167>Mar 6 16:57:54 172.20.245.8 01234567890123456789012345678901-toolong Rest of message... ++01234567890123456789012345678901-toolong+ diff --git a/tests/testsuites/master.pmlastmsg b/tests/testsuites/master.pmlastmsg new file mode 100644 index 00000000..170538b0 --- /dev/null +++ b/tests/testsuites/master.pmlastmsg @@ -0,0 +1,28 @@ +# the following messages should be processed by pmlastmsg: +<13>last message repeated 5 times +last message repeated 5 times +# +<13>last message repeated 0090909787348927349875 times +last message repeated 0090909787348927349875 times +# now slightly malformed formats that should NOT be processed +# by pmlasmsg: +<13>last message repeated 5 times + repeated 5 times +# +<13>last message repeated 5 times -- more data + repeated 5 times -- more data +# message count invalid: +<13>last message repeated 5.2 times + repeated 5.2 times +# +# +# now follow samples of non-pmlastmsg messages: +# +<167>Mar 6 16:57:54 172.20.245.8 TAG: Rest of message... + Rest of message... +# Now exactly with 32 characters +<167>Mar 6 16:57:54 172.20.245.8 TAG long message ================================================================================ + long message ================================================================================ +# RFC5424 messages +<34>1 2003-11-11T22:14:15.003Z mymachine.example.com su - ID47 last message repeated 5 times +last message repeated 5 times diff --git a/tests/testsuites/master.rfctag b/tests/testsuites/master.rfctag new file mode 100644 index 00000000..3f1e0c66 --- /dev/null +++ b/tests/testsuites/master.rfctag @@ -0,0 +1,11 @@ +<167>Mar 6 16:57:54 172.20.245.8 TAG: Rest of message... ++TAG:+ +# now one char, no colon +<167>Mar 6 16:57:54 172.20.245.8 0 Rest of message... ++0+ +# Now exactly with 32 characters +<167>Mar 6 16:57:54 172.20.245.8 01234567890123456789012345678901 Rest of message... ++01234567890123456789012345678901+ +# Now oversize, should be truncated with this config +<167>Mar 6 16:57:54 172.20.245.8 01234567890123456789012345678901-toolong Rest of message... ++01234567890123456789012345678901+ diff --git a/tests/testsuites/master.subsecond b/tests/testsuites/master.subsecond new file mode 100644 index 00000000..ee924877 --- /dev/null +++ b/tests/testsuites/master.subsecond @@ -0,0 +1,8 @@ +<34>1 2003-01-23T12:34:56.003Z mymachine.example.com su - ID47 - MSG +003 +# full precision +<34>1 2003-01-23T12:34:56.123456Z mymachine.example.com su - ID47 - MSG +123456 +# without +<34>1 2003-01-23T12:34:56Z mymachine.example.com su - ID47 - MSG +0 diff --git a/tests/testsuites/master.ts3339 b/tests/testsuites/master.ts3339 new file mode 100644 index 00000000..b4dd5f39 --- /dev/null +++ b/tests/testsuites/master.ts3339 @@ -0,0 +1,22 @@ +<34>1 2003-11-11T22:14:15.003Z mymachine.example.com su - ID47 - MSG +2003-11-11T22:14:15.003Z +# next test +<34>1 2003-01-11T22:14:15.003Z mymachine.example.com su - ID47 - MSG +2003-01-11T22:14:15.003Z +# next test +<34>1 2003-11-01T22:04:15.003Z mymachine.example.com su - ID47 - MSG +2003-11-01T22:04:15.003Z +# next test +<34>1 2003-11-11T02:14:15.003Z mymachine.example.com su - ID47 - MSG +2003-11-11T02:14:15.003Z +# next test +<34>1 2003-11-11T22:04:05.003Z mymachine.example.com su - ID47 - MSG +2003-11-11T22:04:05.003Z +# next test +<34>1 2003-11-11T22:04:05.003+02:00 mymachine.example.com su - ID47 - MSG +2003-11-11T22:04:05.003+02:00 +# next test +<34>1 2003-11-11T22:04:05.003+01:30 mymachine.example.com su - ID47 - MSG +2003-11-11T22:04:05.003+01:30 +<34>1 2003-11-11T22:04:05.123456+01:30 mymachine.example.com su - ID47 - MSG +2003-11-11T22:04:05.123456+01:30 diff --git a/tests/testsuites/master.tsmysql b/tests/testsuites/master.tsmysql new file mode 100644 index 00000000..dc6d85be --- /dev/null +++ b/tests/testsuites/master.tsmysql @@ -0,0 +1,2 @@ +<34>1 2003-01-23T12:34:56.003Z mymachine.example.com su - ID47 - MSG +20030123123456 diff --git a/tests/testsuites/master.tspgsql b/tests/testsuites/master.tspgsql new file mode 100644 index 00000000..d7ac19ff --- /dev/null +++ b/tests/testsuites/master.tspgsql @@ -0,0 +1,2 @@ +<34>1 2003-01-23T12:34:56.003Z mymachine.example.com su - ID47 - MSG +2003-01-23 12:34:56 diff --git a/tests/testsuites/mon1digit.ts3164 b/tests/testsuites/mon1digit.ts3164 new file mode 100644 index 00000000..0cb1c8e2 --- /dev/null +++ b/tests/testsuites/mon1digit.ts3164 @@ -0,0 +1,3 @@ +<167>Jan 6 16:57:54 172.20.245.8 TAG: MSG +Jan 6 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/mon2digit.ts3164 b/tests/testsuites/mon2digit.ts3164 new file mode 100644 index 00000000..9606961c --- /dev/null +++ b/tests/testsuites/mon2digit.ts3164 @@ -0,0 +1,3 @@ +<167>Jan 16 16:57:54 172.20.245.8 TAG: MSG +Jan 16 16:57:54 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/mysql-asyn.conf b/tests/testsuites/mysql-asyn.conf new file mode 100644 index 00000000..acdf9bbd --- /dev/null +++ b/tests/testsuites/mysql-asyn.conf @@ -0,0 +1,5 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/ommysql/.libs/ommysql +$ActionQueueType LinkedList +:msg, contains, "msgnum:" :ommysql:127.0.0.1,Syslog,rsyslog,testbench; diff --git a/tests/testsuites/mysql-basic-cnf6.conf b/tests/testsuites/mysql-basic-cnf6.conf new file mode 100644 index 00000000..12bd0b29 --- /dev/null +++ b/tests/testsuites/mysql-basic-cnf6.conf @@ -0,0 +1,7 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/ommysql/.libs/ommysql +if $msg contains 'msgnum' then { + action(type="ommysql" server="127.0.0.1" + db="Syslog" uid="rsyslog" pwd="testbench") +} diff --git a/tests/testsuites/mysql-basic.conf b/tests/testsuites/mysql-basic.conf new file mode 100644 index 00000000..070094f5 --- /dev/null +++ b/tests/testsuites/mysql-basic.conf @@ -0,0 +1,4 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/ommysql/.libs/ommysql +:msg, contains, "msgnum:" :ommysql:127.0.0.1,Syslog,rsyslog,testbench; diff --git a/tests/testsuites/mysql-select-msg.sql b/tests/testsuites/mysql-select-msg.sql new file mode 100644 index 00000000..2d27a331 --- /dev/null +++ b/tests/testsuites/mysql-select-msg.sql @@ -0,0 +1,2 @@ +use Syslog; +select substring(Message,9,8) from SystemEvents; diff --git a/tests/testsuites/mysql-truncate.sql b/tests/testsuites/mysql-truncate.sql new file mode 100644 index 00000000..ca852beb --- /dev/null +++ b/tests/testsuites/mysql-truncate.sql @@ -0,0 +1,2 @@ +use Syslog; +truncate table SystemEvents; diff --git a/tests/testsuites/nolimittag.conf b/tests/testsuites/nolimittag.conf new file mode 100644 index 00000000..0b6ec387 --- /dev/null +++ b/tests/testsuites/nolimittag.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +$template fmt,"+%syslogtag%+\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/omod-if-array.conf b/tests/testsuites/omod-if-array.conf new file mode 100644 index 00000000..d88db166 --- /dev/null +++ b/tests/testsuites/omod-if-array.conf @@ -0,0 +1,13 @@ +# Test config for array-passing output module interface +# (stanard string passing is already tested via the other test inside +# the testbench, so we do not need to focus on that) +# rgerhards, 2009-04-03 +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ActionOMStdoutArrayInterface on +$ErrorMessagesToStderr off + +# do NOT remove \n, that would hang the test driver! +$template expect,"%PRI%%timestamp%%hostname%%programname%%syslogtag%\n" +*.* :omstdout:;expect diff --git a/tests/testsuites/omruleset-queue.conf b/tests/testsuites/omruleset-queue.conf new file mode 100644 index 00000000..edc33c40 --- /dev/null +++ b/tests/testsuites/omruleset-queue.conf @@ -0,0 +1,20 @@ +# test for omruleset (see .sh file for details) +# rgerhards, 2009-11-02 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/omruleset/.libs/omruleset +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 + +$ruleset rsinclude +# create ruleset main queue with default parameters +$RulesetCreateMainQueue on +# make sure we do not terminate too early! +$MainMsgQueueTimeoutShutdown 10000 +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt + +$ruleset RSYSLOG_DefaultRuleset +$ActionOmrulesetRulesetName rsinclude +*.* :omruleset: diff --git a/tests/testsuites/omruleset.conf b/tests/testsuites/omruleset.conf new file mode 100644 index 00000000..fa6b906e --- /dev/null +++ b/tests/testsuites/omruleset.conf @@ -0,0 +1,16 @@ +# Basic test for omruleset (see .sh file for details) +# rgerhards, 2009-11-02 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/omruleset/.libs/omruleset +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 + +$ruleset rsinclude +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt + +$ruleset RSYSLOG_DefaultRuleset +$ActionOmrulesetRulesetName rsinclude +*.* :omruleset: diff --git a/tests/testsuites/oversizeTag-1.parse1 b/tests/testsuites/oversizeTag-1.parse1 new file mode 100644 index 00000000..d45ba1f2 --- /dev/null +++ b/tests/testsuites/oversizeTag-1.parse1 @@ -0,0 +1,2 @@ +<38>Mar 27 19:06:53 source_server 0123456789012345678901234567890123456789: MSG part +38,auth,info,Mar 27 19:06:53,source_server,0123456789012345678901234567890123456789,0123456789012345678901234567890123456789:, MSG part diff --git a/tests/testsuites/parse-3164-buggyday.conf b/tests/testsuites/parse-3164-buggyday.conf new file mode 100644 index 00000000..937f423a --- /dev/null +++ b/tests/testsuites/parse-3164-buggyday.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$template expect,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%timestamp:::date-rfc3164-buggyday%,%hostname%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;expect diff --git a/tests/testsuites/parse-nodate.conf b/tests/testsuites/parse-nodate.conf new file mode 100644 index 00000000..570638d9 --- /dev/null +++ b/tests/testsuites/parse-nodate.conf @@ -0,0 +1,14 @@ +# test is a test config that does not include the timestamp. This is necessary to +# test some illformed messages that do not contain a date. In that case, the system's +# current timestamp is used, and that of course is a bit hard to compare against +# a fixed template. So the solution in this case is to use a format that does +# not contain any timestamp. Maybe not optimal, but it works ;) +# rgerhards, 2010-03-19 +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse +$template fmt,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%hostname%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/parse1.conf b/tests/testsuites/parse1.conf new file mode 100644 index 00000000..094cd762 --- /dev/null +++ b/tests/testsuites/parse1.conf @@ -0,0 +1,9 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off +$LocalHostName localhost + +# use a special format that we can easily parse in expect +$template expect,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%timestamp%,%hostname%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;expect diff --git a/tests/testsuites/parse1udp.conf b/tests/testsuites/parse1udp.conf new file mode 100644 index 00000000..0fb7d16d --- /dev/null +++ b/tests/testsuites/parse1udp.conf @@ -0,0 +1,9 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$ModLoad ../plugins/imudp/.libs/imudp +$UDPServerRun 12514 + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$template expect,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%timestamp%,%hostname%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;expect diff --git a/tests/testsuites/parse2.conf b/tests/testsuites/parse2.conf new file mode 100644 index 00000000..04d910bc --- /dev/null +++ b/tests/testsuites/parse2.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$template output,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%timestamp%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;output diff --git a/tests/testsuites/parse3.conf b/tests/testsuites/parse3.conf new file mode 100644 index 00000000..8a3cb317 --- /dev/null +++ b/tests/testsuites/parse3.conf @@ -0,0 +1,10 @@ +# note: we need to strip off the TZ designator in the rfc3339 timestamp +# as this test otherwise fails in different timezones! +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$Template output,"%timereported:1:19:date-rfc3339,csv%, %hostname:::csv%, %programname:::csv%, %syslogtag:R,ERE,0,BLANK:[0-9]+--end:csv%, %syslogseverity:::csv%, %msg:::drop-last-lf,csv%\n" +*.* :omstdout:;output diff --git a/tests/testsuites/parse_8bit_escape.conf b/tests/testsuites/parse_8bit_escape.conf new file mode 100644 index 00000000..0598f33f --- /dev/null +++ b/tests/testsuites/parse_8bit_escape.conf @@ -0,0 +1,9 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off +$Escape8BitCharactersOnReceive on + +# use a special format that we can easily parse in expect +$template expect,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%timestamp%,%hostname%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;expect diff --git a/tests/testsuites/parse_invld_regex.conf b/tests/testsuites/parse_invld_regex.conf new file mode 100644 index 00000000..d18a2b3c --- /dev/null +++ b/tests/testsuites/parse_invld_regex.conf @@ -0,0 +1,10 @@ +# note: we need to strip off the TZ designator in the rfc3339 timestamp +# as this test otherwise fails in different timezones! +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$Template output,"%timereported:1:19:date-rfc3339,csv%, %hostname:::csv%, %programname:::csv%, %syslogtag:R,ERE,0,BLANK:[0-9+--end:csv%, %syslogseverity:::csv%, %msg:::drop-last-lf,csv%\n" +*.* :omstdout:;output diff --git a/tests/testsuites/pipe_noreader.conf b/tests/testsuites/pipe_noreader.conf new file mode 100644 index 00000000..63997760 --- /dev/null +++ b/tests/testsuites/pipe_noreader.conf @@ -0,0 +1,10 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%\n" +:msg, contains, "msgnum:" |./rsyslog.pipe diff --git a/tests/testsuites/pipeaction.conf b/tests/testsuites/pipeaction.conf new file mode 100644 index 00000000..f58b6d65 --- /dev/null +++ b/tests/testsuites/pipeaction.conf @@ -0,0 +1,16 @@ +# Test for pipe output action (see .sh file for details) +# rgerhards, 2009-11-05 +$IncludeConfig diag-common.conf + +$MainMsgQueueTimeoutShutdown 10000 + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueFilename mainq +$MainMsgQueueType disk + +$template outfmt,"%msg:F,58:2%\n" +# with pipes, we do not need to use absolute path names, so +# we can simply refer to our working pipe via the usual relative +# path name +:msg, contains, "msgnum:" |rsyslog-testbench-fifo;outfmt diff --git a/tests/testsuites/pmlastmsg.conf b/tests/testsuites/pmlastmsg.conf new file mode 100644 index 00000000..59142dca --- /dev/null +++ b/tests/testsuites/pmlastmsg.conf @@ -0,0 +1,16 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$ModLoad ../plugins/pmlastmsg/.libs/pmlastmsg +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# in this test, we use pmlastmsg, followed by the one-size-fits-all +# pm3164 (built-in) module. So we can test if non-pmlastmsg messages +# are also correctly detected. +$RulesetParser rsyslog.lastline +$RulesetParser rsyslog.rfc5424 +$RulesetParser rsyslog.rfc3164 + +# use a special format +$template fmt,"%msg%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/queue-persist.conf b/tests/testsuites/queue-persist.conf new file mode 100644 index 00000000..8903042d --- /dev/null +++ b/tests/testsuites/queue-persist.conf @@ -0,0 +1,21 @@ +# Test for persisting messages on shutdown +# rgerhards, 2009-04-17 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 1 +$MainMsgQueueSaveOnShutdown on +$InputTCPServerRun 13514 + +$ModLoad ../plugins/omtesting/.libs/omtesting + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueFilename mainq +$IncludeConfig work-queuemode.conf + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt + +$IncludeConfig work-delay.conf diff --git a/tests/testsuites/random.conf b/tests/testsuites/random.conf new file mode 100644 index 00000000..74ec61a8 --- /dev/null +++ b/tests/testsuites/random.conf @@ -0,0 +1,18 @@ +# we write to /dev/null, as we have no chance to verify the output +# in any case. What we really check is that rsyslogd does not +# segfault or otherwise abort. +# rgerhards, 2010-04-01 +$IncludeConfig diag-common.conf + +# The random data will generate TCP framing error messages. We will +# not clutter the test output with them. So we disable error messages +# to stderr. +$ErrorMessagesToStderr off + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%rawmsg%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +*.* /dev/null diff --git a/tests/testsuites/rcvr_fail_restore_rcvr.conf b/tests/testsuites/rcvr_fail_restore_rcvr.conf new file mode 100644 index 00000000..38ebad29 --- /dev/null +++ b/tests/testsuites/rcvr_fail_restore_rcvr.conf @@ -0,0 +1,8 @@ +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# then SENDER sends to this port (not tcpflood!) +$InputTCPServerRun 13515 + +$template outfmt,"%msg:F,58:2%\n" +:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/rcvr_fail_restore_sender.conf b/tests/testsuites/rcvr_fail_restore_sender.conf new file mode 100644 index 00000000..6b11aa4a --- /dev/null +++ b/tests/testsuites/rcvr_fail_restore_sender.conf @@ -0,0 +1,15 @@ +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +$WorkDirectory test-spool +$MainMsgQueueMaxFileSize 1g +$MainMsgQueueFileName mainq + +# we use the shortest resume interval a) to let the test not run too long +# and b) make sure some retries happen before the reconnect +$ActionResumeInterval 1 +$ActionResumeRetryCount -1 +*.* @@127.0.0.1:13515 diff --git a/tests/testsuites/reallife.parse1 b/tests/testsuites/reallife.parse1 new file mode 100644 index 00000000..a83d2dca --- /dev/null +++ b/tests/testsuites/reallife.parse1 @@ -0,0 +1,12 @@ +# New tests should be added to this file if there is no specific +# reason for not doing that. Initially, we could only handle one test +# case per file, but this restriction has been removed some time ago. +# So it is less troublesome (and easier to overlook) to have all related +# tests in a single file. +# This file contains a lot of real-life samples (of course mangled so +# that they can not be traced back to the original submitter). Note +# that IP addr 192.0.2.1 is specifically set aside for testing and +# documentation by IANA. +# rgerhards, 2009-10-19 +<29>Oct 16 20:47:24 example-p exam-pl[12345]: connect host= /192.0.2.1 +29,daemon,notice,Oct 16 20:47:24,example-p,exam-pl,exam-pl[12345]:, connect host= /192.0.2.1 diff --git a/tests/testsuites/reallife.parse2 b/tests/testsuites/reallife.parse2 new file mode 100644 index 00000000..c42f2526 --- /dev/null +++ b/tests/testsuites/reallife.parse2 @@ -0,0 +1,12 @@ +# New tests should be added to this file if there is no specific +# reason for not doing that. Initially, we could only handle one test +# case per file, but this restriction has been removed some time ago. +# So it is less troublesome (and easier to overlook) to have all related +# tests in a single file. +# This file contains a lot of real-life samples (of course mangled so +# that they can not be traced back to the original submitter). Note +# that IP addr 192.0.2.1 is specifically set aside for testing and +# documentation by IANA. +# rgerhards, 2009-10-19 +<175>Oct 16 23:47:31 #001 MSWinEventLog 0#011Security#01119023582#011Fri Oct 16 16:30:44 2009#011592#011Security#011rgabcde#011User#011Success Audit#011XSXSXSN01#011Detailed Tracking#011#0112572#01119013885 +175,local5,debug,Oct 16 23:47:31,#001,#001, MSWinEventLog 0#011Security#01119023582#011Fri Oct 16 16:30:44 2009#011592#011Security#011rgabcde#011User#011Success Audit#011XSXSXSN01#011Detailed Tracking#011#0112572#01119013885 diff --git a/tests/testsuites/reallife.parse3 b/tests/testsuites/reallife.parse3 new file mode 100644 index 00000000..dad3f56e --- /dev/null +++ b/tests/testsuites/reallife.parse3 @@ -0,0 +1,15 @@ +# New tests should be added to this file if there is no specific +# reason for not doing that. Initially, we could only handle one test +# case per file, but this restriction has been removed some time ago. +# So it is less troublesome (and easier to overlook) to have all related +# tests in a single file. +# This file contains a lot of real-life samples (of course mangled so +# that they can not be traced back to the original submitter). Note +# that IP addr 192.0.2.1 is specifically set aside for testing and +# documentation by IANA. +# rgerhards, 2009-10-19 +<175>Oct 16 2009 23:47:31 hostname tag This is a message +"2009-10-16T23:47:31", "hostname", "tag", "", "7", " This is a message" +# +<175>Oct 16 2009 23:47:31 hostname tag[1234] This is a message +"2009-10-16T23:47:31", "hostname", "tag", "1234", "7", " This is a message" diff --git a/tests/testsuites/rfc3164.parse1 b/tests/testsuites/rfc3164.parse1 new file mode 100644 index 00000000..e7a5fa18 --- /dev/null +++ b/tests/testsuites/rfc3164.parse1 @@ -0,0 +1,4 @@ +<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8 +34,auth,crit,Oct 11 22:14:15,mymachine,su,su:, 'su root' failed for lonvick on /dev/pts/8 +#Example from RFC3164, section 5.4 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/rfc5424-1.parse1 b/tests/testsuites/rfc5424-1.parse1 new file mode 100644 index 00000000..8a66d7ba --- /dev/null +++ b/tests/testsuites/rfc5424-1.parse1 @@ -0,0 +1,3 @@ +#Example from RFC5424, section 6.5 / sample 1 +<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8 +34,auth,crit,Oct 11 22:14:15,mymachine.example.com,,su,BOM'su root' failed for lonvick on /dev/pts/8 diff --git a/tests/testsuites/rfc5424-2.parse1 b/tests/testsuites/rfc5424-2.parse1 new file mode 100644 index 00000000..f5449e68 --- /dev/null +++ b/tests/testsuites/rfc5424-2.parse1 @@ -0,0 +1,4 @@ +<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts. +165,local4,notice,Aug 24 05:14:15,192.0.2.1,,myproc[8710],%% It's time to make the do-nuts. +#Example from RFC5424, section 6.5 / sample 2 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/rfc5424-3.parse1 b/tests/testsuites/rfc5424-3.parse1 new file mode 100644 index 00000000..6ad4073d --- /dev/null +++ b/tests/testsuites/rfc5424-3.parse1 @@ -0,0 +1,4 @@ +<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource= "Application" eventID="1011"][examplePriority@32473 class="high"] +165,local4,notice,Oct 11 22:14:15,mymachine.example.com,,evntslog, +#Example from RFC5424, section 6.5 / sample 4 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/rfc5424-4.parse1 b/tests/testsuites/rfc5424-4.parse1 new file mode 100644 index 00000000..ecf27e14 --- /dev/null +++ b/tests/testsuites/rfc5424-4.parse1 @@ -0,0 +1,4 @@ +<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource= "Application" eventID="1011"] BOMAn application event log entry... +165,local4,notice,Oct 11 22:14:15,mymachine.example.com,,evntslog,BOMAn application event log entry... +#Example from RFC5424, section 6.5 / sample 3 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/rfctag.conf b/tests/testsuites/rfctag.conf new file mode 100644 index 00000000..8619e89e --- /dev/null +++ b/tests/testsuites/rfctag.conf @@ -0,0 +1,9 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +# Note: the plus signs are necessary to detect truncated logs! +$template fmt,"+%syslogtag:1:32%+\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/rscript_contains.conf b/tests/testsuites/rscript_contains.conf new file mode 100644 index 00000000..8bead68a --- /dev/null +++ b/tests/testsuites/rscript_contains.conf @@ -0,0 +1,4 @@ +$IncludeConfig diag-common.conf + +$template outfmt,"%msg:F,58:2%\n" +if $msg contains 'msgnum' then ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/rscript_field.conf b/tests/testsuites/rscript_field.conf new file mode 100644 index 00000000..d7eb9066 --- /dev/null +++ b/tests/testsuites/rscript_field.conf @@ -0,0 +1,11 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="list") { + property(name="$!usr!msgnum") + constant(value="\n") +} + +if $msg contains 'msgnum' then { + set $!usr!msgnum = field($msg, 58, 2); + action(type="omfile" file="./rsyslog.out.log" template="outfmt") +} diff --git a/tests/testsuites/rscript_optimizer1.conf b/tests/testsuites/rscript_optimizer1.conf new file mode 100644 index 00000000..7720af7a --- /dev/null +++ b/tests/testsuites/rscript_optimizer1.conf @@ -0,0 +1,12 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="list") { + property(name="msg" field.delimiter="58" field.number="2") + constant(value="\n") +} + +/* tcpflood uses local4.=debug */ +if prifilt("syslog.*") then + stop # it actually doesn't matter what we do here +else + action(type="omfile" file="./rsyslog.out.log" template="outfmt") diff --git a/tests/testsuites/rscript_prifilt.conf b/tests/testsuites/rscript_prifilt.conf new file mode 100644 index 00000000..8cb13a0f --- /dev/null +++ b/tests/testsuites/rscript_prifilt.conf @@ -0,0 +1,10 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="list") { + property(name="msg" field.delimiter="58" field.number="2") + constant(value="\n") +} + +/* tcpflood uses local4.=debug, we use a bit more generic filter */ +if prifilt("local4.*") then + action(type="omfile" file="./rsyslog.out.log" template="outfmt") diff --git a/tests/testsuites/rscript_ruleset_call.conf b/tests/testsuites/rscript_ruleset_call.conf new file mode 100644 index 00000000..96eab293 --- /dev/null +++ b/tests/testsuites/rscript_ruleset_call.conf @@ -0,0 +1,22 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="list") { + property(name="msg" field.delimiter="58" field.number="2") + constant(value="\n") +} + + +# we deliberately include continue/stop to make sure we have more than +# one statement. This catches grammar erorrs +ruleset(name="rs2") { + continue + action(type="omfile" file="./rsyslog.out.log" template="outfmt") + stop +} + +# this time we make sure a single statement is properly supported +ruleset(name="rs1") { + call rs2 +} + +if $msg contains 'msgnum' then call rs1 diff --git a/tests/testsuites/rscript_stop.conf b/tests/testsuites/rscript_stop.conf new file mode 100644 index 00000000..ab9569e5 --- /dev/null +++ b/tests/testsuites/rscript_stop.conf @@ -0,0 +1,13 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="list") { + property(name="$!usr!msgnum") + constant(value="\n") +} + +if $msg contains 'msgnum' then { + set $!usr!msgnum = field($msg, 58, 2); + if cnum($!usr!msgnum) >= 5000 then + stop + action(type="omfile" file="./rsyslog.out.log" template="outfmt") +} diff --git a/tests/testsuites/rscript_stop2.conf b/tests/testsuites/rscript_stop2.conf new file mode 100644 index 00000000..9ac9143e --- /dev/null +++ b/tests/testsuites/rscript_stop2.conf @@ -0,0 +1,18 @@ +$IncludeConfig diag-common.conf + +template(name="outfmt" type="list") { + property(name="$!usr!msgnum") + constant(value="\n") +} + +if not ($msg contains 'msgnum') then + stop + +set $!usr!msgnum = field($msg, 58, 2); +if cnum($!usr!msgnum) >= 5000 then + stop +/* We could use yet another method, but we like to have the action statement + * without a filter in rsyslog.conf top level hierarchy - so this test, as + * a side-effect, also tests this ability. + */ +action(type="omfile" file="./rsyslog.out.log" template="outfmt") diff --git a/tests/testsuites/rsf_getenv.conf b/tests/testsuites/rsf_getenv.conf new file mode 100644 index 00000000..2f2eb58c --- /dev/null +++ b/tests/testsuites/rsf_getenv.conf @@ -0,0 +1,17 @@ +# Test for RainerScript getenv() function (see .sh file for details) +# Note envvar MSGNUM must be set to "msgnum:" +# rgerhards, 2009-11-03 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +# set spool locations and switch queue to disk-only mode +$WorkDirectory test-spool +$MainMsgQueueFilename mainq +$MainMsgQueueType disk + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +if $msg contains getenv('MSGNUM') then ?dynfile;outfmt diff --git a/tests/testsuites/rulesetmultiqueue.conf b/tests/testsuites/rulesetmultiqueue.conf new file mode 100644 index 00000000..c8a82dd6 --- /dev/null +++ b/tests/testsuites/rulesetmultiqueue.conf @@ -0,0 +1,34 @@ +# Test for multiple ruleset queues (see .sh file for details) +# rgerhards, 2009-10-30 +$IncludeConfig diag-common.conf +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 + +# general definition +$template outfmt,"%msg:F,58:2%\n" + +# create the individual rulesets +$ruleset file1 +$RulesetCreateMainQueue on +$template dynfile1,"rsyslog.out1.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile1;outfmt + +$ruleset file2 +$RulesetCreateMainQueue on +$template dynfile2,"rsyslog.out2.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile2;outfmt + +$ruleset file3 +$RulesetCreateMainQueue on +$template dynfile3,"rsyslog.out3.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile3;outfmt + +# start listeners and bind them to rulesets +$InputTCPServerBindRuleset file1 +$InputTCPServerRun 13514 + +$InputTCPServerBindRuleset file2 +$InputTCPServerRun 13515 + +$InputTCPServerBindRuleset file3 +$InputTCPServerRun 13516 diff --git a/tests/testsuites/samples.parse-3164-buggyday b/tests/testsuites/samples.parse-3164-buggyday new file mode 100644 index 00000000..e21df980 --- /dev/null +++ b/tests/testsuites/samples.parse-3164-buggyday @@ -0,0 +1,6 @@ +# in 3164-buggyday mode, we need to have a leading zero in front of the day +<38> Mar 7 19:06:53 example tag: testmessage (only date actually tested) +38,auth,info,Mar 07 19:06:53,example,tag,tag:, testmessage (only date actually tested) +# and now one with a complete date: +<38> Mar 17 19:06:53 example tag: testmessage (only date actually tested) +38,auth,info,Mar 17 19:06:53,example,tag,tag:, testmessage (only date actually tested) diff --git a/tests/testsuites/samples.parse-nodate b/tests/testsuites/samples.parse-nodate new file mode 100644 index 00000000..5432bcac --- /dev/null +++ b/tests/testsuites/samples.parse-nodate @@ -0,0 +1,6 @@ +<27>xapi: [error|xen3|15|Guest liveness monitor D:bca30ab3f1c1|master_connection] Connection to master died. I will continue to retry indefinitely (supressing future logging of this message) +27,daemon,err,localhost.localdomain,xapi,xapi:, [error|xen3|15|Guest liveness monitor D:bca30ab3f1c1|master_connection] Connection to master died. I will continue to retry indefinitely (supressing future logging of this message) +# a message with just text (as permitted by rfc 3164) +# it is questionable if the current sample result is really correct as of 3164! +This is a message! +13,user,notice,This,is,is, a message! diff --git a/tests/testsuites/samples.parse_invld_regex b/tests/testsuites/samples.parse_invld_regex new file mode 100644 index 00000000..0d0e4ce3 --- /dev/null +++ b/tests/testsuites/samples.parse_invld_regex @@ -0,0 +1,16 @@ +# New tests should be added to this file if there is no specific +# reason for not doing that. Initially, we could only handle one test +# case per file, but this restriction has been removed some time ago. +# So it is less troublesome (and easier to overlook) to have all related +# tests in a single file. +# the actual message is not important. There is an error inside the conf +# file, and all messages will trigger the same problem. +# NOTE: it is correct that the "BAD REGULAR EXPRESSION" error message is +# *NOT* run through the rest of the propety replace, in specific through +# the CSV escaper. We do not do this because it could potentially lead +# to an obfuscated error message, and thus making problems hard to find. As +# this is a real error case, there is no problem in not obeying to the +# configured format. +# rgerhards, 2010-02-08 +<175>Feb 08 2008 23:47:31 hostname tag This is a message +"2008-02-08T23:47:31", "hostname", "tag", **NO MATCH** **BAD REGULAR EXPRESSION**, "7", " This is a message" diff --git a/tests/testsuites/samples.snare_ccoff_udp b/tests/testsuites/samples.snare_ccoff_udp new file mode 100644 index 00000000..1ae7e8b4 --- /dev/null +++ b/tests/testsuites/samples.snare_ccoff_udp @@ -0,0 +1,14 @@ +# see comments in snare_ccoff_udp.conf +# note that some of these samples look pretty wild, but they are +# *real* cases (just mangled to anonymize them...) +# Sample 1 - note the absence of PRI! +windowsserver MSWinEventLog 1 Security 1167 Fri Mar 19 15:33:30 2010 540 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Successful Network Logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {79b6eb79-7bcc-8a2e-7dad-953c51dc00fd} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 10.11.11.3 Source Port: 3306 733\n +13,user,notice,localhost.localdomain,windowsserver,windowsserver MSWinEventLog 1 Security 1167 Fri, Mar 19 15:33:30 2010 540 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Successful Network Logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {79b6eb79-7bcc-8a2e-7dad-953c51dc00fd} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 10.11.11.3 Source Port: 3306 733 +# Sample 2 +# the samples below need to be disabled for the "workaround patch" for the message +# parser to work. They need to be re-enabled once a final solution has been crafted +#windowsserver MSWinEventLog 1 Security 1166 Fri Mar 19 15:33:30 2010 576 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Special privileges assigned to new logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Privileges: SeSecurityPrivilege SeBackupPrivilege SeRestorePrivilege SeTakeOwnershipPrivilege SeDebugPrivilege SeSystemEnvironmentPrivilege SeLoadDriverPrivilege SeImpersonatePrivilege SeEnableDelegationPrivilege 732\n +#13,user,notice,localhost,windowsserver,windowsserver MSWinEventLog 1 Security 1166 Fri, Mar 19 15:33:30 2010 576 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Special privileges assigned to new logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Privileges: SeSecurityPrivilege SeBackupPrivilege SeRestorePrivilege SeTakeOwnershipPrivilege SeDebugPrivilege SeSystemEnvironmentPrivilege SeLoadDriverPrivilege SeImpersonatePrivilege SeEnableDelegationPrivilege 732 +# Sample 3 +#windowsserver MSWinEventLog 1 Security 1165 Fri Mar 19 15:33:30 2010 538 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff User Logoff: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF8830B) Logon Type: 3 731\n +#13,user,notice,localhost,windowsserver,windowsserver MSWinEventLog 1 Security 1165 Fri, Mar 19 15:33:30 2010 538 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff User Logoff: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF8830B) Logon Type: 3 731 diff --git a/tests/testsuites/samples.snare_ccoff_udp2 b/tests/testsuites/samples.snare_ccoff_udp2 new file mode 100644 index 00000000..da3a2b14 --- /dev/null +++ b/tests/testsuites/samples.snare_ccoff_udp2 @@ -0,0 +1,26 @@ +# see comments in snare_ccoff_udp.conf +# note that some of these samples look pretty wild, but they are +# *real* cases (just mangled to anonymize them...) +# +# NOTE +# The current responses are probably not correct (handling of messages without PRI). +# However, we keep them inside the test to be consistent. We should look at how +# PRI-less messages are handled and once we have fixed that, the test cases may need +# to be adapted. We do NOT try to preserve misbehaviour on such seriously malformed +# messages. +# +# this is a very simple test, though not snare-based +test +insert into windows (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('', 1, 'test',5, '20100321185328', '20100321185328', 1, '') +# and yet another one we have seen in practice +UX=Abcd-efg-hij-klmno; XXXXX=1111111111, Z123=192.12.231.245:11111, S1234=123456789, XXXXXX=111111111 +insert into windows (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values (' XXXXX=1111111111, Z123=192.12.231.245:11111, S1234=123456789, XXXXXX=111111111', 1, 'localhost.localdomain',5, '20100321185328', '20100321185328', 1, 'UX=Abcd-efg-hij-klmno;') +# Sample 1 - note the absence of PRI! +windowsserver MSWinEventLog 1 Security 1167 Fri Mar 19 15:33:30 2010 540 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Successful Network Logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {79b6eb79-7bcc-8a2e-7dad-953c51dc00fd} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 10.11.11.3 Source Port: 3306 733\n +insert into windows (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values (' Mar 19 15:33:30 2010 540 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Successful Network Logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {79b6eb79-7bcc-8a2e-7dad-953c51dc00fd} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 10.11.11.3 Source Port: 3306 733', 1, 'localhost.localdomain',5, '20100321185328', '20100321185328', 1, 'windowsserver MSWinEventLog 1 Security 1167 Fri') +# Sample 2 +windowsserver MSWinEventLog 1 Security 1166 Fri Mar 19 15:33:30 2010 576 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Special privileges assigned to new logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Privileges: SeSecurityPrivilege SeBackupPrivilege SeRestorePrivilege SeTakeOwnershipPrivilege SeDebugPrivilege SeSystemEnvironmentPrivilege SeLoadDriverPrivilege SeImpersonatePrivilege SeEnableDelegationPrivilege 732\n +insert into windows (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values (' Mar 19 15:33:30 2010 576 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff Special privileges assigned to new logon: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF88396) Privileges: SeSecurityPrivilege SeBackupPrivilege SeRestorePrivilege SeTakeOwnershipPrivilege SeDebugPrivilege SeSystemEnvironmentPrivilege SeLoadDriverPrivilege SeImpersonatePrivilege SeEnableDelegationPrivilege 732', 1, 'localhost.localdomain',5, '20100321185328', '20100321185328', 1, 'windowsserver MSWinEventLog 1 Security 1166 Fri') +# Sample 3 +windowsserver MSWinEventLog 1 Security 1165 Fri Mar 19 15:33:30 2010 538 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff User Logoff: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF8830B) Logon Type: 3 731\n +insert into windows (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values (' Mar 19 15:33:30 2010 538 Security SYSTEM User Success Audit WINDOWSSERVER Logon/Logoff User Logoff: User Name: WINDOWSSERVER$ Domain: DOMX Logon ID: (0x0,0xF8830B) Logon Type: 3 731', 1, 'localhost.localdomain',5, '20100321185328', '20100321185328', 1, 'windowsserver MSWinEventLog 1 Security 1165 Fri') diff --git a/tests/testsuites/snare.parse1 b/tests/testsuites/snare.parse1 new file mode 100644 index 00000000..550b0703 --- /dev/null +++ b/tests/testsuites/snare.parse1 @@ -0,0 +1,83 @@ +# some parse test build around data in snare-format +<141>Mar 10 09:30:20 zuse.xysystems.local MSWinEventLog\0111\011Security\011563\011Wed Mar 10 09:30:15 2010\011538\011Security\011XYWS011$\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: XYWS011$ Domain: XYZSYSTEMS Logon ID: (0x0,0x5984789C) Logon Type: 3 \011552 +141,local1,notice,Mar 10 09:30:20,zuse.xysystems.local,MSWinEventLog#0111#011Security#011563#011Wed,MSWinEventLog#0111#011Security#011563#011Wed, Mar 10 09:30:15 2010#011538#011Security#011XYWS011$#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: XYWS011$ Domain: XYZSYSTEMS Logon ID: (0x0,0x5984789C) Logon Type: 3 #011552 +# +# NEXT MESSAGE +# +Mar 10 09:30:20 zuse.xysystems.local MSWinEventLog\0111\011Security\011564\011Wed Mar 10 09:30:19 2010\011540\011Security\011BACKUP1$\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: BACKUP1$ Domain: XYZSYSTEMS Logon ID: (0x0,0x59848DB4) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {f6f65903-1932-d229-4b75-64816121d569} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.31 Source Port: 0 \011553 +13,user,notice,Mar 10 09:30:20,zuse.xysystems.local,MSWinEventLog#0111#011Security#011564#011Wed,MSWinEventLog#0111#011Security#011564#011Wed, Mar 10 09:30:19 2010#011540#011Security#011BACKUP1$#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: BACKUP1$ Domain: XYZSYSTEMS Logon ID: (0x0,0x59848DB4) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {f6f65903-1932-d229-4b75-64816121d569} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.31 Source Port: 0 #011553 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011566\011Wed Mar 10 09:30:21 2010\011540\011Security\011aadminps\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: aadminps Domain: XYSYSTEMS Logon ID: (0x0,0x5984973C) Logon Type: 3 Logon Process: Authz Authentication Package: Kerberos Workstation Name: ZUSE Logon GUID: - Caller User Name: ZUSE$ Caller Domain: XYSYSTEMS Caller Logon ID: (0x0,0x3E7) Caller Process ID: 1004 Transited Services: - Source Network Address: - Source Port: - \011555 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011566#011Wed,MSWinEventLog#0111#011Security#011566#011Wed, Mar 10 09:30:21 2010#011540#011Security#011aadminps#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: aadminps Domain: XYSYSTEMS Logon ID: (0x0,0x5984973C) Logon Type: 3 Logon Process: Authz Authentication Package: Kerberos Workstation Name: ZUSE Logon GUID: - Caller User Name: ZUSE$ Caller Domain: XYSYSTEMS Caller Logon ID: (0x0,0x3E7) Caller Process ID: 1004 Transited Services: - Source Network Address: - Source Port: - #011555 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011567\011Wed Mar 10 09:30:21 2010\011538\011Security\011aadminps\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: aadminps Domain: XYSYSTEMS Logon ID: (0x0,0x5984973C) Logon Type: 3 \011556 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011567#011Wed,MSWinEventLog#0111#011Security#011567#011Wed, Mar 10 09:30:21 2010#011538#011Security#011aadminps#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: aadminps Domain: XYSYSTEMS Logon ID: (0x0,0x5984973C) Logon Type: 3 #011556 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011568\011Wed Mar 10 09:30:25 2010\011540\011Security\011ANONYMOUS LOGON\011Well Known Group\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: Domain: Logon ID: (0x0,0x5984AB6F) Logon Type: 3 Logon Process: NtLmSsp Authentication Package: NTLM Workstation Name: XYWS083 Logon GUID: - Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.3.91 Source Port: 0 \011557 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011568#011Wed,MSWinEventLog#0111#011Security#011568#011Wed, Mar 10 09:30:25 2010#011540#011Security#011ANONYMOUS LOGON#011Well Known Group#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: Domain: Logon ID: (0x0,0x5984AB6F) Logon Type: 3 Logon Process: NtLmSsp Authentication Package: NTLM Workstation Name: XYWS083 Logon GUID: - Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.3.91 Source Port: 0 #011557 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011569\011Wed Mar 10 09:30:25 2010\011540\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984ACA7) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2318 \011558 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011569#011Wed,MSWinEventLog#0111#011Security#011569#011Wed, Mar 10 09:30:25 2010#011540#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984ACA7) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2318 #011558 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011570\011Wed Mar 10 09:30:25 2010\011538\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984ACA7) Logon Type: 3 \011559 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011570#011Wed,MSWinEventLog#0111#011Security#011570#011Wed, Mar 10 09:30:25 2010#011538#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984ACA7) Logon Type: 3 #011559 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011571\011Wed Mar 10 09:30:25 2010\011540\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AD7C) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2319 \011560\ +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011571#011Wed,MSWinEventLog#0111#011Security#011571#011Wed, Mar 10 09:30:25 2010#011540#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AD7C) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2319 #011560 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011572\011Wed Mar 10 09:30:25 2010\011538\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AD7C) Logon Type: 3 \011561 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011572#011Wed,MSWinEventLog#0111#011Security#011572#011Wed, Mar 10 09:30:25 2010#011538#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AD7C) Logon Type: 3 #011561 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011573\011Wed Mar 10 09:30:25 2010\011680\011Security\011ettore.trezzani\011User\011Success Audit\011ZUSE\011Account Logon\011\011Logon attempt by: MICROSOFT_AUTHENTICATION_PACKAGE_V1_0 Logon account: ettore.trezzani Source Workstation: XYWS083 Error Code: 0x0 \011562 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011573#011Wed,MSWinEventLog#0111#011Security#011573#011Wed, Mar 10 09:30:25 2010#011680#011Security#011ettore.trezzani#011User#011Success Audit#011ZUSE#011Account Logon#011#011Logon attempt by: MICROSOFT_AUTHENTICATION_PACKAGE_V1_0 Logon account: ettore.trezzani Source Workstation: XYWS083 Error Code: 0x0 #011562 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011574\011Wed Mar 10 09:30:25 2010\011540\011Security\011ettore.trezzani\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: ettore.trezzani Domain: XYSYSTEMS Logon ID: (0x0,0x5984ADD5) Logon Type: 3 Logon Process: NtLmSsp Authentication Package: NTLM Workstation Name: XYWS083 Logon GUID: - Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.3.91 Source Port: 0 \011563 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011574#011Wed,MSWinEventLog#0111#011Security#011574#011Wed, Mar 10 09:30:25 2010#011540#011Security#011ettore.trezzani#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: ettore.trezzani Domain: XYSYSTEMS Logon ID: (0x0,0x5984ADD5) Logon Type: 3 Logon Process: NtLmSsp Authentication Package: NTLM Workstation Name: XYWS083 Logon GUID: - Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.3.91 Source Port: 0 #011563 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011575\011Wed Mar 10 09:30:25 2010\011540\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AE49) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2320 \011564 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011575#011Wed,MSWinEventLog#0111#011Security#011575#011Wed, Mar 10 09:30:25 2010#011540#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AE49) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2320 #011564 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011576\011Wed Mar 10 09:30:25 2010\011538\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AE49) Logon Type: 3 \011565 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011576#011Wed,MSWinEventLog#0111#011Security#011576#011Wed, Mar 10 09:30:25 2010#011538#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AE49) Logon Type: 3 #011565 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011577\011Wed Mar 10 09:30:25 2010\011540\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AF00) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2321 \011566 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011577#011Wed,MSWinEventLog#0111#011Security#011577#011Wed, Mar 10 09:30:25 2010#011540#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AF00) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {20014d9a-ce6c-6834-d1ed-607c08f0b6a7} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.0.15 Source Port: 2321 #011566 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011578\011Wed Mar 10 09:30:25 2010\011538\011Security\011SYSTEM\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AF00) Logon Type: 3 \011567 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011578#011Wed,MSWinEventLog#0111#011Security#011578#011Wed, Mar 10 09:30:25 2010#011538#011Security#011SYSTEM#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: ZUSE$ Domain: XYSYSTEMS Logon ID: (0x0,0x5984AF00) Logon Type: 3 #011567 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:25 zuse.xysystems.local MSWinEventLog\0111\011Security\011579\011Wed Mar 10 09:30:25 2010\011538\011Security\011ANONYMOUS LOGON\011Well Known Group\011Success Audit\011ZUSE\011Logon/Logoff\011\011User Logoff: User Name: ANONYMOUS LOGON Domain: NT AUTHORITY Logon ID: (0x0,0x5984AB6F) Logon Type: 3 \011568 +141,local1,notice,Mar 10 09:30:25,zuse.xysystems.local,MSWinEventLog#0111#011Security#011579#011Wed,MSWinEventLog#0111#011Security#011579#011Wed, Mar 10 09:30:25 2010#011538#011Security#011ANONYMOUS LOGON#011Well Known Group#011Success Audit#011ZUSE#011Logon/Logoff#011#011User Logoff: User Name: ANONYMOUS LOGON Domain: NT AUTHORITY Logon ID: (0x0,0x5984AB6F) Logon Type: 3 #011568 +# +# NEXT MESSAGE +# +<141>Mar 10 09:30:30 zuse.xysystems.local MSWinEventLog\0111\011Security\011580\011Wed Mar 10 09:30:29 2010\011540\011Security\011XYWSBADGE$\011User\011Success Audit\011ZUSE\011Logon/Logoff\011\011Successful Network Logon: User Name: XYWSBADGE$ Domain: XYSYSTEMS Logon ID: (0x0,0x59852D73) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {4bc3c075-5a77-4648-5822-bfdf88b4c211} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.3.18 Source Port: 0 \011569 +141,local1,notice,Mar 10 09:30:30,zuse.xysystems.local,MSWinEventLog#0111#011Security#011580#011Wed,MSWinEventLog#0111#011Security#011580#011Wed, Mar 10 09:30:29 2010#011540#011Security#011XYWSBADGE$#011User#011Success Audit#011ZUSE#011Logon/Logoff#011#011Successful Network Logon: User Name: XYWSBADGE$ Domain: XYSYSTEMS Logon ID: (0x0,0x59852D73) Logon Type: 3 Logon Process: Kerberos Authentication Package: Kerberos Workstation Name: Logon GUID: {4bc3c075-5a77-4648-5822-bfdf88b4c211} Caller User Name: - Caller Domain: - Caller Logon ID: - Caller Process ID: - Transited Services: - Source Network Address: 172.16.3.18 Source Port: 0 #011569 diff --git a/tests/testsuites/snare_ccoff_udp.conf b/tests/testsuites/snare_ccoff_udp.conf new file mode 100644 index 00000000..6abbedf4 --- /dev/null +++ b/tests/testsuites/snare_ccoff_udp.conf @@ -0,0 +1,21 @@ +# This test some real-world snare cases. I don't like snare (no wonder +# as I have written EventReporter, the ultimate Windows-to-Syslog tool), +# but besides that snare generates severely malformed messages that +# really stress-test the rsyslog engine. They deserve to be beaten by someone ;) +# This test needs to be run over UDP only, as snare puts LF INTO some of the messages, +# which makes it impossible to try these out via traditional syslog/tcp +# added 2010-03-21 rgerhards +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# snare usses HT as field delimiter, so many users have turned off +# control character escaping to make any sense at all from these messages... +$EscapeControlCharactersOnReceive off + +# use a special format that we can easily check. We do NOT include a timestamp because +# the malformed snare messages usually do not contain one (and we can not check against +# the system time in our test cases). +$template fmt,"%PRI%,%syslogfacility-text%,%syslogseverity-text%,%hostname%,%programname%,%syslogtag%,%msg%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/snare_ccoff_udp2.conf b/tests/testsuites/snare_ccoff_udp2.conf new file mode 100644 index 00000000..9115c14f --- /dev/null +++ b/tests/testsuites/snare_ccoff_udp2.conf @@ -0,0 +1,17 @@ +# Similar to snare_ccoff_udp_2, but with a different template. This template +# has triggered problems in the past, thus a test is granted. +# added 2010-03-21 rgerhards +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# snare usses HT as field delimiter, so many users have turned off +# control character escaping to make any sense at all from these messages... +$EscapeControlCharactersOnReceive off + +# we need to use a fixed timestamp, as otherwise we can not compare :( +# This could be improved in later versions of the testing tools, but requires +# modification to the rsyslog core... +$template fmt,"insert into windows (Message, Facility,FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg:::space-cc%', %syslogfacility%, '%HOSTNAME%',%syslogpriority%, '20100321185328', '20100321185328', %iut%, '%syslogtag:::space-cc%')\n",sql +*.* :omstdout:;fmt diff --git a/tests/testsuites/sndrcv_failover_rcvr.conf b/tests/testsuites/sndrcv_failover_rcvr.conf new file mode 100644 index 00000000..6f7ce34b --- /dev/null +++ b/tests/testsuites/sndrcv_failover_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# then SENDER sends to this port (not tcpflood!) +$InputTCPServerRun 13515 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_failover_sender.conf b/tests/testsuites/sndrcv_failover_sender.conf new file mode 100644 index 00000000..b8e7c186 --- /dev/null +++ b/tests/testsuites/sndrcv_failover_sender.conf @@ -0,0 +1,13 @@ +# see tcpsndrcv.sh for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +*.* @@127.0.0.1:13516 # this must be DEAD +$ActionExecOnlyWhenPreviousIsSuspended on +& @@127.0.0.1:13515 +& ./rsyslog.empty +$ActionExecOnlyWhenPreviousIsSuspended off diff --git a/tests/testsuites/sndrcv_gzip_rcvr.conf b/tests/testsuites/sndrcv_gzip_rcvr.conf new file mode 100644 index 00000000..6f7ce34b --- /dev/null +++ b/tests/testsuites/sndrcv_gzip_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# then SENDER sends to this port (not tcpflood!) +$InputTCPServerRun 13515 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_gzip_sender.conf b/tests/testsuites/sndrcv_gzip_sender.conf new file mode 100644 index 00000000..c874c068 --- /dev/null +++ b/tests/testsuites/sndrcv_gzip_sender.conf @@ -0,0 +1,8 @@ +# see tcpsndrcv.sh for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 + +*.* @@127.0.0.1:13515 diff --git a/tests/testsuites/sndrcv_omudpspoof_nonstdpt_rcvr.conf b/tests/testsuites/sndrcv_omudpspoof_nonstdpt_rcvr.conf new file mode 100644 index 00000000..65659f00 --- /dev/null +++ b/tests/testsuites/sndrcv_omudpspoof_nonstdpt_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-12 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imudp/.libs/imudp +# then SENDER sends to this port (not tcpflood!) +$UDPServerRun 2514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_omudpspoof_nonstdpt_sender.conf b/tests/testsuites/sndrcv_omudpspoof_nonstdpt_sender.conf new file mode 100644 index 00000000..29a30145 --- /dev/null +++ b/tests/testsuites/sndrcv_omudpspoof_nonstdpt_sender.conf @@ -0,0 +1,18 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +$ModLoad ../plugins/omudpspoof/.libs/omudpspoof +$template spoofaddr,"127.0.0.1" + +#begin action definition +$ActionOMUDPSpoofSourceNameTemplate spoofaddr +$ActionOMUDPSpoofTargetHost 127.0.0.1 +$ActionOMUDPSpoofTargetPort 2514 +$ActionOMUDPSpoofSourcePortStart 514 +$ActionOMUDPSpoofSourcePortEnd 514 +*.* :omudpspoof: diff --git a/tests/testsuites/sndrcv_omudpspoof_rcvr.conf b/tests/testsuites/sndrcv_omudpspoof_rcvr.conf new file mode 100644 index 00000000..e5401811 --- /dev/null +++ b/tests/testsuites/sndrcv_omudpspoof_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-12 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imudp/.libs/imudp +# then SENDER sends to this port (not tcpflood!) +$UDPServerRun 514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_omudpspoof_sender.conf b/tests/testsuites/sndrcv_omudpspoof_sender.conf new file mode 100644 index 00000000..c0d25935 --- /dev/null +++ b/tests/testsuites/sndrcv_omudpspoof_sender.conf @@ -0,0 +1,17 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +$ModLoad ../plugins/omudpspoof/.libs/omudpspoof +$template spoofaddr,"127.0.0.1" + +#begin action definition +$ActionOMUDPSpoofSourceNameTemplate spoofaddr +$ActionOMUDPSpoofTargetHost 127.0.0.1 +$ActionOMUDPSpoofSourcePortStart 514 +$ActionOMUDPSpoofSourcePortEnd 514 +*.* :omudpspoof: diff --git a/tests/testsuites/sndrcv_rcvr.conf b/tests/testsuites/sndrcv_rcvr.conf new file mode 100644 index 00000000..6f7ce34b --- /dev/null +++ b/tests/testsuites/sndrcv_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# then SENDER sends to this port (not tcpflood!) +$InputTCPServerRun 13515 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_sender.conf b/tests/testsuites/sndrcv_sender.conf new file mode 100644 index 00000000..f3d6ba53 --- /dev/null +++ b/tests/testsuites/sndrcv_sender.conf @@ -0,0 +1,9 @@ +# see tcpsndrcv.sh for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +*.* @@127.0.0.1:13515 diff --git a/tests/testsuites/sndrcv_tls_anon_rcvr.conf b/tests/testsuites/sndrcv_tls_anon_rcvr.conf new file mode 100644 index 00000000..01143b22 --- /dev/null +++ b/tests/testsuites/sndrcv_tls_anon_rcvr.conf @@ -0,0 +1,22 @@ +# see equally-named shell file for details +# this is the config fil for the TLS server +# rgerhards, 2009-11-11 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp + +# certificates +$DefaultNetstreamDriverCAFile testsuites/x.509/ca.pem +$DefaultNetstreamDriverCertFile testsuites/x.509/client-cert.pem +$DefaultNetstreamDriverKeyFile testsuites/x.509/client-key.pem + +$DefaultNetstreamDriver gtls # use gtls netstream driver + +# then SENDER sends to this port (not tcpflood!) +$InputTCPServerStreamDriverMode 1 +$InputTCPServerStreamDriverAuthMode anon +$InputTCPServerRun 13515 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_tls_anon_rebind_rcvr.conf b/tests/testsuites/sndrcv_tls_anon_rebind_rcvr.conf new file mode 100644 index 00000000..01143b22 --- /dev/null +++ b/tests/testsuites/sndrcv_tls_anon_rebind_rcvr.conf @@ -0,0 +1,22 @@ +# see equally-named shell file for details +# this is the config fil for the TLS server +# rgerhards, 2009-11-11 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp + +# certificates +$DefaultNetstreamDriverCAFile testsuites/x.509/ca.pem +$DefaultNetstreamDriverCertFile testsuites/x.509/client-cert.pem +$DefaultNetstreamDriverKeyFile testsuites/x.509/client-key.pem + +$DefaultNetstreamDriver gtls # use gtls netstream driver + +# then SENDER sends to this port (not tcpflood!) +$InputTCPServerStreamDriverMode 1 +$InputTCPServerStreamDriverAuthMode anon +$InputTCPServerRun 13515 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_tls_anon_rebind_sender.conf b/tests/testsuites/sndrcv_tls_anon_rebind_sender.conf new file mode 100644 index 00000000..47633349 --- /dev/null +++ b/tests/testsuites/sndrcv_tls_anon_rebind_sender.conf @@ -0,0 +1,20 @@ +# see tcpsndrcv.sh for details +# this is the TLS client +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +# certificates +$DefaultNetstreamDriverCAFile testsuites/x.509/ca.pem +$DefaultNetstreamDriverCertFile testsuites/x.509/client-cert.pem +$DefaultNetstreamDriverKeyFile testsuites/x.509/client-key.pem + +# Note: no TLS for the listener, this is for tcpflood! +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 + +# set up the action +$DefaultNetstreamDriver gtls # use gtls netstream driver +$ActionSendStreamDriverMode 1 # require TLS for the connection +$ActionSendStreamDriverAuthMode anon +$ActionSendTCPRebindInterval 50 +*.* @@127.0.0.1:13515 diff --git a/tests/testsuites/sndrcv_tls_anon_sender.conf b/tests/testsuites/sndrcv_tls_anon_sender.conf new file mode 100644 index 00000000..4a944455 --- /dev/null +++ b/tests/testsuites/sndrcv_tls_anon_sender.conf @@ -0,0 +1,19 @@ +# see tcpsndrcv.sh for details +# this is the TLS client +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +# certificates +$DefaultNetstreamDriverCAFile testsuites/x.509/ca.pem +$DefaultNetstreamDriverCertFile testsuites/x.509/client-cert.pem +$DefaultNetstreamDriverKeyFile testsuites/x.509/client-key.pem + +# Note: no TLS for the listener, this is for tcpflood! +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 + +# set up the action +$DefaultNetstreamDriver gtls # use gtls netstream driver +$ActionSendStreamDriverMode 1 # require TLS for the connection +$ActionSendStreamDriverAuthMode anon +*.* @@127.0.0.1:13515 diff --git a/tests/testsuites/sndrcv_udp_nonstdpt_rcvr.conf b/tests/testsuites/sndrcv_udp_nonstdpt_rcvr.conf new file mode 100644 index 00000000..65659f00 --- /dev/null +++ b/tests/testsuites/sndrcv_udp_nonstdpt_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-12 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imudp/.libs/imudp +# then SENDER sends to this port (not tcpflood!) +$UDPServerRun 2514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_udp_nonstdpt_sender.conf b/tests/testsuites/sndrcv_udp_nonstdpt_sender.conf new file mode 100644 index 00000000..2975f938 --- /dev/null +++ b/tests/testsuites/sndrcv_udp_nonstdpt_sender.conf @@ -0,0 +1,9 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +*.* @127.0.0.1:2514 diff --git a/tests/testsuites/sndrcv_udp_rcvr.conf b/tests/testsuites/sndrcv_udp_rcvr.conf new file mode 100644 index 00000000..e5401811 --- /dev/null +++ b/tests/testsuites/sndrcv_udp_rcvr.conf @@ -0,0 +1,11 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-12 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imudp/.libs/imudp +# then SENDER sends to this port (not tcpflood!) +$UDPServerRun 514 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/sndrcv_udp_sender.conf b/tests/testsuites/sndrcv_udp_sender.conf new file mode 100644 index 00000000..28e913ef --- /dev/null +++ b/tests/testsuites/sndrcv_udp_sender.conf @@ -0,0 +1,9 @@ +# see equally-named shell file for details +# rgerhards, 2009-11-11 +$IncludeConfig diag-common2.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +# this listener is for message generation by the test framework! +$InputTCPServerRun 13514 + +*.* @127.0.0.1 diff --git a/tests/testsuites/subsecond.conf b/tests/testsuites/subsecond.conf new file mode 100644 index 00000000..58c26cc7 --- /dev/null +++ b/tests/testsuites/subsecond.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +$template fmt,"%timestamp:::date-subseconds%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/tabescape_dflt.conf b/tests/testsuites/tabescape_dflt.conf new file mode 100644 index 00000000..b9d92a37 --- /dev/null +++ b/tests/testsuites/tabescape_dflt.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format that we can easily parse in expect +$template fmt,"%msg%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/tabescape_off.conf b/tests/testsuites/tabescape_off.conf new file mode 100644 index 00000000..c1eca305 --- /dev/null +++ b/tests/testsuites/tabescape_off.conf @@ -0,0 +1,10 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +$EscapeControlCharacterTab off + +# use a special format that we can easily parse in expect +$template fmt,"%msg%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/tcp-msgreduc-vg.conf b/tests/testsuites/tcp-msgreduc-vg.conf new file mode 100644 index 00000000..72420f04 --- /dev/null +++ b/tests/testsuites/tcp-msgreduc-vg.conf @@ -0,0 +1,10 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-05-22 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$InputTCPServerRun 13514 +$RepeatedMsgReduction on + +$template outfmt,"%msg:F,58:2%\n" +*.* ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/tcp_forwarding_tpl.conf b/tests/testsuites/tcp_forwarding_tpl.conf new file mode 100644 index 00000000..686c73ac --- /dev/null +++ b/tests/testsuites/tcp_forwarding_tpl.conf @@ -0,0 +1,7 @@ +$IncludeConfig diag-common.conf +$MainMsgQueueTimeoutShutdown 10000 +template(name="outfmt" type="string" string="%msg:F,58:2%\n") + +if $msg contains "msgnum:" then + action(type="omfwd" template="outfmt" + target="127.0.0.1" port="13514" protocol="tcp") diff --git a/tests/testsuites/threadingmq.conf b/tests/testsuites/threadingmq.conf new file mode 100644 index 00000000..b98f9b5a --- /dev/null +++ b/tests/testsuites/threadingmq.conf @@ -0,0 +1,16 @@ +# Threading test, we run a tcp flood to via an +# engine instructed to use multiple threads +# rgerhards, 2009-06-26 +$IncludeConfig diag-common.conf + +$MainMsgQueueTimeoutShutdown 100000 + +$MainMsgQueueWorkerThreadMinimumMessages 10 +$MainMsgQueueWorkerThreads 5 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +# write quickly to the output file: +$OMFileFlushOnTXEnd off +$OMFileIOBufferSize 256k +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/threadingmqaq.conf b/tests/testsuites/threadingmqaq.conf new file mode 100644 index 00000000..f0d39057 --- /dev/null +++ b/tests/testsuites/threadingmqaq.conf @@ -0,0 +1,20 @@ +# Threading test, we run a tcp flood to via an +# engine instructed to use multiple threads +# rgerhards, 2009-06-26 +$IncludeConfig diag-common.conf + +$MainMsgQueueTimeoutShutdown 10000 + +$MainMsgQueueWorkerThreadMinimumMessages 10 +$MainMsgQueueWorkerThreads 5 + +$template outfmt,"%msg:F,58:2%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +# write quickly to the output file: +$OMFileFlushOnTXEnd off +$OMFileIOBufferSize 256k +# This time, also run the action queue detached +$ActionQueueWorkerThreadMinimumMessages 10 +$ActionQueueWorkerThreads 5 +$ActionQueueType LinkedList +:msg, contains, "msgnum:" ?dynfile;outfmt diff --git a/tests/testsuites/ts3164.conf b/tests/testsuites/ts3164.conf new file mode 100644 index 00000000..7aa6a8ef --- /dev/null +++ b/tests/testsuites/ts3164.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +$template fmt,"%timestamp:::date-rfc3164%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/ts3339.conf b/tests/testsuites/ts3339.conf new file mode 100644 index 00000000..df8f23ac --- /dev/null +++ b/tests/testsuites/ts3339.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +$template fmt,"%timestamp:::date-rfc3339%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/tsmysql.conf b/tests/testsuites/tsmysql.conf new file mode 100644 index 00000000..f97d4b0a --- /dev/null +++ b/tests/testsuites/tsmysql.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +$template fmt,"%timestamp:::date-mysql%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/tspgsql.conf b/tests/testsuites/tspgsql.conf new file mode 100644 index 00000000..eb18c091 --- /dev/null +++ b/tests/testsuites/tspgsql.conf @@ -0,0 +1,8 @@ +$ModLoad ../plugins/omstdout/.libs/omstdout +$IncludeConfig nettest.input.conf # This picks the to be tested input from the test driver! + +$ErrorMessagesToStderr off + +# use a special format +$template fmt,"%timestamp:::date-pgsql%\n" +*.* :omstdout:;fmt diff --git a/tests/testsuites/udp-msgreduc-orgmsg-vg.conf b/tests/testsuites/udp-msgreduc-orgmsg-vg.conf new file mode 100644 index 00000000..5e80e49b --- /dev/null +++ b/tests/testsuites/udp-msgreduc-orgmsg-vg.conf @@ -0,0 +1,11 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-05-22 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imudp/.libs/imudp +$UDPServerRun 13514 +$RepeatedMsgReduction on +$RepeatedMsgContainsOriginalMsg on + +$template outfmt,"%msg:F,58:2%\n" +*.* ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/udp-msgreduc-vg.conf b/tests/testsuites/udp-msgreduc-vg.conf new file mode 100644 index 00000000..150bef2e --- /dev/null +++ b/tests/testsuites/udp-msgreduc-vg.conf @@ -0,0 +1,11 @@ +# Test for queue disk mode (see .sh file for details) +# rgerhards, 2009-05-22 +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imudp/.libs/imudp +$UDPServerRun 13514 +$RepeatedMsgReduction on + +$template outfmt,"%msg:F,58:2%\n" +*.* ./rsyslog.out.log;outfmt +#:msg, contains, "msgnum:" ./rsyslog.out.log;outfmt diff --git a/tests/testsuites/upcase-date.parse1 b/tests/testsuites/upcase-date.parse1 new file mode 100644 index 00000000..2d21222a --- /dev/null +++ b/tests/testsuites/upcase-date.parse1 @@ -0,0 +1,4 @@ +<6>AUG 10 22:18:24 2009 netips-warden2-p [audit] user=[*SMS] src=192.168.11.11 iface=5 access=9 Update State Reset +6,kern,info,Aug 10 22:18:24,2009,,, netips-warden2-p [audit] user=[*SMS] src=192.168.11.11 iface=5 access=9 Update State Reset +#Example from RFC3164, section 5.4 +#Only the first two lines are important, you may place anything behind them! diff --git a/tests/testsuites/uxsock_simple.conf b/tests/testsuites/uxsock_simple.conf new file mode 100644 index 00000000..efffdd90 --- /dev/null +++ b/tests/testsuites/uxsock_simple.conf @@ -0,0 +1,10 @@ +# Test for pipe output action (see .sh file for details) +# rgerhards, 2009-11-05 +$IncludeConfig diag-common.conf + +$MainMsgQueueTimeoutShutdown 10000 + +$ModLoad ../plugins/omuxsock/.libs/omuxsock +$template outfmt,"%msg:F,58:2%\n" +$OMUXSockSocket rsyslog-testbench-dgram-uxsock +:msg, contains, "msgnum:" :omuxsock:;outfmt diff --git a/tests/testsuites/valid.conf b/tests/testsuites/valid.conf new file mode 100644 index 00000000..250f0546 --- /dev/null +++ b/tests/testsuites/valid.conf @@ -0,0 +1,3 @@ +# This is an invalid config file that shall trigger an exit code +# with the config verification run +*.* /tmp/data.log diff --git a/tests/testsuites/weird.parse1 b/tests/testsuites/weird.parse1 new file mode 100644 index 00000000..907198a1 --- /dev/null +++ b/tests/testsuites/weird.parse1 @@ -0,0 +1,37 @@ +# some really weird samples, some of them seen in practice, +# some other deliberately generated. The main point is that they +# should not cause an abort... +<14>Aug 30 23:00:05 X4711 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +14,user,info,Aug 30 23:00:05,X4711,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +# important: the following line has a SP at the end of the line! +<14>Aug 30 23:00:05 X4711 +14,user,info,Aug 30 23:00:05,X4711,,, +# and this one NOT +<14>Aug 30 23:00:05 X4711 +14,user,info,Aug 30 23:00:05,X4711,,, +# there is a SP at the end of the line +<14>Aug 30 23:00:05 +14,user,info,Aug 30 23:00:05,localhost.localdomain,,, +# and here is no SP at the end of the line +<14>Aug 30 23:00:05 +14,user,info,Aug 30 23:00:05,localhost.localdomain,,, +# unfortunately, I can not test missing dates with this test suite, because +# we would have the current date in the response, which we can not check against +# +# and now the same tests with RFC3339 data - this can make a difference +# as a different date parser is involved. +# +<14>2010-08-30T23:00:05Z X4711 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +14,user,info,Aug 30 23:00:05,X4711,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, +# important: the following line has a SP at the end of the line! +<14>2010-08-30T23:00:05Z X4711 +14,user,info,Aug 30 23:00:05,X4711,,, +# and this one NOT +<14>2010-08-30T23:00:05Z X4711 +14,user,info,Aug 30 23:00:05,X4711,,, +# there is a SP at the end of the line +<14>2010-08-30T23:00:05Z +14,user,info,Aug 30 23:00:05,localhost.localdomain,,, +# and here is no SP at the end of the line +<14>2010-08-30T23:00:05Z +14,user,info,Aug 30 23:00:05,localhost.localdomain,,, diff --git a/tests/testsuites/wr_large.conf b/tests/testsuites/wr_large.conf new file mode 100644 index 00000000..b64f132b --- /dev/null +++ b/tests/testsuites/wr_large.conf @@ -0,0 +1,16 @@ +# simple async writing test +# rgerhards, 2010-03-09 +$MaxMessageSize 10k +$IncludeConfig diag-common.conf + +$ModLoad ../plugins/imtcp/.libs/imtcp +$MainMsgQueueTimeoutShutdown 10000 +$InputTCPServerRun 13514 + +$template outfmt,"%msg:F,58:2%,%msg:F,58:3%,%msg:F,58:4%\n" +$template dynfile,"rsyslog.out.log" # trick to use relative path names! +$OMFileFlushOnTXEnd off +$OMFileFlushInterval 2 +$OMFileIOBufferSize 256k +$IncludeConfig rsyslog.action.1.include +local0.* ?dynfile;outfmt diff --git a/tests/testsuites/x.509/ca-key.pem b/tests/testsuites/x.509/ca-key.pem new file mode 100644 index 00000000..1e1a0b26 --- /dev/null +++ b/tests/testsuites/x.509/ca-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDZnIJGJH80j2DPBXdxgmFmBRMoBnpwQb8yhRJcJacaWigRAhp4 +wdo07rR+EpuBJHD/5ImIygUwCj/XWAs4JKm3LqK2ih1gUy/s6Tg2O5t3k11kdjEH +MKUxDOLs441dEwERPQtePEoy2POzViIyy959ZJorkdnwC4LBKdQVLEELlwIDAQAB +AoGAEQWvoRoAw1VF3tvQHJZ01Pyno3ViRX63HJYROhkN6b9MrAvsky6iyYo0nzoI +ZQE7P6EaaxNWdYwPs2IlOoaPqeos1sGVDaK/JFuja/DduoXBdCy9RFWRaugDX/1U +iMtjtu29euvegP0r2RIxaIl9dapF5alNH5MLMyBl7XTB+/kCQQDiwHnW8jS1paSc +/risF6Ie5rKuUfVDG8hqMEiKyczSHwUVYushwCclshjM6E1TBFZqMz/8PbFW51pK +OzFS2s6/AkEA9a4044RL3AWe37LIU4hbz2Y+auRvPh8x4i2cWLzdok8Rc1EHDGLN +eHBoOQ3Q2nQS94cOx6HxpRztzBgiwpTRKQJADX9BgV7nbkyO0N2EppG9j7NRvXiZ +bcYwlsmK99/tNjCsf8pkjpy+d8rzGPdW6vMeJbIpQ910OeUJhdOiKvllRwJBAIw3 +rP/dVd5xZseNpj/mp1+rnxwq3EK8UyAfoAgVYvlr3y3NpRQwn8yJezJ07CqB7QFR +F+JgTyZJaH7/l3cusGECQQCM3HmkADAKxX6RwKe8X0Kj/36rjXEMNoq0ZdXOB7Qz +f5N6og4Da9y/ZO+XMo6P3XR/TYIYrMD8nuoR33X69kb1 +-----END RSA PRIVATE KEY----- diff --git a/tests/testsuites/x.509/ca.pem b/tests/testsuites/x.509/ca.pem new file mode 100644 index 00000000..a733eb86 --- /dev/null +++ b/tests/testsuites/x.509/ca.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyzCCAjagAwIBAgIESFo2XjALBgkqhkiG9w0BAQUwezELMAkGA1UEBhMCVVMx +EDAOBgNVBAoTB1NvbWVPcmcxDzANBgNVBAsTBlNvbWVPVTESMBAGA1UEBxMJU29t +ZXdoZXJlMQswCQYDVQQIEwJDQTEoMCYGA1UEAxMfc29tZU5hbWUgKG5vdCBuZWNl +c3NhcmlseSBETlMhKTAeFw0wODA2MTkxMDM1MTJaFw0xODA2MTcxMDM1MjVaMHsx +CzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdTb21lT3JnMQ8wDQYDVQQLEwZTb21lT1Ux +EjAQBgNVBAcTCVNvbWV3aGVyZTELMAkGA1UECBMCQ0ExKDAmBgNVBAMTH3NvbWVO +YW1lIChub3QgbmVjZXNzYXJpbHkgRE5TISkwgZwwCwYJKoZIhvcNAQEBA4GMADCB +iAKBgNmcgkYkfzSPYM8Fd3GCYWYFEygGenBBvzKFElwlpxpaKBECGnjB2jTutH4S +m4EkcP/kiYjKBTAKP9dYCzgkqbcuoraKHWBTL+zpODY7m3eTXWR2MQcwpTEM4uzj +jV0TARE9C148SjLY87NWIjLL3n1kmiuR2fALgsEp1BUsQQuXAgMBAAGjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wHgYDVR0RBBcwFYETc29tZW9uZUBleGFtcGxlLm5ldDAP +BgNVHQ8BAf8EBQMDBwQAMB0GA1UdDgQWBBT7/paNEKc65bcNe0NIhsj4cpl7iTAL +BgkqhkiG9w0BAQUDgYEAlv9ge8Koways837OLoZIam0s7wQCcwd9rWE05caps7BU +T4bfgab9U/e9mmrf3V/zXmtU6y8hhTXF5AcZv3/EmCVwsPRotgrJ+rHXTv5e2PO7 +/8C3K2Lhc89gF4qf4xZwlZU70RasKgCzZa5ivS2Y8pW6LUu6eqqgVw3pPJbW3TE= +-----END CERTIFICATE----- diff --git a/tests/testsuites/x.509/client-cert.pem b/tests/testsuites/x.509/client-cert.pem new file mode 100644 index 00000000..5bf39f81 --- /dev/null +++ b/tests/testsuites/x.509/client-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICijCCAfWgAwIBAgIESFo7ITALBgkqhkiG9w0BAQUwezELMAkGA1UEBhMCVVMx +EDAOBgNVBAoTB1NvbWVPcmcxDzANBgNVBAsTBlNvbWVPVTESMBAGA1UEBxMJU29t +ZXdoZXJlMQswCQYDVQQIEwJDQTEoMCYGA1UEAxMfc29tZU5hbWUgKG5vdCBuZWNl +c3NhcmlseSBETlMhKTAeFw0wODA2MTkxMDU1MzJaFw0xMTAzMTYxMDU1MzlaMA0x +CzAJBgNVBAYTAlVTMIGcMAsGCSqGSIb3DQEBAQOBjAAwgYgCgYC+f6yCet2WJgmw +tgukOReI+avRHOfr2hLhIQkSzCOiNi0tNWMKmaQWw/D+y1FvLRq0wLDUyJK/36rB +67HKfscoNeClKTS8jhAs1mPjT57iyuoqK6VW/d2JoofklRCgDIZQrNfxHiOO+kN3 +ShLmkGqxkA3YyUty/JmF6PKWYIhQWQIDAQABo4GPMIGMMAwGA1UdEwEB/wQCMAAw +HQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdEQQWMBSCEmNsaWVu +dC5leGFtcGxlLm5ldDAdBgNVHQ4EFgQUrDcwsuOF4RiHn0eboCplJSiUhfcwHwYD +VR0jBBgwFoAU+/6WjRCnOuW3DXtDSIbI+HKZe4kwCwYJKoZIhvcNAQEFA4GBAAAh +niy9ORW2AIb6lk/sa3iYczeYpGzxDM9bLZ1xSoIdoHM/v9gPG/WpAZ4ECHjx+Yk8 +4B/9gvaAmMi0FmcoIBQaEOe2P8tcIuzmum3N2F27F2+J4httiNDLJoseWVnXJUvS +dPyVOrKXdl5vVFpmViI5P+VzzMqbAQ6oNlMXIh6e +-----END CERTIFICATE----- diff --git a/tests/testsuites/x.509/client-key.pem b/tests/testsuites/x.509/client-key.pem new file mode 100644 index 00000000..05641213 --- /dev/null +++ b/tests/testsuites/x.509/client-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQC+f6yCet2WJgmwtgukOReI+avRHOfr2hLhIQkSzCOiNi0tNWMK +maQWw/D+y1FvLRq0wLDUyJK/36rB67HKfscoNeClKTS8jhAs1mPjT57iyuoqK6VW +/d2JoofklRCgDIZQrNfxHiOO+kN3ShLmkGqxkA3YyUty/JmF6PKWYIhQWQIDAQAB +AoGAVxrM+BqTIJlC/Ay5lP1QAB9di3ACserUkCFJY1F5h63rCU1sfIfVKl2s3+x6 +z3GZ0QV8tccCpv5wN1x8vqEqkbOvddM3rzpGkEC5PoyfCzuQBun1wnHK/JKjrfk5 +PvcaP60eTNjHZC7w78gOJJCzgzsEMrndtE+55diPmqGVtXMCQQDTZBy5WK8gZwMO +rRz1BKKyBeMYMfTJoJafGfxp0H8AUbTa0V2eb+el3kuzPCm3FQ6IgaHyGj2WqkAw +M0bfAfdXAkEA5rLna1t+2SCtgSd1DotndA4EsH4skBq9kFeD2/8T6Pf13zmBOq6O +4aNEOhgBE/R9/MI4XoU9MbOlkZvKvDuXzwJADdWSb6rXIza6o34+0+Yuw5nRB+dV +DtD8qoLn2wDzHtE6Fcv35YOLVHac26kHTd0J63MYZyDCgRa5Rq5EaBnX1wJAQYRF +XKPbXmZ9X9SI1dyZQMhKZKUwmqw9caSo+e1zBhKFbSOzo6q3QTVQxv7SL4ybyxCN +WaqVOmw+dR+9b7+s2QJAdNAw3r418rWKFKJJNTSqSqr1sYqiKvrQL6w2dpdpAeY4 +3VDCz/7/F9AEn3R7K3fZLQ7W6M62LSEjxxc1Y3LIpQ== +-----END RSA PRIVATE KEY----- diff --git a/tests/testsuites/x.509/machine-cert.pem b/tests/testsuites/x.509/machine-cert.pem new file mode 100644 index 00000000..fa2fd36e --- /dev/null +++ b/tests/testsuites/x.509/machine-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAligAwIBAgIESFo4GTALBgkqhkiG9w0BAQUwezELMAkGA1UEBhMCVVMx +EDAOBgNVBAoTB1NvbWVPcmcxDzANBgNVBAsTBlNvbWVPVTESMBAGA1UEBxMJU29t +ZXdoZXJlMQswCQYDVQQIEwJDQTEoMCYGA1UEAxMfc29tZU5hbWUgKG5vdCBuZWNl +c3NhcmlseSBETlMhKTAeFw0wODA2MTkxMDQyNTRaFw0xMTAzMTYxMDQyNTdaMG8x +CzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdTb21lT3JnMQ8wDQYDVQQLEwZTb21lT1Ux +EjAQBgNVBAcTCVNvbWV3aGVyZTELMAkGA1UECBMCQ0ExHDAaBgNVBAMTE21hY2hp +bmUuZXhhbXBsZS5uZXQwgZwwCwYJKoZIhvcNAQEBA4GMADCBiAKBgLJOW6lIHv8u +c6Ez7tiir64vI3aRuDmUACPybyWtyWqrLebzYtg+borWHj9y5di54NB5wpQhZQsQ +U2awNqanzUYeLGqbecbuxuLtsKlZ4knax+PwHOBTmIcN1SjbpII27Toe0VwHE5Vd +sygFFyorto6OeNLPrIcTFfwXQ2sVw325AgMBAAGjgZAwgY0wDAYDVR0TAQH/BAIw +ADAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHgYDVR0RBBcwFYITbWFj +aGluZS5leGFtcGxlLm5ldDAdBgNVHQ4EFgQUDOHD29GdMfoDWwev4uDvItkLKKww +HwYDVR0jBBgwFoAU+/6WjRCnOuW3DXtDSIbI+HKZe4kwCwYJKoZIhvcNAQEFA4GB +AMt1iED7QzFL2Qk6VivoFY15S2XGF8rJTd3l00bwyLA5qLyLBGlB6z4qkYu7/7SW +5r7tet+1DezgHrj/1eU289m410wnQB8fGwcVLp6OX2PAlhNmVLcsipiN6rielAcP +aIg/VlBtoCFp/ymTLKgvh6DLKWhRUkFPqO2WtcQ3UUo+ +-----END CERTIFICATE----- diff --git a/tests/testsuites/x.509/machine-key.pem b/tests/testsuites/x.509/machine-key.pem new file mode 100644 index 00000000..808f00c9 --- /dev/null +++ b/tests/testsuites/x.509/machine-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCyTlupSB7/LnOhM+7Yoq+uLyN2kbg5lAAj8m8lrclqqy3m82LY +Pm6K1h4/cuXYueDQecKUIWULEFNmsDamp81GHixqm3nG7sbi7bCpWeJJ2sfj8Bzg +U5iHDdUo26SCNu06HtFcBxOVXbMoBRcqK7aOjnjSz6yHExX8F0NrFcN9uQIDAQAB +AoGABHJs2c95Km8bpikX62I/VG5LiaD/wbvdtwfMWtm3PMhRKEHotLD169OERJvW +fK3CHCD1R+F/ViPNmLGLY2Oq/GqKjhKjg4sqAznw8TImBSgXCFho4sl38z+luP1o +TXFDgfV5HDDW1/F5kJlBIfXBLFdl4VO7E0ZnFt4FqSDRW2MCQQDRun/sBGM4i9hM +QdC+QwrdcgCScBpzbz4YXtI9TyGEqNahg8kXgIVUbzDdRmG68G2M98USzRs5DWB7 +YvYwmRoPAkEA2aUdUpFRb/n7XfsAiFLYOk96C82iCCQpJi0si34zlCAEbCRbQ6zw +gVDMCMSccnnWrVzqtxfN+rXycFTNyDFTtwJAPRwymfrNTnSxGcczo7y1NcE6GXFA +w9HuLfuzFtov0g/AOl/EAG0abHfZrSAM6gOUaDbp3YiWHhGfw1QamB6EUQJAClTb +MnsxeXZNZ2Wt3crI9uOk8IB/a5GD3osQbUK9Yg+vBg8nweuoswrJ1LS4lHqSJUKe +5bgckAUpEAoGhrVIuwJBAKIuqx/cSjF4Oa9xT6DzBRe7vAlKFq62lUV5SLfoSEgY +L5dvPBgAD0Styglny1s0Bu5FTlkxlFOMvUAD/O5hsQw= +-----END RSA PRIVATE KEY----- diff --git a/tests/testsuites/x.509/request.pem b/tests/testsuites/x.509/request.pem new file mode 100644 index 00000000..c612325c --- /dev/null +++ b/tests/testsuites/x.509/request.pem @@ -0,0 +1,10 @@ +-----BEGIN NEW CERTIFICATE REQUEST----- +MIIBWDCBxAIBADANMQswCQYDVQQGEwJVUzCBnDALBgkqhkiG9w0BAQEDgYwAMIGI +AoGAvn+sgnrdliYJsLYLpDkXiPmr0Rzn69oS4SEJEswjojYtLTVjCpmkFsPw/stR +by0atMCw1MiSv9+qweuxyn7HKDXgpSk0vI4QLNZj40+e4srqKiulVv3diaKH5JUQ +oAyGUKzX8R4jjvpDd0oS5pBqsZAN2MlLcvyZhejylmCIUFkCAwEAAaARMA8GCSqG +SIb3DQEJBzECEwAwCwYJKoZIhvcNAQEFA4GBAA6mBaHFuRvcJVNoU7wDFcDexjvC +QLpDpFRSbKcKdNEQLBRD8ZNVOY4WBXQE2pE84//QnygQPKPCHSqUVdPPBabi5y2E +A2XvgYyKsrFbsrpKrVkPz5oQB4V7FRytQaQoBi//BSOu3dMaimLcAhfNQZCrQeu8 +SYWdJi5OPvrYGvgT +-----END NEW CERTIFICATE REQUEST----- diff --git a/tests/threadingmq.sh b/tests/threadingmq.sh new file mode 100755 index 00000000..98f195d4 --- /dev/null +++ b/tests/threadingmq.sh @@ -0,0 +1,19 @@ +# test many concurrent tcp connections +# we send 100,000 messages in the hopes that his puts at least a little bit +# of pressure on the threading subsystem. To really prove it, we would need to +# push messages for several minutes, but that takes too long during the +# automatted tests (hint: do this manually after suspect changes). Thankfully, +# in practice many threading bugs result in an abort rather quickly and these +# should be covered by this test here. +# rgerhards, 2009-06-26 +echo \[threadingmq.sh\]: main queue concurrency +source $srcdir/diag.sh init +source $srcdir/diag.sh startup threadingmq.conf +source $srcdir/diag.sh injectmsg 0 100000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +# we give an extra seconds for things to settle, especially +# important on slower test machines +./msleep 1000 +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99999 +source $srcdir/diag.sh exit diff --git a/tests/threadingmqaq.sh b/tests/threadingmqaq.sh new file mode 100755 index 00000000..ae5d3568 --- /dev/null +++ b/tests/threadingmqaq.sh @@ -0,0 +1,21 @@ +# test many concurrent tcp connections +# we send 100,000 messages in the hopes that his puts at least a little bit +# of pressure on the threading subsystem. To really prove it, we would need to +# push messages for several minutes, but that takes too long during the +# automatted tests (hint: do this manually after suspect changes). Thankfully, +# in practice many threading bugs result in an abort rather quickly and these +# should be covered by this test here. +# rgerhards, 2009-06-26 +echo \[threadingmqaq.sh\]: main/action queue concurrency +source $srcdir/diag.sh init +source $srcdir/diag.sh startup threadingmqaq.conf +#source $srcdir/diag.sh tcpflood -c2 -m100000 +#source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh injectmsg 0 100000 +# we need to sleep a bit on some environments, as imdiag can not correctly +# diagnose when the action queues are empty... +sleep 3 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown +source $srcdir/diag.sh seq-check 0 99999 +source $srcdir/diag.sh exit diff --git a/tests/timestamp.sh b/tests/timestamp.sh new file mode 100755 index 00000000..71416c33 --- /dev/null +++ b/tests/timestamp.sh @@ -0,0 +1,13 @@ +echo \[timestamp.sh\]: various timestamp tests +source $srcdir/diag.sh init +source $srcdir/diag.sh nettester ts3164 udp +source $srcdir/diag.sh nettester ts3164 tcp +source $srcdir/diag.sh nettester ts3339 udp +source $srcdir/diag.sh nettester ts3339 tcp +source $srcdir/diag.sh nettester tsmysql udp +source $srcdir/diag.sh nettester tsmysql tcp +source $srcdir/diag.sh nettester tspgsql udp +source $srcdir/diag.sh nettester tspgsql tcp +source $srcdir/diag.sh nettester subsecond udp +source $srcdir/diag.sh nettester subsecond tcp +source $srcdir/diag.sh init diff --git a/tests/tls-certs/ca-key.pem b/tests/tls-certs/ca-key.pem new file mode 100644 index 00000000..181a8ad9 --- /dev/null +++ b/tests/tls-certs/ca-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDDaz5X5YIruPH0wukMPik7xIKqrpCcr8Gm28oz5h4GtX253eWr +piBuk2a/f/CKDjeuqmiWqTs90PFNb+Z1c+Yzvagqv80VzZwDI4RcrwlNaKrBz/9X +iowCcoV8s7GvV2vtZEPSThNzz4FYkxCMvbOYZeJIYQVhZggUcuadfhmDIwIDAQAB +AoGAIG5AUD2jmYDzD+UhiultVgtkifyNaEtsuQsZu/zbt85P2VQ0z4SINlbvrXvc +iJ9tEzzEPa3udHGj/MTDe3OAB4TK5tImX1pe2gw+zaOB/DaH5i4QhXeltU7epCHF +oUv9EVNzL8Bl00MFiWcLY0LisQVfHeW5rcN9U7EbvTlWbRkCQQDR2/Qn1ceavwDU +qYt2TbEicJVC8aQMYYyc6Xvi4mZaNa8gGCpWpurgQop0Ln0QE8vA0601UVs6N3tm +g8FJ8rXpAkEA7mKCtp2MXCbHMdkZCyQt6drUYCyU9N/HtmBEtFGpqt1PdMyUI07m +rlVFDwUH9JFmg18RP1X2ufj7+ZbJzaMtKwJBAJgbw1Z0P19Mfj+mPC2dlnyN+cIx +/2Px+Mdq/J6w1tsf+jVbDqUMC0ZNNKmNYJycnJzBUNRKicMin9DoQttkjrECQQCC +s/aRY+6adBSRi0QE7NBTwUzicm81mCDrKPtilsfdTDyNgMHUXiVy/oO/yXVkLfi0 +HQLa5CpEK3UUkw2Qt2BDAkA0XXvQzW0+tEHiktLNljIluhiyOAx2bBywY/9Qmn6C +hv4sOSCzTR39jNmuNZ0X6ZZvt4VsWTHhpche/ud1+3p6 +-----END RSA PRIVATE KEY----- diff --git a/tests/tls-certs/ca.pem b/tests/tls-certs/ca.pem new file mode 100644 index 00000000..6324c7d5 --- /dev/null +++ b/tests/tls-certs/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYjCCAc2gAwIBAgIBATALBgkqhkiG9w0BAQUwWDELMAkGA1UEBhMCREUxHTAb +BgNVBAoTFHJzeXNsb2cgdGVzdCByb290IENBMQswCQYDVQQLEwJDQTEdMBsGA1UE +AxMUcnN5c2xvZy10ZXN0LXJvb3QtY2EwHhcNMDgwNTIwMTI1ODEyWhcNMTgwNTE4 +MTI1ODI0WjBYMQswCQYDVQQGEwJERTEdMBsGA1UEChMUcnN5c2xvZyB0ZXN0IHJv +b3QgQ0ExCzAJBgNVBAsTAkNBMR0wGwYDVQQDExRyc3lzbG9nLXRlc3Qtcm9vdC1j +YTCBnDALBgkqhkiG9w0BAQEDgYwAMIGIAoGAw2s+V+WCK7jx9MLpDD4pO8SCqq6Q +nK/BptvKM+YeBrV9ud3lq6YgbpNmv3/wig43rqpolqk7PdDxTW/mdXPmM72oKr/N +Fc2cAyOEXK8JTWiqwc//V4qMAnKFfLOxr1dr7WRD0k4Tc8+BWJMQjL2zmGXiSGEF +YWYIFHLmnX4ZgyMCAwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8E +BQMDBwYAMB0GA1UdDgQWBBQzYQQgUm0YLNdarJnc2c1LxYVClDALBgkqhkiG9w0B +AQUDgYEAuGWtH7Jkpa0n/izqQ5ddDQP/LT6taivCwlpEYEU9aumpQPWWxtYywKaP +RfM1JTMLAiYd8MS7TJ8TYRvvR32Y02Y+OhXn11xERkWvBT2M9yzqX6hDfRueN7RT +fPWsfm/NBTVojzjaECcTFenZid7PC5JiFbcU6PSUMZ49/JPhxAo= +-----END CERTIFICATE----- diff --git a/tests/tls-certs/cert.pem b/tests/tls-certs/cert.pem new file mode 100644 index 00000000..6b5b13cd --- /dev/null +++ b/tests/tls-certs/cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIChjCCAfGgAwIBAgIBADALBgkqhkiG9w0BAQUwWDELMAkGA1UEBhMCREUxHTAb +BgNVBAoTFHJzeXNsb2cgdGVzdCByb290IENBMQswCQYDVQQLEwJDQTEdMBsGA1UE +AxMUcnN5c2xvZy10ZXN0LXJvb3QtY2EwHhcNMDgwNTIwMTMwNDE5WhcNMTgwNTE4 +MTMwNDI2WjA6MQswCQYDVQQGEwJERTEQMA4GA1UEChMHcnN5c2xvZzEZMBcGA1UE +CxMQdGVzdCBjZXJ0aWZpY2F0ZTCBnDALBgkqhkiG9w0BAQEDgYwAMIGIAoGAxmHe +fztJgaGxFYEceiUg0hdMlRVWBqoZelJ8BeXTDnXcu/5F2HtM+l+QDyDaGjKlx+NI +K4rkj7d6Wd3AKPgOYS0VSDZe3a1xf9rRYzOthWTv7tYi4/LTqPXqN5lKE71dgrB/ +/gOmvV/1YD776FIxVGCSAT0hHwkFC3slmpJSwD8CAwEAAaOBhDCBgTAMBgNVHRMB +Af8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHREECzAJ +ggdyc3lzbG9nMB0GA1UdDgQWBBQYu6eC9UALvC+5K5VOnFRi5OC98TAfBgNVHSME +GDAWgBQzYQQgUm0YLNdarJnc2c1LxYVClDALBgkqhkiG9w0BAQUDgYEAXaymqsG9 +PNBhhWIRFvXCDMaDM71vUtgSFoNUbxIV607ua2HQosPPM4EHIda6N6hdBK1bMQoG +yqBwhvw0JVaVaO70Kbs2m2Ypk3YcpJtRqyp8q8+2y/w1Mk1QazFZC29aYgX2iNVf +X4/x38YEL7Gu5vqPrTn++agnV4ZXECKuvLQ= +-----END CERTIFICATE----- diff --git a/tests/tls-certs/key.pem b/tests/tls-certs/key.pem new file mode 100644 index 00000000..3ff507f0 --- /dev/null +++ b/tests/tls-certs/key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDGYd5/O0mBobEVgRx6JSDSF0yVFVYGqhl6UnwF5dMOddy7/kXY +e0z6X5APINoaMqXH40griuSPt3pZ3cAo+A5hLRVINl7drXF/2tFjM62FZO/u1iLj +8tOo9eo3mUoTvV2CsH/+A6a9X/VgPvvoUjFUYJIBPSEfCQULeyWaklLAPwIDAQAB +AoGARIwKqmHc+0rYenq7UUVE+vMMBjNyHyllVkvsCMmpzMRS+i5ZCf1I0vZ0O5X5 +ZrX7bH8PL+R1J2eZgjXKMR3NMZBuyKHewItD9t2rIC0eD/ITlwq3VybbaMsw666e +INxSmax+dS5CEcLevHHP3c+Q7S7QAFiWV43TdFUGXWJktIkCQQDPQ5WAZ+/Tvv0Q +vtRjXMeTVaw/bSuKNUeDzFkmGyePnFeCReNFtJLE9PFSQWcPuYcbZgU59JTfA5ac +Un+cHm31AkEA9Qek+q7PcJ+kON9E6SNodCZn6gLyHjnWrq4tf8pZO3NvoX2QiuD4 +rwF7KWjr6q1JzADpLtwXnuYEhyiLFjJA4wJAcElMCEnG2y+ASH8p7z7HfKGQdLg/ +O1wMB3JA5e0WLK5lllUogI4IaZ3N02NNY25+rLBDqpc/w+ZcxQnIypqNtQJATs9p +ofON5wSB1oUBbhckZo9fxuWxqEUkJsUA/2Q+9R843XE8h166vdc1HOmRT8bywHne +hmLl+gazmCFTMw1wzwJAHng+3zGUl4D8Ov3MPFD6hwYYK6/pEdtz/NUsCSazF7eK +XuuP+DXPHNhXOuF1A3tP74pfc/fC1uCUH2G5z3Fy0Q== +-----END RSA PRIVATE KEY----- diff --git a/tests/udp-msgreduc-orgmsg-vg.sh b/tests/udp-msgreduc-orgmsg-vg.sh new file mode 100755 index 00000000..1594c89a --- /dev/null +++ b/tests/udp-msgreduc-orgmsg-vg.sh @@ -0,0 +1,18 @@ +# check if valgrind violations occur. Correct output is not checked. +# added 2011-03-01 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[udp-msgreduc-orgmsg-vg.sh\]: testing msg reduction via udp, with org message +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg udp-msgreduc-orgmsg-vg.conf +source $srcdir/diag.sh wait-startup +./tcpflood -t 127.0.0.1 -m 4 -r -Tudp -M "<133>2011-03-01T11:22:12Z host tag msgh ..." +./tcpflood -t 127.0.0.1 -m 1 -r -Tudp -M "<133>2011-03-01T11:22:12Z host tag msgh ...x" +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +if [ "$RSYSLOGD_EXIT" -eq "10" ] +then + echo "udp-msgreduc-orgmsg-vg.sh FAILED" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/udp-msgreduc-vg.sh b/tests/udp-msgreduc-vg.sh new file mode 100755 index 00000000..e19ffd86 --- /dev/null +++ b/tests/udp-msgreduc-vg.sh @@ -0,0 +1,18 @@ +# check if valgrind violations occur. Correct output is not checked. +# added 2011-03-01 by Rgerhards +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo \[udp-msgreduc-vg.sh\]: testing imtcp multiple listeners +source $srcdir/diag.sh init +source $srcdir/diag.sh startup-vg udp-msgreduc-vg.conf +source $srcdir/diag.sh wait-startup +./tcpflood -t 127.0.0.1 -m 4 -r -Tudp -M "<133>2011-03-01T11:22:12Z host tag msgh ..." +./tcpflood -t 127.0.0.1 -m 1 -r -Tudp -M "<133>2011-03-01T11:22:12Z host tag msgh ...x" +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown-vg +if [ "$RSYSLOGD_EXIT" -eq "10" ] +then + echo "udp-msgreduc-vg.sh FAILED" + exit 1 +fi +source $srcdir/diag.sh exit diff --git a/tests/uxsock_simple.sh b/tests/uxsock_simple.sh new file mode 100755 index 00000000..7f00f4bc --- /dev/null +++ b/tests/uxsock_simple.sh @@ -0,0 +1,31 @@ +# This tests basic omuxsock functionality. A socket receiver is started which sends +# all data to an output file, then a rsyslog instance is started which generates +# messages and sends them to the unix socket. Datagram sockets are being used. +# added 2010-08-06 by Rgerhards +echo =============================================================================== +echo \[uxsock_simple.sh\]: simple tests for omuxsock functionality + +# create the pipe and start a background process that copies data from +# it to the "regular" work file +source $srcdir/diag.sh init +./uxsockrcvr -srsyslog-testbench-dgram-uxsock -orsyslog.out.log & +BGPROCESS=$! +echo background uxsockrcvr process id is $BGPROCESS + +# now do the usual run +source $srcdir/diag.sh startup uxsock_simple.conf +# 10000 messages should be enough +source $srcdir/diag.sh injectmsg 0 10000 +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown + +# wait for the cp process to finish, do pipe-specific cleanup +echo shutting down uxsockrcvr... +# TODO: we should do this more reliable in the long run! (message counter? timeout?) +kill $BGPROCESS +wait $BGPROCESS +echo background process has terminated, continue test... + +# and continue the usual checks +source $srcdir/diag.sh seq-check 0 9999 +source $srcdir/diag.sh exit diff --git a/tests/uxsockrcvr.c b/tests/uxsockrcvr.c new file mode 100644 index 00000000..551f0ef3 --- /dev/null +++ b/tests/uxsockrcvr.c @@ -0,0 +1,157 @@ +/* receives messages from a specified unix sockets and writes + * output to specfied file. + * + * Command line options: + * -s name of socket (required) + * -o name of output file (stdout if not given) + * -l add newline after each message received (default: do not add anything) + * + * Part of the testbench for rsyslog. + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <getopt.h> +#include <sys/un.h> +#include <netdb.h> + +char *sockName = NULL; +int sock; +int addNL = 0; + + +/* called to clean up on exit + */ +void +cleanup(void) +{ + unlink(sockName); + close(sock); +} + + +void +doTerm(int __attribute__((unused)) signum) +{ + exit(1); +} + + +void +usage(void) +{ + fprintf(stderr, "usage: uxsockrcvr -s /socket/name -o /output/file -l\n" + "-l adds newline after each message received\n" + "-s MUST be specified\n" + "if -o ist not specified, stdout is used\n"); + exit(1); +} + + +int +main(int argc, char *argv[]) +{ + int opt; + int rlen; + FILE *fp = stdout; + unsigned char data[128*1024]; + struct sockaddr_un addr; /* address of server */ + struct sockaddr from; + socklen_t fromlen; + + if(argc < 2) { + fprintf(stderr, "error: too few arguments!\n"); + usage(); + } + + while((opt = getopt(argc, argv, "s:o:l")) != EOF) { + switch((char)opt) { + case 'l': + addNL = 1; + break; + case 's': + sockName = optarg; + break; + case 'o': + if((fp = fopen(optarg, "w")) == NULL) { + perror(optarg); + exit(1); + } + break; + default:usage(); + } + } + + if(sockName == NULL) { + fprintf(stderr, "error: -s /socket/name must be specified!\n"); + exit(1); + } + + if(signal(SIGTERM, doTerm) == SIG_ERR) { + perror("signal(SIGTERM, ...)"); + exit(1); + } + if(signal(SIGINT, doTerm) == SIG_ERR) { + perror("signal(SIGINT, ...)"); + exit(1); + } + + /* Create a UNIX datagram socket for server */ + if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { + perror("server: socket"); + exit(1); + } + + atexit(cleanup); + + /* Set up address structure for server socket */ + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, sockName); + + if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) < 0) { + close(sock); + perror("server: bind"); + exit(1); + } + + /* we now run in an endless loop. We do not check who sends us + * data. This should be no problem for our testbench use. + */ + while(1) { + fromlen = sizeof(from); + rlen = recvfrom(sock, data, 2000, 0, &from, &fromlen); + if(rlen == -1) { + perror("uxsockrcvr : recv\n"); + exit(1); + } else { + fwrite(data, 1, rlen, fp); + if(addNL) + fputc('\n', fp); + } + } + + return 0; +} diff --git a/tests/validation-run.sh b/tests/validation-run.sh new file mode 100755 index 00000000..a68ee8ae --- /dev/null +++ b/tests/validation-run.sh @@ -0,0 +1,43 @@ +# check if the configuration test run detects invalid config files. +# +# Part of the testbench for rsyslog. +# +# Copyright 2009 Rainer Gerhards and Adiscon GmbH. +# +# This file is part of rsyslog. +# +# Rsyslog is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. +# +# A copy of the GPL can be found in the file "COPYING" in this distribution. +#set -x +echo \[validation-run.sh\]: testing configuraton validation +echo "testing a failed configuration verification run" +../tools/rsyslogd -dn -u2 -c4 -N1 -f$srcdir/testsuites/invalid.conf -M../runtime/.libs:../.libs +if [ $? -ne 1 ]; then + echo "after test 1: return code ne 1" + exit 1 +fi +echo testing a valid config verification run +../tools/rsyslogd -u2 -c4 -N1 -f$srcdir/testsuites/valid.conf -M../runtime/.libs:../.libs +if [ $? -ne 0 ]; then + echo "after test 2: return code ne 0" + exit 1 +fi +echo testing empty config file +../tools/rsyslogd -u2 -c4 -N1 -f/dev/null -M../runtime/.libs:../.libs +if [ $? -ne 1 ]; then + echo "after test 3: return code ne 1" + exit 1 +fi +echo SUCCESS: validation run tests diff --git a/tests/wr_large.sh b/tests/wr_large.sh new file mode 100755 index 00000000..84f12989 --- /dev/null +++ b/tests/wr_large.sh @@ -0,0 +1,16 @@ +# This tests async writing large data records. We use up to 10K +# record size. + +# added 2010-03-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +cat rsyslog.action.1.include +source $srcdir/diag.sh init +source $srcdir/diag.sh startup wr_large.conf +# send 4000 messages of 10.000bytes plus header max, randomized +source $srcdir/diag.sh tcpflood -m4000 -r -d10000 -P129 +sleep 1 # due to large messages, we need this time for the tcp receiver to settle... +source $srcdir/diag.sh shutdown-when-empty # shut down rsyslogd when done processing messages +source $srcdir/diag.sh wait-shutdown # and wait for it to terminate +source $srcdir/diag.sh seq-check 0 3999 -E +source $srcdir/diag.sh exit diff --git a/tests/wr_large_async.sh b/tests/wr_large_async.sh new file mode 100755 index 00000000..88a1acf8 --- /dev/null +++ b/tests/wr_large_async.sh @@ -0,0 +1,14 @@ +# This tests async writing large data records. We use up to 10K +# record size. + +# added 2010-03-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[wr_large_async.sh\]: test for file writing for large message sets +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +echo "\$OMFileAsyncWriting on" > rsyslog.action.1.include +source $srcdir/wr_large.sh diff --git a/tests/wr_large_sync.sh b/tests/wr_large_sync.sh new file mode 100755 index 00000000..a1c4fd77 --- /dev/null +++ b/tests/wr_large_sync.sh @@ -0,0 +1,14 @@ +# This tests async writing large data records. We use up to 10K +# record size. + +# added 2010-03-10 by Rgerhards +# +# This file is part of the rsyslog project, released under GPLv3 +echo =============================================================================== +echo TEST: \[wr_large_sync.sh\]: test for file writing for large message sets +source $srcdir/diag.sh init +# uncomment for debugging support: +#export RSYSLOG_DEBUG="debug nostdout noprintmutexaction" +#export RSYSLOG_DEBUGLOG="log" +echo "\$OMFileAsyncWriting off" > rsyslog.action.1.include +source $srcdir/wr_large.sh diff --git a/threads.c b/threads.c new file mode 100644 index 00000000..990733a8 --- /dev/null +++ b/threads.c @@ -0,0 +1,284 @@ +/* threads.c + * + * This file implements threading support helpers (and maybe the thread object) + * for rsyslog. + * + * File begun on 2007-12-14 by RGerhards + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <pthread.h> +#include <assert.h> +#if HAVE_SYS_PRCTL_H +# include <sys/prctl.h> +#endif + +#include "rsyslog.h" +#include "dirty.h" +#include "linkedlist.h" +#include "threads.h" +#include "srUtils.h" +#include "unicode-helper.h" + +/* linked list of currently-known threads */ +static linkedList_t llThrds; + +/* methods */ + +/* Construct a new thread object + */ +static rsRetVal +thrdConstruct(thrdInfo_t **ppThis) +{ + DEFiRet; + thrdInfo_t *pThis; + + assert(ppThis != NULL); + + CHKmalloc(pThis = calloc(1, sizeof(thrdInfo_t))); + pthread_mutex_init(&pThis->mutThrd, NULL); + pthread_cond_init(&pThis->condThrdTerm, NULL); + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* Destructs a thread object. The object must not be linked to the + * linked list of threads. Please note that the thread should have been + * stopped before. If not, we try to do it. + */ +static rsRetVal thrdDestruct(thrdInfo_t *pThis) +{ + DEFiRet; + assert(pThis != NULL); + + if(pThis->bIsActive == 1) { + thrdTerminate(pThis); + } + pthread_mutex_destroy(&pThis->mutThrd); + pthread_cond_destroy(&pThis->condThrdTerm); + free(pThis->name); + free(pThis); + + RETiRet; +} + + +/* terminate a thread via the non-cancel interface + * This is a separate function as it involves a bit more of code. + * rgerhads, 2009-10-15 + */ +static inline rsRetVal +thrdTerminateNonCancel(thrdInfo_t *pThis) +{ + struct timespec tTimeout; + int ret; + DEFiRet; + assert(pThis != NULL); + + DBGPRINTF("request term via SIGTTIN for input thread '%s' 0x%x\n", + pThis->name, (unsigned) pThis->thrdID); + pThis->bShallStop = RSTRUE; + do { + d_pthread_mutex_lock(&pThis->mutThrd); + pthread_kill(pThis->thrdID, SIGTTIN); + timeoutComp(&tTimeout, 1000); /* a fixed 1sec timeout */ + ret = d_pthread_cond_timedwait(&pThis->condThrdTerm, &pThis->mutThrd, &tTimeout); + d_pthread_mutex_unlock(&pThis->mutThrd); + if(Debug) { + if(ret == ETIMEDOUT) { + dbgprintf("input thread term: timeout expired waiting on thread %s termination - canceling\n", pThis->name); + pthread_cancel(pThis->thrdID); + pThis->bIsActive = 0; + } else if(ret == 0) { + dbgprintf("input thread term: thread %s returned normally and is terminated\n", pThis->name); + } else { + char errStr[1024]; + int err = errno; + rs_strerror_r(err, errStr, sizeof(errStr)); + dbgprintf("input thread term: cond_wait returned with error %d: %s\n", + err, errStr); + } + } + } while(pThis->bIsActive); + DBGPRINTF("non-cancel input thread termination succeeded for thread %s 0x%x\n", + pThis->name, (unsigned) pThis->thrdID); + + RETiRet; +} + + +/* terminate a thread gracefully. + */ +rsRetVal thrdTerminate(thrdInfo_t *pThis) +{ + DEFiRet; + assert(pThis != NULL); + + if(pThis->bNeedsCancel) { + DBGPRINTF("request term via canceling for input thread 0x%x\n", (unsigned) pThis->thrdID); + pthread_cancel(pThis->thrdID); + pThis->bIsActive = 0; + } else { + thrdTerminateNonCancel(pThis); + } + pthread_join(pThis->thrdID, NULL); /* wait for input thread to complete */ + + /* call cleanup function, if any */ + if(pThis->pAfterRun != NULL) + pThis->pAfterRun(pThis); + + RETiRet; +} + + +/* terminate all known threads gracefully. + */ +rsRetVal thrdTerminateAll(void) +{ + DEFiRet; + llDestroy(&llThrds); + RETiRet; +} + + +/* This is an internal wrapper around the user thread function. Its + * purpose is to handle all the necessary housekeeping stuff so that the + * user function needs not to be aware of the threading calls. The user + * function call has just "normal", non-threading semantics. + * rgerhards, 2007-12-17 + */ +static void* thrdStarter(void *arg) +{ + DEFiRet; + thrdInfo_t *pThis = (thrdInfo_t*) arg; +# if HAVE_PRCTL && defined PR_SET_NAME + uchar thrdName[32] = "in:"; +# endif + + assert(pThis != NULL); + assert(pThis->pUsrThrdMain != NULL); + +# if HAVE_PRCTL && defined PR_SET_NAME + ustrncpy(thrdName+3, pThis->name, 20); + dbgOutputTID((char*)thrdName); + + /* set thread name - we ignore if the call fails, has no harsh consequences... */ + if(prctl(PR_SET_NAME, thrdName, 0, 0, 0) != 0) { + DBGPRINTF("prctl failed, not setting thread name for '%s'\n", pThis->name); + } else { + DBGPRINTF("set thread name to '%s'\n", thrdName); + } +# endif + + /* block all signals */ + sigset_t sigSet; + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + + /* but ignore SIGTTN, which we (ab)use to signal the thread to shutdown -- rgerhards, 2009-07-20 */ + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGTTIN); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + + /* setup complete, we are now ready to execute the user code. We will not + * regain control until the user code is finished, in which case we terminate + * the thread. + */ + iRet = pThis->pUsrThrdMain(pThis); + + dbgprintf("thrdStarter: usrThrdMain %s - 0x%lx returned with iRet %d, exiting now.\n", + pThis->name, (unsigned long) pThis->thrdID, iRet); + + /* signal master control that we exit (we do the mutex lock mostly to + * keep the thread debugger happer, it would not really be necessary with + * the logic we employ...) + */ + d_pthread_mutex_lock(&pThis->mutThrd); + pThis->bIsActive = 0; + pthread_cond_signal(&pThis->condThrdTerm); + d_pthread_mutex_unlock(&pThis->mutThrd); + + ENDfunc + pthread_exit(0); +} + +/* Start a new thread and add it to the list of currently + * executing threads. It is added at the end of the list. + * rgerhards, 2007-12-14 + */ +rsRetVal thrdCreate(rsRetVal (*thrdMain)(thrdInfo_t*), rsRetVal(*afterRun)(thrdInfo_t *), sbool bNeedsCancel, uchar *name) +{ + DEFiRet; + thrdInfo_t *pThis; + + assert(thrdMain != NULL); + + CHKiRet(thrdConstruct(&pThis)); + pThis->bIsActive = 1; + pThis->pUsrThrdMain = thrdMain; + pThis->pAfterRun = afterRun; + pThis->bNeedsCancel = bNeedsCancel; + pThis->name = ustrdup(name); + pthread_create(&pThis->thrdID, +#ifdef HAVE_PTHREAD_SETSCHEDPARAM + &default_thread_attr, +#else + NULL, +#endif + thrdStarter, pThis); + CHKiRet(llAppend(&llThrds, NULL, pThis)); + +finalize_it: + RETiRet; +} + + +/* initialize the thread-support subsystem + * must be called once at the start of the program + */ +rsRetVal thrdInit(void) +{ + DEFiRet; + iRet = llInit(&llThrds, thrdDestruct, NULL, NULL); + RETiRet; +} + + +/* de-initialize the thread subsystem + * must be called once at the end of the program + */ +rsRetVal thrdExit(void) +{ + DEFiRet; + iRet = llDestroy(&llThrds); + RETiRet; +} + + +/* vi:set ai: + */ diff --git a/threads.h b/threads.h new file mode 100644 index 00000000..1ee767a2 --- /dev/null +++ b/threads.h @@ -0,0 +1,47 @@ +/* Definition of the threading support module. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef THREADS_H_INCLUDED +#define THREADS_H_INCLUDED + +/* the thread object */ +struct thrdInfo { + pthread_mutex_t mutThrd;/* mutex for handling long-running operations and shutdown */ + pthread_cond_t condThrdTerm;/* condition: thread terminates (used just for shutdown loop) */ + int bIsActive; /* Is thread running? */ + int bShallStop; /* set to 1 if the thread should be stopped ? */ + rsRetVal (*pUsrThrdMain)(struct thrdInfo*); /* user thread main to be called in new thread */ + rsRetVal (*pAfterRun)(struct thrdInfo*); /* cleanup function */ + pthread_t thrdID; + sbool bNeedsCancel; /* must input be terminated by pthread_cancel()? */ + uchar *name; /* a thread name, mainly for user interaction */ +}; + +/* prototypes */ +rsRetVal thrdExit(void); +rsRetVal thrdInit(void); +rsRetVal thrdTerminate(thrdInfo_t *pThis); +rsRetVal thrdTerminateAll(void); +rsRetVal thrdCreate(rsRetVal (*thrdMain)(thrdInfo_t*), rsRetVal(*afterRun)(thrdInfo_t *), sbool, uchar*); + +/* macros (replace inline functions) */ + +#endif /* #ifndef THREADS_H_INCLUDED */ diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 00000000..6832494e --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,92 @@ +sbin_PROGRAMS = +bin_PROGRAMS = +CLEANFILES = +man1_MANS = +man_MANS = rsyslogd.8 rsyslog.conf.5 + +sbin_PROGRAMS += rsyslogd +rsyslogd_SOURCES = \ + syslogd.c \ + syslogd.h \ + omshell.c \ + omshell.h \ + omusrmsg.c \ + omusrmsg.h \ + omfwd.c \ + omfwd.h \ + omfile.c \ + omfile.h \ + ompipe.c \ + ompipe.h \ + omdiscard.c \ + omdiscard.h \ + pmrfc5424.c \ + pmrfc5424.h \ + pmrfc3164.c \ + pmrfc3164.h \ + smtradfile.c \ + smtradfile.h \ + smfile.c \ + smfile.h \ + smfwd.c \ + smfwd.h \ + smtradfwd.c \ + smtradfwd.h \ + iminternal.c \ + iminternal.h \ + pidfile.c \ + pidfile.h \ + \ + ../dirty.h +rsyslogd_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +# note: it looks like librsyslog.la must be explicitely given on LDDADD, +# otherwise dependencies are not properly calculated (resulting in a +# potentially incomplete build, a problem we had several times...) +rsyslogd_LDADD = ../grammar/libgrammar.la ../runtime/librsyslog.la $(ZLIB_LIBS) $(PTHREADS_LIBS) $(RSRT_LIBS) $(SOL_LIBS) $(LIBUUID_LIBS) +rsyslogd_LDFLAGS = -export-dynamic + +EXTRA_DIST = $(man_MANS) \ + rsgtutil.rst \ + rsgtutil.1 \ + rscryutil.rst \ + rscryutil.1 \ + recover_qi.pl + +if ENABLE_DIAGTOOLS +sbin_PROGRAMS += rsyslog_diag_hostname msggen zpipe +rsyslog_diag_hostname_SOURCES = gethostn.c +zpipe_SOURCES = zpipe.c +zpipe_LDADD = -lz +msggen_SOURCES = msggen.c +endif + +if ENABLE_USERTOOLS +if ENABLE_OMMONGODB +bin_PROGRAMS += logctl +logctl_SOURCES = logctl.c +logctl_CPPFLAGS = $(LIBMONGO_CLIENT_CFLAGS) +logctl_LDADD = $(LIBMONGO_CLIENT_LIBS) +endif +if ENABLE_GUARDTIME +bin_PROGRAMS += rsgtutil +rsgtutil = rsgtutil.c +rsgtutil_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS) +rsgtutil_LDADD = ../runtime/librsgt.la $(GUARDTIME_LIBS) +rsgtutil.1: rsgtutil.rst + $(AM_V_GEN) $(RST2MAN) $< $@ +man1_MANS += rsgtutil.1 +CLEANFILES += rsgtutil.1 +EXTRA_DIST+= rsgtutil.1 +endif +if ENABLE_LIBGCRYPT +bin_PROGRAMS += rscryutil +rscryutil = rscryutil.c +rscryutil_CPPFLAGS = -I../runtime $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS) +rscryutil_LDADD = ../runtime/libgcry.la $(LIBGCRYPT_LIBS) +rscryutil.1: rscryutil.rst + $(AM_V_GEN) $(RST2MAN) $< $@ +man1_MANS += rscryutil.1 +CLEANFILES += rscryutil.1 +EXTRA_DIST+= rscryutil.1 +endif +endif diff --git a/tools/gethostn.c b/tools/gethostn.c new file mode 100644 index 00000000..df7ce38b --- /dev/null +++ b/tools/gethostn.c @@ -0,0 +1,47 @@ +/* gethostn - a small diagnostic utility to show what the + * gethostname() API returns. Of course, this tool duplicates + * functionality already found in other tools. But the point is + * that the API shall be called by a program that is compiled like + * rsyslogd and does exactly what rsyslog does. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int main(int __attribute__((unused)) argc, char __attribute__((unused)) *argv[]) +{ + char hostname[4096]; /* this should always be sufficient ;) */ + int err; + + err = gethostname(hostname, sizeof(hostname)); + + if(err) { + perror("gethostname failed"); + exit(1); + } + + printf("hostname of this system is '%s'.\n", hostname); + + return 0; +} diff --git a/tools/gnutls/cert-gen-selfsigned b/tools/gnutls/cert-gen-selfsigned new file mode 100755 index 00000000..e1c25386 --- /dev/null +++ b/tools/gnutls/cert-gen-selfsigned @@ -0,0 +1,6 @@ +#/bin/sh +# generates a self-signed certificate and key suitable for use with rsyslog +# 2008-05-08, rgerhards +# TODO: make this a robust shell script +certtool --generate-privkey --outfile $1-key.pem +certtool --generate-self-signed --load-privkey $1-key.pem --outfile $1-cert.pem diff --git a/tools/gnutls/cert-show-fingerprint b/tools/gnutls/cert-show-fingerprint new file mode 100755 index 00000000..f61c6840 --- /dev/null +++ b/tools/gnutls/cert-show-fingerprint @@ -0,0 +1,6 @@ +#/bin/sh +# must be called with the certificate file as first parameter. Displays all +# fingerprints for the first certificate. +# 2008-05-08, rgerhards +# TODO: make this a robust shell script +certtool -i < $1|grep Fingerprint diff --git a/tools/iminternal.c b/tools/iminternal.c new file mode 100644 index 00000000..167e2b29 --- /dev/null +++ b/tools/iminternal.c @@ -0,0 +1,183 @@ +/* iminternal.c + * This file set implements the internal messages input module for rsyslog. + * Note: we currently do not have an input module spec, but + * we will have one in the future. This module needs then to be + * adapted. + * + * File begun on 2007-08-03 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "syslogd.h" +#include "linkedlist.h" +#include "iminternal.h" + +static linkedList_t llMsgs; + + +/* destructs an iminternal object + */ +static rsRetVal iminternalDestruct(iminternal_t *pThis) +{ + DEFiRet; + + assert(pThis != NULL); + + if(pThis->pMsg != NULL) + msgDestruct(&pThis->pMsg); + + free(pThis); + + RETiRet; +} + + +/* Construct an iminternal object + */ +static rsRetVal iminternalConstruct(iminternal_t **ppThis) +{ + DEFiRet; + iminternal_t *pThis; + + assert(ppThis != NULL); + + if((pThis = (iminternal_t*) calloc(1, sizeof(iminternal_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis != NULL) + iminternalDestruct(pThis); + } + + *ppThis = pThis; + + RETiRet; +} + + +/* add a message to the linked list + * Note: the pMsg reference counter is not incremented. Consequently, + * the caller must NOT decrement it. The caller actually hands over + * full ownership of the pMsg object. + * The interface of this function is modelled after syslogd/logmsg(), + * for which it is an "replacement". + */ +rsRetVal iminternalAddMsg(msg_t *pMsg) +{ + DEFiRet; + iminternal_t *pThis; + + assert(pMsg != NULL); + + CHKiRet(iminternalConstruct(&pThis)); + + pThis->pMsg = pMsg; + + CHKiRet(llAppend(&llMsgs, NULL, (void*) pThis)); + +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("iminternalAddMsg() error %d - can not otherwise report this error, message lost\n", iRet); + if(pThis != NULL) + iminternalDestruct(pThis); + } + + RETiRet; +} + + +/* pull the first error message from the linked list, remove it + * from the list and return it to the caller. The caller is + * responsible for freeing the message! + */ +rsRetVal iminternalRemoveMsg(msg_t **ppMsg) +{ + DEFiRet; + iminternal_t *pThis; + linkedListCookie_t llCookie = NULL; + + assert(ppMsg != NULL); + + CHKiRet(llGetNextElt(&llMsgs, &llCookie, (void*)&pThis)); + *ppMsg = pThis->pMsg; + pThis->pMsg = NULL; /* we do no longer own it - important for destructor */ + + if(llDestroyRootElt(&llMsgs) != RS_RET_OK) { + dbgprintf("Root element of iminternal linked list could not be destroyed - there is " + "nothing we can do against it, we ignore it for now. Things may go wild " + "from here on. This is most probably a program logic error.\n"); + } + +finalize_it: + RETiRet; +} + +/* tell the caller if we have any messages ready for processing. + * 0 means we have none, everything else means there is at least + * one message ready. + */ +rsRetVal iminternalHaveMsgReady(int* pbHaveOne) +{ + assert(pbHaveOne != NULL); + + return llGetNumElts(&llMsgs, pbHaveOne); +} + + +/* initialize the iminternal subsystem + * must be called once at the start of the program + */ +rsRetVal modInitIminternal(void) +{ + DEFiRet; + + iRet = llInit(&llMsgs, iminternalDestruct, NULL, NULL); + + RETiRet; +} + + +/* de-initialize the iminternal subsystem + * must be called once at the end of the program + * Note: the error list must have been pulled first. We do + * NOT care if there are any errors left - we simply destroy + * them. + */ +rsRetVal modExitIminternal(void) +{ + DEFiRet; + + iRet = llDestroy(&llMsgs); + + RETiRet; +} + +/* vim:set ai: + */ diff --git a/tools/iminternal.h b/tools/iminternal.h new file mode 100644 index 00000000..8a9e2506 --- /dev/null +++ b/tools/iminternal.h @@ -0,0 +1,47 @@ +/* Definition of the internal messages input module. + * + * Note: we currently do not have an input module spec, but + * we will have one in the future. This module needs then to be + * adapted. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#ifndef IMINTERNAL_H_INCLUDED +#define IMINTERNAL_H_INCLUDED +#include "template.h" + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct iminternal_s { /* config file sysline parse entry */ + msg_t *pMsg; /* the message (in all its glory) */ +}; +typedef struct iminternal_s iminternal_t; + +/* prototypes */ +rsRetVal modInitIminternal(void); +rsRetVal modExitIminternal(void); +rsRetVal iminternalAddMsg(msg_t *pMsg); +rsRetVal iminternalHaveMsgReady(int* pbHaveOne); +rsRetVal iminternalRemoveMsg(msg_t **ppMsg); + +#endif /* #ifndef IMINTERNAL_H_INCLUDED */ diff --git a/tools/logctl.c b/tools/logctl.c new file mode 100644 index 00000000..1ab8ead0 --- /dev/null +++ b/tools/logctl.c @@ -0,0 +1,456 @@ +/** + * logctl - a tool to access lumberjack logs in MongoDB + * ... and potentially other sources in the future. + * + * Copyright 2012 Ulrike Gerhards and Adiscon GmbH. + * + * long short + + * level l read records with level x + * severity s read records with severity x + * ret r number of records to return + * skip k number of records to skip + * sys y read records of system x + * msg m read records with message containing x + * datef f read records starting on time received x + * dateu u read records until time received x + * + * examples: + * + * logctl -f 15/05/2012-12:00:00 -u 15/05/2012-12:37:00 + * logctl -s 50 --ret 10 + * logctl -m "closed" + * logctl -l "INFO" + * logctl -s 3 + * logctl -y "ubuntu" + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <mongo.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <getopt.h> +#include <unistd.h> + +#define N 80 + +static struct option long_options[] = +{ + {"level", required_argument, NULL, 'l'}, + {"severity", required_argument, NULL, 's'}, + {"ret", required_argument, NULL, 'r'}, + {"skip", required_argument, NULL, 'k'}, + {"sys", required_argument, NULL, 'y'}, + {"msg", required_argument, NULL, 'm'}, + {"datef", required_argument, NULL, 'f'}, + {"dateu", required_argument, NULL, 'u'}, + {NULL, 0, NULL, 0} +}; + +struct queryopt +{ + gint32 e_sever; + gint32 e_ret; + gint32 e_skip; + char *e_date; + char *e_level; + char *e_msg; + char *e_sys; + char *e_dateu; + int bsever; + int blevel; + int bskip; + int bret; + int bsys; + int bmsg; + int bdate; + int bdatef; + int bdateu; +}; + +struct ofields +{ + const char *msg; + const char *syslog_tag; + const char *prog; + char *date; + gint64 date_r; +}; + +struct query_doc +{ + bson *query; +}; + +struct select_doc +{ + bson *select; +}; + +struct db_connect +{ + mongo_sync_connection *conn; +}; + +struct output +{ + mongo_packet *p; +}; + +struct db_cursor +{ + mongo_sync_cursor *cursor; +}; + +struct results +{ + bson *result; +}; + + + +void formater(struct ofields *fields) +{ + time_t rtime; + rtime = (time_t) (fields->date_r / 1000); + char str[N]; + strftime(str, N, "%b %d %H:%M:%S", gmtime(&rtime)); + printf("%s %s %s %s\n", str, fields->prog, fields->syslog_tag, fields->msg); + +} + +struct ofields* get_data(struct results *res) +{ + struct ofields *fields; + const char *msg; + const char *prog; + const char *syslog_tag; + gint64 date_r; + bson_cursor *c; + + fields = malloc(sizeof(struct ofields)); + + c = bson_find (res->result, "msg"); + if (!bson_cursor_get_string (c, &msg)) + { + perror ("bson_cursor_get_string()"); + exit (1); + } + bson_cursor_free (c); + + c = bson_find (res->result, "sys"); + if (!bson_cursor_get_string (c, &prog)) + { + perror ("bson_cursor_get_string()"); + exit (1); + } + bson_cursor_free (c); + + c = bson_find (res->result, "syslog_tag"); + if (!bson_cursor_get_string (c, &syslog_tag)) + { + perror ("bson_cursor_get_string()"); + exit (1); + } + bson_cursor_free (c); + + c = bson_find (res->result, "time_rcvd"); + if (!bson_cursor_get_utc_datetime (c, &date_r)) + { + perror ("bson_cursor_get_utc_datetime()"); + exit (1); + } + + bson_cursor_free (c); + fields->msg = msg; + fields->prog = prog; + fields->syslog_tag = syslog_tag; + fields->date_r = date_r; + + return fields; + +} + +void getoptions(int argc, char *argv[], struct queryopt *opt) +{ + int iarg; + while ((iarg = getopt_long(argc, argv, "l:s:r:k:y:f:u:m:", long_options, NULL)) != -1) + { + // check to see if a single character or long option came through + switch (iarg) + { + // short option 's' + case 's': + opt->bsever = 1; + opt->e_sever = atoi(optarg); + break; + // short option 'r' + case 'r': + opt->bret = 1; + opt->e_ret = atoi(optarg); + break; + // short option 'f' : date from + case 'f': + opt->bdate = 1; + opt->bdatef = 1; + opt->e_date = optarg; + break; + // short option 'u': date until + case 'u': + opt->bdate = 1; + opt->bdateu = 1; + opt->e_dateu = optarg; + break; + // short option 'k' + case 'k': + opt->bskip = 1; + opt->e_skip = atoi(optarg); + break; + // short option 'l' + case 'l': + opt->blevel = 1; + opt->e_level = optarg; + break; + // short option 'm' + case 'm': + opt->bmsg = 1; + opt->e_msg = optarg; + break; + // short option 'y' + case 'y': + opt->bsys = 1; + opt->e_sys = optarg; + break; + } // end switch iarg + } // end while + +} // end void getoptions + +struct select_doc* create_select() +// BSON object indicating the fields to return +{ + struct select_doc *s_doc; + s_doc = malloc(sizeof(struct select_doc)); + s_doc->select = bson_new (); + bson_append_string (s_doc->select, "syslog_tag", "s", -1); + bson_append_string (s_doc->select, "msg", "ERROR", -1); + bson_append_string (s_doc->select, "sys", "sys", -1); + bson_append_utc_datetime (s_doc->select, "time_rcvd", 1ll); + bson_finish (s_doc->select); + return s_doc; +} + +struct query_doc* create_query(struct queryopt *opt) +{ + struct query_doc *qu_doc; + bson *query_what, *order_what, *msg_what, *date_what; + struct tm tm; + time_t t; + gint64 ts; + qu_doc = malloc(sizeof(struct query_doc)); + qu_doc->query = bson_new (); + + query_what = bson_new (); + if (opt->bsever == 1) + { + bson_append_int32 (query_what, "syslog_sever", opt->e_sever); + } + if (opt->blevel == 1) + { + bson_append_string (query_what, "level", opt->e_level, -1); + } + + if (opt->bmsg == 1) + { + msg_what = bson_new (); + bson_append_string (msg_what, "$regex", opt->e_msg, -1); + bson_append_string (msg_what, "$options", "i", -1); + bson_finish (msg_what); + bson_append_document (query_what, "msg", msg_what); + } + + if (opt->bdate == 1) + { + date_what = bson_new (); + if (opt->bdatef == 1) + { + tm.tm_isdst = -1; + strptime(opt->e_date, "%d/%m/%Y-%H:%M:%S", &tm); + tm.tm_hour = tm.tm_hour + 1; + t = mktime(&tm); + ts = 1000 * (gint64) t; + + bson_append_utc_datetime (date_what,"$gt", ts) ; + } + + if (opt->bdateu == 1) + { + tm.tm_isdst = -1; + strptime(opt->e_dateu, "%d/%m/%Y-%H:%M:%S", &tm); + tm.tm_hour = tm.tm_hour +1; + t = mktime(&tm); + ts = 1000 * (gint64) t; + bson_append_utc_datetime (date_what,"$lt", ts); + + } + bson_finish (date_what); + bson_append_document (query_what, "time_rcvd", date_what); + } + + if (opt->bsys == 1) + { + bson_append_string (query_what, "sys", opt->e_sys, -1); + } + + bson_finish (query_what); + + order_what = bson_new (); + bson_append_utc_datetime (order_what, "time_rcvd", 1ll); + bson_finish (order_what); + + bson_append_document (qu_doc->query, "$query", query_what); + bson_append_document (qu_doc->query, "$orderby", order_what); + bson_finish (qu_doc->query); + bson_free (order_what); + return qu_doc; +} + +struct db_connect* create_conn() +{ + struct db_connect *db_conn; + db_conn = malloc(sizeof(struct db_connect)); + db_conn->conn = mongo_sync_connect ("localhost", 27017, TRUE); + + if (!db_conn->conn) + { + perror ("mongo_sync_connect()"); + exit (1); + } + return db_conn; +} + +void close_conn(struct db_connect *db_conn) +{ + mongo_sync_disconnect (db_conn->conn); +} + +void free_cursor(struct db_cursor *db_c) +{ + mongo_sync_cursor_free (db_c->cursor); +} + +struct output* launch_query(struct queryopt *opt, struct select_doc *s_doc, + struct query_doc *qu_doc, + struct db_connect *db_conn) +{ + struct output *out; + out = malloc(sizeof(struct output)); + out->p = mongo_sync_cmd_query (db_conn->conn, "syslog.log", 0, + opt->e_skip, opt->e_ret, qu_doc->query, s_doc->select); + if (!out->p) + { + perror ("mongo_sync_cmd_query()"); + printf("no records found\n"); + exit (1); + } + return out; +} + +struct db_cursor* open_cursor(struct db_connect *db_conn, struct output *out) +{ + struct db_cursor *db_c; + db_c = malloc(sizeof(struct db_cursor)); + + db_c->cursor = mongo_sync_cursor_new (db_conn->conn, "syslog.log", out->p); + if (!db_c->cursor) + { + perror ("mongo_sync_cursor_new()"); + exit (1); + } + return db_c; +} + +struct results* read_data(struct db_cursor *db_c) +{ + struct results *res; + res = malloc(sizeof(struct results)); + res->result = mongo_sync_cursor_get_data (db_c->cursor); + if (!res->result) + { + perror ("mongo_sync_cursor_get_data()"); + exit (1); + } + return res; +} + +gboolean cursor_next (struct db_cursor *db_c) +{ + if (!mongo_sync_cursor_next (db_c->cursor)) + return FALSE; + else + return TRUE; + +} + +int main (int argc, char *argv[]) +{ + + struct queryopt opt; + struct ofields *fields; + struct select_doc *s_doc; + struct query_doc *qu_doc; + struct db_connect *db_conn; + struct output *out; + struct db_cursor *db_c; + struct results *res; + + opt.e_skip = 0; // standard + opt.e_ret = 0; // standard + opt.bsever = 0; + opt.blevel = 0; + opt.bdate = 0; + opt.bdateu = 0; + opt.bdatef = 0; + opt.bmsg = 0; + opt.bskip = 0; + opt.bsys = 0; + + getoptions(argc, argv, &opt); + qu_doc = create_query(&opt); // crate query + s_doc = create_select(); + db_conn = create_conn(); // create connection + out = launch_query(&opt, s_doc, qu_doc, db_conn); // launch the query + db_c = open_cursor(db_conn, out); // open cursor + + while (cursor_next(db_c)) + { + res = read_data(db_c); + fields = get_data(res); + formater(fields); // formate output + free(fields); + } + + free_cursor(db_c); + close_conn(db_conn); + + return (0); +} diff --git a/tools/logsigner.c b/tools/logsigner.c new file mode 100644 index 00000000..f6887696 --- /dev/null +++ b/tools/logsigner.c @@ -0,0 +1,159 @@ +/* This is a tool for offline signing logfiles via the guardtime API. + * + * NOTE: this currently is only a PoC and WiP! NOT suitable for + * production use! + * + * Current hardcoded timestamper (use this if you do not have an + * idea of which one else to use): + * http://stamper.guardtime.net/gt-signingservice + * Check the GuardTime website for the URLs of nearest public services. + * + * Copyright 2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either exprs or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <gt_base.h> +#include <gt_http.h> + +#include "librsgt.h" + + +#if 0 +void +outputhash(GTDataHash *hash) +{ + int i; + for(i = 0 ; i < hash->digest_length ; ++i) + printf("%2.2x", hash->digest[i]); + printf("\n"); +} + +void +timestampIt(GTDataHash *hash) +{ + int r = GT_OK; + GTTimestamp *timestamp = NULL; + unsigned char *der = NULL; + char *sigFile = "logsigner.TIMESTAMP"; + size_t der_len; + + /* Get the timestamp. */ + r = GTHTTP_createTimestampHash(hash, + "http://stamper.guardtime.net/gt-signingservice", ×tamp); + + if(r != GT_OK) { + fprintf(stderr, "GTHTTP_createTimestampHash() failed: %d (%s)\n", + r, GTHTTP_getErrorString(r)); + goto done; + } + + /* Encode timestamp. */ + r = GTTimestamp_getDEREncoded(timestamp, &der, &der_len); + if(r != GT_OK) { + fprintf(stderr, "GTTimestamp_getDEREncoded() failed: %d (%s)\n", + r, GT_getErrorString(r)); + goto done; + } + + /* Save DER-encoded timestamp to file. */ + r = GT_saveFile(sigFile, der, der_len); + if(r != GT_OK) { + fprintf(stderr, "Cannot save timestamp to file %s: %d (%s)\n", + sigFile, r, GT_getErrorString(r)); + if(r == GT_IO_ERROR) { + fprintf(stderr, "\t%d (%s)\n", errno, strerror(errno)); + } + goto done; + } + printf("Timestamping succeeded!\n"); +done: + GT_free(der); + GTTimestamp_free(timestamp); +} + + +void +sign(const char *buf, const size_t len) +{ + int r; + GTDataHash *hash = NULL; + + printf("hash for '%s' is ", buf); + r = GTDataHash_create(GT_HASHALG_SHA256, (const unsigned char*)buf, len, &hash); + if(r != GT_OK) { + fprintf(stderr, "GTTDataHash_create() failed: %d (%s)\n", + r, GT_getErrorString(r)); + goto done; + } + outputhash(hash); + timestampIt(hash); /* of course, this needs to be moved to once at end ;) */ +done: GTDataHash_free(hash); +} +#endif + +void +processFile(char *name) +{ + FILE *fp; + size_t len; + char line[64*1024+1]; + gtctx ctx = NULL; + + ctx = rsgtCtxNew((unsigned char*)"SIGFILE", GT_HASHALG_SHA256); + sigblkInit(ctx); + if(!strcmp(name, "-")) + fp = stdin; + else + fp = fopen(name, "r"); + + while(1) { + if(fgets(line, sizeof(line), fp) == NULL) { + if(!feof(fp)) + perror(name); + break; + } + len = strlen(line); + if(line[len-1] == '\n') { + --len; + line[len] = '\0'; + } + //sign(line, len); + sigblkAddRecord(ctx, (unsigned char*)line, len); + } + + if(fp != stdin) + fclose(fp); + sigblkFinish(ctx); + rsgtCtxDel(ctx); +} + + +int +main(int argc, char *argv[]) +{ + rsgtInit("rsyslog logsigner " VERSION); + processFile("-"); + rsgtExit(); + return 0; +} diff --git a/tools/msggen.c b/tools/msggen.c new file mode 100644 index 00000000..29ade3a7 --- /dev/null +++ b/tools/msggen.c @@ -0,0 +1,39 @@ +/* msggen - a small diagnostic utility that does very quick + * syslog() calls. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <syslog.h> + +int main(int __attribute__((unused)) argc, char __attribute__((unused)) *argv[]) +{ + int i; + + openlog("msggen", 0 , LOG_LOCAL0); + + for(i = 0 ; i < 10 ; ++i) + syslog(LOG_NOTICE, "This is message number %d", i); + + closelog(); + return 0; +} diff --git a/tools/omdiscard.c b/tools/omdiscard.c new file mode 100644 index 00000000..15c6ea82 --- /dev/null +++ b/tools/omdiscard.c @@ -0,0 +1,128 @@ +/* omdiscard.c + * This is the implementation of the built-in discard output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-24 by RGerhards + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "syslogd.h" +#include "syslogd-types.h" +#include "omdiscard.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg); + +typedef struct _instanceData { + EMPTY_STRUCT +} instanceData; + +/* we do not need a createInstance()! +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance +*/ + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* do nothing */ +ENDdbgPrintInstInfo + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we are not compatible with repeated msg reduction feature, so do not allow it */ +ENDisCompatibleWithFeature + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + iRet = RS_RET_DISCARDMSG; +ENDdoAction + + +BEGINfreeInstance +CODESTARTfreeInstance + /* we do not have instance data, so we do not need to + * do anything here. -- rgerhards, 2007-07-25 + */ +ENDfreeInstance + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(0) + pData = NULL; /* this action does not have any instance data */ + p = *pp; + + if(*p == '~') { + dbgprintf("discard\n"); + errmsg.LogError(0, RS_RET_DEPRECATED, "warning: ~ action " + "is deprecated, consider using the 'stop' " + "statement instead"); + } else { + iRet = RS_RET_CONFLINE_UNPROCESSED; + } +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(Discard) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/tools/omdiscard.h b/tools/omdiscard.h new file mode 100644 index 00000000..505ff85f --- /dev/null +++ b/tools/omdiscard.h @@ -0,0 +1,32 @@ +/* omdiscard.h + * These are the definitions for the built-in discard output module. + * + * File begun on 2007-07-24 by RGerhards + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OMDISCARD_H_INCLUDED +#define OMDISCARD_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitDiscard(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMDISCARD_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/tools/omfile.c b/tools/omfile.c new file mode 100644 index 00000000..2ebb7df9 --- /dev/null +++ b/tools/omfile.c @@ -0,0 +1,1392 @@ +/* omfile.c + * This is the implementation of the build-in file output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c, which + * at the time of the fork from sysklogd was under BSD license) + * + * A large re-write of this file was done in June, 2009. The focus was + * to introduce many more features (like zipped writing), clean up the code + * and make it more reliable. In short, that rewrite tries to provide a new + * solid basis for the next three to five years to come. During it, bugs + * may have been introduced ;) -- rgerhards, 2009-06-04 + * + * Note that as of 2010-02-28 this module does no longer handle + * pipes. These have been moved to ompipe, to reduced the entanglement + * between the two different functionalities. -- rgerhards + * + * Copyright 2007-2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <libgen.h> +#include <unistd.h> +#include <sys/file.h> +#ifdef OS_SOLARIS +# include <fcntl.h> +#endif +#ifdef HAVE_ATOMIC_BUILTINS +# include <pthread.h> +#endif + + +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "outchannel.h" +#include "omfile.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" +#include "stream.h" +#include "unicode-helper.h" +#include "atomic.h" +#include "statsobj.h" +#include "sigprov.h" +#include "cryprov.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omfile") + +/* forward definitions */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(strm) +DEFobjCurrIf(statsobj) + +/* for our current LRU mechanism, we need a monotonically increasing counters. We use + * it much like a "Lamport logical clock": we do not need the actual time, we just need + * to know the sequence in which files were accessed. So we use a simple counter to + * create that sequence. We use an unsigned 64 bit value which is extremely unlike to + * wrap within the lifetime of a process. If we process 1,000,000 file writes per + * second, the process could still exist over 500,000 years before a wrap to 0 happens. + * That should be sufficient (and even than, there would no really bad effect ;)). + * The variable below is the global counter/clock. + */ +#if HAVE_ATOMIC_BUILTINS_64BIT +static uint64 clockFileAccess = 0; +#else +static unsigned clockFileAccess = 0; +#endif +/* and the "tick" function */ +#ifndef HAVE_ATOMIC_BUILTINS +static pthread_mutex_t mutClock; +#endif +static inline uint64 +getClockFileAccess(void) +{ +#if HAVE_ATOMIC_BUILTINS_64BIT + return ATOMIC_INC_AND_FETCH_uint64(&clockFileAccess, &mutClock); +#else + return ATOMIC_INC_AND_FETCH_unsigned(&clockFileAccess, &mutClock); +#endif +} + + +/* The following structure is a dynafile name cache entry. + */ +struct s_dynaFileCacheEntry { + uchar *pName; /* name currently open, if dynamic name */ + strm_t *pStrm; /* our output stream */ + void *sigprovFileData; /* opaque data ptr for provider use */ + uint64 clkTickAccessed;/* for LRU - based on clockFileAccess */ +}; +typedef struct s_dynaFileCacheEntry dynaFileCacheEntry; + + +#define IOBUF_DFLT_SIZE 4096 /* default size for io buffers */ +#define FLUSH_INTRVL_DFLT 1 /* default buffer flush interval (in seconds) */ +#define USE_ASYNCWRITER_DFLT 0 /* default buffer use async writer */ +#define FLUSHONTX_DFLT 1 /* default for flush on TX end */ + + +typedef struct _instanceData { + uchar *f_fname; /* file or template name (display only) */ + uchar *tplName; /* name of assigned template */ + strm_t *pStrm; /* our output stream */ + char bDynamicName; /* 0 - static name, 1 - dynamic name (with properties) */ + int fCreateMode; /* file creation mode for open() */ + int fDirCreateMode; /* creation mode for mkdir() */ + int bCreateDirs; /* auto-create directories? */ + int bSyncFile; /* should the file by sync()'ed? 1- yes, 0- no */ + uid_t fileUID; /* IDs for creation */ + uid_t dirUID; + gid_t fileGID; + gid_t dirGID; + int bFailOnChown; /* fail creation if chown fails? */ + uchar *sigprovName; /* signature provider */ + uchar *sigprovNameFull;/* full internal signature provider name */ + sigprov_if_t sigprov; /* ptr to signature provider interface */ + void *sigprovData; /* opaque data ptr for provider use */ + void *sigprovFileData;/* opaque data ptr for file instance */ + sbool useSigprov; /* quicker than checkig ptr (1 vs 8 bytes!) */ + uchar *cryprovName; /* crypto provider */ + uchar *cryprovNameFull;/* full internal crypto provider name */ + void *cryprovData; /* opaque data ptr for provider use */ + cryprov_if_t cryprov; /* ptr to crypto provider interface */ + sbool useCryprov; /* quicker than checkig ptr (1 vs 8 bytes!) */ + int iCurrElt; /* currently active cache element (-1 = none) */ + int iCurrCacheSize; /* currently cache size (1-based) */ + int iDynaFileCacheSize; /* size of file handle cache */ + /* The cache is implemented as an array. An empty element is indicated + * by a NULL pointer. Memory is allocated as needed. The following + * pointer points to the overall structure. + */ + dynaFileCacheEntry **dynCache; + off_t iSizeLimit; /* file size limit, 0 = no limit */ + uchar *pszSizeLimitCmd; /* command to carry out when size limit is reached */ + int iZipLevel; /* zip mode to use for this selector */ + int iIOBufSize; /* size of associated io buffer */ + int iFlushInterval; /* how fast flush buffer on inactivity? */ + sbool bFlushOnTXEnd; /* flush write buffers when transaction has ended? */ + sbool bUseAsyncWriter; /* use async stream writer? */ + sbool bVeryRobustZip; + statsobj_t *stats; /* dynafile, primarily cache stats */ + STATSCOUNTER_DEF(ctrRequests, mutCtrRequests); + STATSCOUNTER_DEF(ctrLevel0, mutCtrLevel0); + STATSCOUNTER_DEF(ctrEvict, mutCtrEvict); + STATSCOUNTER_DEF(ctrMiss, mutCtrMiss); + STATSCOUNTER_DEF(ctrMax, mutCtrMax); +} instanceData; + + +typedef struct configSettings_s { + int iDynaFileCacheSize; /* max cache for dynamic files */ + int fCreateMode; /* mode to use when creating files */ + int fDirCreateMode; /* mode to use when creating files */ + int bFailOnChown; /* fail if chown fails? */ + uid_t fileUID; /* UID to be used for newly created files */ + uid_t fileGID; /* GID to be used for newly created files */ + uid_t dirUID; /* UID to be used for newly created directories */ + uid_t dirGID; /* GID to be used for newly created directories */ + int bCreateDirs;/* auto-create directories for dynaFiles: 0 - no, 1 - yes */ + int bEnableSync;/* enable syncing of files (no dash in front of pathname in conf): 0 - no, 1 - yes */ + int iZipLevel; /* zip compression mode (0..9 as usual) */ + sbool bFlushOnTXEnd;/* flush write buffers when transaction has ended? */ + int64 iIOBufSize; /* size of an io buffer */ + int iFlushInterval; /* how often flush the output buffer on inactivity? */ + int bUseAsyncWriter; /* should we enable asynchronous writing? */ + EMPTY_STRUCT +} configSettings_t; +static configSettings_t cs; +uchar *pszFileDfltTplName; /* name of the default template to use */ + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + uchar *tplName; /* default template */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + +/* tables for interfacing with the v6 config system */ +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "template", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "dynafilecachesize", eCmdHdlrInt, 0 }, /* legacy: dynafilecachesize */ + { "ziplevel", eCmdHdlrInt, 0 }, /* legacy: omfileziplevel */ + { "flushinterval", eCmdHdlrInt, 0 }, /* legacy: omfileflushinterval */ + { "asyncwriting", eCmdHdlrBinary, 0 }, /* legacy: omfileasyncwriting */ + { "veryrobustzip", eCmdHdlrBinary, 0 }, + { "flushontxend", eCmdHdlrBinary, 0 }, /* legacy: omfileflushontxend */ + { "iobuffersize", eCmdHdlrSize, 0 }, /* legacy: omfileiobuffersize */ + { "dirowner", eCmdHdlrUID, 0 }, /* legacy: dirowner */ + { "dirgroup", eCmdHdlrGID, 0 }, /* legacy: dirgroup */ + { "fileowner", eCmdHdlrUID, 0 }, /* legacy: fileowner */ + { "filegroup", eCmdHdlrGID, 0 }, /* legacy: filegroup */ + { "dircreatemode", eCmdHdlrFileCreateMode, 0 }, /* legacy: dircreatemode */ + { "filecreatemode", eCmdHdlrFileCreateMode, 0 }, /* legacy: filecreatemode */ + { "failonchownfailure", eCmdHdlrBinary, 0 }, /* legacy: failonchownfailure */ + { "createdirs", eCmdHdlrBinary, 0 }, /* legacy: createdirs */ + { "sync", eCmdHdlrBinary, 0 }, /* legacy: actionfileenablesync */ + { "file", eCmdHdlrString, 0 }, /* either "file" or ... */ + { "dynafile", eCmdHdlrString, 0 }, /* "dynafile" MUST be present */ + { "sig.provider", eCmdHdlrGetWord, 0 }, + { "cry.provider", eCmdHdlrGetWord, 0 }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + + +/* this function gets the default template. It coordinates action between + * old-style and new-style configuration parts. + */ +static inline uchar* +getDfltTpl(void) +{ + if(loadModConf != NULL && loadModConf->tplName != NULL) + return loadModConf->tplName; + else if(pszFileDfltTplName == NULL) + return (uchar*)"RSYSLOG_FileFormat"; + else + return pszFileDfltTplName; +} + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + pszFileDfltTplName = NULL; /* make sure this can be free'ed! */ + iRet = resetConfigVariables(NULL, NULL); /* params are dummies */ +ENDinitConfVars + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + if(pData->bDynamicName) { + dbgprintf("[dynamic]\n"); + } else { /* regular file */ + dbgprintf("%s%s\n", pData->f_fname, + (pData->pStrm == NULL) ? " (closed)" : ""); + } + + dbgprintf("\ttemplate='%s'\n", pData->f_fname); + dbgprintf("\tuse async writer=%d\n", pData->bUseAsyncWriter); + dbgprintf("\tflush on TX end=%d\n", pData->bFlushOnTXEnd); + dbgprintf("\tflush interval=%d\n", pData->iFlushInterval); + dbgprintf("\tfile cache size=%d\n", pData->iDynaFileCacheSize); + dbgprintf("\tcreate directories: %s\n", pData->bCreateDirs ? "on" : "off"); + dbgprintf("\tvery robust zip: %s\n", pData->bCreateDirs ? "on" : "off"); + dbgprintf("\tfile owner %d, group %d\n", (int) pData->fileUID, (int) pData->fileGID); + dbgprintf("\tdirectory owner %d, group %d\n", (int) pData->dirUID, (int) pData->dirGID); + dbgprintf("\tdir create mode 0%3.3o, file create mode 0%3.3o\n", + pData->fDirCreateMode, pData->fCreateMode); + dbgprintf("\tfail if owner/group can not be set: %s\n", pData->bFailOnChown ? "yes" : "no"); +ENDdbgPrintInstInfo + + + +/* set the default template to be used + * This is a module-global parameter, and as such needs special handling. It needs to + * be coordinated with values set via the v2 config system (rsyslog v6+). What we do + * is we do not permit this directive after the v2 config system has been used to set + * the parameter. + */ +rsRetVal +setLegacyDfltTpl(void __attribute__((unused)) *pVal, uchar* newVal) +{ + DEFiRet; + + if(loadModConf != NULL && loadModConf->tplName != NULL) { + free(newVal); + errmsg.LogError(0, RS_RET_ERR, "omfile: default template already set via module " + "global parameter - can no longer be changed"); + ABORT_FINALIZE(RS_RET_ERR); + } + free(pszFileDfltTplName); + pszFileDfltTplName = newVal; +finalize_it: + RETiRet; +} + + +/* set the dynaFile cache size. Does some limit checking. + * rgerhards, 2007-07-31 + */ +rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal) +{ + DEFiRet; + uchar errMsg[128]; /* for dynamic error messages */ + + if(iNewVal < 1) { + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "DynaFileCacheSize must be greater 0 (%d given), changed to 1.", iNewVal); + errno = 0; + errmsg.LogError(0, RS_RET_VAL_OUT_OF_RANGE, "%s", errMsg); + iRet = RS_RET_VAL_OUT_OF_RANGE; + iNewVal = 1; + } else if(iNewVal > 1000) { + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "DynaFileCacheSize maximum is 1,000 (%d given), changed to 1,000.", iNewVal); + errno = 0; + errmsg.LogError(0, RS_RET_VAL_OUT_OF_RANGE, "%s", errMsg); + iRet = RS_RET_VAL_OUT_OF_RANGE; + iNewVal = 1000; + } + + cs.iDynaFileCacheSize = iNewVal; + DBGPRINTF("DynaFileCacheSize changed to %d.\n", iNewVal); + + RETiRet; +} + + +/* Helper to cfline(). Parses a output channel name up until the first + * comma and then looks for the template specifier. Tries + * to find that template. Maps the output channel to the + * proper filed structure settings. Everything is stored in the + * filed struct. Over time, the dependency on filed might be + * removed. + * rgerhards 2005-06-21 + */ +static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts) +{ + DEFiRet; + size_t i; + struct outchannel *pOch; + char szBuf[128]; /* should be more than sufficient */ + + ++p; /* skip '$' */ + i = 0; + /* get outchannel name */ + while(*p && *p != ';' && *p != ' ' && + i < sizeof(szBuf) / sizeof(char)) { + szBuf[i++] = *p++; + } + szBuf[i] = '\0'; + + /* got the name, now look up the channel... */ + pOch = ochFind(szBuf, i); + + if(pOch == NULL) { + char errMsg[128]; + errno = 0; + snprintf(errMsg, sizeof(errMsg)/sizeof(char), + "outchannel '%s' not found - ignoring action line", + szBuf); + errmsg.LogError(0, RS_RET_NOT_FOUND, "%s", errMsg); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + /* check if there is a file name in the outchannel... */ + if(pOch->pszFileTemplate == NULL) { + char errMsg[128]; + errno = 0; + snprintf(errMsg, sizeof(errMsg)/sizeof(char), + "outchannel '%s' has no file name template - ignoring action line", + szBuf); + errmsg.LogError(0, RS_RET_ERR, "%s", errMsg); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* OK, we finally got a correct template. So let's use it... */ + pData->f_fname = ustrdup(pOch->pszFileTemplate); + pData->iSizeLimit = pOch->uSizeLimit; + /* WARNING: It is dangerous "just" to pass the pointer. As we + * never rebuild the output channel description, this is acceptable here. + */ + pData->pszSizeLimitCmd = pOch->cmdOnSizeLimit; + + iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, getDfltTpl()); + +finalize_it: + RETiRet; +} + + +/* This function deletes an entry from the dynamic file name + * cache. A pointer to the cache must be passed in as well + * as the index of the to-be-deleted entry. This index may + * point to an unallocated entry, in whcih case the + * function immediately returns. Parameter bFreeEntry is 1 + * if the entry should be d_free()ed and 0 if not. + */ +static rsRetVal +dynaFileDelCacheEntry(instanceData *pData, int iEntry, int bFreeEntry) +{ + dynaFileCacheEntry **pCache = pData->dynCache; + DEFiRet; + ASSERT(pCache != NULL); + + if(pCache[iEntry] == NULL) + FINALIZE; + + DBGPRINTF("Removing entry %d for file '%s' from dynaCache.\n", iEntry, + pCache[iEntry]->pName == NULL ? UCHAR_CONSTANT("[OPEN FAILED]") : pCache[iEntry]->pName); + + if(pCache[iEntry]->pName != NULL) { + d_free(pCache[iEntry]->pName); + pCache[iEntry]->pName = NULL; + } + + if(pCache[iEntry]->pStrm != NULL) { + strm.Destruct(&pCache[iEntry]->pStrm); + if(pData->useSigprov) { + pData->sigprov.OnFileClose(pCache[iEntry]->sigprovFileData); + pCache[iEntry]->sigprovFileData = NULL; + } + } + + if(bFreeEntry) { + d_free(pCache[iEntry]); + pCache[iEntry] = NULL; + } + +finalize_it: + RETiRet; +} + + +/* This function frees all dynamic file name cache entries and closes the + * relevant files. Part of Shutdown and HUP processing. + * rgerhards, 2008-10-23 + */ +static inline void +dynaFileFreeCacheEntries(instanceData *pData) +{ + register int i; + ASSERT(pData != NULL); + + BEGINfunc; + for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { + dynaFileDelCacheEntry(pData, i, 1); + } + pData->iCurrElt = -1; /* invalidate current element */ + ENDfunc; +} + + +/* This function frees the dynamic file name cache. + */ +static void dynaFileFreeCache(instanceData *pData) +{ + ASSERT(pData != NULL); + + BEGINfunc; + dynaFileFreeCacheEntries(pData); + if(pData->dynCache != NULL) + d_free(pData->dynCache); + ENDfunc; +} + + +/* close current file */ +static rsRetVal +closeFile(instanceData *pData) +{ + DEFiRet; + if(pData->useSigprov) { + pData->sigprov.OnFileClose(pData->sigprovFileData); + pData->sigprovFileData = NULL; + } + strm.Destruct(&pData->pStrm); + RETiRet; +} + + +/* This prepares the signature provider to process a file */ +static rsRetVal +sigprovPrepare(instanceData *pData, uchar *fn) +{ + DEFiRet; + pData->sigprov.OnFileOpen(pData->sigprovData, fn, &pData->sigprovFileData); + RETiRet; +} + +/* This is now shared code for all types of files. It simply prepares + * file access, which, among others, means the the file wil be opened + * and any directories in between will be created (based on config, of + * course). -- rgerhards, 2008-10-22 + * changed to iRet interface - 2009-03-19 + */ +static rsRetVal +prepareFile(instanceData *pData, uchar *newFileName) +{ + int fd; + DEFiRet; + + pData->pStrm = NULL; + if(access((char*)newFileName, F_OK) != 0) { + /* file does not exist, create it (and eventually parent directories */ + if(pData->bCreateDirs) { + /* We first need to create parent dirs if they are missing. + * We do not report any errors here ourselfs but let the code + * fall through to error handler below. + */ + if(makeFileParentDirs(newFileName, ustrlen(newFileName), + pData->fDirCreateMode, pData->dirUID, + pData->dirGID, pData->bFailOnChown) != 0) { + ABORT_FINALIZE(RS_RET_ERR); /* we give up */ + } + } + /* no matter if we needed to create directories or not, we now try to create + * the file. -- rgerhards, 2008-12-18 (based on patch from William Tisater) + */ + fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY|O_CLOEXEC, + pData->fCreateMode); + if(fd != -1) { + /* check and set uid/gid */ + if(pData->fileUID != (uid_t)-1 || pData->fileGID != (gid_t) -1) { + /* we need to set owner/group */ + if(fchown(fd, pData->fileUID, pData->fileGID) != 0) { + if(pData->bFailOnChown) { + int eSave = errno; + close(fd); + fd = -1; + errno = eSave; + } + /* we will silently ignore the chown() failure + * if configured to do so. + */ + } + } + close(fd); /* close again, as we need a stream further on */ + } + } + + /* the copies below are clumpsy, but there is no way around given the + * anomalies in dirname() and basename() [they MODIFY the provided buffer...] + */ + uchar szNameBuf[MAXFNAME]; + uchar szDirName[MAXFNAME]; + uchar szBaseName[MAXFNAME]; + ustrncpy(szNameBuf, newFileName, MAXFNAME); + ustrncpy(szDirName, (uchar*)dirname((char*)szNameBuf), MAXFNAME); + ustrncpy(szNameBuf, newFileName, MAXFNAME); + ustrncpy(szBaseName, (uchar*)basename((char*)szNameBuf), MAXFNAME); + + CHKiRet(strm.Construct(&pData->pStrm)); + CHKiRet(strm.SetFName(pData->pStrm, szBaseName, ustrlen(szBaseName))); + CHKiRet(strm.SetDir(pData->pStrm, szDirName, ustrlen(szDirName))); + CHKiRet(strm.SetiZipLevel(pData->pStrm, pData->iZipLevel)); + CHKiRet(strm.SetbVeryReliableZip(pData->pStrm, pData->bVeryRobustZip)); + CHKiRet(strm.SetsIOBufSize(pData->pStrm, (size_t) pData->iIOBufSize)); + CHKiRet(strm.SettOperationsMode(pData->pStrm, STREAMMODE_WRITE_APPEND)); + CHKiRet(strm.SettOpenMode(pData->pStrm, cs.fCreateMode)); + CHKiRet(strm.SetbSync(pData->pStrm, pData->bSyncFile)); + CHKiRet(strm.SetsType(pData->pStrm, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strm.SetiSizeLimit(pData->pStrm, pData->iSizeLimit)); + if(pData->useCryprov) { + CHKiRet(strm.Setcryprov(pData->pStrm, &pData->cryprov)); + CHKiRet(strm.SetcryprovData(pData->pStrm, pData->cryprovData)); + } + /* set the flush interval only if we actually use it - otherwise it will activate + * async processing, which is a real performance waste if we do not do buffered + * writes! -- rgerhards, 2009-07-06 + */ + if(pData->bUseAsyncWriter) + CHKiRet(strm.SetiFlushInterval(pData->pStrm, pData->iFlushInterval)); + if(pData->pszSizeLimitCmd != NULL) + CHKiRet(strm.SetpszSizeLimitCmd(pData->pStrm, ustrdup(pData->pszSizeLimitCmd))); + CHKiRet(strm.ConstructFinalize(pData->pStrm)); + + if(pData->useSigprov) + sigprovPrepare(pData, szNameBuf); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pData->pStrm != NULL) { + closeFile(pData); + } + } + RETiRet; +} + + +/* This function handles dynamic file names. It checks if the + * requested file name is already open and, if not, does everything + * needed to switch to the it. + * Function returns 0 if all went well and non-zero otherwise. + * On successful return pData->fd must point to the correct file to + * be written. + * This is a helper to writeFile(). rgerhards, 2007-07-03 + */ +static inline rsRetVal +prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) +{ + uint64 ctOldest; /* "timestamp" of oldest element */ + int iOldest; + int i; + int iFirstFree; + rsRetVal localRet; + dynaFileCacheEntry **pCache; + DEFiRet; + + ASSERT(pData != NULL); + ASSERT(newFileName != NULL); + + pCache = pData->dynCache; + + /* first check, if we still have the current file */ + if( (pData->iCurrElt != -1) + && !ustrcmp(newFileName, pCache[pData->iCurrElt]->pName)) { + /* great, we are all set */ + pCache[pData->iCurrElt]->clkTickAccessed = getClockFileAccess(); + STATSCOUNTER_INC(pData->ctrLevel0, pData->mutCtrLevel0); + /* LRU needs only a strictly monotonically increasing counter, so such a one could do */ + FINALIZE; + } + + /* ok, no luck. Now let's search the table if we find a matching spot. + * While doing so, we also prepare for creation of a new one. + */ + pData->iCurrElt = -1; /* invalid current element pointer */ + iFirstFree = -1; /* not yet found */ + iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */ + ctOldest = getClockFileAccess(); /* there must always be an older one */ + for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { + if(pCache[i] == NULL || pCache[i]->pName == NULL) { + if(iFirstFree == -1) + iFirstFree = i; + } else { /* got an element, let's see if it matches */ + if(!ustrcmp(newFileName, pCache[i]->pName)) { + /* we found our element! */ + pData->pStrm = pCache[i]->pStrm; + if(pData->useSigprov) + pData->sigprovFileData = pCache[i]->sigprovFileData; + pData->iCurrElt = i; + pCache[i]->clkTickAccessed = getClockFileAccess(); /* update "timestamp" for LRU */ + FINALIZE; + } + /* did not find it - so lets keep track of the counters for LRU */ + if(pCache[i]->clkTickAccessed < ctOldest) { + ctOldest = pCache[i]->clkTickAccessed; + iOldest = i; + } + } + } + + /* we have not found an entry */ + STATSCOUNTER_INC(pData->ctrMiss, pData->mutCtrMiss); + + /* invalidate iCurrElt as we may error-exit out of this function when the currrent + * iCurrElt has been freed or otherwise become unusable. This is a precaution, and + * performance-wise it may be better to do that in each of the exits. However, that + * is error-prone, so I prefer to do it here. -- rgerhards, 2010-03-02 + */ + pData->iCurrElt = -1; + /* similarly, we need to set the current pStrm to NULL, because otherwise, if prepareFile() fails, + * we may end up using an old stream. This bug depends on how exactly prepareFile fails, + * but it could be triggered in the common case of a failed open() system call. + * rgerhards, 2010-03-22 + */ + pData->pStrm = NULL, pData->sigprovFileData = NULL; + + if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) { + /* there is space left, so set it to that index */ + iFirstFree = pData->iCurrCacheSize++; + STATSCOUNTER_SETMAX_NOMUT(pData->ctrMax, (unsigned) pData->iCurrCacheSize); + } + + /* Note that the following code sequence does not work with the cache entry itself, + * but rather with pData->pStrm, the (sole) stream pointer in the non-dynafile case. + * The cache array is only updated after the open was successful. -- rgerhards, 2010-03-21 + */ + if(iFirstFree == -1) { + dynaFileDelCacheEntry(pData, iOldest, 0); + STATSCOUNTER_INC(pData->ctrEvict, pData->mutCtrEvict); + iFirstFree = iOldest; /* this one *is* now free ;) */ + } else { + /* we need to allocate memory for the cache structure */ + CHKmalloc(pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry))); + } + + /* Ok, we finally can open the file */ + localRet = prepareFile(pData, newFileName); /* ignore exact error, we check fd below */ + + /* check if we had an error */ + if(localRet != RS_RET_OK) { + /* do not report anything if the message is an internally-generated + * message. Otherwise, we could run into a never-ending loop. The bad + * news is that we also lose errors on startup messages, but so it is. + */ + if(iMsgOpts & INTERNAL_MSG) { + DBGPRINTF("Could not open dynaFile '%s', state %d, discarding message\n", + newFileName, localRet); + } else { + errmsg.LogError(0, localRet, "Could not open dynamic file '%s' [state %d] - discarding message", newFileName, localRet); + } + ABORT_FINALIZE(localRet); + } + + if((pCache[iFirstFree]->pName = ustrdup(newFileName)) == NULL) { + closeFile(pData); /* need to free failed entry! */ + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + pCache[iFirstFree]->pStrm = pData->pStrm; + if(pData->useSigprov) + pCache[iFirstFree]->sigprovFileData = pData->sigprovFileData; + pCache[iFirstFree]->clkTickAccessed = getClockFileAccess(); + pData->iCurrElt = iFirstFree; + DBGPRINTF("Added new entry %d for file cache, file '%s'.\n", iFirstFree, newFileName); + +finalize_it: + RETiRet; +} + + +/* do the actual write process. This function is to be called once we are ready for writing. + * It will do buffered writes and persist data only when the buffer is full. Note that we must + * be careful to detect when the file handle changed. + * rgerhards, 2009-06-03 + */ +static rsRetVal +doWrite(instanceData *pData, uchar *pszBuf, int lenBuf) +{ + DEFiRet; + ASSERT(pData != NULL); + ASSERT(pszBuf != NULL); + + DBGPRINTF("write to stream, pData->pStrm %p, lenBuf %d\n", pData->pStrm, lenBuf); + if(pData->pStrm != NULL){ + CHKiRet(strm.Write(pData->pStrm, pszBuf, lenBuf)); + if(pData->useSigprov) { + CHKiRet(pData->sigprov.OnRecordWrite(pData->sigprovFileData, pszBuf, lenBuf)); + } + } + +finalize_it: + RETiRet; +} + + +/* rgerhards 2004-11-11: write to a file output. */ +static rsRetVal +writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData) +{ + DEFiRet; + + ASSERT(pData != NULL); + + /* first check if we have a dynamic file name and, if so, + * check if it still is ok or a new file needs to be created + */ + if(pData->bDynamicName) { + CHKiRet(prepareDynFile(pData, ppString[1], iMsgOpts)); + } else { /* "regular", non-dynafile */ + if(pData->pStrm == NULL) { + CHKiRet(prepareFile(pData, pData->f_fname)); + if(pData->pStrm == NULL) { + errmsg.LogError(0, RS_RET_NO_FILE_ACCESS, "Could no open output file '%s'", pData->f_fname); + } + } + } + + CHKiRet(doWrite(pData, ppString[0], strlen(CHAR_CONVERT(ppString[0])))); + +finalize_it: + RETiRet; +} + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->tplName = NULL; +ENDbeginCnfLoad + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for omfile:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "template")) { + loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + if(pszFileDfltTplName != NULL) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "omfile: warning: default template " + "was already set via legacy directive - may lead to inconsistent " + "results."); + } + } else { + dbgprintf("omfile: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(pszFileDfltTplName); + pszFileDfltTplName = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf + free(pModConf->tplName); +ENDfreeCnf + + +BEGINcreateInstance +CODESTARTcreateInstance + pData->pStrm = NULL; +ENDcreateInstance + + +BEGINfreeInstance +CODESTARTfreeInstance + free(pData->tplName); + free(pData->f_fname); + if(pData->bDynamicName) { + dynaFileFreeCache(pData); + } else if(pData->pStrm != NULL) + closeFile(pData); + if(pData->useSigprov) { + pData->sigprov.Destruct(&pData->sigprovData); + obj.ReleaseObj(__FILE__, pData->sigprovNameFull+2, pData->sigprovNameFull, + (void*) &pData->sigprov); + free(pData->sigprovName); + free(pData->sigprovNameFull); + } + if(pData->useCryprov) { + pData->cryprov.Destruct(&pData->cryprovData); + obj.ReleaseObj(__FILE__, pData->cryprovNameFull+2, pData->cryprovNameFull, + (void*) &pData->cryprov); + free(pData->cryprovName); + free(pData->cryprovNameFull); + } +ENDfreeInstance + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINbeginTransaction +CODESTARTbeginTransaction + /* we have nothing to do to begin a transaction */ +ENDbeginTransaction + + +BEGINendTransaction +CODESTARTendTransaction + /* Note: pStrm may be NULL if there was an error opening the stream */ + if(pData->bFlushOnTXEnd && pData->pStrm != NULL) { + /* if we have an async writer, it controls the flush via + * a timeout. However, without it, we actually need to flush, + * else incomplete records are written. + */ + if(!pData->bUseAsyncWriter) + CHKiRet(strm.Flush(pData->pStrm)); + } +finalize_it: +ENDendTransaction + + +BEGINdoAction +CODESTARTdoAction + DBGPRINTF("file to log to: %s\n", + (pData->bDynamicName) ? ppString[1] : pData->f_fname); + DBGPRINTF("omfile: start of data: '%.128s'\n", ppString[0]); + STATSCOUNTER_INC(pData->ctrRequests, pData->mutCtrRequests); + CHKiRet(writeFile(ppString, iMsgOpts, pData)); + if(!bCoreSupportsBatching && pData->bFlushOnTXEnd) { + CHKiRet(strm.Flush(pData->pStrm)); + } +finalize_it: + if(iRet == RS_RET_OK) + iRet = RS_RET_DEFER_COMMIT; +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->f_fname = NULL; + pData->tplName = NULL; + pData->fileUID = -1; + pData->fileGID = -1; + pData->dirUID = -1; + pData->dirGID = -1; + pData->bFailOnChown = 1; + pData->iDynaFileCacheSize = 10; + pData->fCreateMode = 0644; + pData->fDirCreateMode = 0700; + pData->bCreateDirs = 1; + pData->bSyncFile = 0; + pData->iZipLevel = 0; + pData->bVeryRobustZip = 0; + pData->bFlushOnTXEnd = FLUSHONTX_DFLT; + pData->iIOBufSize = IOBUF_DFLT_SIZE; + pData->iFlushInterval = FLUSH_INTRVL_DFLT; + pData->bUseAsyncWriter = USE_ASYNCWRITER_DFLT; + pData->sigprovName = NULL; + pData->cryprovName = NULL; + pData->useSigprov = 0; + pData->useCryprov = 0; +} + + +static rsRetVal +setupInstStatsCtrs(instanceData *pData) +{ + uchar ctrName[512]; + DEFiRet; + + if(!pData->bDynamicName) { + FINALIZE; + } + + /* support statistics gathering */ + snprintf((char*)ctrName, sizeof(ctrName), "dynafile cache %s", pData->f_fname); + ctrName[sizeof(ctrName)-1] = '\0'; /* be on the save side */ + CHKiRet(statsobj.Construct(&(pData->stats))); + CHKiRet(statsobj.SetName(pData->stats, ctrName)); + STATSCOUNTER_INIT(pData->ctrRequests, pData->mutCtrRequests); + CHKiRet(statsobj.AddCounter(pData->stats, UCHAR_CONSTANT("requests"), + ctrType_IntCtr, &(pData->ctrRequests))); + STATSCOUNTER_INIT(pData->ctrLevel0, pData->mutCtrLevel0); + CHKiRet(statsobj.AddCounter(pData->stats, UCHAR_CONSTANT("level0"), + ctrType_IntCtr, &(pData->ctrLevel0))); + STATSCOUNTER_INIT(pData->ctrMiss, pData->mutCtrMiss); + CHKiRet(statsobj.AddCounter(pData->stats, UCHAR_CONSTANT("missed"), + ctrType_IntCtr, &(pData->ctrMiss))); + STATSCOUNTER_INIT(pData->ctrEvict, pData->mutCtrEvict); + CHKiRet(statsobj.AddCounter(pData->stats, UCHAR_CONSTANT("evicted"), + ctrType_IntCtr, &(pData->ctrEvict))); + STATSCOUNTER_INIT(pData->ctrMax, pData->mutCtrMax); + CHKiRet(statsobj.AddCounter(pData->stats, UCHAR_CONSTANT("maxused"), + ctrType_IntCtr, &(pData->ctrMax))); + CHKiRet(statsobj.ConstructFinalize(pData->stats)); + +finalize_it: + RETiRet; +} + +static inline void +initSigprov(instanceData *pData, struct nvlst *lst) +{ + uchar szDrvrName[1024]; + + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmsig_%s", pData->sigprovName) + == sizeof(szDrvrName)) { + errmsg.LogError(0, RS_RET_ERR, "omfile: signature provider " + "name is too long: '%s' - signatures disabled", + pData->sigprovName); + goto done; + } + pData->sigprovNameFull = ustrdup(szDrvrName); + + pData->sigprov.ifVersion = sigprovCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean enough. + */ + if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pData->sigprov) + != RS_RET_OK) { + errmsg.LogError(0, RS_RET_LOAD_ERROR, "omfile: could not load " + "signature provider '%s' - signatures disabled", + szDrvrName); + goto done; + } + + if(pData->sigprov.Construct(&pData->sigprovData) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_SIGPROV_ERR, "omfile: error constructing " + "signature provider %s dataset - signatures disabled", + szDrvrName); + goto done; + } + pData->sigprov.SetCnfParam(pData->sigprovData, lst); + + dbgprintf("loaded signature provider %s, data instance at %p\n", + szDrvrName, pData->sigprovData); + pData->useSigprov = 1; +done: return; +} + +static inline rsRetVal +initCryprov(instanceData *pData, struct nvlst *lst) +{ + uchar szDrvrName[1024]; + DEFiRet; + + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmcry_%s", pData->cryprovName) + == sizeof(szDrvrName)) { + errmsg.LogError(0, RS_RET_ERR, "omfile: crypto provider " + "name is too long: '%s' - encryption disabled", + pData->cryprovName); + ABORT_FINALIZE(RS_RET_ERR); + } + pData->cryprovNameFull = ustrdup(szDrvrName); + + pData->cryprov.ifVersion = cryprovCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean enough. + */ + if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pData->cryprov) + != RS_RET_OK) { + errmsg.LogError(0, RS_RET_LOAD_ERROR, "omfile: could not load " + "crypto provider '%s' - encryption disabled", + szDrvrName); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } + + if(pData->cryprov.Construct(&pData->cryprovData) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_CRYPROV_ERR, "omfile: error constructing " + "crypto provider %s dataset - encryption disabled", + szDrvrName); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } + CHKiRet(pData->cryprov.SetCnfParam(pData->cryprovData, lst, CRYPROV_PARAMTYPE_REGULAR)); + + dbgprintf("loaded crypto provider %s, data instance at %p\n", + szDrvrName, pData->cryprovData); + pData->useCryprov = 1; +finalize_it: + RETiRet; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + uchar *tplToUse; + int i; +CODESTARTnewActInst + DBGPRINTF("newActInst (omfile)\n"); + + pvals = nvlstGetParams(lst, &actpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "omfile: either the \"file\" or " + "\"dynfile\" parameter must be given"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("action param blk in omfile:\n"); + cnfparamsPrint(&actpblk, pvals); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "dynafilecachesize")) { + pData->iDynaFileCacheSize = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "ziplevel")) { + pData->iZipLevel = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "flushinterval")) { + pData->iFlushInterval = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "veryrobustzip")) { + pData->bVeryRobustZip = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "asyncwriting")) { + pData->bUseAsyncWriter = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "flushontxend")) { + pData->bFlushOnTXEnd = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "iobuffersize")) { + pData->iIOBufSize = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "dirowner")) { + pData->dirUID = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "dirgroup")) { + pData->dirGID = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "fileowner")) { + pData->fileUID = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "filegroup")) { + pData->fileGID = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "dircreatemode")) { + pData->fDirCreateMode = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "filecreatemode")) { + pData->fCreateMode = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "failonchownfailure")) { + pData->bFailOnChown = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "sync")) { + pData->bSyncFile = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "createdirs")) { + pData->bCreateDirs = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "file")) { + pData->f_fname = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + CODE_STD_STRING_REQUESTnewActInst(1) + pData->bDynamicName = 0; + } else if(!strcmp(actpblk.descr[i].name, "dynafile")) { + pData->f_fname = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + CODE_STD_STRING_REQUESTnewActInst(2) + pData->bDynamicName = 1; + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "sig.provider")) { + pData->sigprovName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "cry.provider")) { + pData->cryprovName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omfile: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->f_fname == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "omfile: either the \"file\" or " + "\"dynfile\" parameter must be given"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(pData->sigprovName != NULL) { + initSigprov(pData, lst); + } + + if(pData->cryprovName != NULL) { + CHKiRet(initCryprov(pData, lst)); + } + + tplToUse = ustrdup((pData->tplName == NULL) ? getDfltTpl() : pData->tplName); + CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_NO_RQD_TPL_OPTS)); + + if(pData->bDynamicName) { + /* "filename" is actually a template name, we need this as string 1. So let's add it + * to the pOMSR. -- rgerhards, 2007-07-27 + */ + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->f_fname), OMSR_NO_RQD_TPL_OPTS)); + // TODO: create unified code for this (legacy+v6 system) + /* we now allocate the cache table */ + CHKmalloc(pData->dynCache = (dynaFileCacheEntry**) + calloc(pData->iDynaFileCacheSize, sizeof(dynaFileCacheEntry*))); + pData->iCurrElt = -1; /* no current element */ + } +// TODO: add pData->iSizeLimit = 0; /* default value, use outchannels to configure! */ + setupInstStatsCtrs(pData); + +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct + uchar fname[MAXFNAME]; +CODESTARTparseSelectorAct + /* Note: the indicator sequence permits us to use '$' to signify + * outchannel, what otherwise is not possible due to truely + * unresolvable grammar conflicts (*this time no way around*). + * rgerhards, 2011-07-09 + */ + if(!strncmp((char*) p, ":omfile:", sizeof(":omfile:") - 1)) { + p += sizeof(":omfile:") - 1; + } + if(!(*p == '$' || *p == '?' || *p == '/' || *p == '.' || *p == '-')) + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + + CHKiRet(createInstance(&pData)); + + if(*p == '-') { + pData->bSyncFile = 0; + p++; + } else { + pData->bSyncFile = cs.bEnableSync; + } + pData->iSizeLimit = 0; /* default value, use outchannels to configure! */ + + switch(*p) { + case '$': + CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* rgerhards 2005-06-21: this is a special setting for output-channel + * definitions. In the long term, this setting will probably replace + * anything else, but for the time being we must co-exist with the + * traditional mode lines. + * rgerhards, 2007-07-24: output-channels will go away. We keep them + * for compatibility reasons, but seems to have been a bad idea. + */ + CHKiRet(cflineParseOutchannel(pData, p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)); + pData->bDynamicName = 0; + break; + + case '?': /* This is much like a regular file handle, but we need to obtain + * a template name. rgerhards, 2007-07-03 + */ + CODE_STD_STRING_REQUESTparseSelectorAct(2) + ++p; /* eat '?' */ + CHKiRet(cflineParseFileName(p, fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, getDfltTpl())); + pData->f_fname = ustrdup(fname); + pData->bDynamicName = 1; + pData->iCurrElt = -1; /* no current element */ + /* "filename" is actually a template name, we need this as string 1. So let's add it + * to the pOMSR. -- rgerhards, 2007-07-27 + */ + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pData->f_fname), OMSR_NO_RQD_TPL_OPTS)); + /* we now allocate the cache table */ + CHKmalloc(pData->dynCache = (dynaFileCacheEntry**) + calloc(cs.iDynaFileCacheSize, sizeof(dynaFileCacheEntry*))); + break; + + case '/': + case '.': + CODE_STD_STRING_REQUESTparseSelectorAct(1) + CHKiRet(cflineParseFileName(p, fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, getDfltTpl())); + pData->f_fname = ustrdup(fname); + pData->bDynamicName = 0; + break; + default: + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* freeze current paremeters for this action */ + pData->iDynaFileCacheSize = cs.iDynaFileCacheSize; + pData->fCreateMode = cs.fCreateMode; + pData->fDirCreateMode = cs.fDirCreateMode; + pData->bCreateDirs = cs.bCreateDirs; + pData->bFailOnChown = cs.bFailOnChown; + pData->fileUID = cs.fileUID; + pData->fileGID = cs.fileGID; + pData->dirUID = cs.dirUID; + pData->dirGID = cs.dirGID; + pData->iZipLevel = cs.iZipLevel; + pData->bFlushOnTXEnd = cs.bFlushOnTXEnd; + pData->iIOBufSize = (int) cs.iIOBufSize; + pData->iFlushInterval = cs.iFlushInterval; + pData->bUseAsyncWriter = cs.bUseAsyncWriter; + pData->bVeryRobustZip = 0; /* cannot be specified via legacy conf */ + setupInstStatsCtrs(pData); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* Reset config variables for this module to default values. + * rgerhards, 2007-07-17 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cs.fileUID = -1; + cs.fileGID = -1; + cs.dirUID = -1; + cs.dirGID = -1; + cs.bFailOnChown = 1; + cs.iDynaFileCacheSize = 10; + cs.fCreateMode = 0644; + cs.fDirCreateMode = 0700; + cs.bCreateDirs = 1; + cs.bEnableSync = 0; + cs.iZipLevel = 0; + cs.bFlushOnTXEnd = FLUSHONTX_DFLT; + cs.iIOBufSize = IOBUF_DFLT_SIZE; + cs.iFlushInterval = FLUSH_INTRVL_DFLT; + cs.bUseAsyncWriter = USE_ASYNCWRITER_DFLT; + free(pszFileDfltTplName); + pszFileDfltTplName = NULL; + return RS_RET_OK; +} + + +BEGINdoHUP +CODESTARTdoHUP + if(pData->bDynamicName) { + dynaFileFreeCacheEntries(pData); + } else { + if(pData->pStrm != NULL) { + closeFile(pData); + } + } +ENDdoHUP + + +BEGINmodExit +CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); + objRelease(strm, CORE_COMPONENT); + objRelease(statsobj, CORE_COMPONENT); + DESTROY_ATOMIC_HELPER_MUT(mutClock); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +CODEqueryEtryPt_doHUP +ENDqueryEtryPt + + +BEGINmodInit(File) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr +INITLegCnfVars + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(strm, CORE_COMPONENT)); + CHKiRet(objUse(statsobj, CORE_COMPONENT)); + + INIT_ATOMIC_HELPER_MUT(mutClock); + + INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING); + DBGPRINTF("omfile: %susing transactional output interface.\n", bCoreSupportsBatching ? "" : "not "); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dynafilecachesize", 0, eCmdHdlrInt, (void*) setDynaFileCacheSize, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omfileziplevel", 0, eCmdHdlrInt, NULL, &cs.iZipLevel, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omfileflushinterval", 0, eCmdHdlrInt, NULL, &cs.iFlushInterval, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omfileasyncwriting", 0, eCmdHdlrBinary, NULL, &cs.bUseAsyncWriter, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omfileflushontxend", 0, eCmdHdlrBinary, NULL, &cs.bFlushOnTXEnd, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omfileiobuffersize", 0, eCmdHdlrSize, NULL, &cs.iIOBufSize, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirowner", 0, eCmdHdlrUID, NULL, &cs.dirUID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirgroup", 0, eCmdHdlrGID, NULL, &cs.dirGID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"fileowner", 0, eCmdHdlrUID, NULL, &cs.fileUID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"filegroup", 0, eCmdHdlrGID, NULL, &cs.fileGID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dircreatemode", 0, eCmdHdlrFileCreateMode, NULL, &cs.fDirCreateMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"filecreatemode", 0, eCmdHdlrFileCreateMode, NULL, &cs.fCreateMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"createdirs", 0, eCmdHdlrBinary, NULL, &cs.bCreateDirs, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"failonchownfailure", 0, eCmdHdlrBinary, NULL, &cs.bFailOnChown, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omfileforcechown", 0, eCmdHdlrGoneAway, NULL, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionfileenablesync", 0, eCmdHdlrBinary, NULL, &cs.bEnableSync, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionfiledefaulttemplate", 0, eCmdHdlrGetWord, setLegacyDfltTpl, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vi:set ai: + */ diff --git a/tools/omfile.h b/tools/omfile.h new file mode 100644 index 00000000..eca88590 --- /dev/null +++ b/tools/omfile.h @@ -0,0 +1,37 @@ +/* omfile.h + * These are the definitions for the build-in file output module. + * + * File begun on 2007-07-21 by RGerhards + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OMFILE_H_INCLUDED +#define OMFILE_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitFile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +/* the define below is dirty, but we need it for ompipe integration. There is no + * other way to have the functionality (well, one way would be to go through the + * globals, but that seems not yet justified. -- rgerhards, 2010-03-01 + */ +uchar *pszFileDfltTplName; +#endif /* #ifndef OMFILE_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/tools/omfwd.c b/tools/omfwd.c new file mode 100644 index 00000000..179e9614 --- /dev/null +++ b/tools/omfwd.c @@ -0,0 +1,1267 @@ +/* omfwd.c + * This is the implementation of the build-in forwarding output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007-2013 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * TODO v6 config: + * - permitted peer *list* + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fnmatch.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#include <stdint.h> +#ifdef USE_NETZIP +#include <zlib.h> +#endif +#include <pthread.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "net.h" +#include "netstrms.h" +#include "netstrm.h" +#include "omfwd.h" +#include "template.h" +#include "msg.h" +#include "tcpclt.h" +#include "cfsysline.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "unicode-helper.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omfwd") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrms) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(tcpclt) + +typedef struct _instanceData { + uchar *tplName; /* name of assigned template */ + netstrms_t *pNS; /* netstream subsystem */ + netstrm_t *pNetstrm; /* our output netstream */ + uchar *pszStrmDrvr; + uchar *pszStrmDrvrAuthMode; + permittedPeers_t *pPermPeers; + int iStrmDrvrMode; + char *target; + int *pSockArray; /* sockets to use for UDP */ + int bIsConnected; /* are we connected to remote host? 0 - no, 1 - yes, UDP means addr resolved */ + struct addrinfo *f_addr; + int compressionLevel; /* 0 - no compression, else level for zlib */ + char *port; + int protocol; + int iRebindInterval; /* rebind interval */ + int nXmit; /* number of transmissions since last (re-)bind */ +# define FORW_UDP 0 +# define FORW_TCP 1 + /* following fields for TCP-based delivery */ + TCPFRAMINGMODE tcp_framing; + int bResendLastOnRecon; /* should the last message be re-sent on a successful reconnect? */ + tcpclt_t *pTCPClt; /* our tcpclt object */ +# define COMPRESS_NEVER 0 +# define COMPRESS_SINGLE_MSG 1 /* old, single-message compression */ + /* all other settings are for stream-compression */ +# define COMPRESS_STREAM_ALWAYS 2 + uint8_t compressionMode; + sbool bzInitDone; /* did we do an init of zstrm already? */ + z_stream zstrm; /* zip stream to use for tcp compression */ + uchar sndBuf[16*1024]; /* this is intensionally fixed -- see no good reason to make configurable */ + unsigned offsSndBuf; /* next free spot in send buffer */ +} instanceData; + +/* config data */ +typedef struct configSettings_s { + uchar *pszTplName; /* name of the default template to use */ + uchar *pszStrmDrvr; /* name of the stream driver to use */ + int iStrmDrvrMode; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */ + int bResendLastOnRecon; /* should the last message be re-sent on a successful reconnect? */ + uchar *pszStrmDrvrAuthMode; /* authentication mode to use */ + int iTCPRebindInterval; /* support for automatic re-binding (load balancers!). 0 - no rebind */ + int iUDPRebindInterval; /* support for automatic re-binding (load balancers!). 0 - no rebind */ + permittedPeers_t *pPermPeers; +} configSettings_t; +static configSettings_t cs; + +/* tables for interfacing with the v6 config system */ +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "template", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "target", eCmdHdlrGetWord, 0 }, + { "port", eCmdHdlrGetWord, 0 }, + { "protocol", eCmdHdlrGetWord, 0 }, + { "tcp_framing", eCmdHdlrGetWord, 0 }, + { "ziplevel", eCmdHdlrInt, 0 }, + { "compression.mode", eCmdHdlrGetWord, 0 }, + { "rebindinterval", eCmdHdlrInt, 0 }, + { "streamdriver", eCmdHdlrGetWord, 0 }, + { "streamdrivermode", eCmdHdlrInt, 0 }, + { "streamdriverauthmode", eCmdHdlrGetWord, 0 }, + { "streamdriverpermittedpeers", eCmdHdlrGetWord, 0 }, + { "resendlastmsgonreconnect", eCmdHdlrBinary, 0 }, + { "template", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + uchar *tplName; /* default template */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars + cs.pszTplName = NULL; /* name of the default template to use */ + cs.pszStrmDrvr = NULL; /* name of the stream driver to use */ + cs.iStrmDrvrMode = 0; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */ + cs.bResendLastOnRecon = 0; /* should the last message be re-sent on a successful reconnect? */ + cs.pszStrmDrvrAuthMode = NULL; /* authentication mode to use */ + cs.iUDPRebindInterval = 0; /* support for automatic re-binding (load balancers!). 0 - no rebind */ + cs.iTCPRebindInterval = 0; /* support for automatic re-binding (load balancers!). 0 - no rebind */ + cs.pPermPeers = NULL; +ENDinitConfVars + + +static rsRetVal doTryResume(instanceData *pData); +static rsRetVal doZipFinish(instanceData *pData); + +/* this function gets the default template. It coordinates action between + * old-style and new-style configuration parts. + */ +static inline uchar* +getDfltTpl(void) +{ + if(loadModConf != NULL && loadModConf->tplName != NULL) + return loadModConf->tplName; + else if(cs.pszTplName == NULL) + return (uchar*)"RSYSLOG_TraditionalForwardFormat"; + else + return cs.pszTplName; +} + + +/* set the default template to be used + * This is a module-global parameter, and as such needs special handling. It needs to + * be coordinated with values set via the v2 config system (rsyslog v6+). What we do + * is we do not permit this directive after the v2 config system has been used to set + * the parameter. + */ +static rsRetVal +setLegacyDfltTpl(void __attribute__((unused)) *pVal, uchar* newVal) +{ + DEFiRet; + + if(loadModConf != NULL && loadModConf->tplName != NULL) { + free(newVal); + errmsg.LogError(0, RS_RET_ERR, "omfwd default template already set via module " + "global parameter - can no longer be changed"); + ABORT_FINALIZE(RS_RET_ERR); + } + free(cs.pszTplName); + cs.pszTplName = newVal; +finalize_it: + RETiRet; +} + +/* Close the UDP sockets. + * rgerhards, 2009-05-29 + */ +static rsRetVal +closeUDPSockets(instanceData *pData) +{ + DEFiRet; + assert(pData != NULL); + if(pData->pSockArray != NULL) { + net.closeUDPListenSockets(pData->pSockArray); + pData->pSockArray = NULL; + freeaddrinfo(pData->f_addr); + pData->f_addr = NULL; + } +pData->bIsConnected = 0; // TODO: remove this variable altogether + RETiRet; +} + + +/* destruct the TCP helper objects + * This, for example, is needed after something went wrong. + * This function is void because it "can not" fail. + * rgerhards, 2008-06-04 + * Note that we DO NOT discard the current buffer contents + * (if any). This permits us to save data between sessions. In + * the wort case, some duplication occurs, but we do not + * loose data. + */ +static inline void +DestructTCPInstanceData(instanceData *pData) +{ + assert(pData != NULL); + doZipFinish(pData); + if(pData->pNetstrm != NULL) + netstrm.Destruct(&pData->pNetstrm); + if(pData->pNS != NULL) + netstrms.Destruct(&pData->pNS); +} + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->tplName = NULL; +ENDbeginCnfLoad + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for omfwd:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "template")) { + loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + if(cs.pszTplName != NULL) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "omfwd: warning: default template " + "was already set via legacy directive - may lead to inconsistent " + "results."); + } + } else { + dbgprintf("omfwd: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(cs.pszTplName); + cs.pszTplName = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf + free(pModConf->tplName); +ENDfreeCnf + +BEGINcreateInstance +CODESTARTcreateInstance + pData->offsSndBuf = 0; +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + /* final cleanup */ + DestructTCPInstanceData(pData); + closeUDPSockets(pData); + + if(pData->protocol == FORW_TCP) { + tcpclt.Destruct(&pData->pTCPClt); + } + + free(pData->port); + free(pData->target); + free(pData->pszStrmDrvr); + free(pData->pszStrmDrvrAuthMode); + net.DestructPermittedPeers(&pData->pPermPeers); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("%s", pData->target); +ENDdbgPrintInstInfo + + +/* Send a message via UDP + * rgehards, 2007-12-20 + */ +static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len) +{ + DEFiRet; + struct addrinfo *r; + int i; + unsigned lsent = 0; + int bSendSuccess; + + if(pData->iRebindInterval && (pData->nXmit++ % pData->iRebindInterval == 0)) { + dbgprintf("omfwd dropping UDP 'connection' (as configured)\n"); + pData->nXmit = 1; /* else we have an addtl wrap at 2^31-1 */ + CHKiRet(closeUDPSockets(pData)); + } + + if(pData->pSockArray == NULL) { + CHKiRet(doTryResume(pData)); + } + + if(pData->pSockArray != NULL) { + /* we need to track if we have success sending to the remote + * peer. Success is indicated by at least one sendto() call + * succeeding. We track this be bSendSuccess. We can not simply + * rely on lsent, as a call might initially work, but a later + * call fails. Then, lsent has the error status, even though + * the sendto() succeeded. -- rgerhards, 2007-06-22 + */ + bSendSuccess = RSFALSE; + for (r = pData->f_addr; r; r = r->ai_next) { + for (i = 0; i < *pData->pSockArray; i++) { + lsent = sendto(pData->pSockArray[i+1], msg, len, 0, r->ai_addr, r->ai_addrlen); + if (lsent == len) { + bSendSuccess = RSTRUE; + break; + } else { + int eno = errno; + char errStr[1024]; + dbgprintf("sendto() error: %d = %s.\n", + eno, rs_strerror_r(eno, errStr, sizeof(errStr))); + } + } + if (lsent == len && !send_to_all) + break; + } + /* finished looping */ + if (bSendSuccess == RSFALSE) { + dbgprintf("error forwarding via udp, suspending\n"); + iRet = RS_RET_SUSPENDED; + } + } + +finalize_it: + RETiRet; +} + + +/* set the permitted peers -- rgerhards, 2008-05-19 + */ +static rsRetVal +setPermittedPeer(void __attribute__((unused)) *pVal, uchar *pszID) +{ + DEFiRet; + CHKiRet(net.AddPermittedPeer(&cs.pPermPeers, pszID)); + free(pszID); /* no longer needed, but we must free it as of interface def */ +finalize_it: + RETiRet; +} + + + +/* CODE FOR SENDING TCP MESSAGES */ + +static rsRetVal +TCPSendBufUncompressed(instanceData *pData, uchar *buf, unsigned len) +{ + DEFiRet; + unsigned alreadySent; + ssize_t lenSend; + + alreadySent = 0; + CHKiRet(netstrm.CheckConnection(pData->pNetstrm)); /* hack for plain tcp syslog - see ptcp driver for details */ + + while(alreadySent != len) { + lenSend = len - alreadySent; + CHKiRet(netstrm.Send(pData->pNetstrm, buf+alreadySent, &lenSend)); + DBGPRINTF("omfwd: TCP sent %ld bytes, requested %u\n", (long) lenSend, len - alreadySent); + alreadySent += lenSend; + } + +finalize_it: + if(iRet != RS_RET_OK) { + /* error! */ + dbgprintf("TCPSendBuf error %d, destruct TCP Connection!\n", iRet); + DestructTCPInstanceData(pData); + iRet = RS_RET_SUSPENDED; + } + RETiRet; +} + +static rsRetVal +TCPSendBufCompressed(instanceData *pData, uchar *buf, unsigned len) +{ + int zRet; /* zlib return state */ + unsigned outavail; + uchar zipBuf[32*1024]; + DEFiRet; + + if(!pData->bzInitDone) { + /* allocate deflate state */ + pData->zstrm.zalloc = Z_NULL; + pData->zstrm.zfree = Z_NULL; + pData->zstrm.opaque = Z_NULL; + /* see note in file header for the params we use with deflateInit2() */ + zRet = deflateInit(&pData->zstrm, 9); + if(zRet != Z_OK) { + DBGPRINTF("error %d returned from zlib/deflateInit()\n", zRet); + ABORT_FINALIZE(RS_RET_ZLIB_ERR); + } + pData->bzInitDone = RSTRUE; + } + + /* now doing the compression */ + pData->zstrm.next_in = (Bytef*) buf; + pData->zstrm.avail_in = len; + /* run deflate() on buffer until everything has been compressed */ + do { + DBGPRINTF("omfwd: in deflate() loop, avail_in %d, total_in %ld\n", pData->zstrm.avail_in, pData->zstrm.total_in); + pData->zstrm.avail_out = sizeof(zipBuf); + pData->zstrm.next_out = zipBuf; + zRet = deflate(&pData->zstrm, Z_NO_FLUSH); /* no bad return value */ + DBGPRINTF("after deflate, ret %d, avail_out %d\n", zRet, pData->zstrm.avail_out); + outavail = sizeof(zipBuf) - pData->zstrm.avail_out; + if(outavail != 0) { + CHKiRet(TCPSendBufUncompressed(pData, zipBuf, outavail)); + } + } while (pData->zstrm.avail_out == 0); + +finalize_it: + RETiRet; +} + +static rsRetVal +TCPSendBuf(instanceData *pData, uchar *buf, unsigned len) +{ + DEFiRet; + if(pData->compressionMode >= COMPRESS_STREAM_ALWAYS) + iRet = TCPSendBufCompressed(pData, buf, len); + else + iRet = TCPSendBufUncompressed(pData, buf, len); + RETiRet; +} + +/* finish zlib buffer, to be called before closing the ZIP file (if + * running in stream mode). + */ +static rsRetVal +doZipFinish(instanceData *pData) +{ + int zRet; /* zlib return state */ + DEFiRet; + unsigned outavail; + uchar zipBuf[32*1024]; + + if(!pData->bzInitDone) + goto done; + +// TODO: can we get this into a single common function? +dbgprintf("DDDD: in doZipFinish()\n"); + pData->zstrm.avail_in = 0; + /* run deflate() on buffer until everything has been compressed */ + do { + DBGPRINTF("in deflate() loop, avail_in %d, total_in %ld\n", pData->zstrm.avail_in, pData->zstrm.total_in); + pData->zstrm.avail_out = sizeof(zipBuf); + pData->zstrm.next_out = zipBuf; + zRet = deflate(&pData->zstrm, Z_FINISH); /* no bad return value */ + DBGPRINTF("after deflate, ret %d, avail_out %d\n", zRet, pData->zstrm.avail_out); + outavail = sizeof(zipBuf) - pData->zstrm.avail_out; + if(outavail != 0) { + CHKiRet(TCPSendBufUncompressed(pData, zipBuf, outavail)); + } + } while (pData->zstrm.avail_out == 0); + +finalize_it: + zRet = deflateEnd(&pData->zstrm); + if(zRet != Z_OK) { + DBGPRINTF("error %d returned from zlib/deflateEnd()\n", zRet); + } + + pData->bzInitDone = 0; +done: RETiRet; +} + + +/* Add frame to send buffer (or send, if requried) + */ +static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len) +{ + DEFiRet; + instanceData *pData = (instanceData *) pvData; + + DBGPRINTF("omfwd: add %u bytes to send buffer (curr offs %u)\n", + (unsigned) len, pData->offsSndBuf); + if(pData->offsSndBuf != 0 && pData->offsSndBuf + len >= sizeof(pData->sndBuf)) { + /* no buffer space left, need to commit previous records */ + CHKiRet(TCPSendBuf(pData, pData->sndBuf, pData->offsSndBuf)); + pData->offsSndBuf = 0; + iRet = RS_RET_PREVIOUS_COMMITTED; + } + + /* check if the message is too large to fit into buffer */ + if(len > sizeof(pData->sndBuf)) { + CHKiRet(TCPSendBuf(pData, (uchar*)msg, len)); + ABORT_FINALIZE(RS_RET_OK); /* committed everything so far */ + } + + /* we now know the buffer has enough free space */ + memcpy(pData->sndBuf + pData->offsSndBuf, msg, len); + pData->offsSndBuf += len; + iRet = RS_RET_DEFER_COMMIT; + +finalize_it: + RETiRet; +} + + +/* This function is called immediately before a send retry is attempted. + * It shall clean up whatever makes sense. + * rgerhards, 2007-12-28 + */ +static rsRetVal TCPSendPrepRetry(void *pvData) +{ + DEFiRet; + instanceData *pData = (instanceData *) pvData; +dbgprintf("TCPSendPrepRetry performs a DestructTCPInstanceData\n"); + + assert(pData != NULL); + DestructTCPInstanceData(pData); + RETiRet; +} + + +/* initializes everything so that TCPSend can work. + * rgerhards, 2007-12-28 + */ +static rsRetVal TCPSendInit(void *pvData) +{ + DEFiRet; + instanceData *pData = (instanceData *) pvData; + + assert(pData != NULL); + if(pData->pNetstrm == NULL) { + dbgprintf("TCPSendInit CREATE\n"); + CHKiRet(netstrms.Construct(&pData->pNS)); + /* the stream driver must be set before the object is finalized! */ + CHKiRet(netstrms.SetDrvrName(pData->pNS, pData->pszStrmDrvr)); + CHKiRet(netstrms.ConstructFinalize(pData->pNS)); + + /* now create the actual stream and connect to the server */ + CHKiRet(netstrms.CreateStrm(pData->pNS, &pData->pNetstrm)); + CHKiRet(netstrm.ConstructFinalize(pData->pNetstrm)); + CHKiRet(netstrm.SetDrvrMode(pData->pNetstrm, pData->iStrmDrvrMode)); + /* now set optional params, but only if they were actually configured */ + if(pData->pszStrmDrvrAuthMode != NULL) { + CHKiRet(netstrm.SetDrvrAuthMode(pData->pNetstrm, pData->pszStrmDrvrAuthMode)); + } + if(pData->pPermPeers != NULL) { + CHKiRet(netstrm.SetDrvrPermPeers(pData->pNetstrm, pData->pPermPeers)); + } + /* params set, now connect */ + CHKiRet(netstrm.Connect(pData->pNetstrm, glbl.GetDefPFFamily(), + (uchar*)pData->port, (uchar*)pData->target)); + } + +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("TCPSendInit FAILED with %d.\n", iRet); + DestructTCPInstanceData(pData); + } + + RETiRet; +} + + +/* try to resume connection if it is not ready + * rgerhards, 2007-08-02 + */ +static rsRetVal doTryResume(instanceData *pData) +{ + int iErr; + struct addrinfo *res; + struct addrinfo hints; + DEFiRet; + + if(pData->bIsConnected) + FINALIZE; + + /* The remote address is not yet known and needs to be obtained */ + dbgprintf(" %s\n", pData->target); + if(pData->protocol == FORW_UDP) { + memset(&hints, 0, sizeof(hints)); + /* port must be numeric, because config file syntax requires this */ + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_DGRAM; + if((iErr = (getaddrinfo(pData->target, pData->port, &hints, &res))) != 0) { + dbgprintf("could not get addrinfo for hostname '%s':'%s': %d%s\n", + pData->target, pData->port, iErr, gai_strerror(iErr)); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + dbgprintf("%s found, resuming.\n", pData->target); + pData->f_addr = res; + pData->bIsConnected = 1; + if(pData->pSockArray == NULL) { + pData->pSockArray = net.create_udp_socket((uchar*)pData->target, NULL, 0); + } + } else { + CHKiRet(TCPSendInit((void*)pData)); + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(pData->f_addr != NULL) { + freeaddrinfo(pData->f_addr); + pData->f_addr = NULL; + } + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doTryResume(pData); +ENDtryResume + + +BEGINbeginTransaction +CODESTARTbeginTransaction +dbgprintf("omfwd: beginTransaction\n"); +ENDbeginTransaction + + +BEGINdoAction + char *psz; /* temporary buffering */ + register unsigned l; + int iMaxLine; +# ifdef USE_NETZIP + Bytef *out = NULL; /* for compression */ +# endif +CODESTARTdoAction + CHKiRet(doTryResume(pData)); + + iMaxLine = glbl.GetMaxLine(); + + dbgprintf(" %s:%s/%s\n", pData->target, pData->port, + pData->protocol == FORW_UDP ? "udp" : "tcp"); + + psz = (char*) ppString[0]; + l = strlen((char*) psz); + if((int) l > iMaxLine) + l = iMaxLine; + +# ifdef USE_NETZIP + /* Check if we should compress and, if so, do it. We also + * check if the message is large enough to justify compression. + * The smaller the message, the less likely is a gain in compression. + * To save CPU cycles, we do not try to compress very small messages. + * What "very small" means needs to be configured. Currently, it is + * hard-coded but this may be changed to a config parameter. + * rgerhards, 2006-11-30 + */ + if(pData->compressionMode == COMPRESS_SINGLE_MSG && (l > CONF_MIN_SIZE_FOR_COMPRESS)) { + uLongf destLen = iMaxLine + iMaxLine/100 +12; /* recommended value from zlib doc */ + uLong srcLen = l; + int ret; + CHKmalloc(out = (Bytef*) MALLOC(destLen)); + out[0] = 'z'; + out[1] = '\0'; + ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz, + srcLen, pData->compressionLevel); + dbgprintf("Compressing message, length was %d now %d, return state %d.\n", + l, (int) destLen, ret); + if(ret != Z_OK) { + /* if we fail, we complain, but only in debug mode + * Otherwise, we are silent. In any case, we ignore the + * failed compression and just sent the uncompressed + * data, which is still valid. So this is probably the + * best course of action. + * rgerhards, 2006-11-30 + */ + dbgprintf("Compression failed, sending uncompressed message\n"); + } else if(destLen+1 < l) { + /* only use compression if there is a gain in using it! */ + dbgprintf("there is gain in compression, so we do it\n"); + psz = (char*) out; + l = destLen + 1; /* take care for the "z" at message start! */ + } + ++destLen; + } +# endif + + if(pData->protocol == FORW_UDP) { + /* forward via UDP */ + CHKiRet(UDPSend(pData, psz, l)); + } else { + /* forward via TCP */ + iRet = tcpclt.Send(pData->pTCPClt, pData, psz, l); + if(iRet != RS_RET_OK && iRet != RS_RET_DEFER_COMMIT && iRet != RS_RET_PREVIOUS_COMMITTED) { + /* error! */ + dbgprintf("error forwarding via tcp, suspending\n"); + DestructTCPInstanceData(pData); + iRet = RS_RET_SUSPENDED; + } + } +finalize_it: +# ifdef USE_NETZIP + free(out); /* is NULL if it was never used... */ +# endif +ENDdoAction + + +BEGINendTransaction +CODESTARTendTransaction +dbgprintf("omfwd: endTransaction, offsSndBuf %u\n", pData->offsSndBuf); + if(pData->offsSndBuf != 0) { + iRet = TCPSendBuf(pData, pData->sndBuf, pData->offsSndBuf); + pData->offsSndBuf = 0; + } +ENDendTransaction + + + +/* This function loads TCP support, if not already loaded. It will be called + * during config processing. To server ressources, TCP support will only + * be loaded if it actually is used. -- rgerhard, 2008-04-17 + */ +static rsRetVal +loadTCPSupport(void) +{ + DEFiRet; + CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); + +finalize_it: + RETiRet; +} + + +/* initialize TCP structures (if necessary) after the instance has been + * created. + */ +static rsRetVal +initTCP(instanceData *pData) +{ + DEFiRet; + if(pData->protocol == FORW_TCP) { + /* create our tcpclt */ + CHKiRet(tcpclt.Construct(&pData->pTCPClt)); + CHKiRet(tcpclt.SetResendLastOnRecon(pData->pTCPClt, pData->bResendLastOnRecon)); + /* and set callbacks */ + CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendInit)); + CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendFrame)); + CHKiRet(tcpclt.SetSendPrepRetry(pData->pTCPClt, TCPSendPrepRetry)); + CHKiRet(tcpclt.SetFraming(pData->pTCPClt, pData->tcp_framing)); + CHKiRet(tcpclt.SetRebindInterval(pData->pTCPClt, pData->iRebindInterval)); + pData->iStrmDrvrMode = cs.iStrmDrvrMode; + if(cs.pszStrmDrvr != NULL) + CHKmalloc(pData->pszStrmDrvr = (uchar*)strdup((char*)cs.pszStrmDrvr)); + if(cs.pszStrmDrvrAuthMode != NULL) + CHKmalloc(pData->pszStrmDrvrAuthMode = + (uchar*)strdup((char*)cs.pszStrmDrvrAuthMode)); + } +finalize_it: + RETiRet; +} + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->tplName = NULL; + pData->protocol = FORW_UDP; + pData->tcp_framing = TCP_FRAMING_OCTET_STUFFING; + pData->pszStrmDrvr = NULL; + pData->pszStrmDrvrAuthMode = NULL; + pData->iStrmDrvrMode = 0; + pData->iRebindInterval = 0; + pData->bResendLastOnRecon = 0; + pData->pPermPeers = NULL; + pData->compressionLevel = 9; + pData->compressionMode = COMPRESS_NEVER; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + uchar *tplToUse; + char *cstr; + int i; + rsRetVal localRet; + int complevel = -1; +CODESTARTnewActInst + DBGPRINTF("newActInst (omfwd)\n"); + + pvals = nvlstGetParams(lst, &actpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "omfwd: either the \"file\" or " + "\"dynfile\" parameter must be given"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("action param blk in omfwd:\n"); + cnfparamsPrint(&actpblk, pvals); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "target")) { + pData->target = es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "port")) { + pData->port = es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "protocol")) { + if(!es_strcasebufcmp(pvals[i].val.d.estr, (uchar*)"udp", 3)) { + pData->protocol = FORW_UDP; + } else if(!es_strcasebufcmp(pvals[i].val.d.estr, (uchar*)"tcp", 3)) { + localRet = loadTCPSupport(); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "could not activate network stream modules for TCP " + "(internal error %d) - are modules missing?", localRet); + ABORT_FINALIZE(localRet); + } + pData->protocol = FORW_TCP; + } else { + uchar *str; + str = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_INVLD_PROTOCOL, + "omfwd: invalid protocol \"%s\"", str); + free(str); + ABORT_FINALIZE(RS_RET_INVLD_PROTOCOL); + } + } else if(!strcmp(actpblk.descr[i].name, "tcp_framing")) { + if(!es_strcasebufcmp(pvals[i].val.d.estr, (uchar*)"traditional", 11)) { + pData->tcp_framing = TCP_FRAMING_OCTET_STUFFING; + } else if(!es_strcasebufcmp(pvals[i].val.d.estr, (uchar*)"octet-counted", 13)) { + pData->tcp_framing = TCP_FRAMING_OCTET_COUNTING; + } else { + uchar *str; + str = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_CNF_INVLD_FRAMING, + "omfwd: invalid framing \"%s\"", str); + free(str); + ABORT_FINALIZE(RS_RET_CNF_INVLD_FRAMING ); + } + } else if(!strcmp(actpblk.descr[i].name, "rebindinterval")) { + pData->iRebindInterval = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "streamdriver")) { + pData->pszStrmDrvr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "streamdrivermode")) { + pData->iStrmDrvrMode = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "streamdriverauthmode")) { + pData->pszStrmDrvrAuthMode = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "streamdriverpermittedpeers")) { + uchar *start, *str; + uchar save; + uchar *p; + int lenStr; + str = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + start = str; + lenStr = ustrlen(start); /* we need length after '\0' has been dropped... */ + while(lenStr > 0) { + p = start; + while(*p && *p != ',' && lenStr--) + p++; + if(*p == ',') { + *p = '\0'; + } + save = *(p+1); /* we always have this, at least the \0 byte at EOS */ + *(p+1) = '\0'; + if(*start == '\0') { + DBGPRINTF("omfwd: ignoring empty permitted peer\n"); + } else { + dbgprintf("omfwd: adding permitted peer: '%s'\n", start); + CHKiRet(net.AddPermittedPeer(&(pData->pPermPeers), start)); + } + start = p+1; + if(lenStr) + --lenStr; + *(p+1) = save; + } + free(str); + } else if(!strcmp(actpblk.descr[i].name, "ziplevel")) { +# ifdef USE_NETZIP + complevel = pvals[i].val.d.n; + if(complevel >= 0 && complevel <= 10) { + pData->compressionLevel = complevel; + pData->compressionMode = COMPRESS_SINGLE_MSG; + } else { + errmsg.LogError(0, NO_ERRCODE, "Invalid ziplevel %d specified in " + "forwardig action - NOT turning on compression.", + complevel); + } +# else + errmsg.LogError(0, NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " + "with compression support - request ignored."); +# endif /* #ifdef USE_NETZIP */ + } else if(!strcmp(actpblk.descr[i].name, "resendlastmsgonreconnect")) { + pData->bResendLastOnRecon = (int) pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "compression.mode")) { + cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + if(!strcasecmp(cstr, "stream:always")) { + pData->compressionMode = COMPRESS_STREAM_ALWAYS; + } else if(!strcasecmp(cstr, "none")) { + pData->compressionMode = COMPRESS_NEVER; + } else if(!strcasecmp(cstr, "single")) { + pData->compressionMode = COMPRESS_SINGLE_MSG; + } else { + errmsg.LogError(0, RS_RET_PARAM_ERROR, "omfwd: invalid value for 'compression.mode' " + "parameter (given is '%s')", cstr); + free(cstr); + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + } + free(cstr); + } else { + DBGPRINTF("omfwd: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(complevel != -1) { + pData->compressionLevel = complevel; + if(pData->compressionMode == COMPRESS_NEVER) { + /* to keep compatible with pre-7.3.11, only setting the + * compresion level means old-style single-message mode. + */ + pData->compressionMode = COMPRESS_SINGLE_MSG; + } + } + + CODE_STD_STRING_REQUESTnewActInst(1) + + tplToUse = ustrdup((pData->tplName == NULL) ? getDfltTpl() : pData->tplName); + CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_NO_RQD_TPL_OPTS)); + + CHKiRet(initTCP(pData)); +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct + uchar *q; + int i; + rsRetVal localRet; + struct addrinfo; + TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(*p != '@') + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + + CHKiRet(createInstance(&pData)); + + ++p; /* eat '@' */ + if(*p == '@') { /* indicator for TCP! */ + localRet = loadTCPSupport(); + if(localRet != RS_RET_OK) { + errmsg.LogError(0, localRet, "could not activate network stream modules for TCP " + "(internal error %d) - are modules missing?", localRet); + ABORT_FINALIZE(localRet); + } + pData->protocol = FORW_TCP; + ++p; /* eat this '@', too */ + } else { + pData->protocol = FORW_UDP; + } + /* we are now after the protocol indicator. Now check if we should + * use compression. We begin to use a new option format for this: + * @(option,option)host:port + * The first option defined is "z[0..9]" where the digit indicates + * the compression level. If it is not given, 9 (best compression) is + * assumed. An example action statement might be: + * @@(z5,o)127.0.0.1:1400 + * Which means send via TCP with medium (5) compresion (z) to the local + * host on port 1400. The '0' option means that octet-couting (as in + * IETF I-D syslog-transport-tls) is to be used for framing (this option + * applies to TCP-based syslog only and is ignored when specified with UDP). + * That is not yet implemented. + * rgerhards, 2006-12-07 + * In order to support IPv6 addresses, we must introduce an extension to + * the hostname. If it is in square brackets, whatever is in them is treated as + * the hostname - without any exceptions ;) -- rgerhards, 2008-08-05 + */ + if(*p == '(') { + /* at this position, it *must* be an option indicator */ + do { + ++p; /* eat '(' or ',' (depending on when called) */ + /* check options */ + if(*p == 'z') { /* compression */ +# ifdef USE_NETZIP + ++p; /* eat */ + if(isdigit((int) *p)) { + int iLevel; + iLevel = *p - '0'; + ++p; /* eat */ + pData->compressionLevel = iLevel; + pData->compressionMode = COMPRESS_SINGLE_MSG; + } else { + errmsg.LogError(0, NO_ERRCODE, "Invalid compression level '%c' specified in " + "forwardig action - NOT turning on compression.", + *p); + } +# else + errmsg.LogError(0, NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " + "with compression support - request ignored."); +# endif /* #ifdef USE_NETZIP */ + } else if(*p == 'o') { /* octet-couting based TCP framing? */ + ++p; /* eat */ + /* no further options settable */ + tcp_framing = TCP_FRAMING_OCTET_COUNTING; + } else { /* invalid option! Just skip it... */ + errmsg.LogError(0, NO_ERRCODE, "Invalid option %c in forwarding action - ignoring.", *p); + ++p; /* eat invalid option */ + } + /* the option processing is done. We now do a generic skip + * to either the next option or the end of the option + * block. + */ + while(*p && *p != ')' && *p != ',') + ++p; /* just skip it */ + } while(*p && *p == ','); /* Attention: do.. while() */ + if(*p == ')') + ++p; /* eat terminator, on to next */ + else + /* we probably have end of string - leave it for the rest + * of the code to handle it (but warn the user) + */ + errmsg.LogError(0, NO_ERRCODE, "Option block not terminated in forwarding action."); + } + + /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0') + * now skip to port and then template name. rgerhards 2005-07-06 + */ + if(*p == '[') { /* everything is hostname upto ']' */ + ++p; /* skip '[' */ + for(q = p ; *p && *p != ']' ; ++p) + /* JUST SKIP */; + if(*p == ']') { + *p = '\0'; /* trick to obtain hostname (later)! */ + ++p; /* eat it */ + } + } else { /* traditional view of hostname */ + for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p) + /* JUST SKIP */; + } + + pData->tcp_framing = tcp_framing; + pData->port = NULL; + if(*p == ':') { /* process port */ + uchar * tmp; + + *p = '\0'; /* trick to obtain hostname (later)! */ + tmp = ++p; + for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i) + /* SKIP AND COUNT */; + pData->port = MALLOC(i + 1); + if(pData->port == NULL) { + errmsg.LogError(0, NO_ERRCODE, "Could not get memory to store syslog forwarding port, " + "using default port, results may not be what you intend\n"); + /* we leave f_forw.port set to NULL, this is then handled below */ + } else { + memcpy(pData->port, tmp, i); + *(pData->port + i) = '\0'; + } + } + /* check if no port is set. If so, we use the IANA-assigned port of 514 */ + if(pData->port == NULL) { + CHKmalloc(pData->port = strdup("514")); + } + + /* now skip to template */ + while(*p && *p != ';' && *p != '#' && !isspace((int) *p)) + ++p; /*JUST SKIP*/ + + if(*p == ';' || *p == '#' || isspace(*p)) { + uchar cTmp = *p; + *p = '\0'; /* trick to obtain hostname (later)! */ + CHKmalloc(pData->target = strdup((char*) q)); + *p = cTmp; + } else { + CHKmalloc(pData->target = strdup((char*) q)); + } + + /* copy over config data as needed */ + pData->iRebindInterval = (pData->protocol == FORW_TCP) ? + cs.iTCPRebindInterval : cs.iUDPRebindInterval; + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, getDfltTpl())); + + if(pData->protocol == FORW_TCP) { + pData->bResendLastOnRecon = cs.bResendLastOnRecon; + pData->iStrmDrvrMode = cs.iStrmDrvrMode; + if(cs.pszStrmDrvr != NULL) + CHKmalloc(pData->pszStrmDrvr = (uchar*)strdup((char*)cs.pszStrmDrvr)); + if(cs.pszStrmDrvrAuthMode != NULL) + CHKmalloc(pData->pszStrmDrvrAuthMode = + (uchar*)strdup((char*)cs.pszStrmDrvrAuthMode)); + if(cs.pPermPeers != NULL) { + pData->pPermPeers = cs.pPermPeers; + cs.pPermPeers = NULL; + } + } + CHKiRet(initTCP(pData)); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* a common function to free our configuration variables - used both on exit + * and on $ResetConfig processing. -- rgerhards, 2008-05-16 + */ +static void +freeConfigVars(void) +{ + free(cs.pszStrmDrvr); + cs.pszStrmDrvr = NULL; + free(cs.pszStrmDrvrAuthMode); + cs.pszStrmDrvrAuthMode = NULL; + free(cs.pPermPeers); + cs.pPermPeers = NULL; /* TODO: fix in older builds! */ +} + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(netstrms, LM_NETSTRMS_FILENAME); + objRelease(tcpclt, LM_TCPCLT_FILENAME); + freeConfigVars(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + * rgerhards, 2008-03-28 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + freeConfigVars(); + + /* we now must reset all non-string values */ + cs.iStrmDrvrMode = 0; + cs.bResendLastOnRecon = 0; + cs.iUDPRebindInterval = 0; + cs.iTCPRebindInterval = 0; + + return RS_RET_OK; +} + + +BEGINmodInit(Fwd) +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net,LM_NET_FILENAME)); + + CHKiRet(regCfSysLineHdlr((uchar *)"actionforwarddefaulttemplate", 0, eCmdHdlrGetWord, setLegacyDfltTpl, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendtcprebindinterval", 0, eCmdHdlrInt, NULL, &cs.iTCPRebindInterval, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendudprebindinterval", 0, eCmdHdlrInt, NULL, &cs.iUDPRebindInterval, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriver", 0, eCmdHdlrGetWord, NULL, &cs.pszStrmDrvr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdrivermode", 0, eCmdHdlrInt, NULL, &cs.iStrmDrvrMode, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriverauthmode", 0, eCmdHdlrGetWord, NULL, &cs.pszStrmDrvrAuthMode, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriverpermittedpeer", 0, eCmdHdlrGetWord, setPermittedPeer, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendresendlastmsgonreconnect", 0, eCmdHdlrBinary, NULL, &cs.bResendLastOnRecon, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/tools/omfwd.h b/tools/omfwd.h new file mode 100644 index 00000000..379da4fd --- /dev/null +++ b/tools/omfwd.h @@ -0,0 +1,33 @@ +/* omfwd.h + * These are the definitions for the build-in forwarding output module. + * + * File begun on 2007-07-13 by RGerhards + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OMFWD_H_INCLUDED +#define OMFWD_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitFwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMFWD_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/ompipe.c b/tools/ompipe.c new file mode 100644 index 00000000..420e2b11 --- /dev/null +++ b/tools/ompipe.c @@ -0,0 +1,405 @@ +/* ompipe.c + * This is the implementation of the build-in pipe output module. + * Note that this module stems back to the "old" (4.4.2 and below) + * omfile. There were some issues with the new omfile code and pipes + * (namely in regard to xconsole), so we took out the pipe code and moved + * that to a separate module. That a) immediately solves the issue for a + * less common use case and probably makes it much easier to enhance + * file and pipe support (now independently) in the future (we always + * needed to think about pipes in omfile so far, what we now no longer + * need to, hopefully resulting in reduction of complexity). + * + * NOTE: read comments in module-template.h to understand how this pipe + * works! + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/file.h> + +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "ompipe.h" +#include "omfile.h" /* for dirty trick: access to $ActionFileDefaultTemplate value */ +#include "cfsysline.h" +#include "module-template.h" +#include "conf.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("ompipe") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + + +typedef struct _instanceData { + uchar *pipe; /* pipe or template name (display only) */ + uchar *tplName; /* format template to use */ + short fd; /* pipe descriptor for (current) pipe */ + sbool bHadError; /* did we already have/report an error on this pipe? */ +} instanceData; + +typedef struct configSettings_s { + EMPTY_STRUCT +} configSettings_t; +static configSettings_t __attribute__((unused)) cs; + +/* tables for interfacing with the v6 config system */ +/* module-global parameters */ +static struct cnfparamdescr modpdescr[] = { + { "template", eCmdHdlrGetWord, 0 }, +}; +static struct cnfparamblk modpblk = + { CNFPARAMBLK_VERSION, + sizeof(modpdescr)/sizeof(struct cnfparamdescr), + modpdescr + }; + +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "pipe", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +struct modConfData_s { + rsconf_t *pConf; /* our overall config object */ + uchar *tplName; /* default template */ +}; + +static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ +static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ + +/* this function gets the default template */ +static inline uchar* +getDfltTpl(void) +{ + if(loadModConf != NULL && loadModConf->tplName != NULL) + return loadModConf->tplName; + else + return (uchar*)"RSYSLOG_FileFormat"; +} + + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars +ENDinitConfVars + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("pipe %s", pData->pipe); + if (pData->fd == -1) + dbgprintf(" (unused)"); +ENDdbgPrintInstInfo + + +/* This is now shared code for all types of files. It simply prepares + * pipe access, which, among others, means the the pipe wil be opened + * and any directories in between will be created (based on config, of + * course). -- rgerhards, 2008-10-22 + * changed to iRet interface - 2009-03-19 + */ +static inline rsRetVal +preparePipe(instanceData *pData) +{ + DEFiRet; + pData->fd = open((char*) pData->pipe, O_RDWR|O_NONBLOCK|O_CLOEXEC); + if(pData->fd < 0 ) { + pData->fd = -1; + if(!pData->bHadError) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_NO_FILE_ACCESS, "Could no open output pipe '%s': %s", + pData->pipe, errStr); + pData->bHadError = 1; + } + DBGPRINTF("Error opening log pipe: %s\n", pData->pipe); + } + RETiRet; +} + + +/* rgerhards 2004-11-11: write to a pipe output. This + * will be called for all outputs using pipe semantics, + * for example also for pipes. + */ +static rsRetVal writePipe(uchar **ppString, instanceData *pData) +{ + int iLenWritten; + DEFiRet; + + ASSERT(pData != NULL); + + if(pData->fd == -1) { + rsRetVal iRetLocal; + iRetLocal = preparePipe(pData); + if((iRetLocal != RS_RET_OK) || (pData->fd == -1)) + ABORT_FINALIZE(RS_RET_SUSPENDED); /* whatever the failure was, we need to retry */ + } + + /* create the message based on format specified */ + iLenWritten = write(pData->fd, ppString[0], strlen((char*)ppString[0])); + if(iLenWritten < 0) { + int e = errno; + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + DBGPRINTF("pipe (%d) write error %d: %s\n", pData->fd, e, errStr); + + /* If a named pipe is full, we suspend this action for a while */ + if(e == EAGAIN) + ABORT_FINALIZE(RS_RET_SUSPENDED); + + close(pData->fd); + pData->fd = -1; /* tell that fd is no longer open! */ + iRet = RS_RET_SUSPENDED; + errno = e; + errmsg.LogError(0, NO_ERRCODE, "%s", pData->pipe); + } + +finalize_it: + RETiRet; +} + + +BEGINbeginCnfLoad +CODESTARTbeginCnfLoad + loadModConf = pModConf; + pModConf->pConf = pConf; + pModConf->tplName = NULL; +ENDbeginCnfLoad + +BEGINsetModCnf + struct cnfparamvals *pvals = NULL; + int i; +CODESTARTsetModCnf + pvals = nvlstGetParams(lst, &modpblk, NULL); + if(pvals == NULL) { + errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " + "config parameters [module(...)]"); + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + if(Debug) { + dbgprintf("module (global) param blk for ompipe:\n"); + cnfparamsPrint(&modpblk, pvals); + } + + for(i = 0 ; i < modpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(modpblk.descr[i].name, "template")) { + loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + if(pszFileDfltTplName != NULL) { + errmsg.LogError(0, RS_RET_DUP_PARAM, "ompipe: warning: default template " + "was already set via legacy directive - may lead to inconsistent " + "results."); + } + } else { + dbgprintf("ompipe: program error, non-handled " + "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); + } + } +finalize_it: + if(pvals != NULL) + cnfparamvalsDestruct(pvals, &modpblk); +ENDsetModCnf + +BEGINendCnfLoad +CODESTARTendCnfLoad + loadModConf = NULL; /* done loading */ + /* free legacy config vars */ + free(pszFileDfltTplName); + pszFileDfltTplName = NULL; +ENDendCnfLoad + +BEGINcheckCnf +CODESTARTcheckCnf +ENDcheckCnf + +BEGINactivateCnf +CODESTARTactivateCnf + runModConf = pModConf; +ENDactivateCnf + +BEGINfreeCnf +CODESTARTfreeCnf + free(pModConf->tplName); +ENDfreeCnf + +BEGINcreateInstance +CODESTARTcreateInstance + pData->pipe = NULL; + pData->fd = -1; + pData->bHadError = 0; +ENDcreateInstance + + +BEGINfreeInstance +CODESTARTfreeInstance + free(pData->pipe); + if(pData->fd != -1) + close(pData->fd); +ENDfreeInstance + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + DBGPRINTF(" (%s)\n", pData->pipe); + iRet = writePipe(ppString, pData); +ENDdoAction + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->tplName = NULL; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "pipe")) { + pData->pipe = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("ompipe: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, (uchar*) "RSYSLOG_FileFormat", + OMSR_NO_RQD_TPL_OPTS)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, + (uchar*) strdup((char*) pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + } +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + /* yes, the if below is redundant, but I need it now. Will go away as + * the code further changes. -- rgerhards, 2007-07-25 + */ + if(*p == '|') { + if((iRet = createInstance(&pData)) != RS_RET_OK) { + ENDfunc + return iRet; /* this can not use RET_iRet! */ + } + } else { + /* this is not clean, but we need it for the time being + * TODO: remove when cleaning up modularization + */ + ENDfunc + return RS_RET_CONFLINE_UNPROCESSED; + } + + CODE_STD_STRING_REQUESTparseSelectorAct(1) + CHKmalloc(pData->pipe = malloc(512)); + ++p; + CHKiRet(cflineParseFileName(p, (uchar*) pData->pipe, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + getDfltTpl())); + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINdoHUP +CODESTARTdoHUP + if(pData->fd != -1) { + close(pData->fd); + pData->fd = -1; + } +ENDdoHUP + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_doHUP +CODEqueryEtryPt_STD_CONF2_QUERIES +CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES +CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(Pipe) +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit +/* vi:set ai: + */ diff --git a/tools/ompipe.h b/tools/ompipe.h new file mode 100644 index 00000000..d855def2 --- /dev/null +++ b/tools/ompipe.h @@ -0,0 +1,30 @@ +/* ompipe.h + * These are the definitions for the build-in pipe output module. + * + * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH. + * + * This pipe is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OMPIPE_H_INCLUDED +#define OMPIPE_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitPipe(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMPIPE_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/tools/omshell.c b/tools/omshell.c new file mode 100644 index 00000000..ac62fa62 --- /dev/null +++ b/tools/omshell.c @@ -0,0 +1,154 @@ +/* omshell.c + * This is the implementation of the build-in shell output module. + * + * ************* DO NOT EXTEND THIS MODULE ************** + * This is pure legacy, omprog has much better and more + * secure functionality than this module. It is NOT + * recommended to base new work on it! + * 2012-01-19 rgerhards + * ****************************************************** + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * shell support was initially written by bkalkbrenner 2005-09-20 + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "conf.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "omshell.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + uchar progName[MAXFNAME]; /* program to execute */ +} instanceData; + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("%s", pData->progName); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + /* TODO: using pData->progName is not clean from the point of + * modularization. We'll change that as we go ahead with modularization. + * rgerhards, 2007-07-20 + */ + dbgprintf("\n"); + if(execProg((uchar*) pData->progName, 1, ppString[0]) == 0) + errmsg.LogError(0, NO_ERRCODE, "Executing program '%s' failed", (char*)pData->progName); +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* yes, the if below is redundant, but I need it now. Will go away as + * the code further changes. -- rgerhards, 2007-07-25 + */ + if(*p == '^') { + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + } + + + switch (*p) + { + case '^': /* bkalkbrenner 2005-09-20: execute shell command */ + dbgprintf("exec\n"); + ++p; + iRet = cflineParseFileName(p, (uchar*) pData->progName, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (uchar*)"RSYSLOG_TraditionalFileFormat"); + break; + default: + iRet = RS_RET_CONFLINE_UNPROCESSED; + break; + } + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(Shell) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit + +/* + * vi:set ai: + */ diff --git a/tools/omshell.h b/tools/omshell.h new file mode 100644 index 00000000..3b393ac5 --- /dev/null +++ b/tools/omshell.h @@ -0,0 +1,33 @@ +/* omshell.c + * These are the definitions for the build-in shell output module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ACTSHELL_H_INCLUDED +#define ACTSHELL_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitShell(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef ACTSHELL_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/omusrmsg.c b/tools/omusrmsg.c new file mode 100644 index 00000000..f4cc4094 --- /dev/null +++ b/tools/omusrmsg.c @@ -0,0 +1,451 @@ +/* omusrmsg.c + * This is the implementation of the build-in output module for sending + * user messages. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c, which at the + * time of the fork from sysklogd was under BSD license) + * + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <ctype.h> +#include <sys/param.h> +#ifdef HAVE_UTMP_H +# include <utmp.h> +# define STRUCTUTMP struct utmp +# define UTNAME ut_name +#else +# include <utmpx.h> +# define STRUCTUTMP struct utmpx +# define UTNAME ut_user +#endif +#include <unistd.h> +#include <sys/uio.h> +#include <sys/stat.h> +#include <errno.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#else +#include <sys/msgbuf.h> +#endif +#if HAVE_PATHS_H +#include <paths.h> +#endif +#include "srUtils.h" +#include "stringbuf.h" +#include "syslogd-types.h" +#include "conf.h" +#include "omusrmsg.h" +#include "module-template.h" +#include "errmsg.h" + + +/* portability: */ +#ifndef _PATH_DEV +# define _PATH_DEV "/dev/" +#endif + + +MODULE_TYPE_OUTPUT +MODULE_TYPE_NOKEEP +MODULE_CNFNAME("omusrmsg") + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + int bIsWall; /* 1- is wall, 0 - individual users */ + char uname[MAXUNAMES][UNAMESZ+1]; + uchar *tplName; +} instanceData; + +typedef struct configSettings_s { + EMPTY_STRUCT +} configSettings_t; +static configSettings_t __attribute__((unused)) cs; + + +/* tables for interfacing with the v6 config system */ +/* action (instance) parameters */ +static struct cnfparamdescr actpdescr[] = { + { "users", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "template", eCmdHdlrGetWord, 0 } +}; +static struct cnfparamblk actpblk = + { CNFPARAMBLK_VERSION, + sizeof(actpdescr)/sizeof(struct cnfparamdescr), + actpdescr + }; + +BEGINinitConfVars /* (re)set config variables to default values */ +CODESTARTinitConfVars +ENDinitConfVars + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + free(pData->tplName); +ENDfreeInstance + + +BEGINdbgPrintInstInfo + register int i; +CODESTARTdbgPrintInstInfo + for (i = 0; i < MAXUNAMES && *pData->uname[i]; i++) + dbgprintf("%s, ", pData->uname[i]); +ENDdbgPrintInstInfo + + +/** + * BSD setutent/getutent() replacement routines + * The following routines emulate setutent() and getutent() under + * BSD because they are not available there. We only emulate what we actually + * need! rgerhards 2005-03-18 + */ +#ifdef OS_BSD +/* Since version 900007, FreeBSD has a POSIX compliant <utmpx.h> */ +#if defined(__FreeBSD__) && (__FreeBSD_version >= 900007) +# define setutent(void) setutxent(void) +# define getutent(void) getutxent(void) +# define endutent(void) endutxent(void) +#else +static FILE *BSD_uf = NULL; +void setutent(void) +{ + assert(BSD_uf == NULL); + if ((BSD_uf = fopen(_PATH_UTMP, "r")) == NULL) { + errmsg.LogError(0, NO_ERRCODE, "%s", _PATH_UTMP); + return; + } +} + +STRUCTUTMP* getutent(void) +{ + static STRUCTUTMP st_utmp; + + if(fread((char *)&st_utmp, sizeof(st_utmp), 1, BSD_uf) != 1) + return NULL; + + return(&st_utmp); +} + +void endutent(void) +{ + fclose(BSD_uf); + BSD_uf = NULL; +} +#endif /* if defined(__FreeBSD__) */ +#endif /* #ifdef OS_BSD */ + + +/* WALLMSG -- Write a message to the world at large + * + * Write the specified message to either the entire + * world, or a list of approved users. + * + * rgerhards, 2005-10-19: applying the following sysklogd patch: + * Tue May 4 16:52:01 CEST 2004: Solar Designer <solar@openwall.com> + * Adjust the size of a variable to prevent a buffer overflow + * should _PATH_DEV ever contain something different than "/dev/". + * rgerhards, 2008-07-04: changing the function to no longer use fork() but + * continue run on its thread instead. + */ +static rsRetVal wallmsg(uchar* pMsg, instanceData *pData) +{ + + uchar szErr[512]; + char p[sizeof(_PATH_DEV) + UNAMESZ]; + register int i; + int errnoSave; + int ttyf; + int wrRet; + STRUCTUTMP ut; + STRUCTUTMP *uptr; + struct stat statb; + DEFiRet; + + assert(pMsg != NULL); + + /* open the user login file */ + setutent(); + + /* scan the user login file */ + while((uptr = getutent())) { + memcpy(&ut, uptr, sizeof(ut)); + /* is this slot used? */ + if(ut.UTNAME[0] == '\0') + continue; +#ifndef OS_BSD + if(ut.ut_type != USER_PROCESS) + continue; +#endif + if(!(strncmp (ut.UTNAME,"LOGIN", 6))) /* paranoia */ + continue; + + /* should we send the message to this user? */ + if(pData->bIsWall == 0) { + for(i = 0; i < MAXUNAMES; i++) { + if(!pData->uname[i][0]) { + i = MAXUNAMES; + break; + } + if(strncmp(pData->uname[i], ut.UTNAME, UNAMESZ) == 0) + break; + } + if(i == MAXUNAMES) /* user not found? */ + continue; /* on to next user! */ + } + + /* compute the device name */ + strcpy(p, _PATH_DEV); + strncat(p, ut.ut_line, UNAMESZ); + + /* we must be careful when writing to the terminal. A terminal may block + * (for example, a user has pressed <ctl>-s). In that case, we can not + * wait indefinitely. So we need to use non-blocking I/O. In case we would + * block, we simply do not send the message, because that's the best we can + * do. -- rgerhards, 2008-07-04 + */ + + /* open the terminal */ + if((ttyf = open(p, O_WRONLY|O_NOCTTY|O_NONBLOCK)) >= 0) { + if(fstat(ttyf, &statb) == 0 && (statb.st_mode & S_IWRITE)) { + wrRet = write(ttyf, pMsg, strlen((char*)pMsg)); + if(Debug && wrRet == -1) { + /* we record the state to the debug log */ + errnoSave = errno; + rs_strerror_r(errno, (char*)szErr, sizeof(szErr)); + dbgprintf("write to terminal '%s' failed with [%d]:%s\n", + p, errnoSave, szErr); + } + } + close(ttyf); + } + } + + /* close the user login file */ + endutent(); + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + iRet = wallmsg(ppString[0], pData); +ENDdoAction + + +static inline void +populateUsers(instanceData *pData, es_str_t *usrs) +{ + int i; + int iDst; + es_size_t iUsr; + es_size_t len; + uchar *c; + + len = es_strlen(usrs); + c = es_getBufAddr(usrs); + pData->bIsWall = 0; /* write to individual users */ + iUsr = 0; + for(i = 0 ; i < MAXUNAMES && iUsr < len ; ++i) { + for( iDst = 0 + ; iDst < UNAMESZ && iUsr < len && c[iUsr] != ',' + ; ++iDst, ++iUsr) { + pData->uname[i][iDst] = c[iUsr]; + } + pData->uname[i][iDst] = '\0'; + DBGPRINTF("omusrmsg: send to user '%s'\n", pData->uname[i]); + if(iUsr < len && c[iUsr] != ',') { + errmsg.LogError(0, RS_RET_ERR, "user name '%s...' too long - " + "ignored", pData->uname[i]); + --i; + ++iUsr; + while(iUsr < len && c[iUsr] != ',') + ++iUsr; /* skip to next name */ + } else if(iDst == 0) { + errmsg.LogError(0, RS_RET_ERR, "no user name given - " + "ignored"); + --i; + ++iUsr; + while(iUsr < len && c[iUsr] != ',') + ++iUsr; /* skip to next name */ + } + if(iUsr < len) { + ++iUsr; /* skip "," */ + while(iUsr < len && isspace(c[iUsr])) + ++iUsr; /* skip whitespace */ + } + } + if(i == MAXUNAMES && iUsr != len) { + errmsg.LogError(0, RS_RET_ERR, "omusrmsg supports only up to %d " + "user names in a single action - all others have been ignored", + MAXUNAMES); + } +} + + +static inline void +setInstParamDefaults(instanceData *pData) +{ + pData->bIsWall = 0; + pData->tplName = NULL; +} + +BEGINnewActInst + struct cnfparamvals *pvals; + int i; +CODESTARTnewActInst + if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { + ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); + } + + CHKiRet(createInstance(&pData)); + setInstParamDefaults(pData); + + CODE_STD_STRING_REQUESTnewActInst(1) + for(i = 0 ; i < actpblk.nParams ; ++i) { + if(!pvals[i].bUsed) + continue; + if(!strcmp(actpblk.descr[i].name, "users")) { + if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"*", 1)) { + pData->bIsWall = 1; + } else { + populateUsers(pData, pvals[i].val.d.estr); + } + } else if(!strcmp(actpblk.descr[i].name, "template")) { + pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else { + dbgprintf("omusrmsg: program error, non-handled " + "param '%s'\n", actpblk.descr[i].name); + } + } + + if(pData->tplName == NULL) { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, + (uchar*) strdup(pData->bIsWall ? " WallFmt" : " StdUsrMsgFmt"), + OMSR_NO_RQD_TPL_OPTS)); + } else { + CHKiRet(OMSRsetEntry(*ppOMSR, 0, + (uchar*) strdup((char*) pData->tplName), + OMSR_NO_RQD_TPL_OPTS)); + } +CODE_STD_FINALIZERnewActInst + cnfparamvalsDestruct(pvals, &actpblk); +ENDnewActInst + + +BEGINparseSelectorAct + es_str_t *usrs; + int bHadWarning; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + bHadWarning = 0; + if(!strncmp((char*) p, ":omusrmsg:", sizeof(":omusrmsg:") - 1)) { + p += sizeof(":omusrmsg:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + if(!*p || !((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') + || (*p >= '0' && *p <= '9') || *p == '_' || *p == '.' || *p == '*')) { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } else { + errmsg.LogError(0, RS_RET_OUTDATED_STMT, + "action '%s' treated as ':omusrmsg:%s' - please " + "change syntax, '%s' will not be supported in the future", + p, p, p); + bHadWarning = 1; + } + } + + CHKiRet(createInstance(&pData)); + + if(*p == '*') { /* wall */ + dbgprintf("write-all"); + ++p; /* eat '*' */ + pData->bIsWall = 1; /* write to all users */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " WallFmt")); + } else { + /* everything else is currently treated as a user name */ + usrs = es_newStr(128); + while(*p && *p != ';') { + es_addChar(&usrs, *p); + ++p; + } + populateUsers(pData, usrs); + es_deleteStr(usrs); + if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*)" StdUsrMsgFmt")) + != RS_RET_OK) + goto finalize_it; + } + if(iRet == RS_RET_OK && bHadWarning) + iRet = RS_RET_OK_WARN; +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(UsrMsg) +CODESTARTmodInit +INITLegCnfVars + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/tools/omusrmsg.h b/tools/omusrmsg.h new file mode 100644 index 00000000..dc9c4565 --- /dev/null +++ b/tools/omusrmsg.h @@ -0,0 +1,32 @@ +/* omusrmsg.c + * These are the definitions for the build-in user message output module. + * + * File begun on 2007-07-13 by RGerhards + * + * Copyright 20072-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OMUSRMSG_H_INCLUDED +#define OMUSRMSG_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitUsrMsg(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMUSRMSG_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/tools/pidfile.c b/tools/pidfile.c new file mode 100644 index 00000000..8298b94e --- /dev/null +++ b/tools/pidfile.c @@ -0,0 +1,158 @@ +/* + pidfile.c - interact with pidfiles + Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE> + + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ +#include "config.h" + + +#include "rsyslog.h" + +/* + * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze + * First version (v0.2) released + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#ifdef __sun +#include <fcntl.h> +#endif + +#include "srUtils.h" + +/* read_pid + * + * Reads the specified pidfile and returns the read pid. + * 0 is returned if either there's no pidfile, it's empty + * or no pid can be read. + */ +int read_pid (char *pidfile) +{ + FILE *f; + int pid; + + if (!(f=fopen(pidfile,"r"))) + return 0; + if(fscanf(f,"%d", &pid) != 1) + pid = 0; + fclose(f); + return pid; +} + +/* check_pid + * + * Reads the pid using read_pid and looks up the pid in the process + * table (using /proc) to determine if the process already exists. If + * so 1 is returned, otherwise 0. + */ +int check_pid (char *pidfile) +{ + int pid = read_pid(pidfile); + + /* Amazing ! _I_ am already holding the pid file... */ + if ((!pid) || (pid == getpid ())) + return 0; + + /* + * The 'standard' method of doing this is to try and do a 'fake' kill + * of the process. If an ESRCH error is returned the process cannot + * be found -- GW + */ + /* But... errno is usually changed only on error.. */ + if (kill(pid, 0) && errno == ESRCH) + return(0); + + return pid; +} + +/* write_pid + * + * Writes the pid to the specified file. If that fails 0 is + * returned, otherwise the pid. + */ +int write_pid (char *pidfile) +{ + FILE *f; + int fd; + int pid; + + if ( ((fd = open(pidfile, O_RDWR|O_CREAT|O_CLOEXEC, 0644)) == -1) + || ((f = fdopen(fd, "r+")) == NULL) ) { + fprintf(stderr, "Can't open or create %s.\n", pidfile); + return 0; + } + + /* It seems to be acceptable that we do not lock the pid file + * if we run under Solaris. In any case, it is highly unlikely + * that two instances try to access this file. And flock is really + * causing me grief on my initial steps on Solaris. Some time later, + * we might re-enable it (or use some alternate method). + * 2006-02-16 rgerhards + */ + +#if HAVE_FLOCK + if (flock(fd, LOCK_EX|LOCK_NB) == -1) { + if(fscanf(f, "%d", &pid) != 1) + pid = 0; + fclose(f); + printf("Can't lock, lock is held by pid %d.\n", pid); + return 0; + } +#endif + + pid = getpid(); + if (!fprintf(f,"%d\n", pid)) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + printf("Can't write pid , %s.\n", errStr); + fclose(f); + return 0; + } + fflush(f); + +#if HAVE_FLOCK + if (flock(fd, LOCK_UN) == -1) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + printf("Can't unlock pidfile %s, %s.\n", pidfile, errStr); + fclose(f); + return 0; + } +#endif + fclose(f); + + return pid; +} + +/* remove_pid + * + * Remove the the specified file. The result from unlink(2) + * is returned + */ +int remove_pid (char *pidfile) +{ + return unlink (pidfile); +} + diff --git a/tools/pidfile.h b/tools/pidfile.h new file mode 100644 index 00000000..40be9069 --- /dev/null +++ b/tools/pidfile.h @@ -0,0 +1,51 @@ +/* + pidfile.h - interact with pidfiles + Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE> + + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ + +/* read_pid + * + * Reads the specified pidfile and returns the read pid. + * 0 is returned if either there's no pidfile, it's empty + * or no pid can be read. + */ +int read_pid (char *pidfile); + +/* check_pid + * + * Reads the pid using read_pid and looks up the pid in the process + * table (using /proc) to determine if the process already exists. If + * so 1 is returned, otherwise 0. + */ +int check_pid (char *pidfile); + +/* write_pid + * + * Writes the pid to the specified file. If that fails 0 is + * returned, otherwise the pid. + */ +int write_pid (char *pidfile); + +/* remove_pid + * + * Remove the the specified file. The result from unlink(2) + * is returned + */ +int remove_pid (char *pidfile); diff --git a/tools/pmrfc3164.c b/tools/pmrfc3164.c new file mode 100644 index 00000000..5dfa74f0 --- /dev/null +++ b/tools/pmrfc3164.c @@ -0,0 +1,239 @@ +/* pmrfc3164.c + * This is a parser module for RFC3164(legacy syslog)-formatted messages. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2009-11-04 by RGerhards + * + * Copyright 2007, 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +PARSER_NAME("rsyslog.rfc3164") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* static data */ +static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +/* parse a legay-formatted syslog message. + */ +BEGINparse + uchar *p2parse; + int lenMsg; + int i; /* general index for parsing */ + uchar bufParseTAG[CONF_TAG_MAXSIZE]; + uchar bufParseHOSTNAME[CONF_HOSTNAME_MAXSIZE]; +CODESTARTparse + DBGPRINTF("Message will now be parsed by the legacy syslog parser (one size fits all... ;)).\n"); + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */ + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + setProtocolVersion(pMsg, 0); + + /* Check to see if msg contains a timestamp. We start by assuming + * that the message timestamp is the time of reception (which we + * generated ourselfs and then try to actually find one inside the + * message. There we go from high-to low precison and are done + * when we find a matching one. -- rgerhards, 2008-09-16 + */ + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3339 */; + } else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3164 */; + } else if(*p2parse == ' ' && lenMsg > 1) { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */ + ++p2parse; /* move over space */ + --lenMsg; + if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + /* indeed, we got it! */ + /* we are done - parse pointer is moved by ParseTIMESTAMP3164 */; + } else {/* parse pointer needs to be restored, as we moved it off-by-one + * for this try. + */ + --p2parse; + ++lenMsg; + } + } + + if(pMsg->msgFlags & IGNDATE) { + /* we need to ignore the msg data, so simply copy over reception date */ + memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime)); + } + + /* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we + * do this only when the user has not forbidden this. I now introduce some + * code that allows a user to configure rsyslogd to treat the rest of the + * message as MSG part completely. In this case, the hostname will be the + * machine that we received the message from and the tag will be empty. This + * is meant to be an interim solution, but for now it is in the code. + */ + if(bParseHOSTNAMEandTAG && !(pMsg->msgFlags & INTERNAL_MSG)) { + /* parse HOSTNAME - but only if this is network-received! + * rger, 2005-11-14: we still have a problem with BSD messages. These messages + * do NOT include a host name. In most cases, this leads to the TAG to be treated + * as hostname and the first word of the message as the TAG. Clearly, this is not + * of advantage ;) I think I have now found a way to handle this situation: there + * are certain characters which are frequently used in TAG (e.g. ':'), which are + * *invalid* in host names. So while parsing the hostname, I check for these characters. + * If I find them, I set a simple flag but continue. After parsing, I check the flag. + * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change + * the fields. I think this logic shall work with any type of syslog message. + * rgerhards, 2009-06-23: and I now have extended this logic to every character + * that is not a valid hostname. + */ + if(lenMsg > 0 && pMsg->msgFlags & PARSE_HOSTNAME) { + i = 0; + while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' + || p2parse[i] == '_' || p2parse[i] == '-') && i < (CONF_HOSTNAME_MAXSIZE - 1)) { + bufParseHOSTNAME[i] = p2parse[i]; + ++i; + } + + if(i == lenMsg) { + /* we have a message that is empty immediately after the hostname, + * but the hostname thus is valid! -- rgerhards, 2010-02-22 + */ + p2parse += i; + lenMsg -= i; + bufParseHOSTNAME[i] = '\0'; + MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i); + } else if(i > 0 && p2parse[i] == ' ' && isalnum(p2parse[i-1])) { + /* we got a hostname! */ + p2parse += i + 1; /* "eat" it (including SP delimiter) */ + lenMsg -= i + 1; + bufParseHOSTNAME[i] = '\0'; + MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i); + } + } + + /* now parse TAG - that should be present in message from all sources. + * This code is somewhat not compliant with RFC 3164. As of 3164, + * the TAG field is ended by any non-alphanumeric character. In + * practice, however, the TAG often contains dashes and other things, + * which would end the TAG. So it is not desirable. As such, we only + * accept colon and SP to be terminators. Even there is a slight difference: + * a colon is PART of the TAG, while a SP is NOT part of the tag + * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character + * size limit (from RFC3164) on the tag. This had bad effects on existing + * envrionments, as sysklogd didn't obey it either (probably another bug + * in RFC3164...). We now receive the full size, but will modify the + * outputs so that only 32 characters max are used by default. + */ + i = 0; + while(lenMsg > 0 && *p2parse != ':' && *p2parse != ' ' && i < CONF_TAG_MAXSIZE - 2) { + bufParseTAG[i++] = *p2parse++; + --lenMsg; + } + if(lenMsg > 0 && *p2parse == ':') { + ++p2parse; + --lenMsg; + bufParseTAG[i++] = ':'; + } + + /* no TAG can only be detected if the message immediatly ends, in which case an empty TAG + * is considered OK. So we do not need to check for empty TAG. -- rgerhards, 2009-06-23 + */ + bufParseTAG[i] = '\0'; /* terminate string */ + MsgSetTAG(pMsg, bufParseTAG, i); + } else {/* we enter this code area when the user has instructed rsyslog NOT + * to parse HOSTNAME and TAG - rgerhards, 2006-03-13 + */ + if(!(pMsg->msgFlags & INTERNAL_MSG)) { + DBGPRINTF("HOSTNAME and TAG not parsed by user configuraton.\n"); + } + } + + /* The rest is the actual MSG */ + MsgSetMSGoffs(pMsg, p2parse - pMsg->pszRawMsg); +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(pmrfc3164) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + DBGPRINTF("rfc3164 parser init called\n"); + bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(); /* cache value, is set only during rsyslogd option processing */ + + +ENDmodInit + +/* vim:set ai: + */ diff --git a/tools/pmrfc3164.h b/tools/pmrfc3164.h new file mode 100644 index 00000000..24304094 --- /dev/null +++ b/tools/pmrfc3164.h @@ -0,0 +1,33 @@ +/* pmrfc3164.h + * These are the definitions for the RFC3164 parser module. + * + * File begun on 2009-11-04 by RGerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef PMRFC3164_H_INCLUDED +#define PMRFC3164_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitpmrfc3164(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef PMRFC3164_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/tools/pmrfc5424.c b/tools/pmrfc5424.c new file mode 100644 index 00000000..9b5c6165 --- /dev/null +++ b/tools/pmrfc5424.c @@ -0,0 +1,329 @@ +/* pmrfc5424.c + * This is a parser module for RFC5424-formatted messages. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2009-11-03 by RGerhards + * + * Copyright 2007, 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" +#include "parser.h" +#include "datetime.h" +#include "unicode-helper.h" + +MODULE_TYPE_PARSER +MODULE_TYPE_NOKEEP +PARSER_NAME("rsyslog.rfc5424") + +/* internal structures + */ +DEF_PMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(parser) +DEFobjCurrIf(datetime) + + +/* config data */ + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATUREAutomaticSanitazion) + iRet = RS_RET_OK; + if(eFeat == sFEATUREAutomaticPRIParsing) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +/* Helper to parseRFCSyslogMsg. This function parses a field up to + * (and including) the SP character after it. The field contents is + * returned in a caller-provided buffer. The parsepointer is advanced + * to after the terminating SP. The caller must ensure that the + * provided buffer is large enough to hold the to be extracted value. + * Returns 0 if everything is fine or 1 if either the field is not + * SP-terminated or any other error occurs. -- rger, 2005-11-24 + * The function now receives the size of the string and makes sure + * that it does not process more than that. The *pLenStr counter is + * updated on exit. -- rgerhards, 2009-09-23 + */ +static int parseRFCField(uchar **pp2parse, uchar *pResult, int *pLenStr) +{ + uchar *p2parse; + int iRet = 0; + + assert(pp2parse != NULL); + assert(*pp2parse != NULL); + assert(pResult != NULL); + + p2parse = *pp2parse; + + /* this is the actual parsing loop */ + while(*pLenStr > 0 && *p2parse != ' ') { + *pResult++ = *p2parse++; + --(*pLenStr); + } + + if(*pLenStr > 0 && *p2parse == ' ') { + ++p2parse; /* eat SP, but only if not at end of string */ + --(*pLenStr); + } else { + iRet = 1; /* there MUST be an SP! */ + } + *pResult = '\0'; + + /* set the new parse pointer */ + *pp2parse = p2parse; + return iRet; +} + + +/* Helper to parseRFCSyslogMsg. This function parses the structured + * data field of a message. It does NOT parse inside structured data, + * just gets the field as whole. Parsing the single entities is left + * to other functions. The parsepointer is advanced + * to after the terminating SP. The caller must ensure that the + * provided buffer is large enough to hold the to be extracted value. + * Returns 0 if everything is fine or 1 if either the field is not + * SP-terminated or any other error occurs. -- rger, 2005-11-24 + * The function now receives the size of the string and makes sure + * that it does not process more than that. The *pLenStr counter is + * updated on exit. -- rgerhards, 2009-09-23 + */ +static int parseRFCStructuredData(uchar **pp2parse, uchar *pResult, int *pLenStr) +{ + uchar *p2parse; + int bCont = 1; + int iRet = 0; + int lenStr; + + assert(pp2parse != NULL); + assert(*pp2parse != NULL); + assert(pResult != NULL); + + p2parse = *pp2parse; + lenStr = *pLenStr; + + /* this is the actual parsing loop + * Remeber: structured data starts with [ and includes any characters + * until the first ] followed by a SP. There may be spaces inside + * structured data. There may also be \] inside the structured data, which + * do NOT terminate an element. + */ + if(lenStr == 0 || (*p2parse != '[' && *p2parse != '-')) + return 1; /* this is NOT structured data! */ + + if(*p2parse == '-') { /* empty structured data? */ + *pResult++ = '-'; + ++p2parse; + --lenStr; + } else { + while(bCont) { + if(lenStr < 2) { + /* we now need to check if we have only structured data */ + if(lenStr > 0 && *p2parse == ']') { + *pResult++ = *p2parse; + p2parse++; + lenStr--; + bCont = 0; + } else { + iRet = 1; /* this is not valid! */ + bCont = 0; + } + } else if(*p2parse == '\\' && *(p2parse+1) == ']') { + /* this is escaped, need to copy both */ + *pResult++ = *p2parse++; + *pResult++ = *p2parse++; + lenStr -= 2; + } else if(*p2parse == ']' && *(p2parse+1) == ' ') { + /* found end, just need to copy the ] and eat the SP */ + *pResult++ = *p2parse; + p2parse += 2; + lenStr -= 2; + bCont = 0; + } else { + *pResult++ = *p2parse++; + --lenStr; + } + } + } + + if(lenStr > 0 && *p2parse == ' ') { + ++p2parse; /* eat SP, but only if not at end of string */ + --lenStr; + } else { + iRet = 1; /* there MUST be an SP! */ + } + *pResult = '\0'; + + /* set the new parse pointer */ + *pp2parse = p2parse; + *pLenStr = lenStr; + return iRet; +} + +/* parse a RFC5424-formatted syslog message. This function returns + * 0 if processing of the message shall continue and 1 if something + * went wrong and this messe should be ignored. This function has been + * implemented in the effort to support syslog-protocol. Please note that + * the name (parse *RFC*) stems from the hope that syslog-protocol will + * some time become an RFC. Do not confuse this with informational + * RFC 3164 (which is legacy syslog). + * + * currently supported format: + * + * <PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG + * + * <PRI> is already stripped when this function is entered. VERSION already + * has been confirmed to be "1", but has NOT been stripped from the message. + * + * rger, 2005-11-24 + */ +BEGINparse + uchar *p2parse; + uchar *pBuf = NULL; + int lenMsg; + int bContParse = 1; +CODESTARTparse + assert(pMsg != NULL); + assert(pMsg->pszRawMsg != NULL); + p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */ + lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; + + /* check if we are the right parser */ + if(lenMsg < 2 || p2parse[0] != '1' || p2parse[1] != ' ') { + ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); + } + DBGPRINTF("Message has RFC5424/syslog-protocol format.\n"); + setProtocolVersion(pMsg, 1); + p2parse += 2; + lenMsg -= 2; + + /* Now get us some memory we can use as a work buffer while parsing. + * We simply allocated a buffer sufficiently large to hold all of the + * message, so we can not run into any troubles. I think this is + * wiser than to use individual buffers. + */ + CHKmalloc(pBuf = MALLOC(sizeof(uchar) * (lenMsg + 1))); + + /* IMPORTANT NOTE: + * Validation is not actually done below nor are any errors handled. I have + * NOT included this for the current proof of concept. However, it is strongly + * advisable to add it when this code actually goes into production. + * rgerhards, 2005-11-24 + */ + + /* TIMESTAMP */ + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) { + if(pMsg->msgFlags & IGNDATE) { + /* we need to ignore the msg data, so simply copy over reception date */ + memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime)); + } + } else { + DBGPRINTF("no TIMESTAMP detected!\n"); + bContParse = 0; + } + + /* HOSTNAME */ + if(bContParse) { + parseRFCField(&p2parse, pBuf, &lenMsg); + MsgSetHOSTNAME(pMsg, pBuf, ustrlen(pBuf)); + } + + /* APP-NAME */ + if(bContParse) { + parseRFCField(&p2parse, pBuf, &lenMsg); + MsgSetAPPNAME(pMsg, (char*)pBuf); + } + + /* PROCID */ + if(bContParse) { + parseRFCField(&p2parse, pBuf, &lenMsg); + MsgSetPROCID(pMsg, (char*)pBuf); + } + + /* MSGID */ + if(bContParse) { + parseRFCField(&p2parse, pBuf, &lenMsg); + MsgSetMSGID(pMsg, (char*)pBuf); + } + + /* STRUCTURED-DATA */ + if(bContParse) { + parseRFCStructuredData(&p2parse, pBuf, &lenMsg); + MsgSetStructuredData(pMsg, (char*)pBuf); + } + + /* MSG */ + MsgSetMSGoffs(pMsg, p2parse - pMsg->pszRawMsg); + +finalize_it: + if(pBuf != NULL) + free(pBuf); +ENDparse + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(parser, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_PMOD_QUERIES +CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(pmrfc5424) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(parser, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + dbgprintf("rfc5424 parser init called\n"); + dbgprintf("GetParserName addr %p\n", GetParserName); +ENDmodInit + +/* vim:set ai: + */ diff --git a/tools/pmrfc5424.h b/tools/pmrfc5424.h new file mode 100644 index 00000000..df2a1c81 --- /dev/null +++ b/tools/pmrfc5424.h @@ -0,0 +1,33 @@ +/* pmrfc5424.h + * These are the definitions for the RFCC5424 parser module. + * + * File begun on 2009-11-03 by RGerhards + * + * Copyright 2009 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef PMRFC54254_H_INCLUDED +#define PMRFC54254_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitpmrfc5424(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef PMRFC54254_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/tools/recover_qi.pl b/tools/recover_qi.pl new file mode 100755 index 00000000..4e2cf9d5 --- /dev/null +++ b/tools/recover_qi.pl @@ -0,0 +1,207 @@ +#!/usr/bin/perl -w
+# recover rsyslog disk queue index (.qi) from queue files (.nnnnnnnn).
+#
+# See:
+# runtime/queue.c: qqueuePersist()
+# runtime/queue.c: qqueueTryLoadPersistedInfo()
+#
+# kaiwang.chen@gmail.com 2012-03-14
+#
+use strict;
+use Getopt::Long;
+
+my %opt = ();
+GetOptions(\%opt,"spool|w=s","basename|f=s","digits|d=i","help!");
+if ($opt{help}) {
+ print "Usage:
+\t$0 -w WorkDirectory -f QueueFileName -d 8 > QueueFileName.qi
+";
+ exit;
+}
+
+# runtime/queue.c: qConstructDisk()
+my $iMaxFiles = 10000000; # 0+"1".( "0"x($opt{digits} - 1));
+
+# get the list of queue files, spool directory excluded
+my $re = qr/^\Q$opt{basename}\E\.\d{$opt{digits}}$/;
+opendir(DIR, $opt{spool}) or die "can’t open spool: $!";
+my @qf = grep { /$re/ && -f "$opt{spool}/$_" } readdir(DIR);
+closedir DIR;
+
+# ensure order and continuity
+@qf = sort @qf;
+my ($head) = ($qf[0] =~ /(\d+)$/);
+my ($tail) = ($qf[-1] =~ /(\d+)$/);
+$head += 0;
+$tail += 0;
+if ($tail-$head+1 != @qf || $tail > $iMaxFiles) {
+ die "broken queue: missing file(s) or wrong tail\n";
+}
+
+# collect some counters about the queue, assuming all are unprocessed entries.
+my $sizeOnDisk = 0;
+my $iQueueSize = 0;
+chdir($opt{spool}) or die "can't chdir to spool: $!";
+print STDERR "traversing ". @qf ." files, please wait...\n";
+for (@qf) {
+ open FH, "<", $_ or die "can't read queue file $_\n";
+ $sizeOnDisk += (stat FH)[7];
+ while (<FH>) {
+ $iQueueSize++ if /^<Obj/; # runtime/msg.c: MsgSerialize()
+ }
+ close FH;
+}
+# happen to reuse last stat
+my $iCurrOffs_Write = (stat(_))[7];
+
+# runtime/queue.c: qqueuePersist()
+my $qqueue = Rsyslog::OPB->new("qqueue",1);
+$qqueue->property("iQueueSize", "INT", $iQueueSize);
+$qqueue->property("tVars.disk.sizeOnDisk", "INT64", $sizeOnDisk);
+$qqueue->property("tVars.disk.bytesRead", "INT64", 0);
+
+# runtime/stream.h: strmType_t
+my $STREAMTYPE_FILE_CIRCULAR = 1;
+# runtime/stream.h: strmMode_t
+my $STREAMMODE_READ = 1;
+my $STREAMMODE_WRITE_APPEND = 4;
+
+# runtime/stream.c: strmSerialize()
+# write to end
+my $strm_Write = Rsyslog::Obj->new("strm",1);
+$strm_Write->property( "iCurrFNum", "INT", $tail);
+$strm_Write->property( "pszFName", "PSZ", $opt{basename});
+$strm_Write->property( "iMaxFiles", "INT", $iMaxFiles);
+$strm_Write->property( "bDeleteOnClose", "INT", 0);
+$strm_Write->property( "sType", "INT", $STREAMTYPE_FILE_CIRCULAR);
+$strm_Write->property("tOperationsMode", "INT", $STREAMMODE_WRITE_APPEND);
+$strm_Write->property( "tOpenMode", "INT", 0600);
+$strm_Write->property( "iCurrOffs","INT64", $iCurrOffs_Write);
+# read from head
+my $strm_ReadDel = Rsyslog::Obj->new("strm",1);
+$strm_ReadDel->property( "iCurrFNum", "INT", $head);
+$strm_ReadDel->property( "pszFName", "PSZ", $opt{basename});
+$strm_ReadDel->property( "iMaxFiles", "INT", $iMaxFiles);
+$strm_ReadDel->property( "bDeleteOnClose", "INT", 1);
+$strm_ReadDel->property( "sType", "INT", $STREAMTYPE_FILE_CIRCULAR);
+$strm_ReadDel->property("tOperationsMode", "INT", $STREAMMODE_READ);
+$strm_ReadDel->property( "tOpenMode", "INT", 0600);
+$strm_ReadDel->property( "iCurrOffs","INT64", 0);
+
+# .qi
+print $qqueue->serialize();
+print $strm_Write->serialize();
+print $strm_ReadDel->serialize();
+
+exit;
+#-----------------------------------------------------------------------------
+
+package Rsyslog::Serializable;
+# runtime/obj.c
+sub COOKIE_OBJLINE { '<' }
+sub COOKIE_PROPLINE { '+' }
+sub COOKIE_ENDLINE { '>' }
+sub COOKIE_BLANKLINE { '.' }
+# VARTYPE(short_ptype)
+sub VARTYPE {
+ my ($t) = @_;
+ # runtime/obj-types.h: propType_t
+ my $ptype = "PROPTYPE_".$t;
+ # runtime/var.h: varType_t
+ my %vm = (
+ VARTYPE_NONE => 0,
+ VARTYPE_STR => 1,
+ VARTYPE_NUMBER => 2,
+ VARTYPE_SYSLOGTIME => 3,
+ );
+ # runtime/obj.c: SerializeProp()
+ my %p2v = (
+ #PROPTYPE_NONE => "",
+ PROPTYPE_PSZ => "VARTYPE_STR",
+ PROPTYPE_SHORT => "VARTYPE_NUMBER",
+ PROPTYPE_INT => "VARTYPE_NUMBER",
+ PROPTYPE_LONG => "VARTYPE_NUMBER",
+ PROPTYPE_INT64 => "VARTYPE_NUMBER",
+ PROPTYPE_CSTR => "VARTYPE_STR",
+ #PROPTYPE_SYSLOGTIME => "VARTYPE_SYSLOGTIME",
+ );
+ my $vtype = $p2v{$ptype};
+ unless ($vtype) {
+ die "property type $t is not supported!\n";
+ }
+ return $vm{$vtype};
+}
+sub serialize {
+ my $self = shift;
+ # runtime/obj.c: objSerializeHeader()
+ my $x = COOKIE_OBJLINE();
+ $x .= join(":", $self->type(), $self->cver(), $self->id(), $self->version());
+ $x .= ":\n";
+ for ( values %{$self->{props}} ) {
+ # runtime/obj.c: SerializeProp()
+ $x .= COOKIE_PROPLINE();
+ $x .= join(":",
+ $_->{name},
+ VARTYPE($_->{type}),
+ length($_->{value}),
+ $_->{value});
+ $x .= ":\n";
+ }
+ # runtime/obj.c: EndSerialize()
+ $x .= COOKIE_ENDLINE() . "End\n";
+ $x .= COOKIE_BLANKLINE() . "\n";
+}
+# constructor: new(id,version)
+sub new {
+ my ($class, $id, $version) = @_;
+ $class = ref $class if ref $class;
+ bless {
+ id => $id,
+ version => $version,
+ props => {},
+ }, $class;
+}
+sub id {
+ my $self = shift;
+ if (@_) {
+ my $x = $self->{id};
+ $self->{id} = shift;
+ return $x;
+ }
+ return $self->{id};
+}
+sub version {
+ my $self = shift;
+ if (@_) {
+ my $x = $self->{version};
+ $self->{version} = shift;
+ return $x;
+ }
+ return $self->{version};
+}
+# property(name, type, value)
+sub property {
+ my $self = shift;
+ my $name = shift;
+ if (@_) {
+ my $x = $self->{props}{$name};
+ $self->{props}{$name}{name} = $name;
+ $self->{props}{$name}{type} = shift;
+ $self->{props}{$name}{value} = shift;
+ return $x;
+ }
+ return $self->{props}{$name};
+}
+1;
+package Rsyslog::OPB;
+use base qw(Rsyslog::Serializable);
+sub type { 'OPB' }
+sub cver { 1 }
+sub new { shift->SUPER::new(@_) }
+1;
+package Rsyslog::Obj;
+use base qw(Rsyslog::Serializable);
+sub type { 'Obj' }
+sub cver { 1 }
+sub new { shift->SUPER::new(@_) }
+1;
diff --git a/tools/regexp.c b/tools/regexp.c new file mode 100644 index 00000000..e8bba4f4 --- /dev/null +++ b/tools/regexp.c @@ -0,0 +1,73 @@ +/* A simple regular expression checker for rsyslog test and debug. + * Regular expressions have shown to turn out to be a hot support topic. + * While I have done an online tool at http://www.rsyslog.com/tool-regex + * there are still some situations where one wants to check against the + * actual clib api calls. This is what this small test program does, + * it takes its command line arguments (re first, then sample data) and + * pushes them into the API and then shows the result. This should be + * considered the ultimate reference for any questions arising. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <regex.h> + +int main(int argc, char *argv[]) +{ + regex_t preg; + size_t nmatch = 10; + regmatch_t pmatch[10]; + char *pstr; + int i; + + if(argc != 3) { + fprintf(stderr, "usage: regex regexp sample-data\n"); + exit(1); + } + + pstr = strdup(argv[2]); /* get working copy */ + + i = regcomp(&preg, argv[1], REG_EXTENDED); + printf("regcomp returns %d\n", i); + i = regexec(&preg, pstr, nmatch, pmatch, 0); + printf("regexec returns %d\n", i); + if(i == REG_NOMATCH) { + printf("found no match!\n"); + return 1; + } + + printf("returned substrings:\n"); + for(i = 0 ; i < 10 ; i++) { + printf("%d: so %d, eo %d", i, pmatch[i].rm_so, pmatch[i].rm_eo); + if(pmatch[i].rm_so != -1) { + int j; + printf(", text: '"); + for(j = pmatch[i].rm_so ; j < pmatch[i].rm_eo ; ++j) + putchar(pstr[j]); + putchar('\''); + } + putchar('\n'); + } + return 0; +} diff --git a/tools/rscryutil.c b/tools/rscryutil.c new file mode 100644 index 00000000..2591b2cc --- /dev/null +++ b/tools/rscryutil.c @@ -0,0 +1,512 @@ +/* This is a tool for processing rsyslog encrypted log files. + * + * Copyright 2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either exprs or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <getopt.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <gcrypt.h> + +#include "rsyslog.h" +#include "libgcry.h" + + +static enum { MD_DECRYPT, MD_WRITE_KEYFILE +} mode = MD_DECRYPT; +static int verbose = 0; +static gcry_cipher_hd_t gcry_chd; +static size_t blkLength; + +static char *keyfile = NULL; +static char *keyprog = NULL; +static int randomKeyLen = -1; +static char *cry_key = NULL; +static unsigned cry_keylen = 0; +static int cry_algo = GCRY_CIPHER_AES128; +static int cry_mode = GCRY_CIPHER_MODE_CBC; +static int optionForce = 0; + +/* rectype/value must be EIF_MAX_*_LEN+1 long! + * returns 0 on success or something else on error/EOF + */ +static int +eiGetRecord(FILE *eifp, char *rectype, char *value) +{ + int r; + unsigned short i, j; + char buf[EIF_MAX_RECTYPE_LEN+EIF_MAX_VALUE_LEN+128]; + /* large enough for any valid record */ + + if(fgets(buf, sizeof(buf), eifp) == NULL) { + r = 1; goto done; + } + + for(i = 0 ; i < EIF_MAX_RECTYPE_LEN && buf[i] != ':' ; ++i) + if(buf[i] == '\0') { + r = 2; goto done; + } else + rectype[i] = buf[i]; + rectype[i] = '\0'; + j = 0; + for(++i ; i < EIF_MAX_VALUE_LEN && buf[i] != '\n' ; ++i, ++j) + if(buf[i] == '\0') { + r = 3; goto done; + } else + value[j] = buf[i]; + value[j] = '\0'; + r = 0; +done: return r; +} + +static int +eiCheckFiletype(FILE *eifp) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + int r; + + if((r = eiGetRecord(eifp, rectype, value)) != 0) goto done; + if(strcmp(rectype, "FILETYPE") || strcmp(value, RSGCRY_FILETYPE_NAME)) { + fprintf(stderr, "invalid filetype \"cookie\" in encryption " + "info file\n"); + fprintf(stderr, "\trectype: '%s', value: '%s'\n", rectype, value); + r = 1; goto done; + } + r = 0; +done: return r; +} + +static int +eiGetIV(FILE *eifp, char *iv, size_t leniv) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + size_t valueLen; + unsigned short i, j; + int r; + unsigned char nibble; + + if((r = eiGetRecord(eifp, rectype, value)) != 0) goto done; + if(strcmp(rectype, "IV")) { + fprintf(stderr, "no IV record found when expected, record type " + "seen is '%s'\n", rectype); + r = 1; goto done; + } + valueLen = strlen(value); + if(valueLen/2 != leniv) { + fprintf(stderr, "length of IV is %d, expected %d\n", + valueLen/2, leniv); + r = 1; goto done; + } + + for(i = j = 0 ; i < valueLen ; ++i) { + if(value[i] >= '0' && value[i] <= '9') + nibble = value[i] - '0'; + else if(value[i] >= 'a' && value[i] <= 'f') + nibble = value[i] - 'a' + 10; + else { + fprintf(stderr, "invalid IV '%s'\n", value); + r = 1; goto done; + } + if(i % 2 == 0) + iv[j] = nibble << 4; + else + iv[j++] |= nibble; + } + r = 0; +done: return r; +} + +static int +eiGetEND(FILE *eifp, off64_t *offs) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + int r; + + if((r = eiGetRecord(eifp, rectype, value)) != 0) goto done; + if(strcmp(rectype, "END")) { + fprintf(stderr, "no END record found when expected, record type " + "seen is '%s'\n", rectype); + r = 1; goto done; + } + *offs = atoll(value); + r = 0; +done: return r; +} + +static int +initCrypt(FILE *eifp) +{ + int r = 0; + gcry_error_t gcryError; + char iv[4096]; + + blkLength = gcry_cipher_get_algo_blklen(cry_algo); + if(blkLength > sizeof(iv)) { + fprintf(stderr, "internal error[%s:%d]: block length %d too large for " + "iv buffer\n", __FILE__, __LINE__, blkLength); + r = 1; goto done; + } + if((r = eiGetIV(eifp, iv, blkLength)) != 0) goto done; + + size_t keyLength = gcry_cipher_get_algo_keylen(cry_algo); + if(strlen(cry_key) != keyLength) { + fprintf(stderr, "invalid key length; key is %u characters, but " + "exactly %u characters are required\n", cry_keylen, + keyLength); + r = 1; goto done; + } + + gcryError = gcry_cipher_open(&gcry_chd, cry_algo, cry_mode, 0); + if (gcryError) { + printf("gcry_cipher_open failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + r = 1; goto done; + } + + gcryError = gcry_cipher_setkey(gcry_chd, cry_key, keyLength); + if (gcryError) { + printf("gcry_cipher_setkey failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + r = 1; goto done; + } + + gcryError = gcry_cipher_setiv(gcry_chd, iv, blkLength); + if (gcryError) { + printf("gcry_cipher_setiv failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + r = 1; goto done; + } +done: return r; +} + +static inline void +removePadding(char *buf, size_t *plen) +{ + unsigned len = (unsigned) *plen; + unsigned iSrc, iDst; + char *frstNUL; + + frstNUL = memchr(buf, 0x00, *plen); + if(frstNUL == NULL) + goto done; + iDst = iSrc = frstNUL - buf; + + while(iSrc < len) { + if(buf[iSrc] != 0x00) + buf[iDst++] = buf[iSrc]; + ++iSrc; + } + + *plen = iDst; +done: return; +} + +static void +decryptBlock(FILE *fpin, FILE *fpout, off64_t blkEnd, off64_t *pCurrOffs) +{ + gcry_error_t gcryError; + size_t nRead, nWritten; + size_t toRead; + size_t leftTillBlkEnd; + char buf[64*1024]; + + leftTillBlkEnd = blkEnd - *pCurrOffs; + while(1) { + toRead = sizeof(buf) <= leftTillBlkEnd ? sizeof(buf) : leftTillBlkEnd; + toRead = toRead - toRead % blkLength; + nRead = fread(buf, 1, toRead, fpin); + if(nRead == 0) + break; + leftTillBlkEnd -= nRead, *pCurrOffs += nRead; + gcryError = gcry_cipher_decrypt( + gcry_chd, // gcry_cipher_hd_t + buf, // void * + nRead, // size_t + NULL, // const void * + 0); // size_t + if (gcryError) { + fprintf(stderr, "gcry_cipher_decrypt failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + return; + } + removePadding(buf, &nRead); + nWritten = fwrite(buf, 1, nRead, fpout); + if(nWritten != nRead) { + perror("fpout"); + return; + } + } +} + + +static int +doDecrypt(FILE *logfp, FILE *eifp, FILE *outfp) +{ + off64_t blkEnd; + off64_t currOffs = 0; + int r; + + while(1) { + /* process block */ + if(initCrypt(eifp) != 0) + goto done; + if((r = eiGetEND(eifp, &blkEnd)) != 0) goto done; + decryptBlock(logfp, outfp, blkEnd, &currOffs); + gcry_cipher_close(gcry_chd); + } + r = 0; +done: return r; +} + +static void +decrypt(char *name) +{ + FILE *logfp = NULL, *eifp = NULL; + int r = 0; + char eifname[4096]; + + if(!strcmp(name, "-")) { + fprintf(stderr, "decrypt mode cannot work on stdin\n"); + goto err; + } else { + if((logfp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + snprintf(eifname, sizeof(eifname), "%s%s", name, ENCINFO_SUFFIX); + eifname[sizeof(eifname)-1] = '\0'; + if((eifp = fopen(eifname, "r")) == NULL) { + perror(eifname); + goto err; + } + if(eiCheckFiletype(eifp) != 0) + goto err; + } + + doDecrypt(logfp, eifp, stdout); + + fclose(logfp); logfp = NULL; + fclose(eifp); eifp = NULL; + return; + +err: + fprintf(stderr, "error %d processing file %s\n", r, name); + if(logfp != NULL) + fclose(logfp); +} + +static void +write_keyfile(char *fn) +{ + int fd; + int r; + mode_t fmode; + + fmode = O_WRONLY|O_CREAT; + if(!optionForce) + fmode |= O_EXCL; + if((fd = open(fn, fmode, S_IRUSR)) == -1) { + fprintf(stderr, "error opening keyfile "); + perror(fn); + exit(1); + } + if((r = write(fd, cry_key, cry_keylen)) != (ssize_t)cry_keylen) { + fprintf(stderr, "error writing keyfile (ret=%d) ", r); + perror(fn); + exit(1); + } + close(fd); +} + +static void +getKeyFromFile(char *fn) +{ + int r; + r = gcryGetKeyFromFile(fn, &cry_key, &cry_keylen); + if(r != 0) { + fprintf(stderr, "Error %d reading key from file '%s'\n", r, fn); + exit(1); + } +} + +static void +getRandomKey(void) +{ + int fd; + cry_keylen = randomKeyLen; + cry_key = malloc(randomKeyLen); /* do NOT zero-out! */ + /* if we cannot obtain data from /dev/urandom, we use whatever + * is present at the current memory location as random data. Of + * course, this is very weak and we should consider a different + * option, especially when not running under Linux (for Linux, + * unavailability of /dev/urandom is just a theoretic thing, it + * will always work...). -- TODO -- rgerhards, 2013-03-06 + */ + if((fd = open("/dev/urandom", O_RDONLY)) > 0) { + if(read(fd, cry_key, randomKeyLen)) {}; /* keep compiler happy */ + close(fd); + } +} + + +static void +setKey() +{ + if(randomKeyLen != -1) + getRandomKey(); + else if(keyfile != NULL) + getKeyFromFile(keyfile); + else if(keyprog != NULL) + gcryGetKeyFromProg(keyprog, &cry_key, &cry_keylen); + if(cry_key == NULL) { + fprintf(stderr, "ERROR: key must be set via some method\n"); + exit(1); + } +} + +static struct option long_options[] = +{ + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"decrypt", no_argument, NULL, 'd'}, + {"force", no_argument, NULL, 'f'}, + {"write-keyfile", required_argument, NULL, 'W'}, + {"key", required_argument, NULL, 'K'}, + {"generate-random-key", required_argument, NULL, 'r'}, + {"keyfile", required_argument, NULL, 'k'}, + {"key-program", required_argument, NULL, 'p'}, + {"algo", required_argument, NULL, 'a'}, + {"mode", required_argument, NULL, 'm'}, + {NULL, 0, NULL, 0} +}; + +int +main(int argc, char *argv[]) +{ + int i; + int opt; + int temp; + char *newKeyFile = NULL; + + while(1) { + opt = getopt_long(argc, argv, "a:dfk:K:m:p:r:vVW:", long_options, NULL); + if(opt == -1) + break; + switch(opt) { + case 'd': + mode = MD_DECRYPT; + break; + case 'W': + mode = MD_WRITE_KEYFILE; + newKeyFile = optarg; + break; + case 'k': + keyfile = optarg; + break; + case 'p': + keyprog = optarg; + break; + case 'f': + optionForce = 1; + break; + case 'r': + randomKeyLen = atoi(optarg); + if(randomKeyLen > 64*1024) { + fprintf(stderr, "ERROR: keys larger than 64KiB are " + "not supported\n"); + exit(1); + } + break; + case 'K': + fprintf(stderr, "WARNING: specifying the actual key " + "via the command line is highly insecure\n" + "Do NOT use this for PRODUCTION use.\n"); + cry_key = optarg; + cry_keylen = strlen(cry_key); + break; + case 'a': + temp = rsgcryAlgoname2Algo(optarg); + if(temp == GCRY_CIPHER_NONE) { + fprintf(stderr, "ERROR: algorithm \"%s\" is not " + "kown/supported\n", optarg); + exit(1); + } + cry_algo = temp; + break; + case 'm': + temp = rsgcryModename2Mode(optarg); + if(temp == GCRY_CIPHER_MODE_NONE) { + fprintf(stderr, "ERROR: cipher mode \"%s\" is not " + "kown/supported\n", optarg); + exit(1); + } + cry_mode = temp; + break; + case 'v': + verbose = 1; + break; + case 'V': + fprintf(stderr, "rsgtutil " VERSION "\n"); + exit(0); + break; + case '?': + break; + default:fprintf(stderr, "getopt_long() returns unknown value %d\n", opt); + return 1; + } + } + + setKey(); + + if(mode == MD_WRITE_KEYFILE) { + if(optind != argc) { + fprintf(stderr, "ERROR: no file parameters permitted in " + "--write-keyfile mode\n"); + exit(1); + } + write_keyfile(newKeyFile); + } else { + if(optind == argc) + decrypt("-"); + else { + for(i = optind ; i < argc ; ++i) + decrypt(argv[i]); + } + } + + memset(cry_key, 0, cry_keylen); /* zero-out key store */ + cry_keylen = 0; + return 0; +} diff --git a/tools/rscryutil.rst b/tools/rscryutil.rst new file mode 100644 index 00000000..dfd447d2 --- /dev/null +++ b/tools/rscryutil.rst @@ -0,0 +1,199 @@ +========= +rscryutil +========= + +-------------------------- +Manage Encrypted Log Files +-------------------------- + +:Author: Rainer Gerhards <rgerhards@adiscon.com> +:Date: 2013-04-15 +:Manual section: 1 + +SYNOPSIS +======== + +:: + + rscryutil [OPTIONS] [FILE] ... + + +DESCRIPTION +=========== + +This tool performs various operations on encrypted log files. +Most importantly, it provides the ability to decrypt them. + + +OPTIONS +======= + +-d, --decrypt + Select decryption mode. This is the default mode. + +-W, --write-keyfile <file> + Utility function to write a key to a keyfile. The key can be obtained + via any method. + +-v, --verbose + Select verbose mode. + +-f, --force + Forces operations that otherwise would fail. + +-k, --keyfile <file> + Reads the key from <file>. File _must_ contain the key, only, no headers + or other meta information. Keyfiles can be generated via the + *--write-keyfile* option. + +-p, --key-program <path-to-program> + In this mode, the key is provided by a so-called "key program". This program + is executed and must return the key to (as well as some meta information) + via stdout. The core idea of key programs is that using this interface the + user can implement as complex (and secure) method to obtain keys as + desired, all without the need to make modifications to rsyslog. + +-K, --key <KEY> + TESTING AID, NOT FOR PRODUCTION USE. This uses the KEY specified + on the command line. This is the actual key, and as such this mode + is highly insecure. However, it can be useful for intial testing + steps. This option may be removed in the future. + +-a, --algo <algo> + Sets the encryption algorightm (cipher) to be used. See below + for supported algorithms. The default is "AES128". + +-m, --mode <mode> + Sets the ciphermode to be used. See below for supported modes. + The default is "CBC". + +-r, --generate-random-key <bytes> + Generates a random key of length <bytes>. This option is + meant to be used together with *--write-keyfile* (and it is hard + to envision any other valid use for it). + +OPERATION MODES +=============== + +The operation mode specifies what exactly the tool does with the provided +files. The default operation mode is "dump", but this may change in the future. +Thus, it is recommended to always set the operations mode explicitely. If +multiple operations mode are set on the command line, results are +unpredictable. + +decrypt +------- + +The provided log files are decrypted. Note that the *.encinfo* side files +must exist and be accessible in order for decryption to to work. + +write-keyfile +------------- + +In this mode no log files are processed; thus it is an error to specify +any on the command line. The specified keyfile is written. The key itself +is obtained via the usual key commands. If *--keyfile* is used, that +file is effectively copied. + +For security reasons, existing key files are _not_ overwritten. To permit +this, specify the *--force* option. When doing so, keep in mind that lost +keys cannot be recovered and data encrypted with them may also be considered +lost. + +Keyfiles are always created with 0400 permission, that is read access for only +the user. An exception is when an existing file is overwritten via the +*--force* option, in which case the former permissions still apply. + +EXIT CODES +========== + +The command returns an exit code of 0 if everything went fine, and some +other code in case of failures. + + +SUPPORTED ALGORITHMS +==================== + +We basically support what libgcrypt supports. This is: + + 3DES + CAST5 + BLOWFISH + AES128 + AES192 + AES256 + TWOFISH + TWOFISH128 + ARCFOUR + DES + SERPENT128 + SERPENT192 + SERPENT256 + RFC2268_40 + SEED + CAMELLIA128 + CAMELLIA192 + CAMELLIA256 + + +SUPPORTED CIPHER MODES +====================== + +We basically support what libgcrypt supports. This is: + + ECB + CFB + CBC + STREAM + OFB + CTR + AESWRAP + +EXAMPLES +======== + +**rscryutil logfile** + +Decrypts "logfile" and sends data to stdout. + + +**rscryutil --generate-random-key 16 --keyfile /some/secured/path/keyfile** + +Generates random key and stores it in the specified keyfile. + +LOG SIGNATURES +============== + +Encrypted log files can be used together with signing. To verify such a file, +it must be decrypted first, and the verification tool **rsgtutil(1)** must be +run on the decrypted file. + +SECURITY CONSIDERATIONS +======================= + +Specifying keys directly on the command line (*--key* option) is very +insecure and should +not be done, except for testing purposes with test keys. Even then it is +recommended to use keyfiles, which are also easy to handle during testing. +Keep in mind that command history is usally be kept by bash and can also +easily be monitored. + +Local keyfiles are also a security risk. At a minimum, they should be +used with very restrictive file permissions. For this reason, +the *rscryutil* tool creates them with read permissions for the user, +only, no matter what umask is set to. + +When selecting cipher algorithms and modes, care needs to be taken. The +defaults should be reasonable safe to use, but this tends to change over +time. Keep up with the most current crypto recommendations. + + +SEE ALSO +======== +**rsgtutil(1)**, **rsyslogd(8)** + +COPYRIGHT +========= + +This page is part of the *rsyslog* project, and is available under +LGPLv2. diff --git a/tools/rsgtutil.c b/tools/rsgtutil.c new file mode 100644 index 00000000..095b8066 --- /dev/null +++ b/tools/rsgtutil.c @@ -0,0 +1,431 @@ +/* This is a tool for dumpoing the content of GuardTime TLV + * files in a (somewhat) human-readable manner. + * + * Copyright 2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either exprs or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <gt_base.h> +#include <gt_http.h> +#include <getopt.h> + +#include "librsgt.h" + +typedef unsigned char uchar; + +static enum { MD_DUMP, MD_DETECT_FILE_TYPE, MD_SHOW_SIGBLK_PARAMS, + MD_VERIFY, MD_EXTEND +} mode = MD_DUMP; +static int verbose = 0; + +static void +dumpFile(char *name) +{ + FILE *fp; + uchar hdr[9]; + void *obj; + tlvrecord_t rec; + int r = -1; + + if(!strcmp(name, "-")) + fp = stdin; + else { + printf("Processing file %s:\n", name); + if((fp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + } + if((r = rsgt_tlvrdHeader(fp, hdr)) != 0) goto err; + printf("File Header: '%s'\n", hdr); + while(1) { /* we will err out on EOF */ + if((r = rsgt_tlvrd(fp, &rec, &obj)) != 0) { + if(feof(fp)) + break; + else + goto err; + } + rsgt_tlvprint(stdout, rec.tlvtype, obj, verbose); + rsgt_objfree(rec.tlvtype, obj); + } + + if(fp != stdin) + fclose(fp); + return; +err: fprintf(stderr, "error %d processing file %s\n", r, name); +} + +static void +showSigblkParams(char *name) +{ + FILE *fp; + block_sig_t *bs; + uint8_t bHasRecHashes, bHasIntermedHashes; + uint64_t blkCnt = 0; + int r = -1; + + if(!strcmp(name, "-")) + fp = stdin; + else { + if((fp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + } + if((r = rsgt_chkFileHdr(fp, "LOGSIG10")) != 0) goto err; + + while(1) { /* we will err out on EOF */ + if((r = rsgt_getBlockParams(fp, 0, &bs, &bHasRecHashes, + &bHasIntermedHashes)) != 0) + goto err; + ++blkCnt; + rsgt_printBLOCK_SIG(stdout, bs, verbose); + printf("\t***META INFORMATION:\n"); + printf("\tBlock Nbr in File...: %llu\n", blkCnt); + printf("\tHas Record Hashes...: %d\n", bHasRecHashes); + printf("\tHas Tree Hashes.....: %d\n", bHasIntermedHashes); + } + + if(fp != stdin) + fclose(fp); + return; +err: + if(r != RSGTE_EOF) + fprintf(stderr, "error %d processing file %s\n", r, name); +} + +static void +detectFileType(char *name) +{ + FILE *fp; + char *typeName; + char hdr[9]; + int r = -1; + + if(!strcmp(name, "-")) + fp = stdin; + else { + if((fp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + } + if((r = rsgt_tlvrdHeader(fp, (uchar*)hdr)) != 0) goto err; + if(!strcmp(hdr, "LOGSIG10")) + typeName = "Log Signature File, Version 10"; + else if(!strcmp(hdr, "GTSTAT10")) + typeName = "rsyslog GuardTime Signature State File, Version 10"; + else + typeName = "unknown"; + + printf("%s: %s [%s]\n", name, hdr, typeName); + + if(fp != stdin) + fclose(fp); + return; +err: fprintf(stderr, "error %d processing file %s\n", r, name); +} + +static inline int +doVerifyRec(FILE *logfp, FILE *sigfp, FILE *nsigfp, + block_sig_t *bs, gtfile gf, gterrctx_t *ectx, uint8_t bInBlock) +{ + int r; + size_t lenRec; + char line[128*1024]; + + if(fgets(line, sizeof(line), logfp) == NULL) { + if(feof(logfp)) { + r = RSGTE_EOF; + } else { + perror("log file input"); + r = RSGTE_IO; + } + goto done; + } + lenRec = strlen(line); + if(line[lenRec-1] == '\n') { + line[lenRec-1] = '\0'; + --lenRec; + rsgt_errctxSetErrRec(ectx, line); + } + + /* we need to preserve the first line (record) of each block for + * error-reporting purposes (bInBlock==0 meanst start of block) + */ + if(bInBlock == 0) + rsgt_errctxFrstRecInBlk(ectx, line); + + r = rsgt_vrfy_nextRec(bs, gf, sigfp, nsigfp, (unsigned char*)line, lenRec, ectx); +done: + return r; +} + +/* We handle both verify and extend with the same function as they + * are very similiar. + * + * note: here we need to have the LOG file name, not signature! + */ +static void +verify(char *name) +{ + FILE *logfp = NULL, *sigfp = NULL, *nsigfp = NULL; + block_sig_t *bs = NULL; + gtfile gf; + uint8_t bHasRecHashes, bHasIntermedHashes; + uint8_t bInBlock; + int r = 0; + char sigfname[4096]; + char oldsigfname[4096]; + char nsigfname[4096]; + gterrctx_t ectx; + + if(!strcmp(name, "-")) { + fprintf(stderr, "%s mode cannot work on stdin\n", + mode == MD_VERIFY ? "verify" : "extend"); + goto err; + } else { + snprintf(sigfname, sizeof(sigfname), "%s.gtsig", name); + sigfname[sizeof(sigfname)-1] = '\0'; + if((logfp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + if((sigfp = fopen(sigfname, "r")) == NULL) { + perror(sigfname); + goto err; + } + if(mode == MD_EXTEND) { + snprintf(nsigfname, sizeof(nsigfname), "%s.gtsig.new", name); + nsigfname[sizeof(nsigfname)-1] = '\0'; + if((nsigfp = fopen(nsigfname, "w")) == NULL) { + perror(nsigfname); + goto err; + } + snprintf(oldsigfname, sizeof(oldsigfname), + "%s.gtsig.old", name); + oldsigfname[sizeof(oldsigfname)-1] = '\0'; + } + } + + rsgtInit("rsyslog rsgtutil " VERSION); + rsgt_errctxInit(&ectx); + ectx.verbose = verbose; + ectx.fp = stderr; + ectx.filename = strdup(sigfname); + + if((r = rsgt_chkFileHdr(sigfp, "LOGSIG10")) != 0) goto done; + if(mode == MD_EXTEND) { + if(fwrite("LOGSIG10", 8, 1, nsigfp) != 1) { + perror(nsigfname); + r = RSGTE_IO; + goto done; + } + } + gf = rsgt_vrfyConstruct_gf(); + if(gf == NULL) { + fprintf(stderr, "error initializing signature file structure\n"); + goto done; + } + + bInBlock = 0; + ectx.blkNum = 0; + ectx.recNumInFile = 0; + + while(!feof(logfp)) { + if(bInBlock == 0) { + if(bs != NULL) + rsgt_objfree(0x0902, bs); + if((r = rsgt_getBlockParams(sigfp, 1, &bs, &bHasRecHashes, + &bHasIntermedHashes)) != 0) + goto done; + rsgt_vrfyBlkInit(gf, bs, bHasRecHashes, bHasIntermedHashes); + ectx.recNum = 0; + ++ectx.blkNum; + } + ++ectx.recNum, ++ectx.recNumInFile; + if((r = doVerifyRec(logfp, sigfp, nsigfp, bs, gf, &ectx, bInBlock)) != 0) + goto done; + if(ectx.recNum == bs->recCount) { + if((r = verifyBLOCK_SIG(bs, gf, sigfp, nsigfp, + (mode == MD_EXTEND) ? 1 : 0, &ectx)) != 0) + goto done; + bInBlock = 0; + } else bInBlock = 1; + } + +done: + if(r != RSGTE_EOF) + goto err; + + fclose(logfp); logfp = NULL; + fclose(sigfp); sigfp = NULL; + if(nsigfp != NULL) { + fclose(nsigfp); nsigfp = NULL; + } + + /* everything went fine, so we rename files if we updated them */ + if(mode == MD_EXTEND) { + if(unlink(oldsigfname) != 0) { + if(errno != ENOENT) { + perror("unlink oldsig"); + r = RSGTE_IO; + goto err; + } + } + if(link(sigfname, oldsigfname) != 0) { + perror("link oldsig"); + r = RSGTE_IO; + goto err; + } + if(unlink(sigfname) != 0) { + perror("unlink cursig"); + r = RSGTE_IO; + goto err; + } + if(link(nsigfname, sigfname) != 0) { + perror("link newsig"); + fprintf(stderr, "WARNING: current sig file has been " + "renamed to %s - you need to manually recover " + "it.\n", oldsigfname); + r = RSGTE_IO; + goto err; + } + if(unlink(nsigfname) != 0) { + perror("unlink newsig"); + fprintf(stderr, "WARNING: current sig file has been " + "renamed to %s - you need to manually recover " + "it.\n", oldsigfname); + r = RSGTE_IO; + goto err; + } + } + rsgtExit(); + rsgt_errctxExit(&ectx); + return; + +err: + fprintf(stderr, "error %d processing file %s\n", r, name); + if(logfp != NULL) + fclose(logfp); + if(sigfp != NULL) + fclose(sigfp); + if(nsigfp != NULL) { + fclose(nsigfp); + unlink(nsigfname); + } + rsgtExit(); + rsgt_errctxExit(&ectx); +} + +static void +processFile(char *name) +{ + switch(mode) { + case MD_DETECT_FILE_TYPE: + detectFileType(name); + break; + case MD_DUMP: + dumpFile(name); + break; + case MD_SHOW_SIGBLK_PARAMS: + showSigblkParams(name); + break; + case MD_VERIFY: + case MD_EXTEND: + verify(name); + break; + } +} + + +static struct option long_options[] = +{ + {"dump", no_argument, NULL, 'D'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"detect-file-type", no_argument, NULL, 'T'}, + {"show-sigblock-params", no_argument, NULL, 'B'}, + {"verify", no_argument, NULL, 't'}, /* 't' as in "test signatures" */ + {"extend", no_argument, NULL, 'e'}, + {"publications-server", optional_argument, NULL, 'P'}, + {"show-verified", no_argument, NULL, 's'}, + {NULL, 0, NULL, 0} +}; + +int +main(int argc, char *argv[]) +{ + int i; + int opt; + + while(1) { + opt = getopt_long(argc, argv, "DvVTBtPs", long_options, NULL); + if(opt == -1) + break; + switch(opt) { + case 'v': + verbose = 1; + break; + case 's': + rsgt_read_showVerified = 1; + break; + case 'V': + fprintf(stderr, "rsgtutil " VERSION "\n"); + exit(0); + case 'D': + mode = MD_DUMP; + break; + case 'B': + mode = MD_SHOW_SIGBLK_PARAMS; + break; + case 'P': + rsgt_read_puburl = optarg; + break; + case 'T': + mode = MD_DETECT_FILE_TYPE; + break; + case 't': + mode = MD_VERIFY; + break; + case 'e': + mode = MD_EXTEND; + break; + case '?': + break; + default:fprintf(stderr, "getopt_long() returns unknown value %d\n", opt); + return 1; + } + } + + if(optind == argc) + processFile("-"); + else { + for(i = optind ; i < argc ; ++i) + processFile(argv[i]); + } + + return 0; +} diff --git a/tools/rsgtutil.rst b/tools/rsgtutil.rst new file mode 100644 index 00000000..37958450 --- /dev/null +++ b/tools/rsgtutil.rst @@ -0,0 +1,177 @@ +======== +rsgtutil +======== + +----------------------------------- +Manage (GuardTime) Signed Log Files +----------------------------------- + +:Author: Rainer Gerhards <rgerhards@adiscon.com> +:Date: 2013-03-25 +:Manual section: 1 + +SYNOPSIS +======== + +:: + + rsgtutil [OPTIONS] [FILE] ... + + +DESCRIPTION +=========== + +This tool performs various maintenance operations on signed log files. +It specifically supports the GuardTime signature provider. + +The *rsgtutil* tool is the primary tool to verify log file signatures, +dump signature file contents and carry out other maintenance operations. +The tool offers different operation modes, which are selected via +command line options. + +The processing of multiple files is permitted. Depending on operation +mode, either the signature file or the base log file must be specified. +Within a single call, only a single operations mode is permitted. To +use different modes on different files, multiple calles, one for each +mode, must be made. + +If no file is specified on the command line, stdin is used instead. Note +that not all operation modes support stdin. + +OPTIONS +======= + +-D, --dump + Select "dump" operations mode. + +-t, --verify + Select "verify" operations mode. + +-T, --detect-file-type + Select "detect-file-type" operations mode. + +-B, --show-sigblock-params + Select "show-sigblock-params" operations mode. + +-s, --show-verified + Prints out information about correctly verified blocks (by default, only + errors are printed). + +-v, --verbose + Select verbose mode. Most importantly, hashes and signatures are printed + in full length (can be **very** lengthy) rather than the usual abbreviation. + +-e, --extend + Select extend mode. This extends the RFC3161 signatures. Note that this + mode also implies a full verification. If there are verify errors, extending + will also fail. + +-P <URL>, --publications-server <URL> + Sets the publications server. If not set but required by the operation a + default server is used. The default server is not necessarily optimal + in regard to performance and reliability. + + +OPERATION MODES +=============== + +The operation mode specifies what exactly the tool does with the provided +files. The default operation mode is "dump", but this may change in the future. +Thus, it is recommended to always set the operations mode explicitely. If +multiple operations mode are set on the command line, results are +unpredictable. + +dump +---- + +The provided *signature* files are dumped. For each top-level record, the*u +type code is printed as well as q short description. If there is additional +information available, it will be printed in tab-indented lines below the +main record dump. The actual *log* files need not to be present. + +verify +------ + +This mode does not work with stdin. On the command line, the *log* file names +are specified. The corresponding *signature* files (ending on ".gtsig") must also +be preset at the same location as the log file. In verify mode, both the log +and signature file is read and the validity of the log file checked. If verification +errors are detected these are printed and processing of the file aborted. By default, +each file is verified individually, without taking cross-file hash chains into +account (so the order of files on the command line does not matter). + +Note that the actual amount of what can be verified depends on the parameters with +which the signature file was written. If record and tree hashes are present, they +will be verified and thus fine-granular error reporting is possible. If they are +not present, only the block signature itself is verified. + +By default, only errors are printed. To also print successful verifications, use the +**--show-verified** option. + + +extend +------ +This extends the RFC3161 signatures. This includes a full verification +of the file. If there are verification errors, extending will also fail. +Note that a signature can only be extended when the required hash has been +published. Currently, these hashes are created at the 15th of each month at +0:00hrs UTC. It takes another few days to get them finally published. As such, +it can be assumed that extending is only possible after this happend (which +means it may take slightly above a month). + +To prevent data corruption, a copy of the signature file is created during +extension. So there must be enough disk space available for both files, +otherwise the operation will fail. If the log file is named logfile, the +signature file is logfile.gtsig and the temporary work file is named +logfile.gtsig.new. When extending finished successfully, the original +signature file (logfile.gtsig in our example) is renamed with the .old +postfix (logfile.gtsig.old) and the temporary file written under the +original name. The .old file can be deleted. It is just kept as a +precaution to prevent signature loss. Note that any already existing +.old or .new files are overwritten by these operations. + + +detect-file-type +---------------- +This mode is used to detect the type of some well-know files used inside the +signature system. The detection is based on the file header. This mode is +primarily a debug aid. + + +show-sigblock-params +-------------------- +This mode is used to print signature block parameters. It is similar to *dump* +mode, but will ignore everything except signature blocks. Also, some additional +meta information is printed. This mode is primarily a debug aid. + +EXIT CODES +========== + +The command returns an exit code of 0 if everything went fine, and some +other code in case of failures. + + +EXAMPLES +======== + +**rsgtutil --verify logfile** + +This verifies the file "logfile" via its associated signature file +"logfile.gtsig". If errors are detected, these are reported to stderr. +Otherwise, rsgtutil terminates without messages. + +**rsgtutil --dump logfile.gtsig** + +This dumps the content of the signature file "logfile.gtsig". The +actual log file is not being processed and does not even need to be +present. + +SEE ALSO +======== +**rsyslogd(8)** + +COPYRIGHT +========= + +This page is part of the *rsyslog* project, and is available under +LGPLv2. diff --git a/tools/rsyslog.conf.5 b/tools/rsyslog.conf.5 new file mode 100644 index 00000000..07da6ffd --- /dev/null +++ b/tools/rsyslog.conf.5 @@ -0,0 +1,771 @@ +.\" rsyslog.conf - rsyslogd(8) configuration file +.\" Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. +.\" +.\" This file is part of the rsyslog package, an enhanced system log daemon. +.\" +.\" 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, USA. +.\" +.TH RSYSLOG.CONF 5 "22 October 2012" "Version 7.2.0" "Linux System Administration" +.SH NAME +rsyslog.conf \- rsyslogd(8) configuration file +.SH DESCRIPTION +The +.I rsyslog.conf +file is the main configuration file for the +.BR rsyslogd (8) +which logs system messages on *nix systems. This file specifies rules +for logging. For special features see the +.BR rsyslogd (8) +manpage. Rsyslog.conf is backward-compatible with sysklogd's syslog.conf file. So if you migrate +from sysklogd you can rename it and it should work. + +.B Note that this version of rsyslog ships with extensive documentation in html format. +This is provided in the ./doc subdirectory and probably +in a separate package if you installed rsyslog via a packaging system. +To use rsyslog's advanced features, you +.B need +to look at the html documentation, because the man pages only cover +basic aspects of operation. + + +.SH MODULES + +Rsyslog has a modular design. Consequently, there is a growing number +of modules. See the html documentation for their full description. + +.TP +.I omsnmp +SNMP trap output module +.TP +.I omgssapi +Output module for GSS-enabled syslog +.TP +.I ommysql +Output module for MySQL +.TP +.I omrelp +Output module for the reliable RELP protocol (prevents message loss). +For details, see below at imrelp and the html documentation. +It can be used like this: +.IP +*.* :omrelp:server:port +.IP +*.* :omrelp:192.168.0.1:2514 # actual sample +.TP +.I ompgsql +Output module for PostgreSQL +.TP +.I omlibdbi +Generic database output module (Firebird/Interbase, MS SQL, Sybase, +SQLite, Ingres, Oracle, mSQL) +.TP +.I imfile +Input module for text files +.TP +.I imudp +Input plugin for UDP syslog. Replaces the deprecated -r option. Can be +used like this: +.IP +$ModLoad imudp +.IP +$UDPServerRun 514 +.TP +.I imtcp +Input plugin for plain TCP syslog. Replaces the deprecated -t +option. Can be used like this: +.IP +$ModLoad imtcp +.IP +$InputTCPServerRun 514 +.TP +.TP +.I imrelp +Input plugin for the RELP protocol. RELP can be used instead +of UDP or plain TCP syslog to provide reliable delivery of +syslog messages. Please note that plain TCP syslog does NOT +provide truly reliable delivery, with it messages may be lost +when there is a connection problem or the server shuts down. +RELP prevents message loss in those cases. +It can be used like this: +.IP +$ModLoad imrelp +.IP +$InputRELPServerRun 2514 +.TP +.I imgssapi +Input plugin for plain TCP and GSS-enable syslog +.TP +.I immark +Support for mark messages +.TP +.I imklog +Kernel logging. To include kernel log messages, you need to do +.IP +$ModLoad imklog + +Please note that the klogd daemon is no longer necessary and consequently +no longer provided by the rsyslog package. +.TP +.I imuxsock +Unix sockets, including the system log socket. You need to specify +.IP +$ModLoad imuxsock + +in order to receive log messages from local system processes. This +config directive should only left out if you know exactly what you +are doing. + + +.SH BASIC STRUCTURE + +Lines starting with a hash mark ('#') and empty lines are ignored. +Rsyslog.conf should contain following sections (sorted by recommended order in file): + +.TP +Global directives +Global directives set some global properties of whole rsyslog daemon, for example size of main +message queue ($MainMessageQueueSize), loading external modules ($ModLoad) and so on. +All global directives need to be specified on a line by their own and must start with +a dollar-sign. The complete list of global directives can be found in html documentation in doc +directory or online on web pages. + +.TP +Templates +Templates allow you to specify format of the logged message. They are also used for dynamic +file name generation. They have to be defined before they are used in rules. For more info +about templates see TEMPLATES section of this manpage. + +.TP +Output channels +Output channels provide an umbrella for any type of output that the user might want. +They have to be defined before they are used in rules. For more info about output channels +see OUTPUT CHANNELS section of this manpage. + +.TP +Rules (selector + action) +Every rule line consists of two fields, a selector field and an action field. These +two fields are separated by one or more spaces or tabs. The selector field specifies +a pattern of facilities and priorities belonging to the specified action. + +.SH SELECTORS + +The selector field itself again consists of two parts, a facility and a +priority, separated by a period ('.'). Both parts are case insensitive and can +also be specified as decimal numbers, but don't do that, you have been warned. +Both facilities and priorities are described in syslog(3). The names mentioned +below correspond to the similar LOG_-values in /usr/include/syslog.h. + +The facility is one of the following keywords: auth, authpriv, cron, daemon, +kern, lpr, mail, mark, news, security (same as auth), syslog, user, uucp and +local0 through local7. The keyword security should not be used anymore and mark +is only for internal use and therefore should not be used in applications. +Anyway, you may want to specify and redirect these messages here. The facility +specifies the subsystem that produced the message, i.e. all mail programs log +with the mail facility (LOG_MAIL) if they log using syslog. + +The priority is one of the following keywords, in ascending order: debug, info, +notice, warning, warn (same as warning), err, error (same as err), crit, alert, +emerg, panic (same as emerg). The keywords error, warn and panic are deprecated +and should not be used anymore. The priority defines the severity of the message. + +The behavior of the original BSD syslogd is that all messages of the specified +priority and higher are logged according to the given action. Rsyslogd behaves +the same, but has some extensions. + +In addition to the above mentioned names the rsyslogd(8) understands the +following extensions: An asterisk ('*') stands for all facilities or all +priorities, depending on where it is used (before or after the period). The +keyword none stands for no priority of the given facility. + +You can specify multiple facilities with the same priority pattern in one +statement using the comma (',') operator. You may specify as much facilities as +you want. Remember that only the facility part from such a statement is taken, a +priority part would be skipped. + +Multiple selectors may be specified for a single action using the semicolon +(';') separator. Remember that each selector in the selector field is capable +to overwrite the preceding ones. Using this behavior you can exclude some +priorities from the pattern. + +Rsyslogd has a syntax extension to the original BSD source, that makes its use +more intuitively. You may precede every priority with an equals sign ('=') to +specify only this single priority and not any of the above. You may also (both +is valid, too) precede the priority with an exclamation mark ('!') to ignore +all that priorities, either exact this one or this and any higher priority. If +you use both extensions than the exclamation mark must occur before the equals +sign, just use it intuitively. + +.SH ACTIONS +The action field of a rule describes what to do with the message. In general, message content +is written to a kind of "logfile". But also other actions might be done, like writing to a +database table or forwarding to another host. + +.SS Regular file +Typically messages are logged to real files. The file has to be specified with full pathname, +beginning with a slash ('/'). + +.B Example: +.RS +*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFileFormat # log to a file in the traditional format +.RE + +Note: if you would like to use high-precision timestamps in your log files, +just remove the ";RSYSLOG_TraditionalFormat". That will select the default +template, which, if not changed, uses RFC 3339 timestamps. + +.B Example: +.RS +*.* /var/log/file.log # log to a file with RFC3339 timestamps +.RE + +.SS Named pipes +This version of rsyslogd(8) has support for logging output to named pipes (fifos). A fifo or +named pipe can be used as a destination for log messages by prepending a pipe symbol ('|') +to the name of the file. This is handy for debugging. Note that the fifo must be created with +the mkfifo(1) command before rsyslogd(8) is started. + +.SS Terminal and console +If the file you specified is a tty, special tty-handling is done, same with /dev/console. + +.SS Remote machine +There are three ways to forward message: the traditional UDP transport, which is extremely +lossy but standard, the plain TCP based transport which loses messages only during certain +situations but is widely available and the RELP transport which does not lose messages +but is currently available only as part of rsyslogd 3.15.0 and above. + +To forward messages to another host via UDP, prepend the hostname with the at sign ("@"). +To forward it via plain tcp, prepend two at signs ("@@"). To forward via RELP, prepend the +string ":omrelp:" in front of the hostname. + +.B Example: +.RS +*.* @192.168.0.1 +.RE +.sp +In the example above, messages are forwarded via UDP to the machine 192.168.0.1, the destination +port defaults to 514. Due to the nature of UDP, you will probably lose some messages in transit. +If you expect high traffic volume, you can expect to lose a quite noticeable number of messages +(the higher the traffic, the more likely and severe is message loss). + +.B If you would like to prevent message loss, use RELP: +.RS +*.* :omrelp:192.168.0.1:2514 +.RE +.sp +Note that a port number was given as there is no standard port for relp. + +Keep in mind that you need to load the correct input and output plugins (see "Modules" above). + +Please note that rsyslogd offers a variety of options in regarding to remote +forwarding. For full details, please see the html documentation. + +.SS List of users +Usually critical messages are also directed to ``root'' on that machine. You +can specify a list +of users that shall get the message by simply writing ":omusrmsg:" followed +by the login name. You may specify more than one +user by separating them with commas (','). If they're logged in they +get the message (for example: ":omusrmsg:root,user1,user2"). + +.SS Everyone logged on +Emergency messages often go to all users currently online to notify them that something strange +is happening with the system. To specify this wall(1)-feature use an ":omusrmsg:*". + +.SS Database table +This allows logging of the message to a database table. +By default, a MonitorWare-compatible schema is required for this to work. You can +create that schema with the createDB.SQL file that came with the rsyslog package. You can also +use any other schema of your liking - you just need to define a proper template and assign this +template to the action. + +See the html documentation for further details on database logging. + +.SS Discard +If the discard action is carried out, the received message is immediately discarded. Discard +can be highly effective if you want to filter out some annoying messages that otherwise would +fill your log files. To do that, place the discard actions early in your log files. +This often plays well with property-based filters, giving you great freedom in specifying +what you do not want. + +Discard is just the single tilde character with no further parameters. +.sp +.B Example: +.RS +*.* ~ # discards everything. +.RE + + +.SS Output channel +Binds an output channel definition (see there for details) to this action. Output channel actions +must start with a $-sign, e.g. if you would like to bind your output channel definition "mychannel" +to the action, use "$mychannel". Output channels support template definitions like all all other +actions. + +.SS Shell execute +This executes a program in a subshell. The program is passed the template-generated message as the +only command line parameter. Rsyslog waits until the program terminates and only then continues to run. + +.B Example: +.RS +^program-to-execute;template +.RE + +The program-to-execute can be any valid executable. It receives the template string as a single parameter +(argv[1]). + +.SH FILTER CONDITIONS +Rsyslog offers three different types "filter conditions": +.sp 0 + * "traditional" severity and facility based selectors +.sp 0 + * property-based filters +.sp 0 + * expression-based filters +.RE + +.SS Selectors +.B Selectors are the traditional way of filtering syslog messages. +They have been kept in rsyslog with their original syntax, because it is well-known, highly +effective and also needed for compatibility with stock syslogd configuration files. If you just +need to filter based on priority and facility, you should do this with selector lines. They are +not second-class citizens in rsyslog and offer the best performance for this job. + +.SS Property-Based Filters +Property-based filters are unique to rsyslogd. They allow to filter on any property, like HOSTNAME, +syslogtag and msg. + +A property-based filter must start with a colon in column 0. This tells rsyslogd that it is the new +filter type. The colon must be followed by the property name, a comma, the name of the compare +operation to carry out, another comma and then the value to compare against. This value must be quoted. +There can be spaces and tabs between the commas. Property names and compare operations are +case-sensitive, so "msg" works, while "MSG" is an invalid property name. In brief, the syntax is as follows: +.sp +.RS +:property, [!]compare-operation, "value" +.RE + +The following compare-operations are currently supported: +.sp +.RS +.B contains +.RS +Checks if the string provided in value is contained in the property +.RE +.sp +.B isequal +.RS +Compares the "value" string provided and the property contents. These two values must be exactly equal to match. +.RE +.sp +.B startswith +.RS +Checks if the value is found exactly at the beginning of the property value +.RE +.sp +.B regex +.RS +Compares the property against the provided regular expression. +.RE + +.SS Expression-Based Filters +See the html documentation for this feature. + + +.SH TEMPLATES + +Every output in rsyslog uses templates - this holds true for files, user +messages and so on. Templates compatible with the stock syslogd +formats are hardcoded into rsyslogd. If no template is specified, we use +one of these hardcoded templates. Search for "template_" in syslogd.c and +you will find the hardcoded ones. + +A template consists of a template directive, a name, the actual template text +and optional options. A sample is: + +.RS +.B $template MyTemplateName,"\\\\7Text %property% some more text\\\\n",<options> +.RE + +The "$template" is the template directive. It tells rsyslog that this line +contains a template. The backslash is an escape character. For example, \\7 rings the +bell (this is an ASCII value), \\n is a new line. The set in rsyslog is a bit restricted +currently. + +All text in the template is used literally, except for things within percent +signs. These are properties and allow you access to the contents of the syslog +message. Properties are accessed via the property replacer and it can for example +pick a substring or do date-specific formatting. More on this is the PROPERTY REPLACER +section of this manpage. + +To escape: +.sp 0 + % = \\% +.sp 0 + \\ = \\\\ --> '\\' is used to escape (as in C) +.sp 0 +$template TraditionalFormat,"%timegenerated% %HOSTNAME% %syslogtag%%msg%\\n" + +Properties can be accessed by the property replacer (see there for details). + +.B Please note that templates can also by used to generate selector lines with dynamic file names. +For example, if you would like to split syslog messages from different hosts +to different files (one per host), you can define the following template: + +.RS +.B $template DynFile,"/var/log/system-%HOSTNAME%.log" +.RE + +This template can then be used when defining an output selector line. It will +result in something like "/var/log/system-localhost.log" + +.SS Template options +The <options> part is optional. It carries options influencing the template as whole. +See details below. Be sure NOT to mistake template options with property options - the +later ones are processed by the property replacer and apply to a SINGLE property, only +(and not the whole template). + +Template options are case-insensitive. Currently defined are: + +.RS +.TP +sql +format the string suitable for a SQL statement in MySQL format. This will replace single +quotes ("'") and the backslash character by their backslash-escaped counterpart +("\'" and "\\") inside each field. Please note that in MySQL configuration, the NO_BACKSLASH_ESCAPES +mode must be turned off for this format to work (this is the default). + +.TP +stdsql +format the string suitable for a SQL statement that is to be sent to a standards-compliant +sql server. This will replace single quotes ("'") by two single quotes ("''") inside each field. +You must use stdsql together with MySQL if in MySQL configuration the NO_BACKSLASH_ESCAPES +is turned on. +.RE + +Either the +.B sql +or +.B stdsql +option +.B MUST +be specified when a template is used for writing to a database, +otherwise injection might occur. Please note that due to the unfortunate fact +that several vendors have violated the sql standard and introduced their own +escape methods, it is impossible to have a single option doing all the work. +So you yourself must make sure you are using the right format. +.B If you choose the wrong one, you are still vulnerable to sql injection. + +Please note that the database writer *checks* that the sql option is present +in the template. If it is not present, the write database action is disabled. +This is to guard you against accidental forgetting it and then becoming +vulnerable to SQL injection. The sql option can also be useful with files - +especially if you want to import them into a database on another machine for +performance reasons. However, do NOT use it if you do not have a real need for +it - among others, it takes some toll on the processing time. Not much, but on +a really busy system you might notice it ;) + +The default template for the write to database action has the sql option set. + +.SS Template examples +Please note that the samples are split across multiple lines. A template MUST +NOT actually be split across multiple lines. + +A template that resembles traditional syslogd file output: +.sp +.RS +$template TraditionalFormat,"%timegenerated% %HOSTNAME% +.sp 0 +%syslogtag%%msg:::drop-last-lf%\\n" +.RE + +A template that tells you a little more about the message: +.sp +.RS +$template precise,"%syslogpriority%,%syslogfacility%,%timegenerated%,%HOSTNAME%, +.sp 0 +%syslogtag%,%msg%\\n" +.RE + +A template for RFC 3164 format: +.sp +.RS +$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%" +.RE + +A template for the format traditionally used for user messages: +.sp +.RS +$template usermsg," XXXX%syslogtag%%msg%\\n\\r" +.RE + +And a template with the traditional wall-message format: +.sp +.RS +$template wallmsg,"\\r\\n\\7Message from syslogd@%HOSTNAME% at %timegenerated%" +.RE + +.B A template that can be used for writing to a database (please note the SQL template option) +.sp +.RS +.ad l +$template MySQLInsert,"insert iut, message, receivedat values +('%iut%', '%msg:::UPPERCASE%', '%timegenerated:::date-mysql%') +into systemevents\\r\\n", SQL + +NOTE 1: This template is embedded into core application under name +.B StdDBFmt +, so you don't need to define it. +.sp +NOTE 2: You have to have MySQL module installed to use this template. +.ad +.RE + +.SH OUTPUT CHANNELS + +Output Channels are a new concept first introduced in rsyslog 0.9.0. As of this writing, +it is most likely that they will be replaced by something different in the future. +So if you use them, be prepared to change you configuration file syntax when you upgrade +to a later release. + +Output channels are defined via an $outchannel directive. It's syntax is as follows: +.sp +.RS +.B $outchannel name,file-name,max-size,action-on-max-size +.RE + +name is the name of the output channel (not the file), file-name is the file name to be +written to, max-size the maximum allowed size and action-on-max-size a command to be issued +when the max size is reached. This command always has exactly one parameter. The binary is +that part of action-on-max-size before the first space, its parameter is everything behind +that space. + +Keep in mind that $outchannel just defines a channel with "name". It does not activate it. +To do so, you must use a selector line (see below). That selector line includes the channel +name plus ":omfile:$" in front of it. A sample might be: +.sp +.RS +*.* :omfile:$mychannel +.RE + +.SH PROPERTY REPLACER +The property replacer is a core component in rsyslogd's output system. A syslog message has +a number of well-defined properties (see below). Each of this properties can be accessed and +manipulated by the property replacer. With it, it is easy to use only part of a property value +or manipulate the value, e.g. by converting all characters to lower case. + +.SS Accessing Properties +Syslog message properties are used inside templates. They are accessed by putting them between +percent signs. Properties can be modified by the property replacer. The full syntax is as follows: +.sp +.RS +.B %propname:fromChar:toChar:options% +.RE + +propname is the name of the property to access. +.B It is case-sensitive. + +.SS Available Properties +.TP +.B msg +the MSG part of the message (aka "the message" ;)) +.TP +.B rawmsg +the message exactly as it was received from the socket. Should be useful for debugging. +.TP +.B HOSTNAME +hostname from the message +.TP +.B FROMHOST +hostname of the system the message was received from (in a relay chain, this is the system immediately +in front of us and not necessarily the original sender) +.TP +.B syslogtag +TAG from the message +.TP +.B programname +the "static" part of the tag, as defined by BSD syslogd. For example, when TAG is "named[12345]", +programname is "named". +.TP +.B PRI +PRI part of the message - undecoded (single value) +.TP +.B PRI-text +the PRI part of the message in a textual form (e.g. "syslog.info") +.TP +.B IUT +the monitorware InfoUnitType - used when talking to a MonitorWare backend (also for phpLogCon) +.TP +.B syslogfacility +the facility from the message - in numerical form +.TP +.B syslogfacility-text +the facility from the message - in text form +.TP +.B syslogseverity +severity from the message - in numerical form +.TP +.B syslogseverity-text +severity from the message - in text form +.TP +.B timegenerated +timestamp when the message was RECEIVED. Always in high resolution +.TP +.B timereported +timestamp from the message. Resolution depends on what was provided in the message (in most cases, only seconds) +.TP +.B TIMESTAMP +alias for timereported +.TP +.B PROTOCOL-VERSION +The contents of the PROTOCOL-VERSION field from IETF draft draft-ietf-syslog-protocol +.TP +.B STRUCTURED-DATA +The contents of the STRUCTURED-DATA field from IETF draft draft-ietf-syslog-protocol +.TP +.B APP-NAME +The contents of the APP-NAME field from IETF draft draft-ietf-syslog-protocol +.TP +.B PROCID +The contents of the PROCID field from IETF draft draft-ietf-syslog-protocol +.TP +.B MSGID +The contents of the MSGID field from IETF draft draft-ietf-syslog-protocol +.TP +.B $NOW +The current date stamp in the format YYYY-MM-DD +.TP +.B $YEAR +The current year (4-digit) +.TP +.B $MONTH +The current month (2-digit) +.TP +.B $DAY +The current day of the month (2-digit) +.TP +.B $HOUR +The current hour in military (24 hour) time (2-digit) +.TP +.B $MINUTE +The current minute (2-digit) + +.P +Properties starting with a $-sign are so-called system properties. These do NOT stem from the +message but are rather internally-generated. + +.SS Character Positions +FromChar and toChar are used to build substrings. They specify the offset within the string that +should be copied. Offset counting starts at 1, so if you need to obtain the first 2 characters of +the message text, you can use this syntax: "%msg:1:2%". If you do not wish to specify from and to, +but you want to specify options, you still need to include the colons. For example, if you would +like to convert the full message text to lower case, use "%msg:::lowercase%". If you would like to +extract from a position until the end of the string, you can place a dollar-sign ("$") in toChar +(e.g. %msg:10:$%, which will extract from position 10 to the end of the string). + +There is also support for +.B regular expressions. +To use them, you need to place a "R" into FromChar. +This tells rsyslog that a regular expression instead of position-based extraction is desired. The +actual regular expression +.B must +then be provided in toChar. The regular expression must be followed +by the string "--end". It denotes the end of the regular expression and will not become part of it. +If you are using regular expressions, the property replacer will return the part of the property text +that matches the regular expression. An example for a property replacer sequence with a regular +expression is: "%msg:R:.*Sev:. \\(.*\\) \\[.*--end%" + +Also, extraction can be done based on so-called "fields". To do so, place a "F" into FromChar. A field +in its current definition is anything that is delimited by a delimiter character. The delimiter by +default is TAB (US-ASCII value 9). However, if can be changed to any other US-ASCII character by +specifying a comma and the decimal US-ASCII value of the delimiter immediately after the "F". For example, +to use comma (",") as a delimiter, use this field specifier: "F,44". If your syslog data is delimited, +this is a quicker way to extract than via regular expressions (actually, a *much* quicker way). Field +counting starts at 1. Field zero is accepted, but will always lead to a "field not found" error. The same +happens if a field number higher than the number of fields in the property is requested. The field number +must be placed in the "ToChar" parameter. An example where the 3rd field (delimited by TAB) from the msg +property is extracted is as follows: "%msg:F:3%". The same example with semicolon as delimiter is +"%msg:F,59:3%". + +Please note that the special characters "F" and "R" are case-sensitive. Only upper case works, lower case +will return an error. There are no white spaces permitted inside the sequence (that will lead to error +messages and will NOT provide the intended result). + +.SS Property Options +Property options are case-insensitive. Currently, the following options are defined: +.TP +uppercase +convert property to lowercase only +.TP +lowercase +convert property text to uppercase only +.TP +drop-last-lf +The last LF in the message (if any), is dropped. Especially useful for PIX. +.TP +date-mysql +format as mysql date +.TP +date-rfc3164 +format as RFC 3164 date +.TP +date-rfc3339 +format as RFC 3339 date +.TP +escape-cc +replace control characters (ASCII value 127 and values less then 32) with an escape sequence. The sequence is "#<charval>" where charval is the 3-digit decimal value of the control character. For example, a tabulator would be replaced by "#009". +.TP +space-cc +replace control characters by spaces +.TP +drop-cc +drop control characters - the resulting string will neither contain control characters, escape sequences nor any other replacement character like space. + +.SH QUEUED OPERATIONS +Rsyslogd supports queued operations to handle offline outputs +(like remote syslogd's or database servers being down). When running in +queued mode, rsyslogd buffers messages to memory and optionally to disk +(on an as-needed basis). Queues survive rsyslogd restarts. + +It is highly suggested to use remote forwarding and database writing +in queued mode, only. + +To learn more about queued operations, see the html documentation. + +.SH FILES +.PD 0 +.TP +.I /etc/rsyslog.conf +Configuration file for +.B rsyslogd + +.SH SEE ALSO +.BR rsyslogd (8), +.BR logger (1), +.BR syslog (3) + +The complete documentation can be found in the doc folder of the rsyslog distribution or online at + +.RS +.B http://www.rsyslog.com/doc + +.RE +Please note that the man page reflects only a subset of the configuration options. Be sure to read +the html documentation for all features and details. This is especially vital if you plan to set +up a more-then-extremely-simple system. + +.SH AUTHORS +.B rsyslogd +is taken from sysklogd sources, which have been heavily modified +by Rainer Gerhards (rgerhards@adiscon.com) and others. diff --git a/tools/rsyslogd.8 b/tools/rsyslogd.8 new file mode 100644 index 00000000..ac732b88 --- /dev/null +++ b/tools/rsyslogd.8 @@ -0,0 +1,401 @@ +.\" Copyright 2004-2008 Rainer Gerhards and Adiscon for the rsyslog modifications +.\" May be distributed under the GNU General Public License +.\" +.TH RSYSLOGD 8 "16 October 2012" "Version 6.4.3" "Linux System Administration" +.SH NAME +rsyslogd \- reliable and extended syslogd +.SH SYNOPSIS +.B rsyslogd +.RB [ " \-4 " ] +.RB [ " \-6 " ] +.RB [ " \-A " ] +.RB [ " \-d " ] +.RB [ " \-D " ] +.RB [ " \-f " +.I config file +] +.br +.RB [ " \-i " +.I pid file +] +.RB [ " \-l " +.I hostlist +] +.RB [ " \-n " ] +.RB [ " \-N " +.I level +] +.br +.RB [ " \-q " ] +.RB [ " \-Q " ] +.RB [ " \-s " +.I domainlist +] +.RB [ " \-u " +.I userlevel +] +.RB [ " \-v " ] +.RB [ " \-w " ] +.RB [ " \-x " ] +.LP +.SH DESCRIPTION +.B Rsyslogd +is a system utility providing support for message logging. +Support of both internet and +unix domain sockets enables this utility to support both local +and remote logging. + +.B Note that this version of rsyslog ships with extensive documentation in html format. +This is provided in the ./doc subdirectory and probably +in a separate package if you installed rsyslog via a packaging system. +To use rsyslog's advanced features, you +.B need +to look at the html documentation, because the man pages only cover +basic aspects of operation. +.B For details and configuration examples, see the rsyslog.conf (5) +.B man page and the online documentation at http://www.rsyslog.com/doc + +.BR Rsyslogd (8) +is derived from the sysklogd package which in turn is derived from the +stock BSD sources. + +.B Rsyslogd +provides a kind of logging that many modern programs use. Every logged +message contains at least a time and a hostname field, normally a +program name field, too, but that depends on how trusty the logging +program is. The rsyslog package supports free definition of output formats +via templates. It also supports precise timestamps and writing directly +to databases. If the database option is used, tools like phpLogCon can +be used to view the log data. + +While the +.B rsyslogd +sources have been heavily modified a couple of notes +are in order. First of all there has been a systematic attempt to +ensure that rsyslogd follows its default, standard BSD behavior. Of course, +some configuration file changes are necessary in order to support the +template system. However, rsyslogd should be able to use a standard +syslog.conf and act like the original syslogd. However, an original syslogd +will not work correctly with a rsyslog-enhanced configuration file. At +best, it will generate funny looking file names. +The second important concept to note is that this version of rsyslogd +interacts transparently with the version of syslog found in the +standard libraries. If a binary linked to the standard shared +libraries fails to function correctly we would like an example of the +anomalous behavior. + +The main configuration file +.I /etc/rsyslog.conf +or an alternative file, given with the +.B "\-f" +option, is read at startup. Any lines that begin with the hash mark +(``#'') and empty lines are ignored. If an error occurs during parsing +the error element is ignored. It is tried to parse the rest of the line. + +.LP +.SH OPTIONS +.TP +.BI "\-A" +When sending UDP messages, there are potentially multiple paths to +the target destination. By default, +.B rsyslogd +only sends to the first target it can successfully send to. If -A +is given, messages are sent to all targets. This may improve +reliability, but may also cause message duplication. This option +should be enabled only if it is fully understood. +.TP +.BI "\-4" +Causes +.B rsyslogd +to listen to IPv4 addresses only. +If neither -4 nor -6 is given, +.B rsyslogd +listens to all configured addresses of the system. +.TP +.BI "\-6" +Causes +.B rsyslogd +to listen to IPv6 addresses only. +If neither -4 nor -6 is given, +.B rsyslogd +listens to all configured addresses of the system. +.TP +.BI "\-c " "version" +This option has been obsoleted and has no function any longer. It is still +accepted in order not to break existing scripts. However, future versions +may not support it. +.TP +.B "\-D" +Runs the Bison config parser in debug mode. This may help when hard to find +syntax errors are reported. Please note that the output generated is deeply +technical and orignally targeted towards developers. +.TP +.B "\-d" +Turns on debug mode. Using this the daemon will not proceed a +.BR fork (2) +to set itself in the background, but opposite to that stay in the +foreground and write much debug information on the current tty. See the +DEBUGGING section for more information. +.TP +.BI "\-f " "config file" +Specify an alternative configuration file instead of +.IR /etc/rsyslog.conf "," +which is the default. +.TP +.BI "\-i " "pid file" +Specify an alternative pid file instead of the default one. +This option must be used if multiple instances of rsyslogd should +run on a single machine. +.TP +.BI "\-l " "hostlist" +Specify a hostname that should be logged only with its simple hostname +and not the fqdn. Multiple hosts may be specified using the colon +(``:'') separator. +.TP +.B "\-n" +Avoid auto-backgrounding. This is needed especially if the +.B rsyslogd +is started and controlled by +.BR init (8). +.TP +.B "\-N " "level" +Do a coNfig check. Do NOT run in regular mode, just check configuration +file correctness. +This option is meant to verify a config file. To do so, run rsyslogd +interactively in foreground, specifying -f <config-file> and -N level. +The level argument modifies behaviour. Currently, 0 is the same as +not specifying the -N option at all (so this makes limited sense) and +1 actually activates the code. Later, higher levels will mean more +verbosity (this is a forward-compatibility option). +.B rsyslogd +is started and controlled by +.BR init (8). +.TP +.BI "\-q " "add hostname if DNS fails during ACL processing" +During ACL processing, hostnames are resolved to IP addresses for +performance reasons. If DNS fails during that process, the hostname +is added as wildcard text, which results in proper, but somewhat +slower operation once DNS is up again. +.TP +.BI "\-Q " "do not resolve hostnames during ACL processing" +Do not resolve hostnames to IP addresses during ACL processing. +.TP +.BI "\-s " "domainlist" +Specify a domainname that should be stripped off before +logging. Multiple domains may be specified using the colon (``:'') +separator. +Please be advised that no sub-domains may be specified but only entire +domains. For example if +.B "\-s north.de" +is specified and the host logging resolves to satu.infodrom.north.de +no domain would be cut, you will have to specify two domains like: +.BR "\-s north.de:infodrom.north.de" . +.TP +.BI "\-S ip_address" "local client source IP" +rsyslogd uses ip_address as local client address while connecting +to remote logserver. Currently used by omrelp only and only with tcp. +.TP +.BI "\-u " "userlevel" +This is a "catch all" option for some very seldomly-used user settings. +The "userlevel" variable selects multiple things. Add the specific values +to get the combined effect of them. +A value of 1 prevents rsyslogd from parsing hostnames and tags inside +messages. +A value of 2 prevents rsyslogd from changing to the root directory. This +is almost never a good idea in production use. This option was introduced +in support of the internal testbed. +To combine these two features, use a userlevel of 3 (1+2). Whenever you use +an -u option, make sure you really understand what you do and why you do it. +.TP +.B "\-v" +Print version and exit. +.TP +.B "\-w" +Suppress warnings issued when messages are received from non-authorized +machines (those, that are in no AllowedSender list). +.TP +.B "\-x" +Disable DNS for remote messages. +.LP +.SH SIGNALS +.B Rsyslogd +reacts to a set of signals. You may easily send a signal to +.B rsyslogd +using the following: +.IP +.nf +kill -SIGNAL $(cat /var/run/rsyslogd.pid) +.fi +.PP +Note that -SIGNAL must be replaced with the actual signal +you are trying to send, e.g. with HUP. So it then becomes: +.IP +.nf +kill -HUP $(cat /var/run/rsyslogd.pid) +.fi +.PP +.TP +.B HUP +This lets +.B rsyslogd +perform close all open files. +Also, in v3 a full restart will be done in order to read changed configuration files. +Note that this means a full rsyslogd restart is done. This has, among others, +the consequence that TCP and other connections are torn down. Also, if any +queues are not running in disk assisted mode or are not set to persist data +on shutdown, queue data is lost. HUPing rsyslogd is an extremely expensive +operation and should only be done when actually necessary. Actually, it is +a rsyslgod stop immediately followed by a restart. Future versions will remove +this restart functionality of HUP (it will go away in v5). So it is advised to use +HUP only for closing files, and a "real restart" (e.g. /etc/rc.d/rsyslogd restart) +to activate configuration changes. +.TP +.B TERM ", " INT ", " QUIT +.B Rsyslogd +will die. +.TP +.B USR1 +Switch debugging on/off. This option can only be used if +.B rsyslogd +is started with the +.B "\-d" +debug option. +.TP +.B CHLD +Wait for childs if some were born, because of wall'ing messages. +.LP +.SH SECURITY THREATS +There is the potential for the rsyslogd daemon to be +used as a conduit for a denial of service attack. +A rogue program(mer) could very easily flood the rsyslogd daemon with +syslog messages resulting in the log files consuming all the remaining +space on the filesystem. Activating logging over the inet domain +sockets will of course expose a system to risks outside of programs or +individuals on the local machine. + +There are a number of methods of protecting a machine: +.IP 1. +Implement kernel firewalling to limit which hosts or networks have +access to the 514/UDP socket. +.IP 2. +Logging can be directed to an isolated or non-root filesystem which, +if filled, will not impair the machine. +.IP 3. +The ext2 filesystem can be used which can be configured to limit a +certain percentage of a filesystem to usage by root only. \fBNOTE\fP +that this will require rsyslogd to be run as a non-root process. +\fBALSO NOTE\fP that this will prevent usage of remote logging on the default port since +rsyslogd will be unable to bind to the 514/UDP socket. +.IP 4. +Disabling inet domain sockets will limit risk to the local machine. +.SS Message replay and spoofing +If remote logging is enabled, messages can easily be spoofed and replayed. +As the messages are transmitted in clear-text, an attacker might use +the information obtained from the packets for malicious things. Also, an +attacker might replay recorded messages or spoof a sender's IP address, +which could lead to a wrong perception of system activity. These can +be prevented by using GSS-API authentication and encryption. Be sure +to think about syslog network security before enabling it. +.LP +.SH DEBUGGING +When debugging is turned on using +.B "\-d" +option then +.B rsyslogd +will be very verbose by writing much of what it does on stdout. +.SH FILES +.PD 0 +.TP +.I /etc/rsyslog.conf +Configuration file for +.BR rsyslogd . +See +.BR rsyslog.conf (5) +for exact information. +.TP +.I /dev/log +The Unix domain socket to from where local syslog messages are read. +.TP +.I /var/run/rsyslogd.pid +The file containing the process id of +.BR rsyslogd . +.TP +.I prefix/lib/rsyslog +Default directory for +.B rsyslogd +modules. The +.I prefix +is specified during compilation (e.g. /usr/local). +.SH ENVIRONMENT +.TP +.B RSYSLOG_DEBUG +Controls runtime debug support.It contains an option string with the +following options possible (all are case insensitive): + +.RS +.IP LogFuncFlow +Print out the logical flow of functions (entering and exiting them) +.IP FileTrace +Specifies which files to trace LogFuncFlow. If not set (the +default), a LogFuncFlow trace is provided for all files. Set to +limit it to the files specified.FileTrace may be specified multiple +times, one file each (e.g. export RSYSLOG_DEBUG="LogFuncFlow +FileTrace=vm.c FileTrace=expr.c" +.IP PrintFuncDB +Print the content of the debug function database whenever debug +information is printed (e.g. abort case)! +.IP PrintAllDebugInfoOnExit +Print all debug information immediately before rsyslogd exits +(currently not implemented!) +.IP PrintMutexAction +Print mutex action as it happens. Useful for finding deadlocks and +such. +.IP NoLogTimeStamp +Do not prefix log lines with a timestamp (default is to do that). +.IP NoStdOut +Do not emit debug messages to stdout. If RSYSLOG_DEBUGLOG is not +set, this means no messages will be displayed at all. +.IP Help +Display a very short list of commands - hopefully a life saver if +you can't access the documentation... +.RE + +.TP +.B RSYSLOG_DEBUGLOG +If set, writes (almost) all debug message to the specified log file +in addition to stdout. +.TP +.B RSYSLOG_MODDIR +Provides the default directory in which loadable modules reside. +.PD +.SH BUGS +Please review the file BUGS for up-to-date information on known +bugs and annoyances. +.SH Further Information +Please visit +.BR http://www.rsyslog.com/doc +for additional information, tutorials and a support forum. +.SH SEE ALSO +.BR rsyslog.conf (5), +.BR logger (1), +.BR syslog (2), +.BR syslog (3), +.BR services (5), +.BR savelog (8) +.LP +.SH COLLABORATORS +.B rsyslogd +is derived from sysklogd sources, which in turn was taken from +the BSD sources. Special thanks to Greg Wettstein (greg@wind.enjellic.com) +and Martin Schulze (joey@linux.de) for the fine sysklogd package. + +.PD 0 +.TP +Rainer Gerhards +.TP +Adiscon GmbH +.TP +Grossrinderfeld, Germany +.TP +rgerhards@adiscon.com +.PD diff --git a/tools/smfile.c b/tools/smfile.c new file mode 100644 index 00000000..1e0bf091 --- /dev/null +++ b/tools/smfile.c @@ -0,0 +1,136 @@ +/* smfile.c + * This is a strgen module for the traditional file format. + * + * Format generated: + * "%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" + * Note that this is the same as smtradfile.c, except that we do have a RFC3339 timestamp. However, + * we have copied over the code from there, it is too simple to go through all the hassle + * of having a single code base. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2010-06-01 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "unicode-helper.h" + +MODULE_TYPE_STRGEN +MODULE_TYPE_NOKEEP +STRGEN_NAME("RSYSLOG_FileFormat") + +/* internal structures + */ +DEF_SMOD_STATIC_DATA + + +/* config data */ + + +/* This strgen tries to minimize the amount of reallocs be first obtaining pointers to all strings + * needed (including their length) and then calculating the actual space required. So when we + * finally copy, we know exactly what we need. So we do at most one alloc. + */ +BEGINstrgen + register int iBuf; + uchar *pTimeStamp; + size_t lenTimeStamp; + uchar *pHOSTNAME; + size_t lenHOSTNAME; + uchar *pTAG; + int lenTAG; + uchar *pMSG; + size_t lenMSG; + size_t lenTotal; +CODESTARTstrgen + /* first obtain all strings and their length (if not fixed) */ + pTimeStamp = (uchar*) getTimeReported(pMsg, tplFmtRFC3339Date); + lenTimeStamp = ustrlen(pTimeStamp); + pHOSTNAME = (uchar*) getHOSTNAME(pMsg); + lenHOSTNAME = getHOSTNAMELen(pMsg); + getTAG(pMsg, &pTAG, &lenTAG); + pMSG = getMSG(pMsg); + lenMSG = getMSGLen(pMsg); + + /* calculate len, constants for spaces and similar fixed strings */ + lenTotal = lenTimeStamp + 1 + lenHOSTNAME + 1 + lenTAG + lenMSG + 2; + if(pMSG[0] != ' ') + ++lenTotal; /* then we need to introduce one additional space */ + + /* now make sure buffer is large enough */ + if(lenTotal >= *pLenBuf) + CHKiRet(ExtendBuf(ppBuf, pLenBuf, lenTotal)); + + /* and concatenate the resulting string */ + memcpy(*ppBuf, pTimeStamp, lenTimeStamp); + iBuf = lenTimeStamp; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pHOSTNAME, lenHOSTNAME); + iBuf += lenHOSTNAME; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pTAG, lenTAG); + iBuf += lenTAG; + + if(pMSG[0] != ' ') + *(*ppBuf + iBuf++) = ' '; + memcpy(*ppBuf + iBuf, pMSG, lenMSG); + iBuf += lenMSG; + + /* trailer */ + *(*ppBuf + iBuf++) = '\n'; + *(*ppBuf + iBuf) = '\0'; + +finalize_it: +ENDstrgen + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_SMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(smfile) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + + dbgprintf("rsyslog standard file format strgen init called, compiled with version %s\n", VERSION); +ENDmodInit diff --git a/tools/smfile.h b/tools/smfile.h new file mode 100644 index 00000000..10946db5 --- /dev/null +++ b/tools/smfile.h @@ -0,0 +1,31 @@ +/* smfile.h + * These are the definitions for the traditional file format stringen module. + * + * File begun on 2010-06-04 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef SMFILE_H_INCLUDED +#define SMFILE_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitsmfile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef SMFILE_H_INCLUDED */ diff --git a/tools/smfwd.c b/tools/smfwd.c new file mode 100644 index 00000000..60fe94a7 --- /dev/null +++ b/tools/smfwd.c @@ -0,0 +1,143 @@ +/* smfwd.c + * This is a strgen module for the traditional (network) forwarding format. + * + * Format generated: + * "<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%" + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2010-06-01 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "unicode-helper.h" + +MODULE_TYPE_STRGEN +MODULE_TYPE_NOKEEP +STRGEN_NAME("RSYSLOG_ForwardFormat") + +/* internal structures + */ +DEF_SMOD_STATIC_DATA + + +/* config data */ + + +/* This strgen tries to minimize the amount of reallocs be first obtaining pointers to all strings + * needed (including their length) and then calculating the actual space required. So when we + * finally copy, we know exactly what we need. So we do at most one alloc. + */ +BEGINstrgen + register int iBuf; + char *pPRI; + size_t lenPRI; + uchar *pTimeStamp; + size_t lenTimeStamp; + uchar *pHOSTNAME; + size_t lenHOSTNAME; + uchar *pTAG; + int lenTAG; + uchar *pMSG; + size_t lenMSG; + size_t lenTotal; +CODESTARTstrgen + /* first obtain all strings and their length (if not fixed) */ + pPRI = getPRI(pMsg); + lenPRI = strlen(pPRI); + pTimeStamp = (uchar*) getTimeReported(pMsg, tplFmtRFC3339Date); + lenTimeStamp = ustrlen(pTimeStamp); + pHOSTNAME = (uchar*) getHOSTNAME(pMsg); + lenHOSTNAME = getHOSTNAMELen(pMsg); + getTAG(pMsg, &pTAG, &lenTAG); + if(lenTAG > 32) + lenTAG = 32; /* for forwarding, a max of 32 chars is permitted (RFC!) */ + pMSG = getMSG(pMsg); + lenMSG = getMSGLen(pMsg); + + /* calculate len, constants for spaces and similar fixed strings */ + lenTotal = 1 + lenPRI + 1 + lenTimeStamp + 1 + lenHOSTNAME + 1 + lenTAG + lenMSG + 1; + if(pMSG[0] != ' ') + ++lenTotal; /* then we need to introduce one additional space */ + + /* now make sure buffer is large enough */ + if(lenTotal >= *pLenBuf) + CHKiRet(ExtendBuf(ppBuf, pLenBuf, lenTotal)); + + /* and concatenate the resulting string */ + **ppBuf = '<'; + memcpy(*ppBuf + 1, pPRI, lenPRI); + iBuf = lenPRI + 1; + *(*ppBuf + iBuf++) = '>'; + + memcpy(*ppBuf + iBuf, pTimeStamp, lenTimeStamp); + iBuf += lenTimeStamp; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pHOSTNAME, lenHOSTNAME); + iBuf += lenHOSTNAME; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pTAG, lenTAG); + iBuf += lenTAG; + + if(pMSG[0] != ' ') + *(*ppBuf + iBuf++) = ' '; + memcpy(*ppBuf + iBuf, pMSG, lenMSG); + iBuf += lenMSG; + + /* string terminator */ + *(*ppBuf + iBuf) = '\0'; + +finalize_it: +ENDstrgen + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_SMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(smfwd) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + + dbgprintf("rsyslog standard (network) forward format strgen init called, compiled with version %s\n", VERSION); +ENDmodInit diff --git a/tools/smfwd.h b/tools/smfwd.h new file mode 100644 index 00000000..191a6bf1 --- /dev/null +++ b/tools/smfwd.h @@ -0,0 +1,30 @@ +/* smfwd.h + * + * File begun on 2010-06-04 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef SMFWD_H_INCLUDED +#define SMFWD_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitsmfwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef SMFWD_H_INCLUDED */ diff --git a/tools/smtradfile.c b/tools/smtradfile.c new file mode 100644 index 00000000..5484f7be --- /dev/null +++ b/tools/smtradfile.c @@ -0,0 +1,129 @@ +/* smtradfile.c + * This is a strgen module for the traditional file format. + * + * Format generated: + * "%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2010-06-01 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "unicode-helper.h" + +MODULE_TYPE_STRGEN +MODULE_TYPE_NOKEEP +STRGEN_NAME("RSYSLOG_TraditionalFileFormat") + +/* internal structures + */ +DEF_SMOD_STATIC_DATA + + +/* config data */ + + +/* This strgen tries to minimize the amount of reallocs be first obtaining pointers to all strings + * needed (including their length) and then calculating the actual space required. So when we + * finally copy, we know exactly what we need. So we do at most one alloc. + */ +BEGINstrgen + register int iBuf; + uchar *pTimeStamp; + uchar *pHOSTNAME; + size_t lenHOSTNAME; + uchar *pTAG; + int lenTAG; + uchar *pMSG; + size_t lenMSG; + size_t lenTotal; +CODESTARTstrgen + /* first obtain all strings and their length (if not fixed) */ + pTimeStamp = (uchar*) getTimeReported(pMsg, tplFmtRFC3164Date); + pHOSTNAME = (uchar*) getHOSTNAME(pMsg); + lenHOSTNAME = getHOSTNAMELen(pMsg); + getTAG(pMsg, &pTAG, &lenTAG); + pMSG = getMSG(pMsg); + lenMSG = getMSGLen(pMsg); + + /* calculate len, constants for spaces and similar fixed strings */ + lenTotal = CONST_LEN_TIMESTAMP_3164 + 1 + lenHOSTNAME + 1 + lenTAG + lenMSG + 2; + if(pMSG[0] != ' ') + ++lenTotal; /* then we need to introduce one additional space */ + + /* now make sure buffer is large enough */ + if(lenTotal >= *pLenBuf) + CHKiRet(ExtendBuf(ppBuf, pLenBuf, lenTotal)); + + /* and concatenate the resulting string */ + memcpy(*ppBuf, pTimeStamp, CONST_LEN_TIMESTAMP_3164); + *(*ppBuf + CONST_LEN_TIMESTAMP_3164) = ' '; + + memcpy(*ppBuf + CONST_LEN_TIMESTAMP_3164 + 1, pHOSTNAME, lenHOSTNAME); + iBuf = CONST_LEN_TIMESTAMP_3164 + 1 + lenHOSTNAME; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pTAG, lenTAG); + iBuf += lenTAG; + + if(pMSG[0] != ' ') + *(*ppBuf + iBuf++) = ' '; + memcpy(*ppBuf + iBuf, pMSG, lenMSG); + iBuf += lenMSG; + + /* trailer */ + *(*ppBuf + iBuf++) = '\n'; + *(*ppBuf + iBuf) = '\0'; + +finalize_it: +ENDstrgen + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_SMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(smtradfile) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + dbgprintf("traditional file format strgen init called, compiled with version %s\n", VERSION); +ENDmodInit diff --git a/tools/smtradfile.h b/tools/smtradfile.h new file mode 100644 index 00000000..afc737ed --- /dev/null +++ b/tools/smtradfile.h @@ -0,0 +1,31 @@ +/* smtradfile.h + * These are the definitions for the traditional file format stringen module. + * + * File begun on 2010-06-01 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef SMTRADFILE_H_INCLUDED +#define SMTRADFILE_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitsmtradfile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef SMTRADFILE_H_INCLUDED */ diff --git a/tools/smtradfwd.c b/tools/smtradfwd.c new file mode 100644 index 00000000..37717434 --- /dev/null +++ b/tools/smtradfwd.c @@ -0,0 +1,140 @@ +/* smtradfwd.c + * This is a strgen module for the traditional forwarding format. + * + * Format generated: + * "<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%" + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2010-06-01 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include "syslogd.h" +#include "conf.h" +#include "syslogd-types.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "unicode-helper.h" + +MODULE_TYPE_STRGEN +MODULE_TYPE_NOKEEP +STRGEN_NAME("RSYSLOG_TraditionalForwardFormat") + +/* internal structures + */ +DEF_SMOD_STATIC_DATA + + +/* config data */ + + +/* This strgen tries to minimize the amount of reallocs be first obtaining pointers to all strings + * needed (including their length) and then calculating the actual space required. So when we + * finally copy, we know exactly what we need. So we do at most one alloc. + */ +BEGINstrgen + register int iBuf; + char *pPRI; + size_t lenPRI; + uchar *pTimeStamp; + uchar *pHOSTNAME; + size_t lenHOSTNAME; + uchar *pTAG; + int lenTAG; + uchar *pMSG; + size_t lenMSG; + size_t lenTotal; +CODESTARTstrgen + /* first obtain all strings and their length (if not fixed) */ + pPRI = getPRI(pMsg); + lenPRI = strlen(pPRI); + pTimeStamp = (uchar*) getTimeReported(pMsg, tplFmtRFC3164Date); + pHOSTNAME = (uchar*) getHOSTNAME(pMsg); + lenHOSTNAME = getHOSTNAMELen(pMsg); + getTAG(pMsg, &pTAG, &lenTAG); + if(lenTAG > 32) + lenTAG = 32; /* for forwarding, a max of 32 chars is permitted (RFC!) */ + pMSG = getMSG(pMsg); + lenMSG = getMSGLen(pMsg); + + /* calculate len, constants for spaces and similar fixed strings */ + lenTotal = 1 + lenPRI + 1 + CONST_LEN_TIMESTAMP_3164 + 1 + lenHOSTNAME + 1 + lenTAG + lenMSG + 1; + if(pMSG[0] != ' ') + ++lenTotal; /* then we need to introduce one additional space */ + + /* now make sure buffer is large enough */ + if(lenTotal >= *pLenBuf) + CHKiRet(ExtendBuf(ppBuf, pLenBuf, lenTotal)); + + /* and concatenate the resulting string */ + **ppBuf = '<'; + memcpy(*ppBuf + 1, pPRI, lenPRI); + iBuf = lenPRI + 1; + *(*ppBuf + iBuf++) = '>'; + + memcpy(*ppBuf + iBuf, pTimeStamp, CONST_LEN_TIMESTAMP_3164); + iBuf += CONST_LEN_TIMESTAMP_3164; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pHOSTNAME, lenHOSTNAME); + iBuf += lenHOSTNAME; + *(*ppBuf + iBuf++) = ' '; + + memcpy(*ppBuf + iBuf, pTAG, lenTAG); + iBuf += lenTAG; + + if(pMSG[0] != ' ') + *(*ppBuf + iBuf++) = ' '; + memcpy(*ppBuf + iBuf, pMSG, lenMSG); + iBuf += lenMSG; + + /* string terminator */ + *(*ppBuf + iBuf) = '\0'; + +finalize_it: +ENDstrgen + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_SMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(smtradfwd) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + dbgprintf("rsyslog traditional (network) forward format strgen init called, compiled with version %s\n", VERSION); +ENDmodInit diff --git a/tools/smtradfwd.h b/tools/smtradfwd.h new file mode 100644 index 00000000..9ff0ab54 --- /dev/null +++ b/tools/smtradfwd.h @@ -0,0 +1,30 @@ +/* smtradfwd.h + * + * File begun on 2010-06-04 by RGerhards + * + * Copyright 2010 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef SMTRADFWD_H_INCLUDED +#define SMTRADFWD_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitsmtradfwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef SMTRADFWD_H_INCLUDED */ diff --git a/tools/syncdemo.c b/tools/syncdemo.c new file mode 100644 index 00000000..89a5c6cc --- /dev/null +++ b/tools/syncdemo.c @@ -0,0 +1,440 @@ +/* syncdemo - a program to demonstrate the performance and validity of different + * synchronization methods as well as some timing properties. + * + * The task to be done is very simple: a single gloabl integer is to to incremented + * by multiple threads. All this is done in a very-high concurrency environment. Note that + * the test is unfair to mechanisms likes spinlocks, because we have almost only wait + * time but no real processing time between the waits. However, the test provides + * some good insight into atomic instructions vs. other synchronisation methods. + * It also proves that garbling variables by not doing proper synchronisation is + * highly likely. For best results, this program should be executed on a + * multiprocessor machine (on a uniprocessor, it will probably not display the + * problems caused by missing synchronisation). + * + * Note: partitioned processing mode means that all computation is first done + * locally and the final result is then combined doing proper synchronization. + * This mode is used as a baseline for uninterrupted processing. + * + * compile with $ gcc -O1 -o syncdemo -lpthread syncdemo.c + * + * Alternatively, you may use -O0, but not a higher level. Note that + * the gcc code generator does in neither case generate code really + * suitable to compare "part" and "none" modes. If you absolutely need + * to do that, you need to use inline assembly. However, the results should + * be fairly OK when consitently using either -O0 or -O1. If you see a big loss + * of performance when you compare "none" and "part", be sure to run + * "none" with -t1 and watch out for the results! In any case, looking at the generated + * assembly code is vital to interpret results correctly. Review of generated assembly + * done on 2010-05-05 indicates that -O0 is probably the best choice. Note that we + * use the volatile attribute in one spot. This is used because it results in the + * best comparable result for our gcc 4.4.3, not really to invoke the volatile semantics. + * + * use "gcc -g -Wa,-ahl=syncdemo.s -lpthread syncdemo.c" to obtain a mixed code/assembly listing. + * + * This program REQUIRES linux. With slight modification, it may run on Solaris. + * Note that gcc on Sparc does NOT offer atomic instruction support! + * + * Copyright (C) 2010 by Rainer Gerhards <rgerhards@hq.adiscon.com> + * Released under the GNU GPLv3. + * + * Inspired by (retrieved 2010-04-13) + * http://www.alexonlinux.com/multithreaded-simple-data-type-access-and-atomic-variables + */ +#define _GNU_SOURCE +#include <sched.h> +#include <stdio.h> +#include <pthread.h> +#include <unistd.h> +#include <semaphore.h> +#include <stdlib.h> +#include <linux/unistd.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <errno.h> +#include <getopt.h> + + +typedef enum { part, none, atomic, cas, spinlock, mutex, semaphore } syncType_t; +static syncType_t syncTypes[] = { part, none, atomic, cas, spinlock, mutex, semaphore }; + +/* config settings */ +static int bCPUAffinity = 0; +static int procs = 0; /* number of processors */ +static int numthrds = 0; /* if zero, => equal num of processors */ +static unsigned goal = 50000000; /* 50 million */ +static int bCSV = 0; /* generate CSV output? */ +static int numIterations = 1; /* number of iterations */ +static int dummyLoad = 0; /* number of dummy load iterations to generate */ +syncType_t syncType; +static int bAllSyncTypes = 0; + +static int global_int = 0; /* our global counter */ +static unsigned thrd_WorkToDo; /* number of computations each thread must do */ +static volatile int bStartRun = 0; /* indicate to flag when threads should start */ + +static struct timeval tvStart, tvEnd; /* used for timing one testing iteration */ + +/* statistic counters */ +static long long totalRuntime; +static unsigned minRuntime = 999999999; +static unsigned maxRuntime = 0; + +/* sync objects (if needed) */ +static pthread_mutex_t mut; +static pthread_spinlock_t spin; +static sem_t sem; + +static char* +getSyncMethName(syncType_t st) +{ + switch(st) { + case part : return "partition"; + case none : return "none"; + case atomic : return "atomic op"; + case spinlock : return "spin lock"; + case mutex : return "mutex"; + case semaphore: return "semaphore"; + case cas : return "cas"; + } +} + + +static pid_t +gettid() +{ + return syscall( __NR_gettid ); +} + + +void *workerThread( void *arg ) +{ + int i, j; + volatile int partval = 0; /* use volatile so that gcc generates code similar to global var */ + int *partptr; + int oldval, newval; /* for CAS sync mode */ + int thrd_num = (int)(long)arg; + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(thrd_num % procs, &set); + if(syncType == part) { + partval = 0; + } + + /* if enabled, try to put thread on a fixed CPU (the one that corresponds to the + * thread ID). This may + */ + if(bCPUAffinity) { + if (sched_setaffinity( gettid(), sizeof( cpu_set_t ), &set )) { + perror( "sched_setaffinity" ); + return NULL; + } + } + + /* wait for "go" */ + while(bStartRun == 0) + /*WAIT!*/; + + for (i = 0; i < thrd_WorkToDo; i++) { + switch(syncType) { + case part: + ///* one needs to use inline assembly to get this right... */ + //asm("addl $1, global_int(%rip)"); + partval++; + break; + case none: + global_int++; + break; + case atomic: + __sync_fetch_and_add(&global_int,1); + break; + case cas: + do { + oldval = global_int; + newval = oldval + 1; + } while(!__sync_bool_compare_and_swap(&global_int, oldval, newval)); + break; + case mutex: + pthread_mutex_lock(&mut); + global_int++; + pthread_mutex_unlock(&mut); + break; + case spinlock: + pthread_spin_lock(&spin); + global_int++; + pthread_spin_unlock(&spin); + break; + case semaphore: + sem_wait(&sem); + global_int++; + sem_post(&sem); + break; + } + + /* we now generate "dummy load" if instructed to do so. The idea is that + * we do some other work, as in real life, so that we have a better + * ratio of sync vs. actual work to do. + */ + for(j = 0 ; j < dummyLoad ; ++j) { + /* be careful: compiler may optimize loop out! */; + } + } + + if(syncType == part) { + pthread_mutex_lock(&mut); + global_int += partval; + pthread_mutex_unlock(&mut); + } + + return NULL; +} + + +static void beginTiming(void) +{ + if(!(bCSV || bAllSyncTypes)) { + printf("Test Parameters:\n"); + printf("\tNumber of Cores.........: %d\n", procs); + printf("\tNumber of Threads.......: %d\n", numthrds); + printf("\tSet Affinity............: %s\n", bCPUAffinity ? "yes" : "no"); + printf("\tCount to................: %u\n", goal); + printf("\tWork for each Thread....: %u\n", thrd_WorkToDo); + printf("\tDummy Load Counter......: %d\n", dummyLoad); + printf("\tSync Method used........: %s\n", getSyncMethName(syncType)); + } + gettimeofday(&tvStart, NULL); +} + + +static void endTiming(void) +{ + unsigned delta; + long sec, usec; + long runtime; + + gettimeofday(&tvEnd, NULL); + if(tvStart.tv_usec > tvEnd.tv_usec) { + tvEnd.tv_sec--; + tvEnd.tv_usec += 1000000; + } + + sec = tvEnd.tv_sec - tvStart.tv_sec; + usec = tvEnd.tv_usec - tvStart.tv_usec; + + delta = thrd_WorkToDo * numthrds - global_int; + if(!bAllSyncTypes) { + if(bCSV) { + printf("%s,%d,%d,%d,%u,%u,%ld.%06.6ld\n", + getSyncMethName(syncType), procs, numthrds, bCPUAffinity, goal, delta, sec, usec); + } else { + printf("measured (sytem time) runtime is %ld.% 6.6ld seconds\n", sec, usec); + if(delta == 0) { + printf("Computation was done correctly.\n"); + } else { + printf("Computation INCORRECT,\n" + "\texpected %9u\n" + "\treal %9u\n" + "\toff by %9u\n", + thrd_WorkToDo * numthrds, + global_int, + delta); + } + } + } + + runtime = sec * 1000 + (usec / 1000); + totalRuntime += runtime; + if(runtime < minRuntime) + minRuntime = runtime; + if(runtime > maxRuntime) + maxRuntime = runtime; +} + + +static void +usage(void) +{ + fprintf(stderr, "Usage: syncdemo -a -c<num> -t<num>\n"); + fprintf(stderr, "\t-a set CPU affinity\n"); + fprintf(stderr, "\t-i number of iterations\n"); + fprintf(stderr, "\t-c<num> count to <num>\n"); + fprintf(stderr, "\t-d<num> dummy load, <num> iterations\n"); + fprintf(stderr, "\t-t<num> number of threads to use\n"); + fprintf(stderr, "\t-s<type> sync-type to use (none, atomic, mutex, spin, semaphore)\n"); + fprintf(stderr, "\t-C generate CSV output\n"); + fprintf(stderr, "\t-A test ALL sync types\n"); + exit(2); +} + + +/* carry out the actual test (one iteration) + */ +static void +singleTest(void) +{ + int i; + pthread_t *thrs; + + global_int = 0; + bStartRun = 0; + + thrs = malloc(sizeof(pthread_t) * numthrds); + if (thrs == NULL) { + perror( "malloc" ); + exit(1); + } + + thrd_WorkToDo = goal / numthrds; + + for (i = 0; i < numthrds; i++) { + if(pthread_create( &thrs[i], NULL, workerThread, (void *)(long)i )) { + perror( "pthread_create" ); + procs = i; + break; + } + } + + beginTiming(); + bStartRun = 1; /* start the threads (they are busy-waiting so far!) */ + + for (i = 0; i < numthrds; i++) + pthread_join( thrs[i], NULL ); + + endTiming(); + + free( thrs ); + +} + + +/* display an unsigned ms runtime count as string. Note that the + * string is inside a dynamically allocated buffer, which the caller + * must free to prevent a memory leak. + */ +char * +dispRuntime(unsigned rt) +{ + static char *fmtbuf; + + fmtbuf = malloc(32 * sizeof(char)); + snprintf(fmtbuf, 32, "%u.%03.3u", + rt / 1000, rt % 1000); + return(fmtbuf); +} + + +doTest(syncType_t st) +{ + int i; + + syncType = st; + totalRuntime = 0; + minRuntime = 999999999; + maxRuntime = 0; + for(i = 0 ; i < numIterations ; ++i) { + //printf("starting iteration %d\n", i); + singleTest(); + } + + /* we have a memory leak due to calling dispRuntime(), but we don't + * care as we terminate immediately. + */ + printf("%-10s: total runtime %6ld.%3.3u, avg %s, min %s, max %s\n", + getSyncMethName(st), + (long)totalRuntime/1000, (unsigned)(totalRuntime % 1000), + dispRuntime((unsigned) (totalRuntime / numIterations)), + dispRuntime(minRuntime), + dispRuntime(maxRuntime)); +} + + +int +main(int argc, char *argv[]) +{ + int i; + int opt; + + while((opt = getopt(argc, argv, "ac:d:i:t:s:CA")) != EOF) { + switch((char)opt) { + case 'A': + bAllSyncTypes = 1; + break; + case 'a': + bCPUAffinity = 1; + break; + case 'c': + goal = (unsigned) atol(optarg); + break; + case 'd': + dummyLoad = atoi(optarg); + break; + case 'i': + numIterations = atoi(optarg); + break; + case 't': + numthrds = atoi(optarg); + break; + case 'C': + bCSV = 1; + break; + case 's': + if(!strcmp(optarg, "none")) + syncType = none; + else if(!strcmp(optarg, "part")) + syncType = part; + else if(!strcmp(optarg, "atomic")) + syncType = atomic; + else if(!strcmp(optarg, "cas")) + syncType = cas; + else if(!strcmp(optarg, "mutex")) { + syncType = mutex; + pthread_mutex_init(&mut, NULL); + } else if(!strcmp(optarg, "spin")) { + syncType = spinlock; + } else if(!strcmp(optarg, "semaphore")) { + syncType = semaphore; + sem_init(&sem, 0, 1); + } else { + fprintf(stderr, "error: invalid sync mode '%s'\n", optarg); + usage(); + } + break; + default:usage(); + break; + } + } + + /* for simplicity, we init all sync helpers no matter if we need them */ + pthread_mutex_init(&mut, NULL); + pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE); + sem_init(&sem, 0, 1); + + /* Getting number of CPUs */ + procs = (int)sysconf(_SC_NPROCESSORS_ONLN); + if(procs < 0) { + perror("sysconf"); + return -1; + } + + if(numthrds < 1) { + numthrds = procs; + } + + if(bAllSyncTypes) { + for(i = 0 ; i < sizeof(syncTypes) / sizeof(syncType_t) ; ++i) { + doTest(syncTypes[i]); + } + printf("Done running tests, result based on:\n"); + printf("\tNumber of Cores.........: %d\n", procs); + printf("\tNumber of Threads.......: %d\n", numthrds); + printf("\tSet CPU Affinity........: %s\n", bCPUAffinity ? "yes" : "no"); + printf("\tCount to................: %u\n", goal); + printf("\tWork for each Thread....: %u\n", thrd_WorkToDo); + printf("\tDummy Load Counter......: %d\n", dummyLoad); + printf("\tIterations..............: %d\n", numIterations); + } else { + doTest(syncType); + } + + return 0; +} diff --git a/tools/syslogd.c b/tools/syslogd.c new file mode 100644 index 00000000..2f0f64c3 --- /dev/null +++ b/tools/syslogd.c @@ -0,0 +1,2079 @@ +/** + * \brief This is the main file of the rsyslogd daemon. + * + * Please visit the rsyslog project at + * + * http://www.rsyslog.com + * + * to learn more about it and discuss any questions you may have. + * + * rsyslog had initially been forked from the sysklogd project. + * I would like to express my thanks to the developers of the sysklogd + * package - without it, I would have had a much harder start... + * + * Please note that while rsyslog started from the sysklogd code base, + * it nowadays has almost nothing left in common with it. Allmost all + * parts of the code have been rewritten. + * + * This Project was intiated and is maintained by + * Rainer Gerhards <rgerhards@hq.adiscon.com>. + * + * For further information, please see http://www.rsyslog.com + * + * rsyslog - An Enhanced syslogd Replacement. + * Copyright 2003-2012 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" + +#define DEFUPRI (LOG_USER|LOG_NOTICE) + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <ctype.h> +#include <limits.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include <assert.h> + +#ifdef OS_SOLARIS +# include <errno.h> +# include <fcntl.h> +# include <stropts.h> +# include <sys/termios.h> +# include <sys/types.h> +#else +# include <libgen.h> +# include <sys/errno.h> +#endif + +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/file.h> +#include <sys/resource.h> +#include <grp.h> + +#if HAVE_SYS_TIMESPEC_H +# include <sys/timespec.h> +#endif + +#if HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif + +#include <signal.h> + +#if HAVE_PATHS_H +#include <paths.h> +#endif + +#ifdef USE_NETZIP +#include <zlib.h> +#endif + +extern int yydebug; /* interface to flex */ + +#include <netdb.h> + +#include "pidfile.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "syslogd-types.h" +#include "template.h" +#include "outchannel.h" +#include "syslogd.h" + +#include "msg.h" +#include "modules.h" +#include "action.h" +#include "iminternal.h" +#include "cfsysline.h" +#include "threads.h" +#include "wti.h" +#include "queue.h" +#include "stream.h" +#include "conf.h" +#include "errmsg.h" +#include "datetime.h" +#include "parser.h" +#include "batch.h" +#include "unicode-helper.h" +#include "ruleset.h" +#include "net.h" +#include "prop.h" +#include "rsconf.h" +#include "dnscache.h" +#include "sd-daemon.h" +#include "rainerscript.h" +#include "ratelimit.h" + +/* definitions for objects we access */ +DEFobjCurrIf(obj) +DEFobjCurrIf(glbl) +DEFobjCurrIf(datetime) /* TODO: make go away! */ +DEFobjCurrIf(conf) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(ruleset) +DEFobjCurrIf(prop) +DEFobjCurrIf(parser) +DEFobjCurrIf(rsconf) +DEFobjCurrIf(net) /* TODO: make go away! */ + + +/* forward definitions */ +static rsRetVal GlobalClassExit(void); +static rsRetVal queryLocalHostname(void); + + +#ifndef _PATH_LOGCONF +#define _PATH_LOGCONF "/etc/rsyslog.conf" +#endif + +#ifndef _PATH_MODDIR +# if defined(__FreeBSD__) +# define _PATH_MODDIR "/usr/local/lib/rsyslog/" +# else +# define _PATH_MODDIR "/lib/rsyslog/" +# endif +#endif + +#if defined(SYSLOGD_PIDNAME) +# undef _PATH_LOGPID +# if defined(FSSTND) +# ifdef OS_BSD +# define _PATH_VARRUN "/var/run/" +# endif +# if defined(__sun) || defined(__hpux) +# define _PATH_VARRUN "/var/run/" +# endif +# define _PATH_LOGPID _PATH_VARRUN SYSLOGD_PIDNAME +# else +# define _PATH_LOGPID "/etc/" SYSLOGD_PIDNAME +# endif +#else +# ifndef _PATH_LOGPID +# if defined(__sun) || defined(__hpux) +# define _PATH_VARRUN "/var/run/" +# endif +# if defined(FSSTND) +# define _PATH_LOGPID _PATH_VARRUN "rsyslogd.pid" +# else +# define _PATH_LOGPID "/etc/rsyslogd.pid" +# endif +# endif +#endif + +#ifndef _PATH_TTY +# define _PATH_TTY "/dev/tty" +#endif + +rsconf_t *ourConf; /* our config object */ + +static prop_t *pInternalInputName = NULL; /* there is only one global inputName for all internally-generated messages */ +static uchar *ConfFile = (uchar*) _PATH_LOGCONF; /* read-only after startup */ +static char *PidFile = _PATH_LOGPID; /* read-only after startup */ + +/* mypid is read-only after the initial fork() */ +static int bHadHUP = 0; /* did we have a HUP? */ + +static int bFinished = 0; /* used by termination signal handler, read-only except there + * is either 0 or the number of the signal that requested the + * termination. + */ +int iConfigVerify = 0; /* is this just a config verify run? */ + +#define LIST_DELIMITER ':' /* delimiter between two hosts */ + +static pid_t ppid; /* This is a quick and dirty hack used for spliting main/startup thread */ + +struct queuefilenames_s { + struct queuefilenames_s *next; + uchar *name; +} *queuefilenames = NULL; + + +static ratelimit_t *dflt_ratelimiter = NULL; /* ratelimiter for submits without explicit one */ +static ratelimit_t *internalMsg_ratelimiter = NULL; /* ratelimiter for rsyslog-own messages */ +int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ +int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */ +static int doFork = 1; /* fork - run in daemon mode - read-only after startup */ +int bHaveMainQueue = 0;/* set to 1 if the main queue - in queueing mode - is available + * If the main queue is either not yet ready or not running in + * queueing mode (mode DIRECT!), then this is set to 0. + */ + +extern int errno; + +/* main message queue and its configuration parameters */ +qqueue_t *pMsgQueue = NULL; /* the main message queue */ + + +/* up to the next comment, prototypes that should be removed by reordering */ +/* Function prototypes. */ +static char **crunch_list(char *list); +static void reapchild(); +static void debug_switch(); +static void sighup_handler(); + + +static int usage(void) +{ + fprintf(stderr, "usage: rsyslogd [-46AdnqQvwx] [-l<hostlist>] [-s<domainlist>]\n" + " [-f<conffile>] [-i<pidfile>] [-N<level>] [-M<module load path>]\n" + " [-u<number>]\n" + "For further information see http://www.rsyslog.com/doc\n"); + exit(1); /* "good" exit - done to terminate usage() */ +} + + +/* ------------------------------ some support functions for imdiag ------------------------------ * + * This is a bit dirty, but the only way to do it, at least with reasonable effort. + * rgerhards, 2009-05-25 + */ + +/* return back the approximate current number of messages in the main message queue + * This number includes the messages that reside in an associated DA queue (if + * it exists) -- rgerhards, 2009-10-14 + */ +rsRetVal +diagGetMainMsgQSize(int *piSize) +{ + DEFiRet; + assert(piSize != NULL); + *piSize = (pMsgQueue->pqDA != NULL) ? pMsgQueue->pqDA->iQueueSize : 0; + *piSize += pMsgQueue->iQueueSize; + RETiRet; +} + + +/* ------------------------------ end support functions for imdiag ------------------------------ */ + + +/* rgerhards, 2005-10-24: crunch_list is called only during option processing. So + * it is never called once rsyslogd is running. This code + * contains some exits, but they are considered safe because they only happen + * during startup. Anyhow, when we review the code here, we might want to + * reconsider the exit()s. + */ +static char **crunch_list(char *list) +{ + int count, i; + char *p, *q; + char **result = NULL; + + p = list; + + /* strip off trailing delimiters */ + while (p[strlen(p)-1] == LIST_DELIMITER) { + count--; + p[strlen(p)-1] = '\0'; + } + /* cut off leading delimiters */ + while (p[0] == LIST_DELIMITER) { + count--; + p++; + } + + /* count delimiters to calculate elements */ + for (count=i=0; p[i]; i++) + if (p[i] == LIST_DELIMITER) count++; + + if ((result = (char **)MALLOC(sizeof(char *) * (count+2))) == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + + /* + * We now can assume that the first and last + * characters are different from any delimiters, + * so we don't have to care about this. + */ + count = 0; + while ((q=strchr(p, LIST_DELIMITER))) { + result[count] = (char *) MALLOC((q - p + 1) * sizeof(char)); + if (result[count] == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + strncpy(result[count], p, q - p); + result[count][q - p] = '\0'; + p = q; p++; + count++; + } + if ((result[count] = \ + (char *)MALLOC(sizeof(char) * strlen(p) + 1)) == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + strcpy(result[count],p); + result[++count] = NULL; + +#if 0 + count=0; + while (result[count]) + DBGPRINTF("#%d: %s\n", count, StripDomains[count++]); +#endif + return result; +} + + +void untty(void) +#ifdef HAVE_SETSID +{ + if(!Debug) { + setsid(); + } + return; +} +#else +{ + int i; + pid_t pid; + + if(!Debug) { + pid = getpid(); + if (setpgid(pid, pid) < 0) { + perror("setpgid"); + exit(1); + } + + i = open(_PATH_TTY, O_RDWR|O_CLOEXEC); + if (i >= 0) { +# if !defined(__hpux) + (void) ioctl(i, (int) TIOCNOTTY, NULL); +# else + /* TODO: we need to implement something for HP UX! -- rgerhards, 2008-03-04 */ + /* actually, HP UX should have setsid, so the code directly above should + * trigger. So the actual question is why it doesn't do that... + */ +# endif + close(i); + } + } +} +#endif + + +/* This takes a received message that must be decoded and submits it to + * the main message queue. This is a legacy function which is being provided + * to aid older input plugins that do not support message creation via + * the new interfaces themselves. It is not recommended to use this + * function for new plugins. -- rgerhards, 2009-10-12 + */ +rsRetVal +parseAndSubmitMessage(uchar *hname, uchar *hnameIP, uchar *msg, int len, int flags, flowControl_t flowCtlType, + prop_t *pInputName, struct syslogTime *stTime, time_t ttGenTime, ruleset_t *pRuleset) +{ + prop_t *pProp = NULL; + msg_t *pMsg; + DEFiRet; + + /* we now create our own message object and submit it to the queue */ + if(stTime == NULL) { + CHKiRet(msgConstruct(&pMsg)); + } else { + CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime)); + } + if(pInputName != NULL) + MsgSetInputName(pMsg, pInputName); + MsgSetRawMsg(pMsg, (char*)msg, len); + MsgSetFlowControlType(pMsg, flowCtlType); + MsgSetRuleset(pMsg, pRuleset); + pMsg->msgFlags = flags | NEEDS_PARSING; + + MsgSetRcvFromStr(pMsg, hname, ustrlen(hname), &pProp); + CHKiRet(prop.Destruct(&pProp)); + CHKiRet(MsgSetRcvFromIPStr(pMsg, hnameIP, ustrlen(hnameIP), &pProp)); + CHKiRet(prop.Destruct(&pProp)); + CHKiRet(submitMsg2(pMsg)); + +finalize_it: + RETiRet; +} + + +/* this is a special function used to submit an error message. This + * function is also passed to the runtime library as the generic error + * message handler. -- rgerhards, 2008-04-17 + */ +rsRetVal +submitErrMsg(int iErr, uchar *msg) +{ + DEFiRet; + iRet = logmsgInternal(iErr, LOG_SYSLOG|LOG_ERR, msg, 0); + RETiRet; +} + + +static inline rsRetVal +submitMsgWithDfltRatelimiter(msg_t *pMsg) +{ + return ratelimitAddMsg(dflt_ratelimiter, NULL, pMsg); +} + +/* rgerhards 2004-11-09: the following is a function that can be used + * to log a message orginating from the syslogd itself. + */ +rsRetVal +logmsgInternal(int iErr, int pri, uchar *msg, int flags) +{ + uchar pszTag[33]; + msg_t *pMsg; + DEFiRet; + + CHKiRet(msgConstruct(&pMsg)); + MsgSetInputName(pMsg, pInternalInputName); + MsgSetRawMsgWOSize(pMsg, (char*)msg); + MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); + MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); + MsgSetRcvFromIP(pMsg, glbl.GetLocalHostIP()); + MsgSetMSGoffs(pMsg, 0); + /* check if we have an error code associated and, if so, + * adjust the tag. -- rgerhards, 2008-06-27 + */ + if(iErr == NO_ERRCODE) { + MsgSetTAG(pMsg, UCHAR_CONSTANT("rsyslogd:"), sizeof("rsyslogd:") - 1); + } else { + size_t len = snprintf((char*)pszTag, sizeof(pszTag), "rsyslogd%d:", iErr); + pszTag[32] = '\0'; /* just to make sure... */ + MsgSetTAG(pMsg, pszTag, len); + } + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + flags |= INTERNAL_MSG; + pMsg->msgFlags = flags; + + /* we now check if we should print internal messages out to stderr. This was + * suggested by HKS as a way to help people troubleshoot rsyslog configuration + * (by running it interactively. This makes an awful lot of sense, so I add + * it here. -- rgerhards, 2008-07-28 + * Note that error messages can not be disable during a config verify. This + * permits us to process unmodified config files which otherwise contain a + * supressor statement. + */ + if(((Debug == DEBUG_FULL || !doFork) && ourConf->globals.bErrMsgToStderr) || iConfigVerify) { + if(LOG_PRI(pri) == LOG_ERR) + fprintf(stderr, "rsyslogd: %s\n", msg); + } + + if(bHaveMainQueue == 0) { /* not yet in queued mode */ + iminternalAddMsg(pMsg); + } else { + /* we have the queue, so we can simply provide the + * message to the queue engine. + */ + ratelimitAddMsg(internalMsg_ratelimiter, NULL, pMsg); + //submitMsgWithDfltRatelimiter(pMsg); + } +finalize_it: + RETiRet; +} + + +/* preprocess a batch of messages, that is ready them for actual processing. This is done + * as a first stage and totally in parallel to any other worker active in the system. So + * it helps us keep up the overall concurrency level. + * rgerhards, 2010-06-09 + */ +static inline rsRetVal +preprocessBatch(batch_t *pBatch) { + prop_t *ip; + prop_t *fqdn; + prop_t *localName; + prop_t *propFromHost = NULL; + prop_t *propFromHostIP = NULL; + int bSingleRuleset; + ruleset_t *batchRuleset; /* the ruleset used for all message inside the batch, if there is a single one */ + int bIsPermitted; + msg_t *pMsg; + int i; + rsRetVal localRet; + DEFiRet; + + bSingleRuleset = 1; + batchRuleset = (pBatch->nElem > 0) ? pBatch->pElem[0].pMsg->pRuleset : NULL; + + for(i = 0 ; i < pBatch->nElem && !*(pBatch->pbShutdownImmediate) ; i++) { + pMsg = pBatch->pElem[i].pMsg; + if((pMsg->msgFlags & NEEDS_ACLCHK_U) != 0) { + DBGPRINTF("msgConsumer: UDP ACL must be checked for message (hostname-based)\n"); + if(net.cvthname(pMsg->rcvFrom.pfrominet, &localName, &fqdn, &ip) != RS_RET_OK) + continue; + bIsPermitted = net.isAllowedSender2((uchar*)"UDP", + (struct sockaddr *)pMsg->rcvFrom.pfrominet, (char*)propGetSzStr(fqdn), 1); + if(!bIsPermitted) { + DBGPRINTF("Message from '%s' discarded, not a permitted sender host\n", + propGetSzStr(fqdn)); + pBatch->eltState[i] = BATCH_STATE_DISC; + } else { + /* save some of the info we obtained */ + MsgSetRcvFrom(pMsg, localName); + CHKiRet(MsgSetRcvFromIP(pMsg, ip)); + pMsg->msgFlags &= ~NEEDS_ACLCHK_U; + } + } + if((pMsg->msgFlags & NEEDS_PARSING) != 0) { + if((localRet = parser.ParseMsg(pMsg)) != RS_RET_OK) { + DBGPRINTF("Message discarded, parsing error %d\n", localRet); + pBatch->eltState[i] = BATCH_STATE_DISC; + } + } + if(pMsg->pRuleset != batchRuleset) + bSingleRuleset = 0; + } + + batchSetSingleRuleset(pBatch, bSingleRuleset); + +finalize_it: + if(propFromHost != NULL) + prop.Destruct(&propFromHost); + if(propFromHostIP != NULL) + prop.Destruct(&propFromHostIP); + RETiRet; +} + +/* The consumer of dequeued messages. This function is called by the + * queue engine on dequeueing of a message. It runs on a SEPARATE + * THREAD. It receives an array of pointers, which it must iterate + * over. We do not do any further batching, as this is of no benefit + * for the main queue. + */ +static rsRetVal +msgConsumer(void __attribute__((unused)) *notNeeded, batch_t *pBatch, int *pbShutdownImmediate) +{ + DEFiRet; + assert(pBatch != NULL); + pBatch->pbShutdownImmediate = pbShutdownImmediate; /* TODO: move this to batch creation! */ + preprocessBatch(pBatch); + ruleset.ProcessBatch(pBatch); +//TODO: the BATCH_STATE_COMM must be set somewhere down the road, but we +//do not have this yet and so we emulate -- 2010-06-10 +int i; + for(i = 0 ; i < pBatch->nElem && !*pbShutdownImmediate ; i++) { + pBatch->eltState[i] = BATCH_STATE_COMM; + } + RETiRet; +} + + +/* submit a message to the main message queue. This is primarily + * a hook to prevent the need for callers to know about the main message queue + * rgerhards, 2008-02-13 + */ +rsRetVal +submitMsg2(msg_t *pMsg) +{ + qqueue_t *pQueue; + ruleset_t *pRuleset; + DEFiRet; + + ISOBJ_TYPE_assert(pMsg, msg); + + pRuleset = MsgGetRuleset(pMsg); + pQueue = (pRuleset == NULL) ? pMsgQueue : ruleset.GetRulesetQueue(pRuleset); + + /* if a plugin logs a message during shutdown, the queue may no longer exist */ + if(pQueue == NULL) { + DBGPRINTF("submitMsg2() could not submit message - " + "queue does (no longer?) exist - ignored\n"); + FINALIZE; + } + + qqueueEnqMsg(pQueue, pMsg->flowCtlType, pMsg); + +finalize_it: + RETiRet; +} + +rsRetVal +submitMsg(msg_t *pMsg) +{ + return submitMsgWithDfltRatelimiter(pMsg); +} + + +/* submit multiple messages at once, very similar to submitMsg, just + * for multi_submit_t. All messages need to go into the SAME queue! + * rgerhards, 2009-06-16 + */ +rsRetVal +multiSubmitMsg2(multi_submit_t *pMultiSub) +{ + qqueue_t *pQueue; + ruleset_t *pRuleset; + DEFiRet; + assert(pMultiSub != NULL); + + if(pMultiSub->nElem == 0) + FINALIZE; + + pRuleset = MsgGetRuleset(pMultiSub->ppMsgs[0]); + pQueue = (pRuleset == NULL) ? pMsgQueue : ruleset.GetRulesetQueue(pRuleset); + + /* if a plugin logs a message during shutdown, the queue may no longer exist */ + if(pQueue == NULL) { + DBGPRINTF("multiSubmitMsg() could not submit message - " + "queue does (no longer?) exist - ignored\n"); + FINALIZE; + } + + iRet = pQueue->MultiEnq(pQueue, pMultiSub); + pMultiSub->nElem = 0; + +finalize_it: + RETiRet; +} +rsRetVal +multiSubmitMsg(multi_submit_t *pMultiSub) /* backward compat. level */ +{ + return multiSubmitMsg2(pMultiSub); +} + + +/* flush multiSubmit, e.g. at end of read records */ +rsRetVal +multiSubmitFlush(multi_submit_t *pMultiSub) +{ + DEFiRet; + if(pMultiSub->nElem > 0) { + iRet = multiSubmitMsg2(pMultiSub); + } + RETiRet; +} + + +static void +reapchild() +{ + int saved_errno = errno; + struct sigaction sigAct; + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = reapchild; + sigaction(SIGCHLD, &sigAct, NULL); /* reset signal handler -ASP */ + + while(waitpid(-1, NULL, WNOHANG) > 0); + errno = saved_errno; +} + + +static void debug_switch() +{ + time_t tTime; + struct tm tp; + struct sigaction sigAct; + + datetime.GetTime(&tTime); + localtime_r(&tTime, &tp); + if(debugging_on == 0) { + debugging_on = 1; + dbgprintf("\n"); + dbgprintf("\n"); + dbgprintf("********************************************************************************\n"); + dbgprintf("Switching debugging_on to true at %2.2d:%2.2d:%2.2d\n", + tp.tm_hour, tp.tm_min, tp.tm_sec); + dbgprintf("********************************************************************************\n"); + } else { + dbgprintf("********************************************************************************\n"); + dbgprintf("Switching debugging_on to false at %2.2d:%2.2d:%2.2d\n", + tp.tm_hour, tp.tm_min, tp.tm_sec); + dbgprintf("********************************************************************************\n"); + dbgprintf("\n"); + dbgprintf("\n"); + debugging_on = 0; + } + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = debug_switch; + sigaction(SIGUSR1, &sigAct, NULL); +} + + +/* doDie() is a signal handler. If called, it sets the bFinished variable + * to indicate the program should terminate. However, it does not terminate + * it itself, because that causes issues with multi-threading. The actual + * termination is then done on the main thread. This solution might introduce + * a minimal delay, but it is much cleaner than the approach of doing everything + * inside the signal handler. + * rgerhards, 2005-10-26 + * Note: + * - we do not call DBGPRINTF() as this may cause us to block in case something + * with the threading is wrong. + * - we do not really care about the return state of write(), but we need this + * strange check we do to silence compiler warnings (thanks, Ubuntu!) + */ +static void doDie(int sig) +{ +# define MSG1 "DoDie called.\n" +# define MSG2 "DoDie called 5 times - unconditional exit\n" + static int iRetries = 0; /* debug aid */ + dbgprintf(MSG1); + if(Debug == DEBUG_FULL) { + if(write(1, MSG1, sizeof(MSG1) - 1)) {} + } + if(iRetries++ == 4) { + if(Debug == DEBUG_FULL) { + if(write(1, MSG2, sizeof(MSG2) - 1)) {} + } + abort(); + } + bFinished = sig; +# undef MSG1 +# undef MSG2 +} + + +/* Finalize and destruct all actions. + */ +static inline void +destructAllActions(void) +{ + ruleset.DestructAllActions(runConf); + bHaveMainQueue = 0; // flag that internal messages need to be temporarily stored +} + + +/* die() is called when the program shall end. This typically only occurs + * during sigterm or during the initialization. + * As die() is intended to shutdown rsyslogd, it is + * safe to call exit() here. Just make sure that die() itself is not called + * at inapropriate places. As a general rule of thumb, it is a bad idea to add + * any calls to die() in new code! + * rgerhards, 2005-10-24 + */ +static void +die(int sig) +{ + char buf[256]; + + DBGPRINTF("exiting on signal %d\n", sig); + + /* IMPORTANT: we should close the inputs first, and THEN send our termination + * message. If we do it the other way around, logmsgInternal() may block on + * a full queue and the inputs still fill up that queue. Depending on the + * scheduling order, we may end up with logmsgInternal being held for a quite + * long time. When the inputs are terminated first, that should not happen + * because the queue is drained in parallel. The situation could only become + * an issue with extremely long running actions in a queue full environment. + * However, such actions are at least considered poorly written, if not + * outright wrong. So we do not care about this very remote problem. + * rgerhards, 2008-01-11 + */ + + /* close the inputs */ + DBGPRINTF("Terminating input threads...\n"); + glbl.SetGlobalInputTermination(); + thrdTerminateAll(); + + /* and THEN send the termination log message (see long comment above) */ + if(sig && runConf->globals.bLogStatusMsgs) { + (void) snprintf(buf, sizeof(buf) / sizeof(char), + " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ + "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"]" " exiting on signal %d.", + (int) glblGetOurPid(), sig); + errno = 0; + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)buf, 0); + } + /* we sleep for 50ms to give the queue a chance to pick up the exit message; + * otherwise we have seen cases where the message did not make it to log + * files, even on idle systems. + */ + srSleep(0, 50); + + /* drain queue (if configured so) and stop main queue worker thread pool */ + DBGPRINTF("Terminating main queue...\n"); + qqueueDestruct(&pMsgQueue); + pMsgQueue = NULL; + + /* Free ressources and close connections. This includes flushing any remaining + * repeated msgs. + */ + DBGPRINTF("Terminating outputs...\n"); + destructAllActions(); + + DBGPRINTF("all primary multi-thread sources have been terminated - now doing aux cleanup...\n"); + + DBGPRINTF("destructing current config...\n"); + rsconf.Destruct(&runConf); + + /* rger 2005-02-22 + * now clean up the in-memory structures. OK, the OS + * would also take care of that, but if we do it + * ourselfs, this makes finding memory leaks a lot + * easier. + */ + /* de-init some modules */ + modExitIminternal(); + + /*dbgPrintAllDebugInfo(); / * this is the last spot where this can be done - below output modules are unloaded! */ + + /* the following line cleans up CfSysLineHandlers that were not based on loadable + * modules. As such, they are not yet cleared. + */ + unregCfSysLineHdlrs(); + + /* destruct our global properties */ + if(pInternalInputName != NULL) + prop.Destruct(&pInternalInputName); + + /* terminate the remaining classes */ + GlobalClassExit(); + + module.UnloadAndDestructAll(eMOD_LINK_ALL); + + DBGPRINTF("Clean shutdown completed, bye\n"); + /* dbgClassExit MUST be the last one, because it de-inits the debug system */ + dbgClassExit(); + + /* NO CODE HERE - dbgClassExit() must be the last thing before exit()! */ + remove_pid(PidFile); + exit(0); /* "good" exit, this is the terminator function for rsyslog [die()] */ +} + +/* + * Signal handler to terminate the parent process. + * rgerhards, 2005-10-24: this is only called during forking of the + * detached syslogd. I consider this method to be safe. + */ +static void doexit() +{ + exit(0); /* "good" exit, only during child-creation */ +} + +#if 0 /* TODO: re-enable, currently not used */ +/* helper to generateConfigDAG, to print out all actions via + * the llExecFunc() facility. + * rgerhards, 2007-08-02 + */ +struct dag_info { + FILE *fp; /* output file */ + int iActUnit; /* current action unit number */ + int iAct; /* current action in unit */ + int bDiscarded; /* message discarded (config error) */ + }; +DEFFUNC_llExecFunc(generateConfigDAGAction) +{ + action_t *pAction; + uchar *pszModName; + uchar *pszVertexName; + struct dag_info *pDagInfo; + DEFiRet; + + pDagInfo = (struct dag_info*) pParam; + pAction = (action_t*) pData; + + pszModName = module.GetStateName(pAction->pMod); + + /* vertex */ + if(pAction->pszName == NULL) { + if(!strcmp((char*)pszModName, "builtin-discard")) + pszVertexName = (uchar*)"discard"; + else + pszVertexName = pszModName; + } else { + pszVertexName = pAction->pszName; + } + + fprintf(pDagInfo->fp, "\tact%d_%d\t\t[label=\"%s\"%s%s]\n", + pDagInfo->iActUnit, pDagInfo->iAct, pszVertexName, + pDagInfo->bDiscarded ? " style=dotted color=red" : "", + (pAction->pQueue->qType == QUEUETYPE_DIRECT) ? "" : " shape=hexagon" + ); + + /* edge */ + if(pDagInfo->iAct == 0) { + } else { + fprintf(pDagInfo->fp, "\tact%d_%d -> act%d_%d[%s%s]\n", + pDagInfo->iActUnit, pDagInfo->iAct - 1, + pDagInfo->iActUnit, pDagInfo->iAct, + pDagInfo->bDiscarded ? " style=dotted color=red" : "", + pAction->bExecWhenPrevSusp ? " label=\"only if\\nsuspended\"" : "" ); + } + + /* check for discard */ + if(!strcmp((char*) pszModName, "builtin-discard")) { + fprintf(pDagInfo->fp, "\tact%d_%d\t\t[shape=box]\n", + pDagInfo->iActUnit, pDagInfo->iAct); + pDagInfo->bDiscarded = 1; + } + + + ++pDagInfo->iAct; + + RETiRet; +} + + +/* create config DAG + * This functions takes a rsyslog config and produces a .dot file for use + * with graphviz (http://www.graphviz.org). This is done in an effort to + * document, and also potentially troubleshoot, configurations. Plus, I + * consider it a nice feature to explain some concepts. Note that the + * current version only produces a graph with relatively little information. + * This is a foundation that may be later expanded (if it turns out to be + * useful enough). + * rgerhards, 2009-05-11 + */ +static rsRetVal +generateConfigDAG(uchar *pszDAGFile) +{ + //rule_t *f; + FILE *fp; + int iActUnit = 1; + //int bHasFilter = 0; /* filter associated with this action unit? */ + //int bHadFilter; + //int i; + struct dag_info dagInfo; + //char *pszFilterName; + char szConnectingNode[64]; + DEFiRet; + + assert(pszDAGFile != NULL); + + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*) + "Configuration graph generation is unfortunately disabled " + "in the current code base.", 0); + ABORT_FINALIZE(RS_RET_FILENAME_INVALID); + + if((fp = fopen((char*) pszDAGFile, "w")) == NULL) { + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*) + "configuraton graph output file could not be opened, none generated", 0); + ABORT_FINALIZE(RS_RET_FILENAME_INVALID); + } + + dagInfo.fp = fp; + + /* from here on, we assume writes go well. This here is a really + * unimportant utility function and if something goes wrong, it has + * almost no effect. So let's not overdo this... + */ + fprintf(fp, "# graph created by rsyslog " VERSION "\n\n" + "# use the dot tool from http://www.graphviz.org to visualize!\n" + "digraph rsyslogConfig {\n" + "\tinputs [shape=tripleoctagon]\n" + "\tinputs -> act0_0\n" + "\tact0_0 [label=\"main\\nqueue\" shape=hexagon]\n" + /*"\tmainq -> act1_0\n"*/ + ); + strcpy(szConnectingNode, "act0_0"); + dagInfo.bDiscarded = 0; + +/* TODO: re-enable! */ +#if 0 + for(f = Files; f != NULL ; f = f->f_next) { + /* BSD-Style filters are currently ignored */ + bHadFilter = bHasFilter; + if(f->f_filter_type == FILTER_PRI) { + bHasFilter = 0; + for (i = 0; i <= LOG_NFACILITIES; i++) + if (f->f_filterData.f_pmask[i] != 0xff) { + bHasFilter = 1; + break; + } + } else { + bHasFilter = 1; + } + + /* we know we have a filter, so it can be false */ + switch(f->f_filter_type) { + case FILTER_PRI: + pszFilterName = "pri filter"; + break; + case FILTER_PROP: + pszFilterName = "property filter"; + break; + case FILTER_EXPR: + pszFilterName = "script filter"; + break; + } + + /* write action unit node */ + if(bHasFilter) { + fprintf(fp, "\t%s -> act%d_end\t[label=\"%s:\\nfalse\"]\n", + szConnectingNode, iActUnit, pszFilterName); + fprintf(fp, "\t%s -> act%d_0\t[label=\"%s:\\ntrue\"]\n", + szConnectingNode, iActUnit, pszFilterName); + fprintf(fp, "\tact%d_end\t\t\t\t[shape=point]\n", iActUnit); + snprintf(szConnectingNode, sizeof(szConnectingNode), "act%d_end", iActUnit); + } else { + fprintf(fp, "\t%s -> act%d_0\t[label=\"no filter\"]\n", + szConnectingNode, iActUnit); + snprintf(szConnectingNode, sizeof(szConnectingNode), "act%d_0", iActUnit); + } + + /* draw individual nodes */ + dagInfo.iActUnit = iActUnit; + dagInfo.iAct = 0; + dagInfo.bDiscarded = 0; + llExecFunc(&f->llActList, generateConfigDAGAction, &dagInfo); /* actions */ + + /* finish up */ + if(bHasFilter && !dagInfo.bDiscarded) { + fprintf(fp, "\tact%d_%d -> %s\n", + iActUnit, dagInfo.iAct - 1, szConnectingNode); + } + + ++iActUnit; + } +#endif + + fprintf(fp, "\t%s -> act%d_0\n", szConnectingNode, iActUnit); + fprintf(fp, "\tact%d_0\t\t[label=discard shape=box]\n" + "}\n", iActUnit); + fclose(fp); + +finalize_it: + RETiRet; +} +#endif + + +/* create a main message queue, now also used for ruleset queues. This function + * needs to be moved to some other module, but it is considered acceptable for + * the time being (remember that we want to restructure config processing at large!). + * rgerhards, 2009-10-27 + */ +rsRetVal createMainQueue(qqueue_t **ppQueue, uchar *pszQueueName, struct nvlst *lst) +{ + struct queuefilenames_s *qfn; + uchar *qfname = NULL; + static int qfn_renamenum = 0; + uchar qfrenamebuf[1024]; + DEFiRet; + + /* create message queue */ + CHKiRet_Hdlr(qqueueConstruct(ppQueue, ourConf->globals.mainQ.MainMsgQueType, ourConf->globals.mainQ.iMainMsgQueueNumWorkers, ourConf->globals.mainQ.iMainMsgQueueSize, msgConsumer)) { + /* no queue is fatal, we need to give up in that case... */ + errmsg.LogError(0, iRet, "could not create (ruleset) main message queue"); \ + } + /* name our main queue object (it's not fatal if it fails...) */ + obj.SetName((obj_t*) (*ppQueue), pszQueueName); + + if(lst == NULL) { /* use legacy parameters? */ + /* ... set some properties ... */ + # define setQPROP(func, directive, data) \ + CHKiRet_Hdlr(func(*ppQueue, data)) { \ + errmsg.LogError(0, NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } + # define setQPROPstr(func, directive, data) \ + CHKiRet_Hdlr(func(*ppQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \ + errmsg.LogError(0, NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } + + if(ourConf->globals.mainQ.pszMainMsgQFName != NULL) { + /* check if the queue file name is unique, else emit an error */ + for(qfn = queuefilenames ; qfn != NULL ; qfn = qfn->next) { + dbgprintf("check queue file name '%s' vs '%s'\n", qfn->name, ourConf->globals.mainQ.pszMainMsgQFName ); + if(!ustrcmp(qfn->name, ourConf->globals.mainQ.pszMainMsgQFName)) { + snprintf((char*)qfrenamebuf, sizeof(qfrenamebuf), "%d-%s-%s", + ++qfn_renamenum, ourConf->globals.mainQ.pszMainMsgQFName, + (pszQueueName == NULL) ? "NONAME" : (char*)pszQueueName); + qfname = ustrdup(qfrenamebuf); + errmsg.LogError(0, NO_ERRCODE, "Error: queue file name '%s' already in use " + " - using '%s' instead", ourConf->globals.mainQ.pszMainMsgQFName, qfname); + break; + } + } + if(qfname == NULL) + qfname = ustrdup(ourConf->globals.mainQ.pszMainMsgQFName); + qfn = malloc(sizeof(struct queuefilenames_s)); + qfn->name = qfname; + qfn->next = queuefilenames; + queuefilenames = qfn; + } + + setQPROP(qqueueSetMaxFileSize, "$MainMsgQueueFileSize", ourConf->globals.mainQ.iMainMsgQueMaxFileSize); + setQPROP(qqueueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace", ourConf->globals.mainQ.iMainMsgQueMaxDiskSpace); + setQPROP(qqueueSetiDeqBatchSize, "$MainMsgQueueDequeueBatchSize", ourConf->globals.mainQ.iMainMsgQueDeqBatchSize); + setQPROPstr(qqueueSetFilePrefix, "$MainMsgQueueFileName", qfname); + setQPROP(qqueueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval", ourConf->globals.mainQ.iMainMsgQPersistUpdCnt); + setQPROP(qqueueSetbSyncQueueFiles, "$MainMsgQueueSyncQueueFiles", ourConf->globals.mainQ.bMainMsgQSyncQeueFiles); + setQPROP(qqueueSettoQShutdown, "$MainMsgQueueTimeoutShutdown", ourConf->globals.mainQ.iMainMsgQtoQShutdown ); + setQPROP(qqueueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion", ourConf->globals.mainQ.iMainMsgQtoActShutdown); + setQPROP(qqueueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown", ourConf->globals.mainQ.iMainMsgQtoWrkShutdown); + setQPROP(qqueueSettoEnq, "$MainMsgQueueTimeoutEnqueue", ourConf->globals.mainQ.iMainMsgQtoEnq); + setQPROP(qqueueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark", ourConf->globals.mainQ.iMainMsgQHighWtrMark); + setQPROP(qqueueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark", ourConf->globals.mainQ.iMainMsgQLowWtrMark); + setQPROP(qqueueSetiDiscardMrk, "$MainMsgQueueDiscardMark", ourConf->globals.mainQ.iMainMsgQDiscardMark); + setQPROP(qqueueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity", ourConf->globals.mainQ.iMainMsgQDiscardSeverity); + setQPROP(qqueueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", ourConf->globals.mainQ.iMainMsgQWrkMinMsgs); + setQPROP(qqueueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", ourConf->globals.mainQ.bMainMsgQSaveOnShutdown); + setQPROP(qqueueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown", ourConf->globals.mainQ.iMainMsgQDeqSlowdown); + setQPROP(qqueueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin", ourConf->globals.mainQ.iMainMsgQueueDeqtWinFromHr); + setQPROP(qqueueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd", ourConf->globals.mainQ.iMainMsgQueueDeqtWinToHr); + + # undef setQPROP + # undef setQPROPstr + } else { /* use new style config! */ + qqueueSetDefaultsRulesetQueue(*ppQueue); + qqueueApplyCnfParam(*ppQueue, lst); + } + + /* ... and finally start the queue! */ + CHKiRet_Hdlr(qqueueStart(*ppQueue)) { + /* no queue is fatal, we need to give up in that case... */ + errmsg.LogError(0, iRet, "could not start (ruleset) main message queue"); \ + } + RETiRet; +} + + +/* INIT -- Initialize syslogd + * Note that if iConfigVerify is set, only the config file is verified but nothing + * else happens. -- rgerhards, 2008-07-28 + */ +static rsRetVal +init(void) +{ + char bufStartUpMsg[512]; + struct sigaction sigAct; + DEFiRet; + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sighup_handler; + sigaction(SIGHUP, &sigAct, NULL); + + CHKiRet(rsconf.Activate(ourConf)); + DBGPRINTF(" started.\n"); + + /* we now generate the startup message. It now includes everything to + * identify this instance. -- rgerhards, 2005-08-17 + */ + if(ourConf->globals.bLogStatusMsgs) { + snprintf(bufStartUpMsg, sizeof(bufStartUpMsg)/sizeof(char), + " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ + "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"] start", + (int) glblGetOurPid()); + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)bufStartUpMsg, 0); + } + +finalize_it: + RETiRet; +} + + +/* + * The following function is resposible for handling a SIGHUP signal. Since + * we are now doing mallocs/free as part of init we had better not being + * doing this during a signal handler. Instead this function simply sets + * a flag variable which will tells the main loop to do "the right thing". + */ +void sighup_handler() +{ + struct sigaction sigAct; + + bHadHUP = 1; + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sighup_handler; + sigaction(SIGHUP, &sigAct, NULL); +} + +void sigttin_handler() +{ +} + +/* this function pulls all internal messages from the buffer + * and puts them into the processing engine. + * We can only do limited error handling, as this would not + * really help us. TODO: add error messages? + * rgerhards, 2007-08-03 + */ +static inline void processImInternal(void) +{ + msg_t *pMsg; + + while(iminternalRemoveMsg(&pMsg) == RS_RET_OK) { + submitMsgWithDfltRatelimiter(pMsg); + } +} + + +/* helper to doHUP(), this "HUPs" each action. The necessary locking + * is done inside the action class and nothing we need to take care of. + * rgerhards, 2008-10-22 + */ +DEFFUNC_llExecFunc(doHUPActions) +{ + BEGINfunc + actionCallHUPHdlr((action_t*) pData); + ENDfunc + return RS_RET_OK; /* we ignore errors, we can not do anything either way */ +} + + +/* This function processes a HUP after one has been detected. Note that this + * is *NOT* the sighup handler. The signal is recorded by the handler, that record + * detected inside the mainloop and then this function is called to do the + * real work. -- rgerhards, 2008-10-22 + * Note: there is a VERY slim chance of a data race when the hostname is reset. + * We prefer to take this risk rather than sync all accesses, because to the best + * of my analysis it can not really hurt (the actual property is reference-counted) + * but the sync would require some extra CPU for *each* message processed. + * rgerhards, 2012-04-11 + */ +static inline void +doHUP(void) +{ + char buf[512]; + + if(ourConf->globals.bLogStatusMsgs) { + snprintf(buf, sizeof(buf) / sizeof(char), + " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION + "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"] rsyslogd was HUPed", + (int) glblGetOurPid()); + errno = 0; + logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)buf, 0); + } + + queryLocalHostname(); /* re-read our name */ + ruleset.IterateAllActions(ourConf, doHUPActions, NULL); +} + + +/* This is the main processing loop. It is called after successful initialization. + * When it returns, the syslogd terminates. + * Its sole function is to provide some housekeeping things. The real work is done + * by the other threads spawned. + */ +static void +mainloop(void) +{ + struct timeval tvSelectTimeout; + + BEGINfunc + /* first check if we have any internal messages queued and spit them out. We used + * to do that on any loop iteration, but that is no longer necessry. The reason + * is that once we reach this point here, we always run on multiple threads and + * thus the main queue is properly initialized. -- rgerhards, 2008-06-09 + */ + processImInternal(); + + while(!bFinished){ + /* this is now just a wait - please note that we do use a near-"eternal" + * timeout of 1 day. This enables us to help safe the environment + * by not unnecessarily awaking rsyslog on a regular tick (just think + * powertop, for example). In that case, we primarily wait for a signal, + * but a once-a-day wakeup should be quite acceptable. -- rgerhards, 2008-06-09 + */ + tvSelectTimeout.tv_sec = 86400 /*1 day*/; + tvSelectTimeout.tv_usec = 0; + select(1, NULL, NULL, NULL, &tvSelectTimeout); + if(bFinished) + break; /* exit as quickly as possible */ + + if(bHadHUP) { + doHUP(); + bHadHUP = 0; + continue; + } + } + ENDfunc +} + +/* print version and compile-time setting information. + */ +static void printVersion(void) +{ + printf("rsyslogd %s, ", VERSION); + printf("compiled with:\n"); +#ifdef FEATURE_REGEXP + printf("\tFEATURE_REGEXP:\t\t\t\tYes\n"); +#else + printf("\tFEATURE_REGEXP:\t\t\t\tNo\n"); +#endif +#if defined(_LARGE_FILES) || (defined (_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS >= 64) + printf("\tFEATURE_LARGEFILE:\t\t\tYes\n"); +#else + printf("\tFEATURE_LARGEFILE:\t\t\tNo\n"); +#endif +#if defined(SYSLOG_INET) && defined(USE_GSSAPI) + printf("\tGSSAPI Kerberos 5 support:\t\tYes\n"); +#else + printf("\tGSSAPI Kerberos 5 support:\t\tNo\n"); +#endif +#ifndef NDEBUG + printf("\tFEATURE_DEBUG (debug build, slow code):\tYes\n"); +#else + printf("\tFEATURE_DEBUG (debug build, slow code):\tNo\n"); +#endif +#ifdef HAVE_ATOMIC_BUILTINS + printf("\t32bit Atomic operations supported:\tYes\n"); +#else + printf("\t32bit Atomic operations supported:\tNo\n"); +#endif +#ifdef HAVE_ATOMIC_BUILTINS_64BIT + printf("\t64bit Atomic operations supported:\tYes\n"); +#else + printf("\t64bit Atomic operations supported:\tNo\n"); +#endif +#ifdef RTINST + printf("\tRuntime Instrumentation (slow code):\tYes\n"); +#else + printf("\tRuntime Instrumentation (slow code):\tNo\n"); +#endif +#ifdef USE_LIBUUID + printf("\tuuid support:\t\t\t\tYes\n"); +#else + printf("\tuuid support:\t\t\t\tNo\n"); +#endif + printf("\nSee http://www.rsyslog.com for more information.\n"); +} + + +/* Method to initialize all global classes and use the objects that we need. + * rgerhards, 2008-01-04 + * rgerhards, 2008-04-16: the actual initialization is now carried out by the runtime + */ +static rsRetVal +InitGlobalClasses(void) +{ + DEFiRet; + char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */ + + /* Intialize the runtime system */ + pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */ + CHKiRet(rsrtInit(&pErrObj, &obj)); + CHKiRet(rsrtSetErrLogger(submitErrMsg)); /* set out error handler */ + + /* Now tell the system which classes we need ourselfs */ + pErrObj = "glbl"; + CHKiRet(objUse(glbl, CORE_COMPONENT)); + pErrObj = "errmsg"; + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + pErrObj = "module"; + CHKiRet(objUse(module, CORE_COMPONENT)); + pErrObj = "datetime"; + CHKiRet(objUse(datetime, CORE_COMPONENT)); + pErrObj = "ruleset"; + CHKiRet(objUse(ruleset, CORE_COMPONENT)); + pErrObj = "conf"; + CHKiRet(objUse(conf, CORE_COMPONENT)); + pErrObj = "prop"; + CHKiRet(objUse(prop, CORE_COMPONENT)); + pErrObj = "parser"; + CHKiRet(objUse(parser, CORE_COMPONENT)); + pErrObj = "rsconf"; + CHKiRet(objUse(rsconf, CORE_COMPONENT)); + + /* intialize some dummy classes that are not part of the runtime */ + pErrObj = "action"; + CHKiRet(actionClassInit()); + pErrObj = "template"; + CHKiRet(templateInit()); + + /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ + pErrObj = "net"; + CHKiRet(objUse(net, LM_NET_FILENAME)); + dnscacheInit(); + initRainerscript(); + ratelimitModInit(); + +finalize_it: + if(iRet != RS_RET_OK) { + /* we know we are inside the init sequence, so we can safely emit + * messages to stderr. -- rgerhards, 2008-04-02 + */ + fprintf(stderr, "Error during class init for object '%s' - failing...\n", pErrObj); + } + + RETiRet; +} + + +/* Method to exit all global classes. We do not do any error checking here, + * because that wouldn't help us at all. So better try to deinit blindly + * as much as succeeds (which usually means everything will). We just must + * be careful to do the de-init in the opposite order of the init, because + * of the dependencies. However, its not as important this time, because + * we have reference counting. + * rgerhards, 2008-03-10 + */ +static rsRetVal +GlobalClassExit(void) +{ + DEFiRet; + + /* first, release everything we used ourself */ + objRelease(net, LM_NET_FILENAME);/* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ + objRelease(prop, CORE_COMPONENT); + objRelease(conf, CORE_COMPONENT); + objRelease(ruleset, CORE_COMPONENT); + parserClassExit(); /* this is hack, currently core_modules do not get this automatically called */ + rsconfClassExit(); /* this is hack, currently core_modules do not get this automatically called */ + objRelease(datetime, CORE_COMPONENT); + + /* TODO: implement the rest of the deinit */ + /* dummy "classes */ + strExit(); + ratelimitModExit(); + +#if 0 + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + /* the following classes were intialized by objClassInit() */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); +#endif + dnscacheDeinit(); + rsrtExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + + RETiRet; +} + + +/* query our host and domain names - we need to do this early as we may emit + * rgerhards, 2012-04-11 + */ +static rsRetVal +queryLocalHostname(void) +{ + uchar *LocalHostName; + uchar *LocalDomain; + uchar *LocalFQDNName; + uchar *p; + struct hostent *hent; + DEFiRet; + + net.getLocalHostname(&LocalFQDNName); + CHKmalloc(LocalHostName = (uchar*) strdup((char*)LocalFQDNName)); + glbl.SetLocalFQDNName(LocalFQDNName); /* set the FQDN before we modify it */ + if((p = (uchar*)strchr((char*)LocalHostName, '.'))) { + *p++ = '\0'; + LocalDomain = p; + } else { + LocalDomain = (uchar*)""; + + /* It's not clearly defined whether gethostname() + * should return the simple hostname or the fqdn. A + * good piece of software should be aware of both and + * we want to distribute good software. Joey + * + * Good software also always checks its return values... + * If syslogd starts up before DNS is up & /etc/hosts + * doesn't have LocalHostName listed, gethostbyname will + * return NULL. + */ + /* TODO: gethostbyname() is not thread-safe, but replacing it is + * not urgent as we do not run on multiple threads here. rgerhards, 2007-09-25 + */ + hent = gethostbyname((char*)LocalHostName); + if(hent) { + int i = 0; + + if(hent->h_aliases) { + size_t hnlen; + + hnlen = strlen((char *) LocalHostName); + + for (i = 0; hent->h_aliases[i]; i++) { + if (!strncmp(hent->h_aliases[i], (char *) LocalHostName, hnlen) + && hent->h_aliases[i][hnlen] == '.') { + /* found a matching hostname */ + break; + } + } + } + + free(LocalHostName); + if(hent->h_aliases && hent->h_aliases[i]) { + CHKmalloc(LocalHostName = (uchar*)strdup(hent->h_aliases[i])); + } else { + CHKmalloc(LocalHostName = (uchar*)strdup(hent->h_name)); + } + + if((p = (uchar*)strchr((char*)LocalHostName, '.'))) + { + *p++ = '\0'; + LocalDomain = p; + } + } + } + + /* LocalDomain is "" or part of LocalHostName, allocate a new string */ + CHKmalloc(LocalDomain = (uchar*)strdup((char*)LocalDomain)); + + /* Convert to lower case to recognize the correct domain laterly */ + for(p = LocalDomain ; *p ; p++) + *p = (char)tolower((int)*p); + + /* we now have our hostname and can set it inside the global vars. + * TODO: think if all of this would better be a runtime function + * rgerhards, 2008-04-17 + */ + glbl.SetLocalHostName(LocalHostName); + glbl.SetLocalDomain(LocalDomain); + + if ( strlen((char*)LocalDomain) ) { + CHKmalloc(LocalFQDNName = (uchar*)malloc(strlen((char*)LocalDomain)+strlen((char*)LocalHostName)+2));/* one for dot, one for NUL! */ + if ( sprintf((char*)LocalFQDNName,"%s.%s",(char*)LocalHostName,(char*)LocalDomain) ) + glbl.SetLocalFQDNName(LocalFQDNName); + } + + glbl.GenerateLocalHostNameProperty(); /* must be redone after conf processing, FQDN setting may have changed */ +finalize_it: + RETiRet; +} + + +/* some support for command line option parsing. Any non-trivial options must be + * buffered until the complete command line has been parsed. This is necessary to + * prevent dependencies between the options. That, in turn, means we need to have + * something that is capable of buffering options and there values. The follwing + * functions handle that. + * rgerhards, 2008-04-04 + */ +typedef struct bufOpt { + struct bufOpt *pNext; + char optchar; + char *arg; +} bufOpt_t; +static bufOpt_t *bufOptRoot = NULL; +static bufOpt_t *bufOptLast = NULL; + +/* add option buffer */ +static rsRetVal +bufOptAdd(char opt, char *arg) +{ + DEFiRet; + bufOpt_t *pBuf; + + if((pBuf = MALLOC(sizeof(bufOpt_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pBuf->optchar = opt; + pBuf->arg = arg; + pBuf->pNext = NULL; + + if(bufOptLast == NULL) { + bufOptRoot = pBuf; /* then there is also no root! */ + } else { + bufOptLast->pNext = pBuf; + } + bufOptLast = pBuf; + +finalize_it: + RETiRet; +} + + + +/* remove option buffer from top of list, return values and destruct buffer itself. + * returns RS_RET_END_OF_LINKEDLIST when no more options are present. + * (we use int *opt instead of char *opt to keep consistent with getopt()) + */ +static rsRetVal +bufOptRemove(int *opt, char **arg) +{ + DEFiRet; + bufOpt_t *pBuf; + + if(bufOptRoot == NULL) + ABORT_FINALIZE(RS_RET_END_OF_LINKEDLIST); + pBuf = bufOptRoot; + + *opt = pBuf->optchar; + *arg = pBuf->arg; + + bufOptRoot = pBuf->pNext; + free(pBuf); + +finalize_it: + RETiRet; +} + + +/* global initialization, to be done only once and before the mainloop is started. + * rgerhards, 2008-07-28 (extracted from realMain()) + */ +static rsRetVal +doGlblProcessInit(void) +{ + struct sigaction sigAct; + int num_fds; + int i; + DEFiRet; + + thrdInit(); + + if(doFork) { + DBGPRINTF("Checking pidfile '%s'.\n", PidFile); + if (!check_pid(PidFile)) + { + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = doexit; + sigaction(SIGTERM, &sigAct, NULL); + + /* stop writing debug messages to stdout (if debugging is on) */ + stddbg = -1; + + dbgprintf("ready for forking\n"); + if (fork()) { + /* Parent process + */ + dbgprintf("parent process going to sleep for 60 secs\n"); + sleep(60); + /* Not reached unless something major went wrong. 1 + * minute should be a fair amount of time to wait. + * The parent should not exit before rsyslogd is + * properly initilized (at least almost) or the init + * system may get a wrong impression of our readyness. + * Note that we exit before being completely initialized, + * but at this point it is very, very unlikely that something + * bad can happen. We do this here, because otherwise we would + * need to have much more code to handle priv drop (which we + * don't consider worth for the init system, especially as it + * is going away on the majority of distros). + */ + exit(1); /* "good" exit - after forking, not diasabling anything */ + } + + num_fds = getdtablesize(); + close(0); + /* we keep stdout and stderr open in case we have to emit something */ + i = 3; + dbgprintf("in child, finalizing initialization\n"); + + /* if (sd_booted()) */ { + const char *e; + char buf[24] = { '\0' }; + char *p = NULL; + unsigned long l; + int sd_fds; + + /* fork & systemd socket activation: + * fetch listen pid and update to ours, + * when it is set to pid of our parent. + */ + if ( (e = getenv("LISTEN_PID"))) { + errno = 0; + l = strtoul(e, &p, 10); + if (errno == 0 && l > 0 && (!p || !*p)) { + if (getppid() == (pid_t)l) { + snprintf(buf, sizeof(buf), "%d", + getpid()); + setenv("LISTEN_PID", buf, 1); + } + } + } + + /* + * close only all further fds, except + * of the fds provided by systemd. + */ + sd_fds = sd_listen_fds(0); + if (sd_fds > 0) + i = SD_LISTEN_FDS_START + sd_fds; + } + for ( ; i < num_fds; i++) + if(i != dbgGetDbglogFd()) + close(i); + + untty(); + } else { + fputs(" Already running. If you want to run multiple instances, you need " + "to specify different pid files (use -i option)\n", stderr); + exit(1); /* "good" exit, done if syslogd is already running */ + } + } + + /* tuck my process id away */ + DBGPRINTF("Writing pidfile '%s'.\n", PidFile); + if (!check_pid(PidFile)) + { + if (!write_pid(PidFile)) + { + fputs("Can't write pid.\n", stderr); + exit(1); /* exit during startup - questionable */ + } + } + else + { + fputs("Pidfile (and pid) already exist.\n", stderr); + exit(1); /* exit during startup - questionable */ + } + glblSetOurPid(getpid()); + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + + sigAct.sa_handler = sigsegvHdlr; + sigaction(SIGSEGV, &sigAct, NULL); + sigAct.sa_handler = sigsegvHdlr; + sigaction(SIGABRT, &sigAct, NULL); + sigAct.sa_handler = doDie; + sigaction(SIGTERM, &sigAct, NULL); + sigAct.sa_handler = Debug ? doDie : SIG_IGN; + sigaction(SIGINT, &sigAct, NULL); + sigaction(SIGQUIT, &sigAct, NULL); + sigAct.sa_handler = reapchild; + sigaction(SIGCHLD, &sigAct, NULL); + sigAct.sa_handler = Debug ? debug_switch : SIG_IGN; + sigaction(SIGUSR1, &sigAct, NULL); + sigAct.sa_handler = sigttin_handler; + sigaction(SIGTTIN, &sigAct, NULL); /* (ab)used to interrupt input threads */ + sigAct.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sigAct, NULL); + sigaction(SIGXFSZ, &sigAct, NULL); /* do not abort if 2gig file limit is hit */ + + RETiRet; +} + + +/* This is the main entry point into rsyslogd. Over time, we should try to + * modularize it a bit more... + */ +int realMain(int argc, char **argv) +{ + rsRetVal localRet; + int ch; + extern int optind; + extern char *optarg; + int bEOptionWasGiven = 0; + int iHelperUOpt; + int bChDirRoot = 1; /* change the current working directory to "/"? */ + char *arg; /* for command line option processing */ + char cwdbuf[128]; /* buffer to obtain/display current working directory */ + DEFiRet; + + /* first, parse the command line options. We do not carry out any actual work, just + * see what we should do. This relieves us from certain anomalies and we can process + * the parameters down below in the correct order. For example, we must know the + * value of -M before we can do the init, but at the same time we need to have + * the base classes init before we can process most of the options. Now, with the + * split of functionality, this is no longer a problem. Thanks to varmofekoj for + * suggesting this algo. + * Note: where we just need to set some flags and can do so without knowledge + * of other options, we do this during the inital option processing. + * rgerhards, 2008-04-04 + */ + while((ch = getopt(argc, argv, "46a:Ac:dDef:g:hi:l:m:M:nN:op:qQr::s:S:t:T:u:vwx")) != EOF) { + switch((char)ch) { + case '4': + case '6': + case 'A': + case 'a': + case 'f': /* configuration file */ + case 'h': + case 'i': /* pid file name */ + case 'l': + case 'm': /* mark interval */ + case 'n': /* don't fork */ + case 'N': /* enable config verify mode */ + case 'o': + case 'p': + case 'q': /* add hostname if DNS resolving has failed */ + case 'Q': /* dont resolve hostnames in ACL to IPs */ + case 's': + case 'S': /* Source IP for local client to be used on multihomed host */ + case 'T': /* chroot on startup (primarily for testing) */ + case 'u': /* misc user settings */ + case 'w': /* disable disallowed host warnings */ + case 'x': /* disable dns for remote messages */ + case 'g': /* enable tcp gssapi logging */ + case 'r': /* accept remote messages */ + case 't': /* enable tcp logging */ + CHKiRet(bufOptAdd(ch, optarg)); + break; + case 'c': /* compatibility mode */ + fprintf(stderr, "rsyslogd: error: option -c is no longer supported - ignored\n"); + break; + case 'd': /* debug - must be handled now, so that debug is active during init! */ + debugging_on = 1; + Debug = 1; + yydebug = 1; + break; + case 'D': /* BISON debug */ + yydebug = 1; + break; + case 'e': /* log every message (no repeat message supression) */ + bEOptionWasGiven = 1; + break; + case 'M': /* default module load path -- this MUST be carried out immediately! */ + glblModPath = (uchar*) optarg; + break; + case 'v': /* MUST be carried out immediately! */ + printVersion(); + exit(0); /* exit for -v option - so this is a "good one" */ + case '?': + default: + usage(); + } + } + + if(argc - optind) + usage(); + + DBGPRINTF("rsyslogd %s startup, module path '%s', cwd:%s\n", + VERSION, glblModPath == NULL ? "" : (char*)glblModPath, + getcwd(cwdbuf, sizeof(cwdbuf))); + + /* we are done with the initial option parsing and processing. Now we init the system. */ + + ppid = getpid(); + + CHKiRet_Hdlr(InitGlobalClasses()) { + fprintf(stderr, "rsyslogd initializiation failed - global classes could not be initialized.\n" + "Did you do a \"make install\"?\n" + "Suggested action: run rsyslogd with -d -n options to see what exactly " + "fails.\n"); + FINALIZE; + } + + /* doing some core initializations */ + + /* we need to create the inputName property (only once during our lifetime) */ + CHKiRet(prop.Construct(&pInternalInputName)); + CHKiRet(prop.SetString(pInternalInputName, UCHAR_CONSTANT("rsyslogd"), sizeof("rsyslogd") - 1)); + CHKiRet(prop.ConstructFinalize(pInternalInputName)); + + /* get our host and domain names - we need to do this early as we may emit + * error log messages, which need the correct hostname. -- rgerhards, 2008-04-04 + */ + queryLocalHostname(); + + /* initialize the objects */ + if((iRet = modInitIminternal()) != RS_RET_OK) { + fprintf(stderr, "fatal error: could not initialize errbuf object (error code %d).\n", + iRet); + exit(1); /* "good" exit, leaving at init for fatal error */ + } + + + /* END core initializations - we now come back to carrying out command line options*/ + + while((iRet = bufOptRemove(&ch, &arg)) == RS_RET_OK) { + DBGPRINTF("deque option %c, optarg '%s'\n", ch, (arg == NULL) ? "" : arg); + switch((char)ch) { + case '4': + glbl.SetDefPFFamily(PF_INET); + break; + case '6': + glbl.SetDefPFFamily(PF_INET6); + break; + case 'A': + send_to_all++; + break; + case 'a': + fprintf(stderr, "rsyslogd: error -a is no longer supported, use module imuxsock instead"); + break; + case 'S': /* Source IP for local client to be used on multihomed host */ + if(glbl.GetSourceIPofLocalClient() != NULL) { + fprintf (stderr, "rsyslogd: Only one -S argument allowed, the first one is taken.\n"); + } else { + glbl.SetSourceIPofLocalClient((uchar*)arg); + } + break; + case 'f': /* configuration file */ + ConfFile = (uchar*) arg; + break; + case 'g': /* enable tcp gssapi logging */ + fprintf(stderr, "rsyslogd: -g option no longer supported - ignored\n"); + case 'h': + fprintf(stderr, "rsyslogd: error -h is no longer supported - ignored"); + break; + case 'i': /* pid file name */ + PidFile = arg; + break; + case 'l': + if(glbl.GetLocalHosts() != NULL) { + fprintf (stderr, "rsyslogd: Only one -l argument allowed, the first one is taken.\n"); + } else { + glbl.SetLocalHosts(crunch_list(arg)); + } + break; + case 'm': /* mark interval */ + fprintf(stderr, "rsyslogd: error -m is no longer supported - use immark instead"); + break; + case 'n': /* don't fork */ + doFork = 0; + break; + case 'N': /* enable config verify mode */ + iConfigVerify = atoi(arg); + break; + case 'o': + fprintf(stderr, "error -o is no longer supported, use module imuxsock instead"); + break; + case 'p': + fprintf(stderr, "error -p is no longer supported, use module imuxsock instead"); + break; + case 'q': /* add hostname if DNS resolving has failed */ + *(net.pACLAddHostnameOnFail) = 1; + break; + case 'Q': /* dont resolve hostnames in ACL to IPs */ + *(net.pACLDontResolve) = 1; + break; + case 'r': /* accept remote messages */ + fprintf(stderr, "rsyslogd: error option -r is no longer supported - ignored"); + break; + case 's': + if(glbl.GetStripDomains() != NULL) { + fprintf (stderr, "rsyslogd: Only one -s argument allowed, the first one is taken.\n"); + } else { + glbl.SetStripDomains(crunch_list(arg)); + } + break; + case 't': /* enable tcp logging */ + fprintf(stderr, "rsyslogd: error option -t is no longer supported - ignored"); + break; + case 'T':/* chroot() immediately at program startup, but only for testing, NOT security yet */ + if(chroot(arg) != 0) { + perror("chroot"); + exit(1); + } + break; + case 'u': /* misc user settings */ + iHelperUOpt = atoi(arg); + if(iHelperUOpt & 0x01) + glbl.SetParseHOSTNAMEandTAG(0); + if(iHelperUOpt & 0x02) + bChDirRoot = 0; + break; + case 'w': /* disable disallowed host warnigs */ + glbl.SetOption_DisallowWarning(0); + break; + case 'x': /* disable dns for remote messages */ + glbl.SetDisableDNS(1); + break; + case '?': + default: + usage(); + } + } + + if(iRet != RS_RET_END_OF_LINKEDLIST) + FINALIZE; + + if(iConfigVerify) { + fprintf(stderr, "rsyslogd: version %s, config validation run (level %d), master config %s\n", + VERSION, iConfigVerify, ConfFile); + } + + localRet = rsconf.Load(&ourConf, ConfFile); + queryLocalHostname(); /* need to re-query to pick up a changed hostname due to config */ + + if(localRet == RS_RET_NONFATAL_CONFIG_ERR) { + if(loadConf->globals.bAbortOnUncleanConfig) { + fprintf(stderr, "rsyslogd: $AbortOnUncleanConfig is set, and config is not clean.\n" + "Check error log for details, fix errors and restart. As a last\n" + "resort, you may want to remove $AbortOnUncleanConfig to permit a\n" + "startup with a dirty config.\n"); + exit(2); + } + if(iConfigVerify) { + /* a bit dirty, but useful... */ + exit(1); + } + localRet = RS_RET_OK; + } + CHKiRet(localRet); + + CHKiRet(ratelimitNew(&dflt_ratelimiter, "rsyslogd", "dflt")); + /* TODO: add linux-type limiting capability */ + CHKiRet(ratelimitNew(&internalMsg_ratelimiter, "rsyslogd", "internal_messages")); + ratelimitSetLinuxLike(internalMsg_ratelimiter, 5, 500); + /* TODO: make internalMsg ratelimit settings configurable */ + + if(bChDirRoot) { + if(chdir("/") != 0) + fprintf(stderr, "Can not do 'cd /' - still trying to run\n"); + } + + /* process compatibility mode settings */ + if(bEOptionWasGiven) { + errmsg.LogError(0, NO_ERRCODE, "WARNING: \"message repeated n times\" feature MUST be turned on in " + "rsyslog.conf - CURRENTLY EVERY MESSAGE WILL BE LOGGED. Visit " + "http://www.rsyslog.com/rptdmsgreduction to learn " + "more and cast your vote if you want us to keep this feature."); + } + + if(!iConfigVerify) + CHKiRet(doGlblProcessInit()); + + /* Send a signal to the parent so it can terminate. */ + if(glblGetOurPid() != ppid) + kill(ppid, SIGTERM); + + CHKiRet(init()); + + if(Debug && debugging_on) { + dbgprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); + } + + /* END OF INTIALIZATION */ + DBGPRINTF("initialization completed, transitioning to regular run mode\n"); + + /* close stderr and stdout if they are kept open during a fork. Note that this + * may introduce subtle security issues: if we are in a jail, one may break out of + * it via these descriptors. But if I close them earlier, error messages will (once + * again) not be emitted to the user that starts the daemon. As root jail support + * is still in its infancy (and not really done), we currently accept this issue. + * rgerhards, 2009-06-29 + */ + if(doFork) { + close(1); + close(2); + ourConf->globals.bErrMsgToStderr = 0; + } + + sd_notify(0, "READY=1"); + + mainloop(); + + /* do any de-init's that need to be done AFTER this comment */ + + die(bFinished); + + thrdExit(); + +finalize_it: + if(iRet == RS_RET_VALIDATION_RUN) { + fprintf(stderr, "rsyslogd: End of config validation run. Bye.\n"); + } else if(iRet != RS_RET_OK) { + fprintf(stderr, "rsyslogd: run failed with error %d (see rsyslog.h " + "or try http://www.rsyslog.com/e/%d to learn what that number means)\n", iRet, iRet*-1); + exit(1); + } + + ENDfunc + return 0; +} + + +/* This is the main entry point into rsyslogd. This must be a function in its own + * right in order to intialize the debug system in a portable way (otherwise we would + * need to have a statement before variable definitions. + * rgerhards, 20080-01-28 + */ +int main(int argc, char **argv) +{ + dbgClassInit(); + return realMain(argc, argv); +} +/* vim:set ai: + */ diff --git a/tools/syslogd.h b/tools/syslogd.h new file mode 100644 index 00000000..88bbd5f3 --- /dev/null +++ b/tools/syslogd.h @@ -0,0 +1,38 @@ +/* common header for syslogd + * Copyright 2007-2012 Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef SYSLOGD_H_INCLUDED +#define SYSLOGD_H_INCLUDED 1 + +#include "syslogd-types.h" +#include "objomsr.h" +#include "modules.h" +#include "template.h" +#include "action.h" +#include "linkedlist.h" + +/* the following prototypes should go away once we have an input + * module interface -- rgerhards, 2007-12-12 + */ +extern int NoHops; +extern int send_to_all; +extern int Debug; +#include "dirty.h" + +#endif /* #ifndef SYSLOGD_H_INCLUDED */ diff --git a/tools/zpipe.c b/tools/zpipe.c new file mode 100644 index 00000000..38069425 --- /dev/null +++ b/tools/zpipe.c @@ -0,0 +1,262 @@ +/* zpipe.c: example of proper use of zlib's inflate() and deflate() + Not copyrighted -- provided to the public domain + Version 1.5 11 December 2005 Mark Adler + Version 2.0 03 June 2009 Rainer Gerhards */ + +/* RSYSLOG NOTE: + * This file is primarily been used as a testing aid for rsyslog. We do NOT + * properly maintain it and it has been brought to our attention that it may + * have some security issues. However, we prefer not to remove the file as it + * may turn out to be useful for further testing. All users are advised NOT + * to base any development on this version here, but rather look for the + * original zpipe.c by the authors mentioned above. + * + * This file is beeing distributed as part of rsyslog, but is just an + * add-on. Most importantly, rsyslog's copyright does not apply but + * rather the (non-) copyright stated above. + */ + +/* Version history: + 1.0 30 Oct 2004 First version + 1.1 8 Nov 2004 Add void casting for unused return values + Use switch statement for inflate() return values + 1.2 9 Nov 2004 Add assertions to document zlib guarantees + 1.3 6 Apr 2005 Remove incorrect assertion in inf() + 1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions + Avoid some compiler warnings for input and output buffers + 2.0 03 Jun 2009 Add hack to support multiple deflate records inside a single + file on inflate. This is needed in order to support reading + files created by rsyslog's zip output writer. + */ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include "zlib.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include <fcntl.h> +# include <io.h> +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#define CHUNK 16384 + +/* Compress from file source to file dest until EOF on source. + def() returns Z_OK on success, Z_MEM_ERROR if memory could not be + allocated for processing, Z_STREAM_ERROR if an invalid compression + level is supplied, Z_VERSION_ERROR if the version of zlib.h and the + version of the library linked do not match, or Z_ERRNO if there is + an error reading or writing the files. */ +int def(FILE *source, FILE *dest, int level) +{ + int ret, flush; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = deflateInit(&strm, level); + if (ret != Z_OK) + return ret; + + /* compress until end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + (void)deflateEnd(&strm); + return Z_ERRNO; + } + flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; + strm.next_in = in; + + /* run deflate() on input until output buffer not full, finish + compression if all of source has been read in */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); /* no bad return value */ + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + (void)deflateEnd(&strm); + return Z_ERRNO; + } + } while (strm.avail_out == 0); + assert(strm.avail_in == 0); /* all input will be used */ + + /* done when last data in file processed */ + } while (flush != Z_FINISH); + assert(ret == Z_STREAM_END); /* stream will be complete */ + + /* clean up and return */ + (void)deflateEnd(&strm); + return Z_OK; +} + + +/* initialize stream for deflating (we need this in case of + * multiple records. + * rgerhards, 2009-06-03 + */ +int doInflateInit(z_stream *strm) +{ + int ret; + + /* allocate inflate state */ + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = Z_NULL; + ret = inflateInit(strm); + return ret; +} + + +/* Decompress from file source to file dest until stream ends or EOF. + inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be + allocated for processing, Z_DATA_ERROR if the deflate data is + invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and + the version of the library linked do not match, or Z_ERRNO if there + is an error reading or writing the files. */ +int inf(FILE *source, FILE *dest) +{ + int ret; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + int len; + unsigned char *next_in_save; + unsigned char out[CHUNK]; + + ret = doInflateInit(&strm); + if (ret != Z_OK) + return ret; + + /* decompress until deflate stream ends or end of file */ + do { + len = fread(in, 1, CHUNK, source); + if (ferror(source)) { + (void)inflateEnd(&strm); + return Z_ERRNO; + } + if (len == 0) { + break; + } + strm.avail_in = len; + strm.next_in = in; + + /* run inflate() on input until output buffer not full */ + strm.avail_out = CHUNK; + strm.next_out = out; + do { + /* fprintf(stderr, "---inner LOOP---, avail_in %d, avail_out %d Byte 0: %x, 1: %x\n", strm.avail_in, strm.avail_out, *strm.next_in, *(strm.next_in+1));*/ + do { + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + (void)inflateEnd(&strm); + return Z_ERRNO; + } + } while (strm.avail_out == 0); + /* handle the case that more than one deflate record is contained + * in a single file. -- rgerhards, 2009-06-03 + */ + if(ret == Z_STREAM_END) { + len -= strm.total_in; + if(len > 0) { + next_in_save = strm.next_in; + (void)inflateEnd(&strm); + ret = doInflateInit(&strm); + if (ret != Z_OK) + return ret; + strm.avail_in = len; + strm.next_in = next_in_save; + strm.avail_out = CHUNK; + strm.next_out = out; + ret = Z_OK; /* continue outer loop */ + } + } + } while (strm.avail_in > 0); + + /* done when inflate() says it's done */ + } while (ret != Z_STREAM_END); + + /* clean up and return */ + (void)inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; +} + +/* report a zlib or i/o error */ +void zerr(int ret) +{ + fputs("zpipe: ", stdout); + switch (ret) { + case Z_ERRNO: + if (ferror(stdin)) + fputs("error reading stdin\n", stdout); + if (ferror(stdout)) + fputs("error writing stdout\n", stdout); + break; + case Z_STREAM_ERROR: + fputs("invalid compression level\n", stdout); + break; + case Z_DATA_ERROR: + fputs("invalid or incomplete deflate data\n", stdout); + break; + case Z_MEM_ERROR: + fputs("out of memory\n", stdout); + break; + case Z_VERSION_ERROR: + fputs("zlib version mismatch!\n", stdout); + } +} + +/* compress or decompress from stdin to stdout */ +int main(int argc, char **argv) +{ + int ret; + + /* avoid end-of-line conversions */ + SET_BINARY_MODE(stdin); + SET_BINARY_MODE(stdout); + + /* do compression if no arguments */ + if (argc == 1) { + ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION); + if (ret != Z_OK) + zerr(ret); + return ret; + } + + /* do decompression if -d specified */ + else if (argc == 2 && strcmp(argv[1], "-d") == 0) { + ret = inf(stdin, stdout); + if (ret != Z_OK) + zerr(ret); + return ret; + } + + /* otherwise, report usage */ + else { + fputs("zpipe usage: zpipe [-d] < source > dest\n", stdout); + return 1; + } +} |