diff options
228 files changed, 40863 insertions, 13022 deletions
@@ -1,6 +1,8 @@ .tar.gz .deps .libs +*.lo +*.la Makefile Makefile.in autom4te.cache @@ -2,6 +2,10 @@ Rainer Gerhards <rgerhards@adiscon.com>, Adiscon GmbH Michael Meckelein <mmeckelein@hq.adiscon.com>, Adiscon GmbH Contributors +Michael Biebl + - helped continously with autotools + - provided numerous advise on how to do things under Linux + - provided excellent advise in many, many cases Andres Riancho (andres-dot-riancho-at-gmail-dot-com) (alias APR in code files) - supplied regexp functionality for the property replacer - a great feature. @@ -10,3 +14,6 @@ Bjoern Kalkbrenner - provided code for the "execute shell script" action Peter Vrabec - provided IPv6-enabling code +varmojfekoj + - helped with a variety of things and, most importantly, contributed + the gssapi functionality @@ -1,285 +1,626 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 675 Mass Ave, Cambridge, MA 02139, USA + 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 + Preamble - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to + 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 -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. +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 make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. + 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 give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. +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. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to + + 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. - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or + 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 -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will +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 a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - Appendix: How to Apply These Terms to Your New Programs + 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 @@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least +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) 19yy <name of author> + Copyright (C) <year> <name of author> - This program is free software; you can redistribute it and/or modify + 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 + 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, @@ -304,36 +645,31 @@ the "copyright" line and a pointer to where the full notice is found. 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., 675 Mass Ave, Cambridge, MA 02139, USA. + 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 is interactive, make it output a short notice like this -when it starts in an interactive mode: + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) 19yy name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + <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, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: +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". - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. + 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/>. - <signature of Ty Coon>, 1 April 1989 - Ty Coon, President of Vice + 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>. -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. 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. + @@ -1,3 +1,683 @@ +- 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.7 V2-STABLE (rgerhards), 2008-??-?? - bugfix: "$CreateDirs off" also disabled file creation @@ -870,6 +1550,1892 @@ Version 0.9.1 (RGer) 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... --------------------------------------------------------------------------- diff --git a/Makefile.am b/Makefile.am index 6c1b24bd..0e75710c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,21 +1,29 @@ -sbin_PROGRAMS = rklogd rfc3195d rsyslogd - -rklogd_SOURCES = \ - klogd.c \ - klogd.h \ - syslog.c \ - pidfile.c \ - pidfile.h \ - ksym.c \ - ksyms.h \ - ksym_mod.c \ - module.h - -rfc3195d_SOURCES = rfc3195d.c rsyslog.h +#sbin_PROGRAMS = rfc3195d rsyslogd +sbin_PROGRAMS = +man_MANS = +if ENABLE_RSYSLOGD +sbin_PROGRAMS += rsyslogd rsyslogd_SOURCES = \ + datetime.c \ + datetime.h \ + errmsg.c \ + errmsg.h \ syslogd.c \ syslogd.h \ + sysvar.c \ + sysvar.h \ + vm.c \ + vm.h \ + vmstk.c \ + vmstk.h \ + vmprg.c \ + vmprg.h \ + vmop.c \ + vmop.h \ + debug.c \ + debug.h \ + glbl.h \ pidfile.c \ pidfile.h \ template.c \ @@ -30,13 +38,33 @@ rsyslogd_SOURCES = \ template.h \ outchannel.h \ liblogging-stub.h \ + threads.c \ + threads.h \ + stream.c \ + stream.h \ + var.c \ + var.h \ + wtp.c \ + wtp.h \ + wti.c \ + wti.h \ + queue.c \ + queue.h \ sync.c \ sync.h \ - net.c \ - net.h \ + obj.c \ + obj.h \ + obj-types.h \ msg.c \ msg.h \ expr.c \ + expr.h \ + ctok.c \ + ctok.h \ + ctok_token.c \ + ctok_token.h \ + conf.c \ + conf.h \ omshell.c \ omshell.h \ omusrmsg.c \ @@ -62,14 +90,82 @@ rsyslogd_SOURCES = \ iminternal.h \ action.c \ action.h \ - gss-misc.c \ - gss-misc.h + atomic.h -rsyslogd_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" $(pthreads_cflags) -rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(gss_libs) $(dl_libs) $(rt_libs) +rsyslogd_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" $(pthreads_cflags) +rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) rsyslogd_LDFLAGS = -export-dynamic -man_MANS = rfc3195d.8 rklogd.8 rsyslogd.8 rsyslog.conf.5 +man_MANS += rsyslogd.8 rsyslog.conf.5 + +endif # if ENABLE_RSYSLOGD + +# now come the library plugins +pkglib_LTLIBRARIES = + +if ENABLE_RFC3195 +# this does so far not work - a manual build is needed +sbin_PROGRAMS += rfc3195d +rfc3195d_SOURCES = rfc3195d.c rsyslog.h +man_MANS += rfc3195d.8 +endif + + +if ENABLE_INET +pkglib_LTLIBRARIES += lmnet.la lmtcpsrv.la lmtcpclt.la +# +# network support +# +lmnet_la_SOURCES = net.c net.h +lmnet_la_CPPFLAGS = $(pthreads_cflags) +lmnet_la_LDFLAGS = -module -avoid-version +lmnet_la_LIBADD = +# +# +# TCP (stream) server support +# +lmtcpsrv_la_SOURCES = \ + tcps_sess.c \ + tcps_sess.h \ + tcpsrv.c \ + tcpsrv.h +lmtcpsrv_la_CPPFLAGS = $(pthreads_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) +lmtcpclt_la_LDFLAGS = -module -avoid-version +lmtcpclt_la_LIBADD = + +endif # if ENABLE_INET + +# +# regular expression support +# +if ENABLE_REGEXP +pkglib_LTLIBRARIES += lmregexp.la +lmregexp_la_SOURCES = regexp.c regexp.h +lmregexp_la_CPPFLAGS = $(pthreads_cflags) +lmregexp_la_LDFLAGS = -module -avoid-version +lmregexp_la_LIBADD = +endif + +# +# gssapi support +# +if ENABLE_GSSAPI +pkglib_LTLIBRARIES += lmgssutil.la +lmgssutil_la_SOURCES = gss-misc.c gss-misc.h +lmgssutil_la_CPPFLAGS = $(pthreads_cflags) +lmgssutil_la_LDFLAGS = -module -avoid-version +lmgssutil_la_LIBADD = $(gss_libs) +endif EXTRA_DIST = \ redhat/rsyslog.conf \ @@ -79,19 +175,50 @@ EXTRA_DIST = \ freebsd/rsyslogd \ slackware/rc.rsyslogd \ contrib/README \ + rsyslog.conf \ + COPYING.LESSER \ $(man_MANS) -SUBDIRS = doc +SUBDIRS = . doc +SUBDIRS += plugins/immark plugins/imuxsock plugins/imtcp plugins/imudp plugins/omtesting + +if ENABLE_IMKLOG +SUBDIRS += plugins/imklog +endif if ENABLE_GSSAPI -SUBDIRS += plugins/omgssapi +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_IMTEMPLATE +SUBDIRS += plugins/imtemplate +endif + +if ENABLE_IMFILE +SUBDIRS += plugins/imfile +endif + +if ENABLE_MAIL +SUBDIRS += plugins/ommail +endif @@ -1,4 +1,4 @@ -This file has been superseeded by the fils in the doc folder. +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 @@ -6,19 +6,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -28,39 +29,132 @@ #include <assert.h> #include <stdarg.h> #include <stdlib.h> +#include <string.h> +#include <strings.h> #include <time.h> +#include <errno.h> #include "syslogd.h" #include "template.h" #include "action.h" #include "modules.h" #include "sync.h" +#include "cfsysline.h" +#include "srUtils.h" +#include "errmsg.h" +#include "datetime.h" +/* forward definitions */ +rsRetVal actionCallDoAction(action_t *pAction, msg_t *pMsg); /* 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) + static int glbliActionResumeInterval = 30; +int glbliActionResumeRetryCount = 0; /* how often should suspended actions be retried? */ + +/* main message queue and its configuration parameters */ +static queueType_t ActionQueType = QUEUETYPE_DIRECT; /* type of the main message queue above */ +static int iActionQueueSize = 1000; /* size of the main message queue above */ +static int iActionQHighWtrMark = 800; /* high water mark for disk-assisted queues */ +static int iActionQLowWtrMark = 200; /* low water mark for disk-assisted queues */ +static int iActionQDiscardMark = 9800; /* begin to discard messages */ +static int iActionQDiscardSeverity = 8; /* by default, discard nothing to prevent unintentional loss */ +static int iActionQueueNumWorkers = 1; /* number of worker threads for the mm queue above */ +static uchar *pszActionQFName = NULL; /* prefix for the main message queue file */ +static int64 iActionQueMaxFileSize = 1024*1024; +static int iActionQPersistUpdCnt = 0; /* persist queue info every n updates */ +static int iActionQtoQShutdown = 0; /* queue shutdown */ +static int iActionQtoActShutdown = 1000; /* action shutdown (in phase 2) */ +static int iActionQtoEnq = 2000; /* timeout for queue enque */ +static int iActionQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */ +static int iActionQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */ +static int bActionQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ +static int64 iActionQueMaxDiskSpace = 0; /* max disk space allocated 0 ==> unlimited */ +static int iActionQueueDeqSlowdown = 0; /* dequeue slowdown (simple rate limiting) */ +static int iActionQueueDeqtWinFromHr = 0; /* hour begin of time frame when queue is to be dequeued */ +static int iActionQueueDeqtWinToHr = 25; /* hour begin of time frame when queue is to be dequeued */ + +/* 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; + +/* ------------------------------ methods ------------------------------ */ + +/* 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; + + ActionQueType = QUEUETYPE_DIRECT; /* type of the main message queue above */ + iActionQueueSize = 1000; /* size of the main message queue above */ + iActionQHighWtrMark = 800; /* high water mark for disk-assisted queues */ + iActionQLowWtrMark = 200; /* low water mark for disk-assisted queues */ + iActionQDiscardMark = 9800; /* begin to discard messages */ + iActionQDiscardSeverity = 8; /* discard warning and above */ + iActionQueueNumWorkers = 1; /* number of worker threads for the mm queue above */ + iActionQueMaxFileSize = 1024*1024; + iActionQPersistUpdCnt = 0; /* persist queue info every n updates */ + iActionQtoQShutdown = 0; /* queue shutdown */ + iActionQtoActShutdown = 1000; /* action shutdown (in phase 2) */ + iActionQtoEnq = 2000; /* timeout for queue enque */ + iActionQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */ + iActionQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */ + bActionQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ + iActionQueMaxDiskSpace = 0; + iActionQueueDeqSlowdown = 0; + iActionQueueDeqtWinFromHr = 0; + iActionQueueDeqtWinToHr = 25; /* 25 disables time windowed dequeuing */ + + glbliActionResumeRetryCount = 0; /* I guess it is smart to reset this one, too */ + + if(pszActionQFName != NULL) + d_free(pszActionQFName); + pszActionQFName = NULL; /* prefix for the main message queue file */ + + RETiRet; +} + /* destructs an action descriptor object * rgerhards, 2007-08-01 */ rsRetVal actionDestruct(action_t *pThis) { - assert(pThis != NULL); + DEFiRet; + ASSERT(pThis != NULL); + + if(pThis->pQueue != NULL) { + queueDestruct(&pThis->pQueue); + } if(pThis->pMod != NULL) pThis->pMod->freeInstance(pThis->pModData); if(pThis->f_pMsg != NULL) - MsgDestruct(pThis->f_pMsg); + msgDestruct(&pThis->f_pMsg); SYNC_OBJ_TOOL_EXIT(pThis); + pthread_mutex_destroy(&pThis->mutActExec); if(pThis->ppTpl != NULL) - free(pThis->ppTpl); - if(pThis->ppMsgs != NULL) - free(pThis->ppMsgs); - free(pThis); + d_free(pThis->ppTpl); + d_free(pThis); - return RS_RET_OK; + RETiRet; } @@ -72,18 +166,100 @@ rsRetVal actionConstruct(action_t **ppThis) DEFiRet; action_t *pThis; - assert(ppThis != NULL); + ASSERT(ppThis != NULL); if((pThis = (action_t*) calloc(1, sizeof(action_t))) == NULL) { ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } pThis->iResumeInterval = glbliActionResumeInterval; + pThis->iResumeRetryCount = glbliActionResumeRetryCount; + pthread_mutex_init(&pThis->mutActExec, NULL); SYNC_OBJ_TOOL_INIT(pThis); + /* indicate we have a new action */ + ++iActionNbr; + finalize_it: *ppThis = pThis; - return iRet; + RETiRet; +} + + +/* action construction finalizer + */ +rsRetVal +actionConstructFinalize(action_t *pThis) +{ + DEFiRet; + uchar pszQName[64]; /* friendly name of our queue */ + + ASSERT(pThis != NULL); + + /* find a name for our queue */ + snprintf((char*) pszQName, sizeof(pszQName)/sizeof(uchar), "action %d queue", iActionNbr); + + /* we need to make a safety check: if the queue is NOT in direct mode, a single + * message object may be accessed by multiple threads. As such, we need to enable + * msg object thread safety in this case (this costs a bit performance and thus + * is not enabled by default. -- rgerhards, 2008-02-20 + */ + if(ActionQueType != QUEUETYPE_DIRECT) + MsgEnableThreadSafety(); + + /* 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(queueConstruct(&pThis->pQueue, ActionQueType, 1, iActionQueueSize, (rsRetVal (*)(void*,void*))actionCallDoAction)); + obj.SetName((obj_t*) pThis->pQueue, pszQName); + + /* ... set some properties ... */ +# define setQPROP(func, directive, data) \ + CHKiRet_Hdlr(func(pThis->pQueue, data)) { \ + errmsg.LogError(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(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } + + queueSetpUsr(pThis->pQueue, pThis); + setQPROP(queueSetsizeOnDiskMax, "$ActionQueueMaxDiskSpace", iActionQueMaxDiskSpace); + setQPROP(queueSetMaxFileSize, "$ActionQueueFileSize", iActionQueMaxFileSize); + setQPROPstr(queueSetFilePrefix, "$ActionQueueFileName", pszActionQFName); + setQPROP(queueSetiPersistUpdCnt, "$ActionQueueCheckpointInterval", iActionQPersistUpdCnt); + setQPROP(queueSettoQShutdown, "$ActionQueueTimeoutShutdown", iActionQtoQShutdown ); + setQPROP(queueSettoActShutdown, "$ActionQueueTimeoutActionCompletion", iActionQtoActShutdown); + setQPROP(queueSettoWrkShutdown, "$ActionQueueWorkerTimeoutThreadShutdown", iActionQtoWrkShutdown); + setQPROP(queueSettoEnq, "$ActionQueueTimeoutEnqueue", iActionQtoEnq); + setQPROP(queueSetiHighWtrMrk, "$ActionQueueHighWaterMark", iActionQHighWtrMark); + setQPROP(queueSetiLowWtrMrk, "$ActionQueueLowWaterMark", iActionQLowWtrMark); + setQPROP(queueSetiDiscardMrk, "$ActionQueueDiscardMark", iActionQDiscardMark); + setQPROP(queueSetiDiscardSeverity, "$ActionQueueDiscardSeverity", iActionQDiscardSeverity); + setQPROP(queueSetiMinMsgsPerWrkr, "$ActionQueueWorkerThreadMinimumMessages", iActionQWrkMinMsgs); + setQPROP(queueSetbSaveOnShutdown, "$ActionQueueSaveOnShutdown", bActionQSaveOnShutdown); + setQPROP(queueSetiDeqSlowdown, "$ActionQueueDequeueSlowdown", iActionQueueDeqSlowdown); + setQPROP(queueSetiDeqtWinFromHr, "$ActionQueueDequeueTimeBegin", iActionQueueDeqtWinFromHr); + setQPROP(queueSetiDeqtWinToHr, "$ActionQueueDequeueTimeEnd", iActionQueueDeqtWinToHr); + +# undef setQPROP +# undef setQPROPstr + + dbgoprint((obj_t*) pThis->pQueue, "save on shutdown %d, max disk space allowed %lld\n", + bActionQSaveOnShutdown, iActionQueMaxDiskSpace); + + + CHKiRet(queueStart(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; } @@ -93,10 +269,10 @@ static rsRetVal actionResume(action_t *pThis) { DEFiRet; - assert(pThis != NULL); + ASSERT(pThis != NULL); pThis->bSuspended = 0; - return iRet; + RETiRet; } @@ -115,12 +291,12 @@ rsRetVal actionSuspend(action_t *pThis) { DEFiRet; - assert(pThis != NULL); + ASSERT(pThis != NULL); pThis->bSuspended = 1; pThis->ttResumeRtry = time(NULL) + pThis->iResumeInterval; pThis->iNbrResRtry = 0; /* tell that we did not yet retry to resume */ - return iRet; + RETiRet; } /* try to resume an action -- rgerhards, 2007-08-02 @@ -132,7 +308,7 @@ rsRetVal actionTryResume(action_t *pThis) DEFiRet; time_t ttNow; - assert(pThis != NULL); + ASSERT(pThis != NULL); ttNow = time(NULL); /* do the system call just once */ @@ -161,7 +337,7 @@ rsRetVal actionTryResume(action_t *pThis) dbgprintf("actionTryResume: iRet: %d, next retry (if applicable): %u [now %u]\n", iRet, (unsigned) pThis->ttResumeRtry, (unsigned) ttNow); - return iRet; + RETiRet; } @@ -172,24 +348,484 @@ rsRetVal actionDbgPrint(action_t *pThis) { DEFiRet; - printf("%s: ", modGetStateName(pThis->pMod)); + dbgprintf("%s: ", module.GetStateName(pThis->pMod)); pThis->pMod->dbgPrintInstInfo(pThis->pModData); - printf("\n\tInstance data: 0x%lx\n", (unsigned long) pThis->pModData); - printf("\tRepeatedMsgReduction: %d\n", pThis->f_ReduceRepeated); - printf("\tResume Interval: %d\n", pThis->iResumeInterval); - printf("\tSuspended: %d", pThis->bSuspended); + dbgprintf("\n\tInstance data: 0x%lx\n", (unsigned long) pThis->pModData); + dbgprintf("\tRepeatedMsgReduction: %d\n", pThis->f_ReduceRepeated); + dbgprintf("\tResume Interval: %d\n", pThis->iResumeInterval); + dbgprintf("\tSuspended: %d", pThis->bSuspended); if(pThis->bSuspended) { - printf(" next retry: %u, number retries: %d", (unsigned) pThis->ttResumeRtry, pThis->iNbrResRtry); + dbgprintf(" next retry: %u, number retries: %d", (unsigned) pThis->ttResumeRtry, pThis->iNbrResRtry); + } + dbgprintf("\n"); + dbgprintf("\tDisabled: %d\n", !pThis->bEnabled); + dbgprintf("\tExec only when previous is suspended: %d\n", pThis->bExecWhenPrevSusp); + dbgprintf("\n"); + + RETiRet; +} + + +/* call the DoAction output plugin entry point + * rgerhards, 2008-01-28 + */ +rsRetVal +actionCallDoAction(action_t *pAction, msg_t *pMsg) +{ + DEFiRet; + int iRetries; + int i; + int iSleepPeriod; + int bCallAction; + int iCancelStateSave; + uchar **ppMsgs; /* array of message pointers for doAction */ + + ASSERT(pAction != NULL); + + /* create the array for doAction() message pointers */ + if((ppMsgs = calloc(pAction->iNumTpls, sizeof(uchar *))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* here we must loop to process all requested strings */ + for(i = 0 ; i < pAction->iNumTpls ; ++i) { + CHKiRet(tplToString(pAction->ppTpl[i], pMsg, &(ppMsgs[i]))); + } + iRetries = 0; + /* 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 + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pAction->mutActExec); + pthread_cleanup_push(mutexCancelCleanup, &pAction->mutActExec); + pthread_setcancelstate(iCancelStateSave, NULL); + do { + /* on first invocation, this if should never be true. We just put it at the top + * of the loop so that processing (and code) is simplified. This code is actually + * triggered on the 2nd+ invocation. -- rgerhards, 2008-01-30 + */ + if(iRet == RS_RET_SUSPENDED) { + /* ok, this calls for our retry logic... */ + ++iRetries; + iSleepPeriod = pAction->iResumeInterval; + srSleep(iSleepPeriod, 0); + } + /* first check if we are suspended and, if so, retry */ + if(actionIsSuspended(pAction)) { + iRet = actionTryResume(pAction); + if(iRet == RS_RET_OK) + bCallAction = 1; + else + bCallAction = 0; + } else { + bCallAction = 1; + } + + if(bCallAction) { + /* call configured action */ + iRet = pAction->pMod->mod.om.doAction(ppMsgs, pMsg->msgFlags, pAction->pModData); + if(iRet == RS_RET_SUSPENDED) { + dbgprintf("Action requested to be suspended, done that.\n"); + actionSuspend(pAction); + } + } + + } while(iRet == RS_RET_SUSPENDED && (pAction->iResumeRetryCount == -1 || iRetries < pAction->iResumeRetryCount)); /* do...while! */ + + if(iRet == RS_RET_DISABLE_ACTION) { + dbgprintf("Action requested to be disabled, done that.\n"); + pAction->bEnabled = 0; /* that's it... */ + } + + pthread_cleanup_pop(1); /* unlock mutex */ + +finalize_it: + /* cleanup */ + for(i = 0 ; i < pAction->iNumTpls ; ++i) { + if(ppMsgs[i] != NULL) { + d_free(ppMsgs[i]); + } + } + d_free(ppMsgs); + msgDestruct(&pMsg); /* we are now finished with the message */ + + RETiRet; +} + +/* 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")) { + ActionQueType = QUEUETYPE_FIXED_ARRAY; + dbgprintf("action queue type set to FIXED_ARRAY\n"); + } else if (!strcasecmp((char *) pszType, "linkedlist")) { + ActionQueType = QUEUETYPE_LINKEDLIST; + dbgprintf("action queue type set to LINKEDLIST\n"); + } else if (!strcasecmp((char *) pszType, "disk")) { + ActionQueType = QUEUETYPE_DISK; + dbgprintf("action queue type set to DISK\n"); + } else if (!strcasecmp((char *) pszType, "direct")) { + ActionQueType = QUEUETYPE_DIRECT; + dbgprintf("action queue type set to DIRECT (no queueing at all)\n"); + } else { + errmsg.LogError(NO_ERRCODE, "unknown actionqueue parameter: %s", (char *) pszType); + iRet = RS_RET_INVALID_PARAMS; + } + d_free(pszType); /* no longer needed */ + + RETiRet; +} + + +/* rgerhards 2004-11-09: fprintlog() is the actual driver for + * the output channel. It receives the channel description (f) as + * well as the message and outputs them according to the channel + * semantics. The message is typically already contained in the + * channel save buffer (f->f_prevline). This is not only the case + * when a message was already repeated but also when a new message + * arrived. + * rgerhards 2007-08-01: interface changed to use action_t + * rgerhards, 2007-12-11: please note: THIS METHOD MUST ONLY BE + * CALLED AFTER THE CALLER HAS LOCKED THE pAction OBJECT! We do + * not do this here. Failing to do so results in all kinds of + * "interesting" problems! + * RGERHARDS, 2008-01-29: + * This is now the action caller and has been renamed. + */ +rsRetVal +actionWriteToAction(action_t *pAction) +{ + msg_t *pMsgSave; /* to save current message pointer, necessary to restore + it in case it needs to be updated (e.g. repeated msgs) */ + time_t now; + DEFiRet; + + pMsgSave = NULL; /* indicate message poiner not saved */ + /* first check if this is a regular message or the repeation of + * a previous message. If so, we need to change the message text + * to "last message repeated n times" and then go ahead and write + * it. Please note that we can not modify the message object, because + * that would update it in other selectors as well. As such, we first + * need to create a local copy of the message, which we than can update. + * rgerhards, 2007-07-10 + */ + if(pAction->f_prevcount > 1) { + msg_t *pMsg; + uchar szRepMsg[64]; + snprintf((char*)szRepMsg, sizeof(szRepMsg), "last message repeated %d times", + pAction->f_prevcount); + + if((pMsg = MsgDup(pAction->f_pMsg)) == NULL) { + /* it failed - nothing we can do against it... */ + dbgprintf("Message duplication failed, dropping repeat message.\n"); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* We now need to update the other message properties. + * ... RAWMSG is a problem ... Please note that digital + * signatures inside the message are also invalidated. + */ + datetime.getCurrTime(&(pMsg->tRcvdAt)); + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); + MsgSetMSG(pMsg, (char*)szRepMsg); + MsgSetRawMsg(pMsg, (char*)szRepMsg); + + pMsgSave = pAction->f_pMsg; /* save message pointer for later restoration */ + pAction->f_pMsg = pMsg; /* use the new msg (pointer will be restored below) */ + } + + dbgprintf("Called action, logging to %s", module.GetStateName(pAction->pMod)); + + time(&now); /* we need this for message repeation processing AND $ActionExecOnlyOnceEveryInterval */ + if(pAction->tLastExec > now) { + /* if we are traveling back in time, reset tLastExec */ + pAction->tLastExec = (time_t) 0; + } + /* now check if we need to drop the message because otherwise the action would be too + * frequently called. -- rgerhards, 2008-04-08 + */ + if(pAction->f_time != 0 && pAction->iSecsExecOnceInterval + pAction->tLastExec > now) { + /* 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) now, + (int) (pAction->iSecsExecOnceInterval + pAction->tLastExec)); + FINALIZE; + } + + pAction->tLastExec = now; /* we need this OnceInterval */ + pAction->f_time = now; /* we need this for message repeation processing */ + + /* 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 = queueEnqObj(pAction->pQueue, pAction->f_pMsg->flowCtlType, (void*) MsgAddRef(pAction->f_pMsg)); + + if(iRet == RS_RET_OK) + pAction->f_prevcount = 0; /* message processed, so we start a new cycle */ + +finalize_it: + if(pMsgSave != NULL) { + /* we had saved the original message pointer. That was + * done because we needed to create a temporary one + * (most often for "message repeated n time" handling). If so, + * we need to restore the original one now, so that procesing + * can continue as normal. We also need to discard the temporary + * one, as we do not like memory leaks ;) Please note that the original + * message object will be discarded by our callers, so this is nothing + * of our business. rgerhards, 2007-07-10 + */ + msgDestruct(&pAction->f_pMsg); + pAction->f_pMsg = pMsgSave; /* restore it */ + } + + RETiRet; +} + + +/* call the configured action. Does all necessary housekeeping. + * rgerhards, 2007-08-01 + */ +rsRetVal +actionCallAction(action_t *pAction, msg_t *pMsg) +{ + DEFiRet; + int iCancelStateSave; + + ISOBJ_TYPE_assert(pMsg, msg); + ASSERT(pAction != NULL); + + /* Make sure nodbody else modifies/uses this action object. Right now, this + * is important because of "message repeated n times" processing and potentially + * multiple worker threads. -- rgerhards, 2007-12-11 + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + LockObj(pAction); + pthread_cleanup_push(mutexCancelCleanup, pAction->Sync_mut); + pthread_setcancelstate(iCancelStateSave, NULL); + + /* first, we need to check if this is a disabled + * entry. If so, we must not further process it. + * rgerhards 2005-09-26 + * In the future, disabled modules may be re-probed from time + * to time. They are in a perfectly legal state, except that the + * doAction method indicated that it wanted to be disabled - but + * we do not consider this is a solution for eternity... So we + * should check from time to time if affairs have improved. + * rgerhards, 2007-07-24 + */ + if(pAction->bEnabled == 0) { + ABORT_FINALIZE(RS_RET_OK); + } + + /* don't output marks to recently written files */ + if ((pMsg->msgFlags & MARK) && (time(NULL) - pAction->f_time) < MarkInterval / 2) { + ABORT_FINALIZE(RS_RET_OK); + } + + /* suppress duplicate messages + */ + if ((pAction->f_ReduceRepeated == 1) && pAction->f_pMsg != NULL && + (pMsg->msgFlags & MARK) == 0 && getMSGLen(pMsg) == getMSGLen(pAction->f_pMsg) && + !strcmp(getMSG(pMsg), getMSG(pAction->f_pMsg)) && + !strcmp(getHOSTNAME(pMsg), getHOSTNAME(pAction->f_pMsg)) && + !strcmp(getPROCID(pMsg), getPROCID(pAction->f_pMsg)) && + !strcmp(getAPPNAME(pMsg), getAPPNAME(pAction->f_pMsg))) { + pAction->f_prevcount++; + dbgprintf("msg repeated %d times, %ld sec of %d.\n", + pAction->f_prevcount, (long) time(NULL) - pAction->f_time, + repeatinterval[pAction->f_repeatcount]); + /* use current message, so we have the new timestamp (means we need to discard previous one) */ + msgDestruct(&pAction->f_pMsg); + pAction->f_pMsg = MsgAddRef(pMsg); + /* If domark would have logged this by now, flush it now (so we don't hold + * isolated messages), but back off so we'll flush less often in the future. + */ + if(time(NULL) > REPEATTIME(pAction)) { + iRet = actionWriteToAction(pAction); + BACKOFF(pAction); + } + } else { + /* new message, save it */ + /* first check if we have a previous message stored + * if so, emit and then discard it first + */ + if(pAction->f_pMsg != NULL) { + if(pAction->f_prevcount > 0) + actionWriteToAction(pAction); + /* we do not care about iRet above - I think it's right but if we have + * some troubles, you know where to look at ;) -- rgerhards, 2007-08-01 + */ + msgDestruct(&pAction->f_pMsg); + } + pAction->f_pMsg = MsgAddRef(pMsg); + /* call the output driver */ + iRet = actionWriteToAction(pAction); } - printf("\n"); - printf("\tDisabled: %d\n", !pThis->bEnabled); - printf("\tExec only when previous is suspended: %d\n", pThis->bExecWhenPrevSusp); - printf("\n"); - return iRet; +finalize_it: + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + UnlockObj(pAction); + pthread_cleanup_pop(0); /* remove mutex cleanup handler */ + pthread_setcancelstate(iCancelStateSave, NULL); + RETiRet; } -/* - * vi:set ai: +/* add our cfsysline handlers + * rgerhards, 2008-01-28 + */ +rsRetVal +actionAddCfSysLineHdrl(void) +{ + DEFiRet; + + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuefilename", 0, eCmdHdlrGetWord, NULL, &pszActionQFName, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuesize", 0, eCmdHdlrInt, NULL, &iActionQueueSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuemaxdiskspace", 0, eCmdHdlrSize, NULL, &iActionQueMaxDiskSpace, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuehighwatermark", 0, eCmdHdlrInt, NULL, &iActionQHighWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuelowwatermark", 0, eCmdHdlrInt, NULL, &iActionQLowWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuediscardmark", 0, eCmdHdlrInt, NULL, &iActionQDiscardMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuediscardseverity", 0, eCmdHdlrInt, NULL, &iActionQDiscardSeverity, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuecheckpointinterval", 0, eCmdHdlrInt, NULL, &iActionQPersistUpdCnt, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetype", 0, eCmdHdlrGetWord, setActionQueType, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueueworkerthreads", 0, eCmdHdlrInt, NULL, &iActionQueueNumWorkers, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetimeoutshutdown", 0, eCmdHdlrInt, NULL, &iActionQtoQShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetimeoutactioncompletion", 0, eCmdHdlrInt, NULL, &iActionQtoActShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuetimeoutenqueue", 0, eCmdHdlrInt, NULL, &iActionQtoEnq, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueueworkertimeoutthreadshutdown", 0, eCmdHdlrInt, NULL, &iActionQtoWrkShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueueworkerthreadminimummessages", 0, eCmdHdlrInt, NULL, &iActionQWrkMinMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuemaxfilesize", 0, eCmdHdlrSize, NULL, &iActionQueMaxFileSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuesaveonshutdown", 0, eCmdHdlrBinary, NULL, &bActionQSaveOnShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeueslowdown", 0, eCmdHdlrInt, NULL, &iActionQueueDeqSlowdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeuetimebegin", 0, eCmdHdlrInt, NULL, &iActionQueueDeqtWinFromHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionqueuedequeuetimeend", 0, eCmdHdlrInt, NULL, &iActionQueueDeqtWinToHr, NULL)); + +finalize_it: + RETiRet; +} + + +/* 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, 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 processed this config line.\n", module.GetName(pMod)); + + CHKiRet(actionConstruct(&pAction)); /* create action object first */ + pAction->pMod = pMod; + pAction->pModData = pModData; + pAction->bExecWhenPrevSusp = bActExecWhenPrevSusp; + pAction->iSecsExecOnceInterval = iActExecOnceInterval; + + /* 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 */ + if((pAction->ppTpl = calloc(pAction->iNumTpls, sizeof(struct template *))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + } + + 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((pAction->ppTpl[i] = tplFind((char*)pTplName, strlen((char*)pTplName))) == NULL) { + snprintf(errMsg, sizeof(errMsg) / sizeof(char), + " Could not find template '%s' - action disabled\n", + pTplName); + errno = 0; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + /* check required template options */ + if( (iTplOpts & OMSR_RQD_TPL_OPT_SQL) + && (pAction->ppTpl[i]->optFormatForSQL == 0)) { + errno = 0; + errmsg.LogError(NO_ERRCODE, "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); + } + + dbgprintf("template: '%s' assigned\n", pTplName); + } + + pAction->pMod = pMod; + pAction->pModData = pModData; + /* now check if the module is compatible with select features */ + if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) + pAction->f_ReduceRepeated = bReduceRepeatMsgs; + else { + dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); + pAction->f_ReduceRepeated = 0; + } + pAction->bEnabled = 1; /* action is enabled */ + + if(bSuspended) + actionSuspend(pAction); + + CHKiRet(actionConstructFinalize(pAction)); + + /* 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; +} + + +/* 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)); + +finalize_it: + RETiRet; +} + +/* vi:set ai: */ @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -26,18 +27,29 @@ #include "syslogd-types.h" #include "sync.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; + /* the following struct defines the action object data structure */ struct action_s { - time_t f_time; /* time this was last written */ + time_t f_time; /* used for "message repeated n times" - be careful, old, old code */ + time_t tLastExec; /* time this action was last executed */ int bExecWhenPrevSusp;/* execute only when previous action is suspended? */ + int iSecsExecOnceInterval; /* if non-zero, minimum seconds to wait until action is executed again */ short bEnabled; /* is the related action enabled (1) or disabled (0)? */ short bSuspended; /* is the related action temporarily suspended? */ time_t ttResumeRtry; /* when is it time to retry the resume? */ 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 */ - struct moduleInfo *pMod;/* pointer to output module handling this selector */ + struct modInfo_s *pMod;/* pointer to output module handling this selector */ void *pModData; /* pointer to module data - content is module-specific */ int f_ReduceRepeated;/* reduce repeated lines 0 - no, 1 - yes */ int f_prevcount; /* repetition cnt of prevline */ @@ -45,13 +57,13 @@ struct action_s { 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. */ - - uchar **ppMsgs; /* array of message pointers for doAction */ struct msg* f_pMsg; /* pointer to the message (this will replace the other vars with msg * content later). This is preserved after the message has been * processed - it is also used to detect duplicates. */ + queue_t *pQueue; /* action queue */ SYNC_OBJ_TOOL; /* required for mutex support */ + pthread_mutex_t mutActExec; /* mutex to guard actual execution of doAction for single-threaded modules */ }; typedef struct action_s action_t; @@ -59,11 +71,18 @@ typedef struct action_s action_t; /* function prototypes */ rsRetVal actionConstruct(action_t **ppThis); +rsRetVal actionConstructFinalize(action_t *pThis); rsRetVal actionDestruct(action_t *pThis); +rsRetVal actionAddCfSysLineHdrl(void); rsRetVal actionTryResume(action_t *pThis); rsRetVal actionSuspend(action_t *pThis); rsRetVal actionDbgPrint(action_t *pThis); rsRetVal actionSetGlobalResumeInterval(int iNewVal); +rsRetVal actionDoAction(action_t *pAction); +rsRetVal actionCallAction(action_t *pAction, msg_t *pMsg); +rsRetVal actionWriteToAction(action_t *pAction); +rsRetVal actionClassInit(void); +rsRetVal addAction(action_t **ppAction, modInfo_t *pMod, void *pModData, omodStringRequest_t *pOMSR, int bSuspended); #if 1 #define actionIsSuspended(pThis) ((pThis)->bSuspended == 1) @@ -72,6 +91,7 @@ rsRetVal actionSetGlobalResumeInterval(int iNewVal); inline int actionIsSuspended(action_t *pThis) { int i; + ASSERT(pThis != NULL); i = pThis->bSuspended == 1; dbgprintf("in IsSuspend(), returns %d\n", i); return i; diff --git a/atomic.h b/atomic.h new file mode 100644 index 00000000..2421c826 --- /dev/null +++ b/atomic.h @@ -0,0 +1,50 @@ +/* This header supplies atomic operations. So far, we rely on GCC's + * atomic builtins. I have no idea if we can check them via autotools, + * but 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 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" /* autotools! */ + +#ifndef INCLUDED_ATOMIC_H +#define INCLUDED_ATOMIC_H + +/* set the following to 1 if we have atomic operations (and #undef it otherwise) */ +/* #define DO_HAVE_ATOMICS 1 */ +/* 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 + */ +/* make sure they are not used! +#define ATOMIC_INC(data) ((void) __sync_fetch_and_add(&data, 1)) +#define ATOMIC_DEC_AND_FETCH(data) __sync_sub_and_fetch(&data, 1) +*/ +#define ATOMIC_INC(data) (++(data)) + +#endif /* #ifndef INCLUDED_ATOMIC_H */ diff --git a/cfsysline.c b/cfsysline.c index c4d81438..cf8e087a 100644 --- a/cfsysline.c +++ b/cfsysline.c @@ -3,21 +3,22 @@ * * File begun on 2007-07-30 by RGerhards * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * Copyright (C) 2007, 2008 by 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -35,10 +36,15 @@ #include "syslogd.h" /* TODO: when the module interface & library design is done, this should be able to go away */ #include "cfsysline.h" +#include "obj.h" +#include "errmsg.h" #include "srUtils.h" /* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(errmsg) + linkedList_t llCmdList; /* this is NOT a pointer - no typo here ;) */ /* --------------- START functions for handling canned syntaxes --------------- */ @@ -61,7 +67,7 @@ static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void * /* if we are not at a '\0', we have our new char - no validity checks here... */ if(**pp == '\0') { - logerror("No character available"); + errmsg.LogError(NO_ERRCODE, "No character available"); iRet = RS_RET_NOT_FOUND; } else { if(pSetHdlr == NULL) { @@ -75,7 +81,7 @@ static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void * } finalize_it: - return iRet; + RETiRet; } @@ -93,47 +99,138 @@ static rsRetVal doCustomHdlr(uchar **pp, rsRetVal (*pSetHdlr)(uchar**, void*), v CHKiRet(pSetHdlr(pp, pVal)); finalize_it: - return iRet; + RETiRet; } -/* Parse a number from the configuration line. - * rgerhards, 2007-07-31 +/* 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 doGetInt(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +static rsRetVal parseIntVal(uchar **pp, int64 *pVal) { - uchar *p; DEFiRet; - int i; + 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; - logerror("invalid number"); + errmsg.LogError(NO_ERRCODE, "invalid number"); ABORT_FINALIZE(RS_RET_INVALID_INT); } /* pull value */ - for(i = 0 ; *p && isdigit((int) *p) ; ++p) - i = i * 10 + *p - '0'; + 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 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; + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(parseIntVal(pp, &i)); + p = *pp; if(pSetHdlr == NULL) { /* we should set value directly to var */ - *((int*)pVal) = i; + *((int*)pVal) = (int) i; } else { /* we set value via a set function */ - CHKiRet(pSetHdlr(pVal, i)); + CHKiRet(pSetHdlr(pVal, (int) i)); } *pp = p; finalize_it: - return iRet; + 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; + case 'T': i *= 1000000000000; ++(*pp); break; /* tera */ + case 'P': i *= 1000000000000000; ++(*pp); break; /* peta */ + case 'E': i *= 1000000000000000000; ++(*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; } @@ -173,7 +270,7 @@ static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), "value must be octal (e.g 0644)."); errno = 0; - logerror((char*) errMsg); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); ABORT_FINALIZE(RS_RET_INVALID_VALUE); } @@ -194,7 +291,7 @@ static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), *pp = p; finalize_it: - return iRet; + RETiRet; } @@ -218,7 +315,7 @@ static int doParseOnOffOption(uchar **pp) skipWhiteSpace(pp); /* skip over any whitespace */ if(getSubString(pp, (char*) szOpt, sizeof(szOpt) / sizeof(uchar), ' ') != 0) { - logerror("Invalid $-configline - could not extract on/off option"); + errmsg.LogError(NO_ERRCODE, "Invalid $-configline - could not extract on/off option"); return -1; } @@ -227,7 +324,7 @@ static int doParseOnOffOption(uchar **pp) } else if(!strcmp((char*)szOpt, "off")) { return 0; } else { - logerrorSz("Option value must be on or off, but is '%s'", (char*)pOptStart); + errmsg.LogError(NO_ERRCODE, "Option value must be on or off, but is '%s'", (char*)pOptStart); return -1; } } @@ -248,14 +345,14 @@ static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *p assert(*pp != NULL); if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { - logerror("could not extract group name"); + errmsg.LogError(NO_ERRCODE, "could not extract group name"); ABORT_FINALIZE(RS_RET_NOT_FOUND); } getgrnam_r((char*)szName, &gBuf, stringBuf, sizeof(stringBuf), &pgBuf); if(pgBuf == NULL) { - logerrorSz("ID for group '%s' could not be found or error", (char*)szName); + errmsg.LogError(NO_ERRCODE, "ID for group '%s' could not be found or error", (char*)szName); iRet = RS_RET_NOT_FOUND; } else { if(pSetHdlr == NULL) { @@ -271,7 +368,7 @@ static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *p skipWhiteSpace(pp); /* skip over any whitespace */ finalize_it: - return iRet; + RETiRet; } @@ -290,14 +387,14 @@ static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *p assert(*pp != NULL); if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { - logerror("could not extract user name"); + errmsg.LogError(NO_ERRCODE, "could not extract user name"); ABORT_FINALIZE(RS_RET_NOT_FOUND); } getpwnam_r((char*)szName, &pwBuf, stringBuf, sizeof(stringBuf), &ppwBuf); if(ppwBuf == NULL) { - logerrorSz("ID for user '%s' could not be found or error", (char*)szName); + errmsg.LogError(NO_ERRCODE, "ID for user '%s' could not be found or error", (char*)szName); iRet = RS_RET_NOT_FOUND; } else { if(pSetHdlr == NULL) { @@ -313,7 +410,7 @@ static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *p skipWhiteSpace(pp); /* skip over any whitespace */ finalize_it: - return iRet; + RETiRet; } @@ -344,11 +441,45 @@ static rsRetVal doBinaryOptionLine(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), skipWhiteSpace(pp); /* skip over any whitespace */ finalize_it: - return iRet; + RETiRet; } -/* Parse and a word config line option. A word is a consequitive +/* 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(rsCStrConstruct(ppStrB)); + + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* parse out the word */ + p = *pp; + + while(*p && !isspace((int) *p)) { + CHKiRet(rsCStrAppendChar(*ppStrB, *p++)); + } + CHKiRet(rsCStrFinish(*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. @@ -360,28 +491,19 @@ finalize_it: * 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; - rsCStrObj *pStrB; - uchar *p; + cstr_t *pStrB; uchar *pNewVal; - assert(pp != NULL); - assert(*pp != NULL); - - if((pStrB = rsCStrConstruct()) == NULL) - return RS_RET_OUT_OF_MEMORY; - - /* parse out the word */ - p = *pp; - - while(*p && !isspace((int) *p)) { - CHKiRet(rsCStrAppendChar(pStrB, *p++)); - } - CHKiRet(rsCStrFinish(pStrB)); + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + CHKiRet(getWord(pp, &pStrB)); CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &pNewVal, 0)); pStrB = NULL; @@ -396,16 +518,75 @@ static rsRetVal doGetWord(uchar **pp, rsRetVal (*pSetHdlr)(void*, uchar*), void CHKiRet(pSetHdlr(pVal, pNewVal)); } - *pp = p; skipWhiteSpace(pp); /* skip over any whitespace */ finalize_it: if(iRet != RS_RET_OK) { if(pStrB != NULL) - rsCStrDestruct(pStrB); + rsCStrDestruct(&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(rsCStrGetSzStr(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)); } - return iRet; + 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; +} + + +/* 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; } @@ -417,7 +598,7 @@ finalize_it: */ static rsRetVal cslchDestruct(void *pThis) { - assert(pThis != NULL); + ASSERT(pThis != NULL); free(pThis); return RS_RET_OK; @@ -438,7 +619,7 @@ static rsRetVal cslchConstruct(cslCmdHdlr_t **ppThis) finalize_it: *ppThis = pThis; - return iRet; + RETiRet; } /* destructor for linked list keys. As we do not use any dynamic memory, @@ -511,9 +692,18 @@ static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine) 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; @@ -527,7 +717,7 @@ static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine) CHKiRet(pHdlr(ppConfLine, pThis->cslCmdHdlr, pThis->pData)); finalize_it: - return iRet; + RETiRet; } @@ -576,7 +766,7 @@ static rsRetVal cslcConstruct(cslCmd_t **ppThis, int bChainingPermitted) finalize_it: *ppThis = pThis; - return iRet; + RETiRet; } @@ -599,21 +789,7 @@ finalize_it: cslchDestruct(pCmdHdlr); } - return iRet; -} - - -/* function that initializes this module here. This is primarily a hook - * for syslogd. - */ -rsRetVal cfsyslineInit(void) -{ - DEFiRet; - - CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, strcasecmp)); - -finalize_it: - return iRet; + RETiRet; } @@ -626,9 +802,9 @@ finalize_it: rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie) { + DEFiRet; cslCmd_t *pThis; uchar *pMyCmdName; - DEFiRet; iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pThis); if(iRet == RS_RET_NOT_FOUND) { @@ -661,7 +837,7 @@ rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlTy } finalize_it: - return iRet; + RETiRet; } @@ -695,7 +871,7 @@ DEFFUNC_llExecFunc(unregHdlrsHeadExec) } finalize_it: - return iRet; + RETiRet; } /* unregister and destroy cfSysLineHandlers for a specific owner. This method is * most importantly used before unloading a loadable module providing some handlers. @@ -711,7 +887,7 @@ rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie) */ iRet = llExecFunc(&llCmdList, unregHdlrsHeadExec, pOwnerCookie); - return iRet; + RETiRet; } @@ -733,7 +909,7 @@ rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p) iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pCmd); if(iRet == RS_RET_NOT_FOUND) { - logerror("invalid or yet-unknown config file command - have you forgotten to load a module?"); + errmsg.LogError(NO_ERRCODE, "invalid or yet-unknown config file command - have you forgotten to load a module?"); } if(iRet != RS_RET_OK) @@ -765,7 +941,7 @@ rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p) iRet = iRetLL; finalize_it: - return iRet; + RETiRet; } @@ -781,24 +957,38 @@ void dbgPrintCfSysLineHandlers(void) linkedListCookie_t llCookieCmdHdlr; uchar *pKey; - printf("\nSytem Line Configuration Commands:\n"); + dbgprintf("Sytem Line Configuration Commands:\n"); llCookieCmd = NULL; while((iRet = llGetNextElt(&llCmdList, &llCookieCmd, (void*)&pCmd)) == RS_RET_OK) { llGetKey(llCookieCmd, (void*) &pKey); /* TODO: using the cookie is NOT clean! */ - printf("\tCommand '%s':\n", pKey); + dbgprintf("\tCommand '%s':\n", pKey); llCookieCmdHdlr = NULL; while((iRet = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr)) == RS_RET_OK) { - printf("\t\ttype : %d\n", pCmdHdlr->eType); - printf("\t\tpData: 0x%lx\n", (unsigned long) pCmdHdlr->pData); - printf("\t\tHdlr : 0x%lx\n", (unsigned long) pCmdHdlr->cslCmdHdlr); - printf("\t\tOwner: 0x%lx\n", (unsigned long) llCookieCmdHdlr->pKey); - printf("\n"); + 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"); } } - printf("\n"); + dbgprintf("\n"); + ENDfunc +} + +/* 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; } -/* - * vi:set ai: +/* vim:set ai: */ diff --git a/cfsysline.h b/cfsysline.h index 13df135c..2eec18ab 100644 --- a/cfsysline.h +++ b/cfsysline.h @@ -2,19 +2,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -34,7 +35,10 @@ typedef enum cslCmdHdlrType { eCmdHdlrBinary, eCmdHdlrFileCreateMode, eCmdHdlrInt, + eCmdHdlrSize, eCmdHdlrGetChar, + eCmdHdlrFacility, + eCmdHdlrSeverity, eCmdHdlrGetWord } ecslCmdHdrlType; @@ -0,0 +1,1211 @@ +/* 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 + * + * 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 <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 +# include <libgen.h> +#endif + +#include "rsyslog.h" +#include "syslogd.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" + + +/* forward definitions */ +static rsRetVal cfline(uchar *line, selector_t **pfCurr); +static rsRetVal processConfFile(uchar *pConfFile); + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(expr) +DEFobjCurrIf(ctok) +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) + +/* The following 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. These + * variables are no longer used once the configuration has been loaded (except, + * of course, during a reload). rgerhards 2005-10-18 + */ +EHostnameCmpMode eDfltHostnameCmpMode; +cstr_t *pDfltHostnameCmp; +cstr_t *pDfltProgNameCmp; + + +/* process a directory and include all of its files into + * the current config file. There is no specific order of inclusion, + * files are included in the order they are read from the directory. + * The caller must have make sure that the provided parameter is + * indeed a directory. + * rgerhards, 2007-08-01 + */ +static rsRetVal doIncludeDirectory(uchar *pDirName) +{ + DEFiRet; + int iEntriesDone = 0; + DIR *pDir; + union { + struct dirent d; + char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; + } u; + struct dirent *res; + size_t iDirNameLen; + size_t iFileNameLen; + uchar szFullFileName[MAXFNAME]; + + ASSERT(pDirName != NULL); + + if((pDir = opendir((char*) pDirName)) == NULL) { + errmsg.LogError(NO_ERRCODE, "error opening include directory"); + ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); + } + + /* prepare file name buffer */ + iDirNameLen = strlen((char*) pDirName); + memcpy(szFullFileName, pDirName, iDirNameLen); + + /* now read the directory */ + iEntriesDone = 0; + while(readdir_r(pDir, &u.d, &res) == 0) { + if(res == NULL) + break; /* this also indicates end of directory */ +# ifdef DT_REG + /* TODO: find an alternate way to checking for special files if this is + * not defined. This is currently a known problem on HP UX, but the work- + * around is simple: do not create special files in that directory. So + * fixing this is actually not the most important thing on earth... + * rgerhards, 2008-03-04 + */ + if(res->d_type != DT_REG) + continue; /* we are not interested in special files */ +# endif + if(res->d_name[0] == '.') + continue; /* these files we are also not interested in */ + ++iEntriesDone; + /* construct filename */ + iFileNameLen = strlen(res->d_name); + if (iFileNameLen > NAME_MAX) + iFileNameLen = NAME_MAX; + memcpy(szFullFileName + iDirNameLen, res->d_name, iFileNameLen); + *(szFullFileName + iDirNameLen + iFileNameLen) = '\0'; + dbgprintf("including file '%s'\n", szFullFileName); + processConfFile(szFullFileName); + /* we deliberately ignore the iRet of processConfFile() - this is because + * failure to process one file does not mean all files will fail. By ignoring, + * we retry with the next file, which is the best thing we can do. -- rgerhards, 2007-08-01 + */ + } + + if(iEntriesDone == 0) { + /* I just make it a debug output, because I can think of a lot of cases where it + * makes sense not to have any files. E.g. a system maintainer may place a $Include + * into the config file just in case, when additional modules be installed. When none + * are installed, the directory will be empty, which is fine. -- rgerhards 2007-08-01 + */ + dbgprintf("warning: the include directory contained no files - this may be ok.\n"); + } + +finalize_it: + if(pDir != NULL) + closedir(pDir); + + RETiRet; +} + + +/* process a $include config line. That type of line requires + * inclusion of another file. + * rgerhards, 2007-08-01 + */ +rsRetVal +doIncludeLine(uchar **pp, __attribute__((unused)) void* pVal) +{ + DEFiRet; + char pattern[MAXFNAME]; + uchar *cfgFile; + glob_t cfgFiles; + size_t i = 0; + struct stat fileInfo; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + if(getSubString(pp, (char*) pattern, sizeof(pattern) / sizeof(char), ' ') != 0) { + errmsg.LogError(NO_ERRCODE, "could not extract group name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + /* Use GLOB_MARK to append a trailing slash for directories. + * Required by doIncludeDirectory(). + */ + glob(pattern, GLOB_MARK, NULL, &cfgFiles); + + for(i = 0; i < cfgFiles.gl_pathc; i++) { + cfgFile = (uchar*) cfgFiles.gl_pathv[i]; + + if(stat((char*) cfgFile, &fileInfo) != 0) + continue; /* continue with the next file if we can't stat() the file */ + + if(S_ISREG(fileInfo.st_mode)) { /* config file */ + dbgprintf("requested to include config file '%s'\n", cfgFile); + iRet = processConfFile(cfgFile); + } else if(S_ISDIR(fileInfo.st_mode)) { /* config directory */ + dbgprintf("requested to include directory '%s'\n", cfgFile); + iRet = doIncludeDirectory(cfgFile); + } else { /* TODO: shall we handle symlinks or not? */ + dbgprintf("warning: unable to process IncludeConfig directive '%s'\n", cfgFile); + } + } + + globfree(&cfgFiles); + +finalize_it: + RETiRet; +} + + +/* 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(NO_ERRCODE, "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. TODO: clean this up + * For the time being, it is clean enough, it just needs to be done + * differently when we have a full design for loadable plug-ins. For the + * time being, we just mangle the names a bit. + * rgerhards, 2007-08-14 + */ + if(!strcmp((char*) szName, "MySQL")) + pModName = (uchar*) "ommysql.so"; + else + pModName = szName; + + CHKiRet(module.Load(pModName)); + +finalize_it: + RETiRet; +} + + +/* 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(NO_ERRCODE, "Invalid config line: could not extract name - line ignored"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + 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(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]; + uchar errMsg[128]; /* for dynamic error messages */ + + ASSERT(p != NULL); + errno = 0; + if(getSubString(&p, (char*) szCmd, sizeof(szCmd) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(NO_ERRCODE, "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 */ + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "error: extra characters in config line ignored: '%s'", p); + errno = 0; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + } + +finalize_it: + RETiRet; +} + + + + +/* process a configuration file + * started with code from init() by rgerhards on 2007-07-31 + */ +static rsRetVal +processConfFile(uchar *pConfFile) +{ + DEFiRet; + int iLnNbr = 0; + FILE *cf; + selector_t *fCurr = NULL; + uchar *p; + uchar cbuf[BUFSIZ]; + uchar *cline; + int i; + ASSERT(pConfFile != NULL); + + if((cf = fopen((char*)pConfFile, "r")) == NULL) { + ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); + } + + /* Now process the file. + */ + cline = cbuf; + while (fgets((char*)cline, sizeof(cbuf) - (cline - cbuf), cf) != NULL) { + ++iLnNbr; + /* drop LF - TODO: make it better, replace fgets(), but its clean as it is */ + if(cline[strlen((char*)cline)-1] == '\n') { + cline[strlen((char*)cline) -1] = '\0'; + } + /* check for end-of-section, comments, strip off trailing + * spaces and newline character. + */ + p = cline; + skipWhiteSpace(&p); + if (*p == '\0' || *p == '#') + continue; + + /* we now need to copy the characters to the begin of line. As this overlaps, + * we can not use strcpy(). -- rgerhards, 2008-03-20 + * TODO: review the code at whole - this is highly suspect (but will go away + * once we do the rest of RainerScript). + */ + /* was: strcpy((char*)cline, (char*)p); */ + for( i = 0 ; p[i] != '\0' ; ++i) { + cline[i] = p[i]; + } + cline[i] = '\0'; + + for (p = (uchar*) strchr((char*)cline, '\0'); isspace((int) *--p);) + /*EMPTY*/; + if (*p == '\\') { + if ((p - cbuf) > BUFSIZ - 30) { + /* Oops the buffer is full - what now? */ + cline = cbuf; + } else { + *p = 0; + cline = p; + continue; + } + } else + cline = cbuf; + *++p = '\0'; /* TODO: check this */ + + /* we now have the complete line, and are positioned at the first non-whitespace + * character. So let's process it + */ + if(cfline(cbuf, &fCurr) != RS_RET_OK) { + /* we log a message, but otherwise ignore the error. After all, the next + * line can be correct. -- rgerhards, 2007-08-02 + */ + uchar szErrLoc[MAXFNAME + 64]; + dbgprintf("config line NOT successfully processed\n"); + snprintf((char*)szErrLoc, sizeof(szErrLoc) / sizeof(uchar), + "%s, line %d", pConfFile, iLnNbr); + errmsg.LogError(NO_ERRCODE, "the last error occured in %s", (char*)szErrLoc); + } + } + + /* we probably have one selector left to be added - so let's do that now */ + CHKiRet(selectorAddList(fCurr)); + + /* close the configuration file */ + (void) fclose(cf); + +finalize_it: + if(iRet != RS_RET_OK) { + char errStr[1024]; + if(fCurr != NULL) + selectorDestruct(fCurr); + + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("error %d processing config file '%s'; os error (if any): %s\n", + iRet, pConfFile, errStr); + } + 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; + 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(NO_ERRCODE, "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 */ + if(rsCStrConstruct(&pStrB) != RS_RET_OK) { + glblHadMemShortage = 1; + iRet = RS_RET_OUT_OF_MEMORY; + goto finalize_it; + } + + /* now copy the string */ + while(*p && *p != '#' && !isspace((int) *p)) { + CHKiRet(rsCStrAppendChar(pStrB, *p)); + ++p; + } + CHKiRet(rsCStrFinish(pStrB)); + CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &tplName, 0)); + } + + iRet = OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts); + if(iRet != RS_RET_OK) goto finalize_it; + +finalize_it: + *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 + */ +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 != ';' && i < MAXFNAME) { + *pName++ = *p++; + ++i; + } + *pName = '\0'; + + iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, pszTpl); + + RETiRet; +} + + +/* + * Helper to cfline(). This function takes the filter part of a traditional, PRI + * based line and decodes the PRIs given in the selector line. It processed 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 + */ +static rsRetVal cflineProcessTradPRIFilter(uchar **pline, register selector_t *f) +{ + uchar *p; + register uchar *q; + register int i, i2; + uchar *bp; + int pri; + int singlpri = 0; + int ignorepri = 0; + uchar buf[MAXLINE]; + uchar xbuf[200]; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(f != NULL); + + dbgprintf(" - traditional PRI filter\n"); + errno = 0; /* keep strerror_r() stuff out of logerror messages */ + + f->f_filter_type = FILTER_PRI; + /* Note: file structure is pre-initialized to zero because it was + * created with calloc()! + */ + for (i = 0; i <= LOG_NFACILITIES; i++) { + f->f_filterData.f_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++ = *q++; + *bp = '\0'; + + /* skip cruft */ + while (strchr(",;", *q)) + q++; + + /* decode priority name */ + if ( *buf == '!' ) { + ignorepri = 1; + 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(NO_ERRCODE, "%s", xbuf); + return RS_RET_ERR; + } + + /* scan facilities */ + while (*p && !strchr("\t .;", *p)) { + for (bp = buf; *p && !strchr("\t ,;.", *p); ) + *bp++ = *p++; + *bp = '\0'; + if (*buf == '*') { + for (i = 0; i <= LOG_NFACILITIES; i++) { + if ( pri == INTERNAL_NOPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i] = TABLE_ALLPRI; + else + f->f_filterData.f_pmask[i] = TABLE_NOPRI; + } + else if ( singlpri ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i] &= ~(1<<pri); + else + f->f_filterData.f_pmask[i] |= (1<<pri); + } + else + { + if ( pri == TABLE_ALLPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i] = TABLE_NOPRI; + else + f->f_filterData.f_pmask[i] = TABLE_ALLPRI; + } + else + { + if ( ignorepri ) + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i] &= ~(1<<i2); + else + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i] |= (1<<i2); + } + } + } + } else { + i = decodeSyslogName(buf, syslogFacNames); + if (i < 0) { + + snprintf((char*) xbuf, sizeof(xbuf), "unknown facility name \"%s\"", buf); + errmsg.LogError(NO_ERRCODE, "%s", xbuf); + return RS_RET_ERR; + } + + if ( pri == INTERNAL_NOPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; + else + f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; + } else if ( singlpri ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i >> 3] &= ~(1<<pri); + else + f->f_filterData.f_pmask[i >> 3] |= (1<<pri); + } else { + if ( pri == TABLE_ALLPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; + else + f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; + } else { + if ( ignorepri ) + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i >> 3] &= ~(1<<i2); + else + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i >> 3] |= (1<<i2); + } + } + } + while (*p == ',' || *p == ' ') + p++; + } + + p = q; + } + + /* skip to action part */ + while (*p == '\t' || *p == ' ') + p++; + + *pline = p; + return RS_RET_OK; +} + + +/* Helper to cfline(). This function processes an "if" type of filter, + * what essentially means it parses an expression. As usual, + * It processes the line up to the beginning of the action part. + * A pointer to that beginnig is passed back to the caller. + * rgerhards 2008-01-19 + */ +static rsRetVal cflineProcessIfFilter(uchar **pline, register selector_t *f) +{ + DEFiRet; + ctok_t *tok; + ctok_token_t *pToken; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(f != NULL); + + dbgprintf(" - general expression-based filter\n"); + errno = 0; /* keep strerror_r() stuff out of logerror messages */ + +dbgprintf("calling expression parser, pp %p ('%s')\n", *pline, *pline); + f->f_filter_type = FILTER_EXPR; + + /* if we come to over here, pline starts with "if ". We just skip that part. */ + (*pline) += 3; + + /* we first need a tokenizer... */ + CHKiRet(ctok.Construct(&tok)); + CHKiRet(ctok.Setpp(tok, *pline)); + CHKiRet(ctok.ConstructFinalize(tok)); + + /* now construct our expression */ + CHKiRet(expr.Construct(&f->f_filterData.f_expr)); + CHKiRet(expr.ConstructFinalize(f->f_filterData.f_expr)); + + /* ready to go... */ + CHKiRet(expr.Parse(f->f_filterData.f_expr, tok)); + + /* we now need to parse off the "then" - and note an error if it is + * missing... + */ + CHKiRet(ctok.GetToken(tok, &pToken)); + if(pToken->tok != ctok_THEN) { + ctok_token.Destruct(&pToken); + ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); + } + + ctok_token.Destruct(&pToken); /* no longer needed */ + + /* we are done, so we now need to restore things */ + CHKiRet(ctok.Getpp(tok, pline)); + CHKiRet(ctok.Destruct(&tok)); + + /* we now need to skip whitespace to the action part, else we confuse + * the legacy rsyslog conf parser. -- rgerhards, 2008-02-25 + */ + while(isspace(**pline)) + ++(*pline); + +finalize_it: + if(iRet == RS_RET_SYNTAX_ERROR) { + errmsg.LogError(NO_ERRCODE, "syntax error in expression"); + } + + 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 + */ +static rsRetVal cflineProcessPropFilter(uchar **pline, register selector_t *f) +{ + rsParsObj *pPars; + cstr_t *pCSCompOp; + rsRetVal iRet; + int iOffset; /* for compare operations */ + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(f != NULL); + + dbgprintf(" - property-based filter\n"); + errno = 0; /* keep strerror_r() stuff out of logerror messages */ + + f->f_filter_type = FILTER_PROP; + + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, (*pline)+1)) != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "Error %d constructing parser object - ignoring selector", iRet); + return(iRet); + } + + /* read property */ + iRet = parsDelimCStr(pPars, &f->f_filterData.prop.pCSPropName, ',', 1, 1, 1); + if(iRet != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "error %d parsing filter property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* read operation */ + iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1, 1); + if(iRet != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "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) == '!') { + f->f_filterData.prop.isNegated = 1; + iOffset = 1; /* ignore '!' */ + } else { + f->f_filterData.prop.isNegated = 0; + iOffset = 0; + } + } else { + f->f_filterData.prop.isNegated = 0; + iOffset = 0; + } + + if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "contains", 8)) { + f->f_filterData.prop.operation = FIOP_CONTAINS; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isequal", 7)) { + f->f_filterData.prop.operation = FIOP_ISEQUAL; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "startswith", 10)) { + f->f_filterData.prop.operation = FIOP_STARTSWITH; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "regex", 5)) { + f->f_filterData.prop.operation = FIOP_REGEX; + } else { + errmsg.LogError(NO_ERRCODE, "error: invalid compare operation '%s' - ignoring selector", + (char*) rsCStrGetSzStrNoNULL(pCSCompOp)); + } + rsCStrDestruct(&pCSCompOp); /* no longer needed */ + + /* read compare value */ + iRet = parsQuotedCStr(pPars, &f->f_filterData.prop.pCSCompValue); + if(iRet != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "error %d compare value property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* skip to action part */ + if((iRet = parsSkipWhitespace(pPars)) != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "error %d skipping to action part - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* cleanup */ + *pline = *pline + rsParsGetParsePointer(pPars) + 1; + /* we are adding one for the skipped initial ":" */ + + return rsParsDestruct(pPars); +} + + +/* + * Helper to cfline(). This function interprets a BSD host selector line + * from the config file ("+/-hostname"). It stores it for further reference. + * rgerhards 2005-10-19 + */ +static rsRetVal cflineProcessHostSelector(uchar **pline) +{ + rsRetVal iRet; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(**pline == '-' || **pline == '+'); + + dbgprintf(" - host selector line\n"); + + /* check include/exclude setting */ + if(**pline == '+') { + eDfltHostnameCmpMode = HN_COMP_MATCH; + } else { /* we do not check for '-', it must be, else we wouldn't be here */ + eDfltHostnameCmpMode = HN_COMP_NOMATCH; + } + (*pline)++; /* eat + or - */ + + /* the below is somewhat of a quick hack, but it is efficient (this is + * why it is in here. "+*" resets the tag selector with BSD syslog. We mimic + * this, too. As it is easy to check that condition, we do not fire up a + * parser process, just make sure we do not address beyond our space. + * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + */ + if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { + dbgprintf("resetting BSD-like hostname filter\n"); + eDfltHostnameCmpMode = HN_NO_COMP; + if(pDfltHostnameCmp != NULL) { + if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, NULL)) != RS_RET_OK) + return(iRet); + } + } else { + dbgprintf("setting BSD-like hostname filter to '%s'\n", *pline); + if(pDfltHostnameCmp == NULL) { + /* create string for parser */ + if((iRet = rsCStrConstructFromszStr(&pDfltHostnameCmp, *pline)) != RS_RET_OK) + return(iRet); + } else { /* string objects exists, just update... */ + if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, *pline)) != RS_RET_OK) + return(iRet); + } + } + return RS_RET_OK; +} + + +/* + * Helper to cfline(). This function interprets a BSD tag selector line + * from the config file ("!tagname"). It stores it for further reference. + * rgerhards 2005-10-18 + */ +static rsRetVal cflineProcessTagSelector(uchar **pline) +{ + rsRetVal iRet; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(**pline == '!'); + + dbgprintf(" - programname selector line\n"); + + (*pline)++; /* eat '!' */ + + /* the below is somewhat of a quick hack, but it is efficient (this is + * why it is in here. "!*" resets the tag selector with BSD syslog. We mimic + * this, too. As it is easy to check that condition, we do not fire up a + * parser process, just make sure we do not address beyond our space. + * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + */ + if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { + dbgprintf("resetting programname filter\n"); + if(pDfltProgNameCmp != NULL) { + if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, NULL)) != RS_RET_OK) + return(iRet); + } + } else { + dbgprintf("setting programname filter to '%s'\n", *pline); + if(pDfltProgNameCmp == NULL) { + /* create string for parser */ + if((iRet = rsCStrConstructFromszStr(&pDfltProgNameCmp, *pline)) != RS_RET_OK) + return(iRet); + } else { /* string objects exists, just update... */ + if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, *pline)) != RS_RET_OK) + return(iRet); + } + } + return RS_RET_OK; +} + + +/* read the filter part of a configuration line and store the filter + * in the supplied selector_t + * rgerhards, 2007-08-01 + */ +static rsRetVal cflineDoFilter(uchar **pp, selector_t *f) +{ + DEFiRet; + + ASSERT(pp != NULL); + ASSERT(f != NULL); + + /* check which filter we need to pull... */ + switch(**pp) { + case ':': + CHKiRet(cflineProcessPropFilter(pp, f)); + break; + case 'i': /* "if" filter? */ + if(*(*pp+1) && (*(*pp+1) == 'f') && isspace(*(*pp+2))) { + CHKiRet(cflineProcessIfFilter(pp, f)); + break; + } + /*FALLTHROUGH*/ + default: + CHKiRet(cflineProcessTradPRIFilter(pp, f)); + break; + } + + /* we now check if there are some global (BSD-style) filter conditions + * and, if so, we copy them over. rgerhards, 2005-10-18 + */ + if(pDfltProgNameCmp != NULL) { + CHKiRet(rsCStrConstructFromCStr(&(f->pCSProgNameComp), pDfltProgNameCmp)); + } + + if(eDfltHostnameCmpMode != HN_NO_COMP) { + f->eHostnameCmpMode = eDfltHostnameCmpMode; + CHKiRet(rsCStrConstructFromCStr(&(f->pCSHostnameComp), pDfltHostnameCmp)); + } + +finalize_it: + RETiRet; +} + + +/* process the action part of a selector line + * rgerhards, 2007-08-01 + */ +static rsRetVal cflineDoAction(uchar **p, action_t **ppAction) +{ + DEFiRet; + modInfo_t *pMod; + omodStringRequest_t *pOMSR; + action_t *pAction; + void *pModData; + + ASSERT(p != NULL); + ASSERT(ppAction != NULL); + + /* loop through all modules and see if one picks up the line */ + pMod = module.GetNxtType(NULL, eMOD_OUT); + while(pMod != NULL) { + pOMSR = NULL; + 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 || iRet == RS_RET_SUSPENDED) { + if((iRet = addAction(&pAction, pMod, pModData, pOMSR, (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) { + /* now check if the module is compatible with select features */ + if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) + pAction->f_ReduceRepeated = bReduceRepeatMsgs; + else { + dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); + pAction->f_ReduceRepeated = 0; + } + pAction->bEnabled = 1; /* action is enabled */ + } + 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; + } + pMod = module.GetNxtType(pMod, eMOD_OUT); + } + + *ppAction = pAction; + RETiRet; +} + + +/* Process a configuration file line in traditional "filter selector" format + * or one that builds upon this format. + */ +static rsRetVal cflineClassic(uchar *p, selector_t **pfCurr) +{ + DEFiRet; + action_t *pAction; + selector_t *fCurr; + + ASSERT(pfCurr != NULL); + + fCurr = *pfCurr; + + /* lines starting with '&' have no new filters and just add + * new actions to the currently processed selector. + */ + if(*p == '&') { + ++p; /* eat '&' */ + skipWhiteSpace(&p); /* on to command */ + } else { + /* we are finished with the current selector (on previous line). + * So we now need to check + * if it has any actions associated and, if so, link it to the linked + * list. If it has nothing associated with it, we can simply discard + * it. In any case, we create a fresh selector for our new filter. + * We have one special case during initialization: then, the current + * selector is NULL, which means we do not need to care about it at + * all. -- rgerhards, 2007-08-01 + */ + CHKiRet(selectorAddList(fCurr)); + CHKiRet(selectorConstruct(&fCurr)); /* create "fresh" selector */ + CHKiRet(cflineDoFilter(&p, fCurr)); /* pull filters */ + } + + CHKiRet(cflineDoAction(&p, &pAction)); + CHKiRet(llAppend(&fCurr->llActList, NULL, (void*) pAction)); + +finalize_it: + *pfCurr = fCurr; + RETiRet; +} + + +/* process a configuration line + * I re-did this functon because it was desperately time to do so + * rgerhards, 2007-08-01 + */ +static rsRetVal +cfline(uchar *line, selector_t **pfCurr) +{ + DEFiRet; + + ASSERT(line != NULL); + + dbgprintf("cfline: '%s'\n", line); + + /* check type of line and call respective processing */ + switch(*line) { + case '!': + iRet = cflineProcessTagSelector(&line); + break; + case '+': + case '-': + iRet = cflineProcessHostSelector(&line); + break; + case '$': + ++line; /* eat '$' */ + iRet = cfsysline(line); + break; + default: + iRet = cflineClassic(line, pfCurr); + break; + } + + 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->doIncludeLine = doIncludeLine; + pIf->cfline = cfline; + pIf->processConfFile = processConfFile; + +finalize_it: +ENDobjQueryInterface(conf) + + +/* exit our class + * rgerhards, 2008-03-11 + */ +BEGINObjClassExit(conf, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(conf) + /* release objects we no longer need */ + objRelease(expr, CORE_COMPONENT); + objRelease(ctok, CORE_COMPONENT); + objRelease(ctok_token, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); +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(expr, CORE_COMPONENT)); + CHKiRet(objUse(ctok, CORE_COMPONENT)); + CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); /* TODO: make this dependcy go away! */ +ENDObjClassInit(conf) + +/* vi:set ai: + */ @@ -0,0 +1,53 @@ +/* Definitions for config file handling (not yet an object). + * + * 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 INCLUDED_CONF_H +#define INCLUDED_CONF_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}; + +/* 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 (*doIncludeLine)(uchar **pp, __attribute__((unused)) void* pVal); + rsRetVal (*cfline)(uchar *line, selector_t **pfCurr); + rsRetVal (*processConfFile)(uchar *pConfFile); +ENDinterface(conf) +#define confCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(conf); + + +/* TODO: remove them below (means move the config init code) -- rgerhards, 2008-02-19 */ +extern EHostnameCmpMode eDfltHostnameCmpMode; +extern cstr_t *pDfltHostnameCmp; +extern cstr_t *pDfltProgNameCmp; + +#endif /* #ifndef INCLUDED_CONF_H */ diff --git a/configure.ac b/configure.ac index 14fb086c..501cca56 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([rsyslog],[2.0.6],[rsyslog@lists.adiscon.com.]) +AC_INIT([rsyslog],[3.18.6],[rsyslog@lists.adiscon.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([syslogd.c]) AC_CONFIG_HEADERS([config.h]) @@ -21,16 +21,21 @@ AC_CANONICAL_HOST case "${host}" in *-*-linux*) - # This feature indicates if klogd functionality - # should be integrated. If it is switched off, klogd - # is still compiled, but it is an empty shell. - AC_DEFINE([FEATURE_KLOGD], [1], [Description]) + os_type="linux" ;; *-*-*darwin*|*-*-freebsd*|*-*-netbsd*|*-*-openbsd*) - AC_DEFINE([BSD], [1], [Description]) + 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" ;; esac +AC_DEFINE_UNQUOTED([HOSTENV], "$host", [the host environment, can be queried via a system variable]) + # Checks for libraries. save_LIBS=$LIBS LIBS= @@ -48,7 +53,7 @@ AC_SUBST(dl_libs) AC_HEADER_RESOLV AC_HEADER_STDC AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS([arpa/inet.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 syslog.h unistd.h utmp.h]) +AC_CHECK_HEADERS([arpa/inet.h libgen.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]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -83,9 +88,24 @@ AC_TYPE_SIGNAL AC_FUNC_STAT AC_FUNC_STRERROR_R AC_FUNC_VPRINTF -AC_FUNC_WAIT3 -AC_CHECK_FUNCS([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]) +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]) +# 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) +) # Large file support AC_ARG_ENABLE(largefile, @@ -111,6 +131,7 @@ AC_ARG_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 @@ -168,6 +189,10 @@ AC_ARG_ENABLE(pthreads, [enable_pthreads=yes] ) +if test "x$enable_pthreads" = "xno"; then + AC_MSG_ERROR(rsyslog v3 does no longer support single threading mode -- use a previous version for that); +fi + if test "x$enable_pthreads" != "xno"; then AC_CHECK_HEADERS( [pthread.h], @@ -190,19 +215,19 @@ if test "x$enable_pthreads" != "xno"; then ) fi -# klogd -AC_ARG_ENABLE(klogd, - [AS_HELP_STRING([--enable-klogd],[Integrated klogd functionality @<:@default=yes@:>@])], +# klog +AC_ARG_ENABLE(klog, + [AS_HELP_STRING([--enable-klog],[Integrated klog functionality @<:@default=yes@:>@])], [case "${enableval}" in - yes) enable_klogd="yes" ;; - no) enable_klogd="no" ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-klogd) ;; + yes) enable_klog="yes" ;; + no) enable_klog="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-klog) ;; esac], - [enable_klogd="yes"] + [enable_klog="yes"] ) -if test "$enable_klogd" = "yes"; then - AC_DEFINE(FEATURE_KLOGD, 1, [klogd functionality is integrated.]) -fi +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) # # SYSLOG_UNIXAF @@ -234,6 +259,7 @@ AC_ARG_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 @@ -269,10 +295,41 @@ AC_ARG_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 + +# 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 + # MySQL support @@ -312,6 +369,7 @@ AM_CONDITIONAL(ENABLE_MYSQL, test x$enable_mysql = xyes) 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@:>@])], @@ -335,7 +393,7 @@ if test "x$enable_pgsql" = "xyes"; then [pq], [PQconnectdb], [pgsql_cflags="-I`pg_config --includedir`" - pgsql_libs="`pg_config --libdir` -lpq" + pgsql_libs="-L`pg_config --libdir` -lpq" ], [AC_MSG_FAILURE([PgSQL library is missing])], [-L`pg_config --libdir`] @@ -346,21 +404,226 @@ AC_SUBST(pgsql_cflags) AC_SUBST(pgsql_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])] + ) +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) + + +# 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) + + +# 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) + + +# 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 >= 0.1.1) +fi +AM_CONDITIONAL(ENABLE_RELP, test x$enable_relp = xyes) +AC_SUBST(RELP_CFLAGS) +AC_SUBST(RELP_LIBS) + +# RFC 3195 support +# WARNING: THIS IS NOT REALLY PRESENT YET - needs to be build manually! +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 + AC_CHECK_HEADERS( + [librfc3195.h],, + [AC_MSG_FAILURE([RFC3195 library is missing (no headers)])] + ) +# I don't know how to tell that librfc3195 needs -lrt, so I disable +# this check for now - the header check should work well enough... +# rgerhards, 2008-03-25 +# AC_CHECK_LIB( +# [rfc3195], +# [rfc3195EngineGetVersion], +# [rfc3195_cflags="" + rfc3195_libs="-lrfc3195" +# ], +# [AC_MSG_FAILURE([RFC3195 library is missing])] +# ) +fi +AM_CONDITIONAL(ENABLE_RFC3195, test x$enable_rfc3195 = xyes) +AC_SUBST(rfc3195_cflags) +AC_SUBST(rfc3195_libs) + + +# settings for the template input module; copy and modify this code +# if you intend to add your own module. Be sure to replace imtemplate +# by the actual name of your 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] +) +# +# you may want to do some library checks here - see snmp, mysql, pgsql modules +# for samples +# +AM_CONDITIONAL(ENABLE_IMFILE, test x$enable_imfile = xyes) + +AM_CONDITIONAL(ENABLE_IMTEMPLATE, test x$enable_imtemplate = xyes) +# end of copy template - be sure to serach for imtemplate to find everything! +# settings for the template input module; copy and modify this code +# if you intend to add your own module. Be sure to replace imtemplate +# by the actual name of your module. +AC_ARG_ENABLE(imtemplate, + [AS_HELP_STRING([--enable-imtemplate],[Compiles imtemplate template module @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_imtemplate="yes" ;; + no) enable_imtemplate="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-imtemplate) ;; + esac], + [enable_imtemplate=no] +) +# +# you may want to do some library checks here - see snmp, mysql, pgsql modules +# for samples +# +AM_CONDITIONAL(ENABLE_IMTEMPLATE, test x$enable_imtemplate = xyes) +# end of copy template - be sure to serach for imtemplate to find everything! + -AC_CONFIG_FILES([Makefile doc/Makefile plugins/omgssapi/Makefile plugins/ommysql/Makefile plugins/ompgsql/Makefile]) +AC_CONFIG_FILES([Makefile \ + doc/Makefile \ + plugins/imudp/Makefile \ + plugins/imtcp/Makefile \ + plugins/imgssapi/Makefile \ + plugins/imuxsock/Makefile \ + plugins/immark/Makefile \ + plugins/imklog/Makefile \ + plugins/imtemplate/Makefile \ + plugins/imfile/Makefile \ + plugins/imrelp/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]) AC_OUTPUT echo "****************************************************" echo "rsyslog will be compiled with the following settings:" echo echo "Multithreading support enabled: $enable_pthreads" -echo "Klogd functionality enabled: $enable_klogd" +echo "Klog functionality enabled: $enable_klog ($os_type)" echo "Regular expressions support enabled: $enable_regexp" echo "Zlib compression support enabled: $enable_zlib" echo "MySql support enabled: $enable_mysql" +echo "libdbi support enabled: $enable_libdbi" echo "PostgreSQL support enabled: $enable_pgsql" +echo "SNMP support enabled: $enable_snmp" +echo "Mail support enabled: $enable_mail" +echo "RELP support enabled: $enable_relp" +echo "file input module enabled: $enable_imfile" +echo "input template module will be compiled: $enable_imtemplate" echo "Large file support enabled: $enable_largefile" echo "Networking support enabled: $enable_inet" echo "Enable GSSAPI Kerberos 5 support: $want_gssapi_krb5" echo "Debug mode enabled: $enable_debug" +echo "Runtime Instrumentation enabled: $enable_rtinst" +echo "valgrind support settings enabled: $enable_valgrind" +echo "rsyslogd will be built: $enable_rsyslogd" @@ -0,0 +1,593 @@ +/* cfgtok.c - helper class to tokenize an input stream - which surprisingly + * currently does not work with streams but with string. But that will + * probably change over time ;) This class was originally written to support + * the expression module but may evolve when (if) the expression module is + * expanded (or aggregated) by a full-fledged ctoken based config parser. + * Obviously, this class is used together with config files and not any other + * parse function. + * + * Module begun 2008-02-19 by Rainer Gerhards + * + * Copyright (C) 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. + */ + +#include "config.h" +#include <stdlib.h> +#include <ctype.h> +#include <strings.h> +#include <assert.h> + +#include "rsyslog.h" +#include "template.h" +#include "ctok.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(var) + + +/* Standard-Constructor + */ +BEGINobjConstruct(ctok) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(ctok) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal ctokConstructFinalize(ctok_t __attribute__((unused)) *pThis) +{ + DEFiRet; + RETiRet; +} + + +/* destructor for the ctok object */ +BEGINobjDestruct(ctok) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(ctok) + /* ... then free resources */ +ENDobjDestruct(ctok) + + +/* unget character from input stream. At most one character can be ungotten. + * This funtion is only permitted to be called after at least one character + * has been read from the stream. Right now, we handle the situation simply by + * moving the string "stream" pointer one position backwards. If we work with + * real streams (some time), the strm object will handle the functionality + * itself. -- rgerhards, 2008-02-19 + */ +static rsRetVal +ctokUngetCharFromStream(ctok_t *pThis, uchar __attribute__((unused)) c) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok); + --pThis->pp; + + RETiRet; +} + + +/* get the next character from the input "stream" (currently just a in-memory + * string...) -- rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetCharFromStream(ctok_t *pThis, uchar *pc) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pc != NULL); + + /* end of string or begin of comment terminates the "stream" */ + if(*pThis->pp == '\0' || *pThis->pp == '#') { + ABORT_FINALIZE(RS_RET_EOS); + } else { + *pc = *pThis->pp; + ++pThis->pp; + } + +finalize_it: + RETiRet; +} + + +/* skip whitespace in the input "stream". + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokSkipWhitespaceFromStream(ctok_t *pThis) +{ + DEFiRet; + uchar c; + + ISOBJ_TYPE_assert(pThis, ctok); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + while(isspace(c)) { + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + + /* we must unget the one non-whitespace we found */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + +dbgprintf("skipped whitepsace, stream now '%s'\n", pThis->pp); +finalize_it: + RETiRet; +} + + +/* get the next word from the input "stream" (currently just a in-memory + * string...). A word is anything from the current location until the + * first non-alphanumeric character. If the word is longer + * than the provided memory buffer, parsing terminates when buffer length + * has been reached. A buffer of 128 bytes or more should always be by + * far sufficient. -- rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetWordFromStream(ctok_t *pThis, uchar *pWordBuf, size_t lenWordBuf) +{ + DEFiRet; + uchar c; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pWordBuf != NULL); + ASSERT(lenWordBuf > 0); + + CHKiRet(ctokSkipWhitespaceFromStream(pThis)); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + while((isalnum(c) || c == '_' || c == '-') && lenWordBuf > 1) { + *pWordBuf++ = c; + --lenWordBuf; + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + *pWordBuf = '\0'; /* there is always space for this - see while() */ + + /* push back the char that we have read too much */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + +finalize_it: + RETiRet; +} + + +/* read in a constant number + * This is the "number" ABNF element + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetNumber(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + number_t n; /* the parsed number */ + uchar c; + int valC; + int iBase; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + + pToken->tok = ctok_NUMBER; + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + if(c == '0') { /* octal? */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); + if(c == 'x') { /* nope, hex! */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); + c = tolower(c); + iBase = 16; + } else { + iBase = 8; + } + } else { + iBase = 10; + } + + n = 0; + /* this loop is quite simple, a variable name is terminated by whitespace. */ + while(isdigit(c) || (c >= 'a' && c <= 'f')) { + if(isdigit(c)) { + valC = c - '0'; + } else { + valC = c - 'a' + 10; + } + + if(valC >= iBase) { + if(iBase == 8) { + ABORT_FINALIZE(RS_RET_INVALID_OCTAL_DIGIT); + } else { + ABORT_FINALIZE(RS_RET_INVALID_HEX_DIGIT); + } + } + /* we now have the next value and know it is right */ + n = n * iBase + valC; + CHKiRet(ctokGetCharFromStream(pThis, &c)); + c = tolower(c); + } + + /* we need to unget the character that made the loop terminate */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + + CHKiRet(var.SetNumber(pToken->pVar, n)); + +finalize_it: + RETiRet; +} + + +/* read in a variable + * This covers both msgvar and sysvar from the ABNF. + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetVar(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + uchar c; + cstr_t *pstrVal; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + + if(c == '$') { /* second dollar, we have a system variable */ + pToken->tok = ctok_SYSVAR; + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* "eat" it... */ + } else { + pToken->tok = ctok_MSGVAR; + } + + CHKiRet(rsCStrConstruct(&pstrVal)); + /* this loop is quite simple, a variable name is terminated by whitespace. */ + while(!isspace(c)) { + CHKiRet(rsCStrAppendChar(pstrVal, tolower(c))); + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + CHKiRet(rsCStrFinish(pStrB)); + + CHKiRet(var.SetString(pToken->pVar, pstrVal)); + pstrVal = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pstrVal != NULL) { + rsCStrDestruct(&pstrVal); + } + } + + RETiRet; +} + + +/* read in a simple string (simpstr in ABNF) + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetSimpStr(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + uchar c; + int bInEsc = 0; + cstr_t *pstrVal; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + + pToken->tok = ctok_SIMPSTR; + + CHKiRet(rsCStrConstruct(&pstrVal)); + CHKiRet(ctokGetCharFromStream(pThis, &c)); + /* while we are in escape mode (had a backslash), no sequence + * terminates the loop. If outside, it is terminated by a single quote. + */ + while(bInEsc || c != '\'') { + if(bInEsc) { + CHKiRet(rsCStrAppendChar(pstrVal, c)); + bInEsc = 0; + } else { + if(c == '\\') { + bInEsc = 1; + } else { + CHKiRet(rsCStrAppendChar(pstrVal, c)); + } + } + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + CHKiRet(rsCStrFinish(pStrB)); + + CHKiRet(var.SetString(pToken->pVar, pstrVal)); + pstrVal = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pstrVal != NULL) { + rsCStrDestruct(&pstrVal); + } + } + + RETiRet; +} + + +/* Unget a token. The token ungotten will be returned the next time + * ctokGetToken() is called. Only one token can be ungotten at a time. + * If a second token is ungotten, the first is lost. This is considered + * a programming error. + * rgerhards, 2008-02-20 + */ +static rsRetVal +ctokUngetToken(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + ASSERT(pThis->pUngotToken == NULL); + + pThis->pUngotToken = pToken; + + RETiRet; +} + + +/* skip an inine comment (just like a C-comment) + * rgerhards, 2008-02-20 + */ +static rsRetVal +ctokSkipInlineComment(ctok_t *pThis) +{ + DEFiRet; + uchar c; + int bHadAsterisk = 0; + + ISOBJ_TYPE_assert(pThis, ctok); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + while(!(bHadAsterisk && c == '/')) { + bHadAsterisk = (c == '*') ? 1 : 0; + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read next */ + } + +finalize_it: + RETiRet; +} + + + +/* Get the *next* token from the input stream. This parses the next token and + * ignores any whitespace in between. End of stream is communicated via iRet. + * The returned token must either be destructed by the caller OR being passed + * back to ctokUngetToken(). + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetToken(ctok_t *pThis, ctok_token_t **ppToken) +{ + DEFiRet; + ctok_token_t *pToken; + uchar c; + uchar szWord[128]; + int bRetry = 0; /* retry parse? Only needed for inline comments... */ + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(ppToken != NULL); + + /* first check if we have an ungotten token and, if so, provide that + * one back (without any parsing). -- rgerhards, 2008-02-20 + */ + if(pThis->pUngotToken != NULL) { + *ppToken = pThis->pUngotToken; + pThis->pUngotToken = NULL; + FINALIZE; + } + + /* setup the stage - create our token */ + CHKiRet(ctok_token.Construct(&pToken)); + CHKiRet(ctok_token.ConstructFinalize(pToken)); + + /* find the next token. We may loop when we have inline comments */ + do { + bRetry = 0; + CHKiRet(ctokSkipWhitespaceFromStream(pThis)); + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + switch(c) { + case '=': /* == */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + pToken->tok = (c == '=')? ctok_CMP_EQ : ctok_INVALID; + break; + case '!': /* != */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + pToken->tok = (c == '=')? ctok_CMP_NEQ : ctok_INVALID; + break; + case '<': /* <, <=, <> */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + if(c == '=') { + pToken->tok = ctok_CMP_LTEQ; + } else if(c == '>') { + pToken->tok = ctok_CMP_NEQ; + } else { + pToken->tok = ctok_CMP_LT; + } + break; + case '>': /* >, >= */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + if(c == '=') { + pToken->tok = ctok_CMP_GTEQ; + } else { + pToken->tok = ctok_CMP_GT; + } + break; + case '+': + pToken->tok = ctok_PLUS; + break; + case '-': + pToken->tok = ctok_MINUS; + break; + case '*': + pToken->tok = ctok_TIMES; + break; + case '/': /* /, /.* ... *./ (comments, mungled here for obvious reasons...) */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + if(c == '*') { + /* we have a comment and need to skip it */ + ctokSkipInlineComment(pThis); + bRetry = 1; + } else { + CHKiRet(ctokUngetCharFromStream(pThis, c)); /* put back, not processed */ + } + pToken->tok = ctok_DIV; + break; + case '%': + pToken->tok = ctok_MOD; + break; + case '(': + pToken->tok = ctok_LPAREN; + break; + case ')': + pToken->tok = ctok_RPAREN; + break; + case ',': + pToken->tok = ctok_COMMA; + break; + case '&': + pToken->tok = ctok_STRADD; + break; + case '$': + CHKiRet(ctokGetVar(pThis, pToken)); + break; + case '\'': /* simple string, this is somewhat more elaborate */ + CHKiRet(ctokGetSimpStr(pThis, pToken)); + break; + case '"': + /* TODO: template string parser */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + default: + CHKiRet(ctokUngetCharFromStream(pThis, c)); /* push back, we need it in any case */ + if(isdigit(c)) { + CHKiRet(ctokGetNumber(pThis, pToken)); + } else { /* now we check if we have a multi-char sequence */ + CHKiRet(ctokGetWordFromStream(pThis, szWord, sizeof(szWord)/sizeof(uchar))); + if(!strcasecmp((char*)szWord, "and")) { + pToken->tok = ctok_AND; + } else if(!strcasecmp((char*)szWord, "or")) { + pToken->tok = ctok_OR; + } else if(!strcasecmp((char*)szWord, "not")) { + pToken->tok = ctok_NOT; + } else if(!strcasecmp((char*)szWord, "contains")) { + pToken->tok = ctok_CMP_CONTAINS; + } else if(!strcasecmp((char*)szWord, "contains_i")) { + pToken->tok = ctok_CMP_CONTAINSI; + } else if(!strcasecmp((char*)szWord, "startswith")) { + pToken->tok = ctok_CMP_STARTSWITH; + } else if(!strcasecmp((char*)szWord, "startswith_i")) { + pToken->tok = ctok_CMP_STARTSWITHI; + } else if(!strcasecmp((char*)szWord, "then")) { + pToken->tok = ctok_THEN; + } else { + /* finally, we check if it is a function */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + if(c == '(') { + /* push c back, higher level parser needs it */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + pToken->tok = ctok_FUNCTION; + // TODO: fill function name + } else { /* give up... */ + pToken->tok = ctok_INVALID; + } + } + } + break; + } + } while(bRetry); /* warning: do ... while()! */ + + *ppToken = pToken; + dbgoprint((obj_t*) pToken, "token: %d\n", pToken->tok); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pToken != NULL) + ctok_token.Destruct(&pToken); + } + + RETiRet; +} + + +/* property set methods */ +/* simple ones first */ +DEFpropSetMeth(ctok, pp, uchar*) + +/* return the current position of pp - most important as currently we do only + * partial parsing, so the rest must know where to start from... + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetpp(ctok_t *pThis, uchar **pp) +{ + DEFiRet; + ASSERT(pp != NULL); + *pp = pThis->pp; + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(ctok) +CODESTARTobjQueryInterface(ctok) + if(pIf->ifVersion != ctokCURR_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). + */ + //xxxpIf->oID = OBJctok; + + pIf->Construct = ctokConstruct; + pIf->ConstructFinalize = ctokConstructFinalize; + pIf->Destruct = ctokDestruct; + pIf->Getpp = ctokGetpp; + pIf->GetToken = ctokGetToken; + pIf->UngetToken = ctokUngetToken; + pIf->Setpp = ctokSetpp; +finalize_it: +ENDobjQueryInterface(ctok) + + + +BEGINObjClassInit(ctok, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, ctokConstructFinalize); +ENDObjClassInit(ctok) + +/* vi:set ai: + */ @@ -0,0 +1,56 @@ +/* The ctok object (implements a config file tokenizer). + * + * 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_CTOK_H +#define INCLUDED_CTOK_H + +#include "obj.h" +#include "stringbuf.h" +#include "ctok_token.h" + +/* the ctokession object */ +typedef struct ctok_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pp; /* this points to the next unread character, it is a reminescent of pp in + the config parser code ;) */ + ctok_token_t *pUngotToken; /* buffer for ctokUngetToken(), NULL if not set */ +} ctok_t; + + +/* interfaces */ +BEGINinterface(ctok) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(ctok); + INTERFACEpropSetMeth(ctok, pp, uchar*); + rsRetVal (*Construct)(ctok_t **ppThis); + rsRetVal (*ConstructFinalize)(ctok_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(ctok_t **ppThis); + rsRetVal (*Getpp)(ctok_t *pThis, uchar **pp); + rsRetVal (*GetToken)(ctok_t *pThis, ctok_token_t **ppToken); + rsRetVal (*UngetToken)(ctok_t *pThis, ctok_token_t *pToken); +ENDinterface(ctok) +#define ctokCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(ctok); + +#endif /* #ifndef INCLUDED_CTOK_H */ diff --git a/ctok_token.c b/ctok_token.c new file mode 100644 index 00000000..0f340675 --- /dev/null +++ b/ctok_token.c @@ -0,0 +1,131 @@ +/* ctok_token - implements the token_t class. + * + * Module begun 2008-02-20 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 <stdlib.h> +#include <ctype.h> +#include <strings.h> +#include <assert.h> + +#include "rsyslog.h" +#include "template.h" +#include "ctok_token.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) + + +/* Standard-Constructor + */ +BEGINobjConstruct(ctok_token) /* be sure to specify the object type also in END macro! */ + /* TODO: we may optimize the code below and alloc var only if actually + * needed (but we need it quite often) + */ + CHKiRet(var.Construct(&pThis->pVar)); + CHKiRet(var.ConstructFinalize(pThis->pVar)); +finalize_it: +ENDobjConstruct(ctok_token) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal ctok_tokenConstructFinalize(ctok_token_t __attribute__((unused)) *pThis) +{ + DEFiRet; + RETiRet; +} + + +/* destructor for the ctok object */ +BEGINobjDestruct(ctok_token) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(ctok_token) + if(pThis->pVar != NULL) { + var.Destruct(&pThis->pVar); + } +ENDobjDestruct(ctok_token) + + +/* get the cstr_t from the token, but do not destruct it. This is meant to + * be used by a caller who passes on the string to some other function. The + * caller is responsible for destructing it. + * rgerhards, 2008-02-20 + */ +static rsRetVal +ctok_tokenUnlinkVar(ctok_token_t *pThis, var_t **ppVar) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok_token); + ASSERT(ppVar != NULL); + + *ppVar = pThis->pVar; + pThis->pVar = NULL; + + RETiRet; +} + + +/* tell the caller if the supplied token is a compare operation */ +static int ctok_tokenIsCmpOp(ctok_token_t *pThis) +{ + return(pThis->tok >= ctok_CMP_EQ && pThis->tok <= ctok_CMP_GTEQ); +} + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(ctok_token) +CODESTARTobjQueryInterface(ctok_token) + if(pIf->ifVersion != ctok_tokenCURR_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). + */ + //xxxpIf->oID = OBJctok_token; + + pIf->Construct = ctok_tokenConstruct; + pIf->ConstructFinalize = ctok_tokenConstructFinalize; + pIf->Destruct = ctok_tokenDestruct; + pIf->UnlinkVar = ctok_tokenUnlinkVar; + pIf->IsCmpOp = ctok_tokenIsCmpOp; +finalize_it: +ENDobjQueryInterface(ctok_token) + + +BEGINObjClassInit(ctok_token, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, ctok_tokenConstructFinalize); +ENDObjClassInit(ctok_token) + +/* vi:set ai: + */ diff --git a/ctok_token.h b/ctok_token.h new file mode 100644 index 00000000..346d5acd --- /dev/null +++ b/ctok_token.h @@ -0,0 +1,89 @@ +/* The ctok_token object + * + * 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_CTOK_TOKEN_H +#define INCLUDED_CTOK_TOKEN_H + +#include "obj.h" +#include "var.h" + +/* the tokens... I use numbers below so that the tokens can be easier + * identified in debug output. These ID's are also partly resused as opcodes. + * As such, they should be kept below 1,000 so that they do not interfer + * with the rest of the opcodes. + */ +typedef struct { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + enum { + ctok_INVALID = 0, + ctok_OR = 1, + ctok_AND = 2, + ctok_PLUS = 3, + ctok_MINUS = 4, + ctok_TIMES = 5, /* "*" */ + ctok_DIV = 6, + ctok_MOD = 7, + ctok_NOT = 8, + ctok_RPAREN = 9, + ctok_LPAREN = 10, + ctok_COMMA = 11, + ctok_SYSVAR = 12, + ctok_MSGVAR = 13, + ctok_SIMPSTR = 14, + ctok_TPLSTR = 15, + ctok_NUMBER = 16, + ctok_FUNCTION = 17, + ctok_THEN = 18, + ctok_STRADD = 19, + ctok_CMP_EQ = 100, /* all compare operations must be in a row */ + ctok_CMP_NEQ = 101, + ctok_CMP_LT = 102, + ctok_CMP_GT = 103, + ctok_CMP_LTEQ = 104, + ctok_CMP_CONTAINS = 105, + ctok_CMP_STARTSWITH = 106, + ctok_CMP_CONTAINSI = 107, + ctok_CMP_STARTSWITHI = 108, + ctok_CMP_GTEQ = 109, /* end compare operations */ + } tok; + var_t *pVar; + //cstr_t *pstrVal; + //int64 intVal; +} ctok_token_t; + + +/* interfaces */ +BEGINinterface(ctok_token) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(ctok_token); + rsRetVal (*Construct)(ctok_token_t **ppThis); + rsRetVal (*ConstructFinalize)(ctok_token_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(ctok_token_t **ppThis); + rsRetVal (*UnlinkVar)(ctok_token_t *pThis, var_t **ppVar); + int (*IsCmpOp)(ctok_token_t *pThis); +ENDinterface(ctok_token) +#define ctok_tokenCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(ctok_token); + +#endif /* #ifndef INCLUDED_CTOK_TOKEN_H */ diff --git a/datetime.c b/datetime.c new file mode 100644 index 00000000..a178d14c --- /dev/null +++ b/datetime.c @@ -0,0 +1,640 @@ +/* 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 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 <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 "sysvar.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + + +/* ------------------------------ methods ------------------------------ */ + + +/** + * 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, all caller-provided pointers must not be NULL... + */ +static void getCurrTime(struct syslogTime *t) +{ + struct timeval tp; + struct tm *tm; + struct tm tmBuf; + long lBias; +# 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 + tm = localtime_r((time_t*) &(tp.tv_sec), &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 = -(daylight ? 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; +} + + + + +/******************************************************************* + * 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. + * \retval The number parsed. + */ + +static int srSLMGParseInt32(char** ppsz) +{ + int i; + + i = 0; + while(isdigit((int) **ppsz)) + { + i = i * 10 + **ppsz - '0'; + ++(*ppsz); + } + + return i; +} + + +/** + * Parse a TIMESTAMP-3339. + * updates the parse pointer position. + */ +static int +ParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS) +{ + char *pszTS = *ppszTS; + + assert(pTime != NULL); + assert(ppszTS != NULL); + assert(pszTS != NULL); + + pTime->year = srSLMGParseInt32(&pszTS); + + /* 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(*pszTS++ != '-') + return FALSE; + pTime->month = srSLMGParseInt32(&pszTS); + if(pTime->month < 1 || pTime->month > 12) + return FALSE; + + if(*pszTS++ != '-') + return FALSE; + pTime->day = srSLMGParseInt32(&pszTS); + if(pTime->day < 1 || pTime->day > 31) + return FALSE; + + if(*pszTS++ != 'T') + return FALSE; + + pTime->hour = srSLMGParseInt32(&pszTS); + if(pTime->hour < 0 || pTime->hour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->minute = srSLMGParseInt32(&pszTS); + if(pTime->minute < 0 || pTime->minute > 59) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->second = srSLMGParseInt32(&pszTS); + if(pTime->second < 0 || pTime->second > 60) + return FALSE; + + /* Now let's see if we have secfrac */ + if(*pszTS == '.') + { + char *pszStart = ++pszTS; + pTime->secfrac = srSLMGParseInt32(&pszTS); + pTime->secfracPrecision = (int) (pszTS - pszStart); + } + else + { + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + } + + /* check the timezone */ + if(*pszTS == 'Z') + { + pszTS++; /* eat Z */ + pTime->OffsetMode = 'Z'; + pTime->OffsetHour = 0; + pTime->OffsetMinute = 0; + } + else if((*pszTS == '+') || (*pszTS == '-')) + { + pTime->OffsetMode = *pszTS; + pszTS++; + + pTime->OffsetHour = srSLMGParseInt32(&pszTS); + if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->OffsetMinute = srSLMGParseInt32(&pszTS); + if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59) + return FALSE; + } + else + /* there MUST be TZ information */ + return FALSE; + + /* OK, we actually have a 3339 timestamp, so let's indicated this */ + if(*pszTS == ' ') + ++pszTS; + else + return FALSE; + + /* update parse pointer */ + *ppszTS = pszTS; + + return TRUE; +} + + +/** + * Parse a TIMESTAMP-3164. + * Returns TRUE on parse OK, FALSE on parse error. + */ +static int +ParseTIMESTAMP3164(struct syslogTime *pTime, char** ppszTS) +{ + char *pszTS; + + assert(ppszTS != NULL); + pszTS = *ppszTS; + assert(pszTS != NULL); + assert(pTime != NULL); + + getCurrTime(pTime); /* obtain the current year and UTC offsets! */ + + /* 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. + * + * 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 + */ + switch(*pszTS++) + { + case 'J': + if(*pszTS == 'a') { + ++pszTS; + if(*pszTS == 'n') { + ++pszTS; + pTime->month = 1; + } else + return FALSE; + } else if(*pszTS == 'u') { + ++pszTS; + if(*pszTS == 'n') { + ++pszTS; + pTime->month = 6; + } else if(*pszTS == 'l') { + ++pszTS; + pTime->month = 7; + } else + return FALSE; + } else + return FALSE; + break; + case 'F': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'b') { + ++pszTS; + pTime->month = 2; + } else + return FALSE; + } else + return FALSE; + break; + case 'M': + if(*pszTS == 'a') { + ++pszTS; + if(*pszTS == 'r') { + ++pszTS; + pTime->month = 3; + } else if(*pszTS == 'y') { + ++pszTS; + pTime->month = 5; + } else + return FALSE; + } else + return FALSE; + break; + case 'A': + if(*pszTS == 'p') { + ++pszTS; + if(*pszTS == 'r') { + ++pszTS; + pTime->month = 4; + } else + return FALSE; + } else if(*pszTS == 'u') { + ++pszTS; + if(*pszTS == 'g') { + ++pszTS; + pTime->month = 8; + } else + return FALSE; + } else + return FALSE; + break; + case 'S': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'p') { + ++pszTS; + pTime->month = 9; + } else + return FALSE; + } else + return FALSE; + break; + case 'O': + if(*pszTS == 'c') { + ++pszTS; + if(*pszTS == 't') { + ++pszTS; + pTime->month = 10; + } else + return FALSE; + } else + return FALSE; + break; + case 'N': + if(*pszTS == 'o') { + ++pszTS; + if(*pszTS == 'v') { + ++pszTS; + pTime->month = 11; + } else + return FALSE; + } else + return FALSE; + break; + case 'D': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'c') { + ++pszTS; + pTime->month = 12; + } else + return FALSE; + } else + return FALSE; + break; + default: + return FALSE; + } + + /* done month */ + + if(*pszTS++ != ' ') + return FALSE; + + /* we accept a slightly malformed timestamp when receiving. This is + * we accept one-digit days + */ + if(*pszTS == ' ') + ++pszTS; + + pTime->day = srSLMGParseInt32(&pszTS); + if(pTime->day < 1 || pTime->day > 31) + return FALSE; + + if(*pszTS++ != ' ') + return FALSE; + pTime->hour = srSLMGParseInt32(&pszTS); + if(pTime->hour < 0 || pTime->hour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->minute = srSLMGParseInt32(&pszTS); + if(pTime->minute < 0 || pTime->minute > 59) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->second = srSLMGParseInt32(&pszTS); + if(pTime->second < 0 || pTime->second > 60) + return FALSE; + + /* we provide support for an exter ":" 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(*pszTS++ == ':') + ++pszTS; + + /* OK, we actually have a 3164 timestamp, so let's indicate this + * and fill the rest of the properties. */ + pTime->timeType = 1; + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + *ppszTS = pszTS; /* provide updated parse position back to caller */ + return TRUE; +} + +/******************************************************************* + * 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* pDst, size_t iLenDst) +{ + /* 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(pDst != NULL); + + if (iLenDst < 15) /* we need at least 14 bytes + 14 digits for timestamp + '\n' */ + return(0); + + return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); + +} + +int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst) +{ + /* see note in formatTimestampToMySQL, applies here as well */ + assert(ts != NULL); + assert(pDst != NULL); + + if (iLenDst < 21) /* we need 20 bytes + '\n' */ + return(0); + + return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); +} + +/** + * 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, size_t iLenBuf) +{ + int iRet; + char szTZ[7]; /* buffer for TZ information */ + + assert(ts != NULL); + assert(pBuf != NULL); + + if(iLenBuf < 20) + return(0); /* we NEED at least 20 bytes */ + + /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */ + if(ts->OffsetMode == 'Z') { + szTZ[0] = 'Z'; + szTZ[1] = '\0'; + } else { + snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d", + ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute); + } + + if(ts->secfracPrecision > 0) + { /* we now need to include fractional seconds. While doing so, we must look at + * the precision specified. For example, if we have millisec precision (3 digits), a + * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this + * is a huge difference ;). To avoid this, we first create a format string with + * the specific precision and *then* use that format string to do the actual + * formating (mmmmhhh... kind of self-modifying code... ;)). + */ + char szFmtStr[64]; + /* be careful: there is ONE actual %d in the format string below ;) */ + snprintf(szFmtStr, sizeof(szFmtStr), + "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s", + ts->secfracPrecision); + iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second, ts->secfrac, szTZ); + } + else + iRet = snprintf(pBuf, iLenBuf, + "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s", + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second, szTZ); + return(iRet); +} + +/** + * 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. + */ +int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar", + "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec"}; + assert(ts != NULL); + assert(pBuf != NULL); + + if(iLenBuf < 16) + return(0); /* we NEED 16 bytes */ + return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d", + monthNames[ts->month], ts->day, ts->hour, + ts->minute, ts->second + )); +} + +/** + * Format a syslogTimestamp to a text format. + * 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. + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + assert(ts != NULL); + assert(pBuf != NULL); + + if(ts->timeType == 1) { + return(formatTimestamp3164(ts, pBuf, iLenBuf)); + } + + if(ts->timeType == 2) { + return(formatTimestamp3339(ts, pBuf, iLenBuf)); + } + + return(0); +} +#endif +/* 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->ParseTIMESTAMP3339 = ParseTIMESTAMP3339; + pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164; + pIf->formatTimestampToMySQL = formatTimestampToMySQL; + pIf->formatTimestampToPgSQL = formatTimestampToPgSQL; + pIf->formatTimestamp3339 = formatTimestamp3339; + pIf->formatTimestamp3164 = formatTimestamp3164; +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/datetime.h b/datetime.h new file mode 100644 index 00000000..9e115583 --- /dev/null +++ b/datetime.h @@ -0,0 +1,57 @@ +/* The datetime object. Contains time-related functions. + * + * 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 INCLUDED_DATETIME_H +#define INCLUDED_DATETIME_H + +#include "datetime.h" + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the datetime object */ +typedef struct datetime_s { +} datetime_t; + + +/* interfaces */ +BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */ + void (*getCurrTime)(struct syslogTime *t); + //static int srSLMGParseInt32(char** ppsz); + int (*ParseTIMESTAMP3339)(struct syslogTime *pTime, char** ppszTS); + int (*ParseTIMESTAMP3164)(struct syslogTime *pTime, char** pszTS); + int (*formatTimestampToMySQL)(struct syslogTime *ts, char* pDst, size_t iLenDst); + int (*formatTimestampToPgSQL)(struct syslogTime *ts, char *pDst, size_t iLenDst); + int (*formatTimestamp3339)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); + int (*formatTimestamp3164)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); +ENDinterface(datetime) +#define datetimeCURR_IF_VERSION 2 /* 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 + */ + +/* prototypes */ +PROTOTYPEObj(datetime); + +#endif /* #ifndef INCLUDED_DATETIME_H */ diff --git a/debug.c b/debug.c new file mode 100644 index 00000000..29c65cf1 --- /dev/null +++ b/debug.c @@ -0,0 +1,1331 @@ +/* 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 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" /* 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 "rsyslog.h" +#include "debug.h" +#include "atomic.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 char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */ +static FILE *altdbg = NULL; /* and the handle for alternate debug output */ +static FILE *stddbg; + +/* 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 mutdbgprintf; +static pthread_mutex_t mutdbgoprint; + +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 ------------------------- */ + +/* ########################################################################### + * 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; + dbgMutLog_t *pLog; + char pszBuf[128]; + char pszHolderThrdName[64]; + char *pszHolder; + + pthread_mutex_lock(&mutMutLog); + pHolder = dbgMutLogFindHolder(pmut); + pLog = 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 */ + pLog = 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); + assert(pLog != NULL); + + /* 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_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) +{ + 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 + */ +static 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"); + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + + /* 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(); +} + + +/* 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, ...) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + va_list ap; + static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + static char pszWriteBuf[1024]; + size_t lenWriteBuf; + struct timespec t; + + 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 + + + pthread_mutex_lock(&mutdbgoprint); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgoprint); + + /* 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) { + if(stddbg != NULL) fprintf(stddbg, "\n"); + if(altdbg != NULL) fprintf(altdbg, "\n"); + 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) { + clock_gettime(CLOCK_REALTIME, &t); + if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + } + if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); + if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); + /* print object name header if we have an object */ + if(pObj != NULL) { + if(stddbg != NULL) fprintf(stddbg, "%s: ", obj.GetName(pObj)); + if(altdbg != NULL) fprintf(altdbg, "%s: ", obj.GetName(pObj)); + } + } + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ + lenWriteBuf = sizeof(pszWriteBuf) - 1; + } + va_end(ap); + /* + if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); + if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); + */ + if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); + if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); + + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + pthread_cleanup_pop(1); +} + + +/* print some debug output when no object is given + * WARNING: duplicate code, see dbgoprin above! + */ +void +dbgprintf(char *fmt, ...) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + va_list ap; + static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + static char pszWriteBuf[1024]; + size_t lenWriteBuf; + struct timespec t; + + if(!(Debug && debugging_on)) + return; + + pthread_mutex_lock(&mutdbgprintf); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprintf); + + /* 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) { + if(stddbg != NULL) fprintf(stddbg, "\n"); + if(altdbg != NULL) fprintf(altdbg, "\n"); + 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) { + clock_gettime(CLOCK_REALTIME, &t); + if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + } + if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); + if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); + } + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ + lenWriteBuf = sizeof(pszWriteBuf) - 1; + } + va_end(ap); + /* + if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); + if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); + */ + if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); + if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); + + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + pthread_cleanup_pop(1); +} + +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 = dbgGetThrdInfo(); + 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)); + + 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! */ + ATOMIC_INC(pFuncDB->nTimesCalled); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) + 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(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; + +printf("Name %s added to %p\n", pName, *ppRoot); +} + + +/* 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; +} + + +/* read in the runtime options + * rgerhards, 2008-02-28 + */ +static void +dbgGetRuntimeOptions(void) +{ + uchar *pszOpts; + uchar *optval; + uchar *optname; + + /* set some defaults */ + stddbg = stdout; + + 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 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" + "filetrace=file (may be provided multiple times)\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 = 1; + debugging_on = 1; + } 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 = NULL; + } else if(!strcasecmp((char*)optname, "noaborttrace")) { + bAbortTrace = 0; + } else if(!strcasecmp((char*)optname, "filetrace")) { + if(*optval == '\0') { + fprintf(stderr, "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, "Error: invalid debug option '%s', value '%s' - ignored\n", + optval, optname); + } + } + } +} + + +/* end support system to set debug options at runtime */ + +rsRetVal dbgClassInit(void) +{ + DEFiRet; + + struct sigaction sigAct; + sigset_t sigSet; + + (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */ + + /* we initialize all Mutexes with code, as some platforms seem to have + * bugs in the static initializer macros. So better be on the safe side... + * rgerhards, 2008-03-06 + */ + pthread_mutex_init(&mutFuncDBList, NULL); + pthread_mutex_init(&mutMutLog, NULL); + pthread_mutex_init(&mutCallStack, NULL); + pthread_mutex_init(&mutdbgprintf, NULL); + pthread_mutex_init(&mutdbgoprint, NULL); + + /* 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 = fopen(pszAltDbgFileName, "w")) == NULL) { + fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno)); + } + } + + dbgSetThrdName((uchar*)"main thread"); + +finalize_it: + RETiRet; +} + + +rsRetVal dbgClassExit(void) +{ + dbgFuncDBListEntry_t *pFuncDBListEtry, *pToDel; + pthread_key_delete(keyCallStack); + + if(bPrintAllDebugOnExit) + dbgPrintAllDebugInfo(); + + if(altdbg != NULL) + fclose(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/debug.h b/debug.h new file mode 100644 index 00000000..4dcc593a --- /dev/null +++ b/debug.h @@ -0,0 +1,145 @@ +/* debug.h + * + * Definitions for the debug and run-time analysis support module. + * Contains a lot of macros. + * + * 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 DEBUG_H_INCLUDED +#define DEBUG_H_INCLUDED + +#include <pthread.h> +#include "obj-types.h" + +/* 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 */ + +/* 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 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 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); + +/* macros */ +#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 + +/* mutex operations */ +#define MUTOP_LOCKWAIT 1 +#define MUTOP_LOCK 2 +#define MUTOP_UNLOCK 3 + + +/* debug aides */ +#ifdef RTINST +#define d_pthread_mutex_lock(x) dbgMutexLock(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_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/doc/Makefile.am b/doc/Makefile.am index 5dba5e89..34990d90 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,26 +1,49 @@ html_files = \ + index.html \ bugs.html \ + debug.html \ features.html \ generic_design.html \ + expression.html \ history.html \ how2help.html \ install.html \ + build_from_repo.html \ ipv6.html \ + log_rotation_fix_size.html \ manual.html \ man_rsyslogd.html \ modules.html \ property_replacer.html \ + rsyslog_ng_comparison.html \ rsyslog_conf.html \ rsyslog-example.conf \ rsyslog_mysql.html \ rsyslog_packages.html \ + rsyslog_high_database_rate.html \ rsyslog_php_syslog_ng.html \ rsyslog_recording_pri.html \ + rsyslog_reliable_forwarding.html \ rsyslog_stunnel.html \ - professional_support.html \ syslog-protocol.html \ version_naming.html \ contributors.html \ + dev_queue.html \ + omsnmp.html \ + omlibdbi.html \ + imfile.html \ + imtcp.html \ + imgssapi.html \ + imrelp.html \ + imuxsock.html \ + imklog.html \ + professional_support.html \ + queues.html \ + queueWorkerLogic.dia \ + queueWorkerLogic.jpg \ + queueWorkerLogic_small.jpg \ + rainerscript.html \ + rscript_abnf.html \ rsconf1_actionexeconlywhenpreviousissuspended.html \ rsconf1_actionresumeinterval.html \ rsconf1_allowedsender.html \ @@ -44,10 +67,19 @@ html_files = \ 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_umask.html + rsconf1_umask.html \ + v3compatibility.html \ + gssapi.html \ + licensing.html \ + ommail.html \ + omrelp.html \ + status.html \ + troubleshoot.html \ + src/classes.dia EXTRA_DIST = $(html_files) diff --git a/doc/build_from_repo.html b/doc/build_from_repo.html new file mode 100644 index 00000000..8d3b20fe --- /dev/null +++ b/doc/build_from_repo.html @@ -0,0 +1,54 @@ +<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). + +<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/debug.html b/doc/debug.html new file mode 100644 index 00000000..de77f04a --- /dev/null +++ b/doc/debug.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>Debug Support</title></head> +<body> +<h1>Debug Support</h1> +<p> +Rsyslog provides a number of debug aids. 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> +<p style="font-weight: bold;">Signals supported</p> +<p>SIGUSR1 - turns debug messages on and off (expect this signal +to go away over time)</p> +<p>SIGUSR2 - 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> +<p style="font-weight: bold;">Environment Variables</p> +<p>There are two environment variables that set several debug settings. 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. Runtime debug support is controlled by +"RSYSLOG_DEBUG". It contains an option string with the following +options possible (all are case insensitive):</p><ul><li><span style="font-weight: bold;">LogFuncFlow</span> - print out the logical flow of functions (entering and exiting them)</li><li><span style="font-weight: bold;">FileTrace</span> - specifies which files to trace LogFuncFlow. If <span style="font-weight: bold;">not</span> +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><span style="font-weight: bold;">PrintFuncDB</span> - print the content of the debug function database whenever debug information is printed (e.g. abort case)!</li><li><span style="font-weight: bold;">PrintAllDebugInfoOnExit</span> - print all debug information immediately before rsyslogd exits (<span style="font-weight: bold; font-style: italic;">currently not implemented!</span>)</li><li><span style="font-weight: bold;">PrintMutexAction</span> - print mutex action as it happens. Useful for finding deadlocks and such.</li><li><span style="font-weight: bold;">NoLogTimeStamp</span> - do not prefix log lines with a timestamp (default is to do that).</li><li><span style="font-weight: bold;">NoStdOut</span> - do not emit debug messages to stdout. If RSYSLOG_DEBUGLOG is not set, this means no messages will be displayed at all.</li><li><span style="font-weight: bold;">help</span> - display a very short list of commands - hopefully a life saver if you can't access the documentation...</li></ul> +<ul> +</ul> +<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>
\ No newline at end of file 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/expression.html b/doc/expression.html new file mode 100644 index 00000000..e7eb7842 --- /dev/null +++ b/doc/expression.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="en"><title>Expressions</title></head> +<body> +<h1>Expressions</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. For now, please see the formal expression definition in <a href="rainerscript.html">RainerScript ABNF</a>. It is the "expr" node.</p><p>C-like comments (/* some comment */) are supported <span style="font-weight: bold;">inside</span> 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 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/features.html b/doc/features.html index c71194dc..13fc34c6 100644 --- a/doc/features.html +++ b/doc/features.html @@ -1,66 +1,127 @@ -<html> -<head> -<title>rsyslog features</title> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog features</title></head> <body> <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><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> - native support for writing to Postgres databases<li>support for (plain) tcp - based syslog - much better reliability<li>support for sending and receiving - compressed syslog messages<li>ability to configure backup syslog/database - servers - if the primary fails, control is switched to a prioritized list of - backups<li>support for receiving messages via
reliable <a href="http://www.monitorware.com/Common/en/glossary/rfc3195.php">
RFC 3195</a> delivery<li>ability to generate file names and directories (log targets) - dynamically, based on many different properties<li>control of log output format, - including ability to present channel and priority as visible log data<li>good timestamp format control; at a minimum, ISO 8601/RFC 3339 - second-resolution UTC zone<li>ability to reformat message contents and work with substrings<li>support for - log files larger than 2gb<li>support for file size limitation and automatic - rollover command execution<li>support for running multiple rsyslogd - instances on a single machine<li>support for <a href="rsyslog_stunnel.html"> - ssl-protected syslog</a> (via stunnel)<li>ability to filter on any part of - the message, not just facility and severity<li>ability to use regular - expressions in filters<li>support for discarding - messages based on filters<li>ability to execute shell scripts on received - messages<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>ability to - preserve the original hostname in NAT environments and relay chains - <li>ability to limit the allowed network senders<li>powerful BSD-style
hostname and program name blocks for easy multi-host support<li>
multi-threaded (<a href="http://rgerhards.blogspot.com/2007/08/why-is-rsyslog-multi-threaded-and-is-it.html">is - this important? why?</a>)<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> - experimental support for syslog-transport-tls based framing on syslog/tcp - connections<li> - a copy of klogd.c has been included under the name of rklogd for those Linux - systems that need one. So rsyslog is a full replacement for the sysklogd - package<li> - support for IPv6<li> - ability to control repeated line reduction ("last message repeated n times") - on a per selector-line basis<li> - supports sub-configuration files, which can be automatically read from - directories. Includes are specified in the main configuration file<li> - supports multiple actions per selector/filter condition<li> - MySQL and Postgres SQL functionality as a dynamically loadable plug-in<li> - support for GSS-API<li> - modular design for outputs - easily extensible</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_stunnel.html"> +ssl-protected syslog</a> (via stunnel)</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> experimental support for syslog-transport-tls based +framing on syslog/tcp connections</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>support for arbitrary complex boolean, string and +arithmetic expressions in message filters</li> +</ul> <p> </p> <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). <b>Please note that rsyslog v2 is feature-complete. New -features will be implemented in the v3 branch only. </b>Version 3 already has a -number of very exciting additional features.</p> +<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 sourceforge tracker.</p> +<ul> +<li>implement native email-functionality in selector (probably +best done as a plug-in)</li> +<li>port it to more *nix variants (eg AIX and HP UX) - this +needs volunteers with access to those machines and knowledge </li> +<li>support for native SSL enryption of plain tcp syslog +sessions. This will most probably happen based on syslog-transport-tls.</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</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> -</body> -</html> +<a href="http://www.rsyslog.com/Topic4.phtml">rsyslog +change log</a> (online only).</p> +</body></html>
\ No newline at end of file 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/history.html b/doc/history.html index 0f9dbffa..a06aaf5d 100644 --- a/doc/history.html +++ b/doc/history.html @@ -1,7 +1,6 @@ -<html> -<head> -<title>rsyslog history</title> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<title>rsyslog history</title></head> <body> <h1>RSyslog - History</h1> @@ -10,28 +9,32 @@ 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 +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 +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 +(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 support 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> +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 @@ -50,31 +53,31 @@ 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 +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>If you are interested in the <a href="http://en.wikipedia.org/wiki/IHE">IHE</a> +</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 +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>In <b>February 2007</b>, 1.13.1 was released and served for quite a +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>In <b>June 2007</b>, Peter Vrabec from Red Hat helped us to create +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>In <b>July 2007</b>, Andrew +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>Also in <b>July 2007</b> (and beginning of +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>In <b>August 2007</b> +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 @@ -82,7 +85,7 @@ quite happy about that. To facilitate user contributions, we set up a 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> +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 @@ -90,7 +93,7 @@ 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><b>December 2007</b> showed the appearance of a postgres +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 @@ -98,13 +101,24 @@ by these rare bugs. Still, the bug hunt is top priority, but we need to have mor 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>On +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>Be sure to visit Rainer's <a href="http://rgerhards.blogspot.com/">syslog blog</a> +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>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> @@ -112,5 +126,4 @@ Don't be shy to post to either the blog or the <ul> <li><a href="http://www.rsyslog.com/Topic4.phtml">the rsyslog change log</a></li> </ul> -</body> -</html> +</body></html>
\ No newline at end of file diff --git a/doc/imfile.html b/doc/imfile.html new file mode 100644 index 00000000..5bdbce5c --- /dev/null +++ b/doc/imfile.html @@ -0,0 +1,132 @@ +<!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> +<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.</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> +<ul> +<li><strong>$InputFileName /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;">$InputFileTag +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;">$InputFileStateFile +<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.</li> +<li><span style="font-weight: bold;">$InputFileFacility +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;">$InputFileSeverity</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><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> +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 $InputFilePollInterval +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> +<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..d644303e --- /dev/null +++ b/doc/imgssapi.html @@ -0,0 +1,52 @@ +<!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> +<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 imtcp # 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 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/imklog.html b/doc/imklog.html new file mode 100644 index 00000000..b5b21e84 --- /dev/null +++ b/doc/imklog.html @@ -0,0 +1,74 @@ +<!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> +<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>$KLogInternalMsgFacility +<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><span style="font-weight: bold;">$KLogPermitNonKernelFacility +[on/<span style="font-style: italic;">off</span>]<br> +</span>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.<span style="font-weight: bold;"></span></li> +<li><span style="font-weight: bold;"></span>$DebugPrintKernelSymbols +(imklog) [on/<b>off</b>]<br> +Linux only, ignored on other platforms (but may be specified)</li> +<li>$klogSymbolLookup (imklog) [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>$klogUseSyscallInterface (imklog) [on/<b>off</b>] +-- former klogd -s option<br> +Linux only, ignored on other platforms (but may be specified)</li> +<li>$klogSymbolsTwice (imklog) [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> +</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><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="15" cols="60">$ModLoad imklog +</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/imrelp.html b/doc/imrelp.html new file mode 100644 index 00000000..bfdaad84 --- /dev/null +++ b/doc/imrelp.html @@ -0,0 +1,52 @@ +<!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> +<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>Configuration Directives</b>:</p> +<ul> +<li>InputRELPServerRun <port><br> +Starts a RELP server on selected port</li> +</ul> +<b>Caveats/Known Bugs:</b> +<ul> +<li>see description</li> +</ul> +<p><b>Sample:</b></p> +<p>This sets up a RELP server on port 20514.<br> +</p> +<textarea rows="15" 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 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..2c8ead56 --- /dev/null +++ b/doc/imtcp.html @@ -0,0 +1,51 @@ +<!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> +<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>Description</b>:</p> +<p>Provides the ability to receive syslog messages via TCP. +Encryption can be provided by using <a href="rsyslog_stunnel.html">stunnel</a> +(an alternative is the use +the <a href="imgssapi.html">imgssapi</a> +modul).</p> +<p>In the future, multiple receivers may be configured by +specifying +$InputTCPServerRun multiple times. This is not currently supported. +</p> +<p><b>Configuration Directives</b>:</p> +<ul> +<li>$InputTCPServerRun <port><br> +Starts a TCP server on selected port</li> +<li>$InputTCPMaxSessions <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> +<li>can not be loaded together with <a href="imgssapi.html">imgssapi</a> +(which includes the functionality of imtcp)</li> +</ul> +<p><b>Sample:</b></p> +<p>This sets up a TCP server on port 514:<br> +</p> +<textarea rows="15" cols="60">$ModLoad imtcp # +needs to be done just once +$InputTCPServerRun 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 © 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/imuxsock.html b/doc/imuxsock.html new file mode 100644 index 00000000..ee367dbc --- /dev/null +++ b/doc/imuxsock.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>Unix Socket Input</title> + +</head> +<body> +<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>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. 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>Application-provided +timestamps are ignored by default. 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 +$InputUnixListenSocketIgnoreMsgTimestamp and $SystemLogSocketIgnoreMsgTimestamp config directives</p><p><b>Configuration Directives</b>:</p> +<ul> +<li><span style="font-weight: bold;">$InputUnixListenSocketIgnoreMsgTimestamp</span> [<span style="font-weight: bold;">on</span>/off]<strong></strong><br>Ignore timestamps included in the message. Applies to the next socket being added.</li><li><span style="font-weight: bold;">$SystemLogSocketIgnoreMsgTimestamp</span> [<span style="font-weight: bold;">on</span>/off]<br>Ignore timestamps included in the messages, applies to messages received via the system log socket.</li><li><span style="font-weight: bold;">$OmitLocalLogging</span> (imuxsock) [on/<b>off</b>] -- +former -o option</li><li><span style="font-weight: bold;">$SystemLogSocketName</span> <name-of-socket> -- +former -p option</li><li><span style="font-weight: bold;">$AddUnixListenSocket</span> <name-of-socket> adds +additional unix socket, default none -- former -a option</li></ul> +<b>Caveats/Known Bugs:</b><br> +<br> +This documentation is sparse and incomplete. +<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="15" cols="60">$ModLoad imuxsock # needs to be done just once +</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>
\ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 00000000..349c8e57 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,32 @@ +<!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/doc-status.html">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 (or HUP) rsyslogd, because otherwise it won't 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 index bee136ce..48b7f649 100644 --- a/doc/install.html +++ b/doc/install.html @@ -1,5 +1,5 @@ <html><head> -<title>SSL Encrypting syslog with stunnel</title> +<title>A guide on HOWTO install rsyslog</title> <meta name="KEYWORDS" content="syslog encryption, rsyslog, stunnel, secure syslog, tcp, reliable, howto, ssl"> </head> <body> @@ -24,7 +24,11 @@ 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. Load the most recent build +<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> @@ -53,7 +57,18 @@ the rsyslogd and the man pages to the relevant directories.</p> <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. Then, edit rsyslog.conf for any +/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 @@ -139,9 +154,12 @@ comments or bug sighting reports are very welcome. Please <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 (c) 2005, 2007 +<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 @@ -151,6 +169,12 @@ comments or bug sighting reports are very welcome. Please 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/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..0b9a3b2e --- /dev/null +++ b/doc/log_rotation_fix_size.html @@ -0,0 +1,59 @@ +<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> +<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 +*.* $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> + +</body> +</html> diff --git a/doc/manual.html b/doc/manual.html index ce874fce..c84b095b 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1,121 +1,104 @@ -<html> - -<head> - -<title>rsyslog documentation</title> - -</head> - +<!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, 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_stunnel.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 is also <a href="professional_support.html">professional rsyslog support</a> available directly from the source!</p> - -<p><b>This documentation is for version 2.0.6 of rsyslog.</b> +<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_stunnel.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 is +also <a href="professional_support.html">professional +rsyslog support</a> available directly from the source!</p> +<p><b>This documentation is for version 3.18.6 (v3-stable branch) of rsyslog.</b> Visit the <i> <a href="http://www.rsyslog.com/doc-status.html">rsyslog status page</a></i></b> to obtain current -version information and project status.</p> - -version information and ports. -<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>.</p> - -<p><b>Follow the links below for the</b></p> - -<ul> - -<li><a href="man_rsyslogd.html">rsyslogd man page</a> - -<li><a href="rsyslog_conf.html">configuration file syntax (rsyslog.conf)</a><li> -<a href="property_replacer.html">property replacer, an important core component</a><li>a commented <a href="sample.conf.html">sample rsyslog.conf</a> - -<li><a href="bugs.html">rsyslog bug list</a><li><a href="rsyslog_packages.html"> -rsyslog packages</a><li><a href="generic_design.html">backgrounder on generic -syslog application design</a><!-- re-enable when updated <li><a href="contributors.html">contributor "Hall -of Fame"</a> --><li><a href="modules.html">description of rsyslog modules</a></ul> - +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 document!</a> It will work even +if you do not read the doc, but doing so will definitely improve your experience.</p> +<p><span style="font-weight: bold;"></span><b>Follow +the links below for the</b><br></p><ul> + +<li><a href="troubleshoot.html">troubleshooting rsyslog problems</a></li> +<li><a href="rsyslog_conf.html">configuration file syntax (rsyslog.conf)</a></li> +<li> <a href="property_replacer.html">property +replacer, an important core component</a></li> +<li>a commented <a href="sample.conf.html">sample +rsyslog.conf</a> +</li> +<li><a href="bugs.html">rsyslog bug list</a></li> +<li><a href="rsyslog_packages.html"> rsyslog +packages</a></li> +<li><a href="generic_design.html">backgrounder on +generic syslog application design</a><!-- not good as it currently is ;) <li><a href="contributors.html">contributor "Hall of Fame"</a>--></li> +<li><a href="modules.html">description of rsyslog +modules</a></li><li><a href="man_rsyslogd.html">rsyslogd man page</a> +(heavily outdated)</li> +</ul> <p><b>We have some in-depth papers on</b></p> - <ul> - - <li><a href="install.html">installing rsyslog</a></li> - <li><a href="ipv6.html">rsyslog and IPv6</a> (which is fully supported)</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</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="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_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_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><a href="dev_queue.html">the rsyslog message queue object (developer's view)</a></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 - +<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</li> - -<li><a href="http://www.rsyslog.com/module-Static_Docs-view-f-manual.html.phtml">rsyslog online documentation</a></li> - -<li><a href="http://www.rsyslog.com/Topic3.phtml">rsyslog FAQ</a></li> - -<li><a href="http://www.rsyslog.com/PNphpBB2.phtml">rsyslog discussion forum</a></li> - -<li><a href="http://www.rsyslog.com/Topic4.phtml">rsyslog change log</a></li> - -<li><a href="http://www.monitorware.com/en/syslog-enabled-products/">syslog device configuration guide</a> (off-site)</li> - +<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://www.rsyslog.com/PNphpBB2.phtml">rsyslog +discussion forum - use this for technical support</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> </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 - +<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://rgerhards.blogspot.com/">blog</a> an interesting read (filter on -syslog and rsyslog tags).</p> - -</body> - -</html> - +<a href="http://rgerhards.blogspot.com/">blog</a> an +interesting read (filter on syslog and rsyslog tags). +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> +</body></html> diff --git a/doc/omlibdbi.html b/doc/omlibdbi.html new file mode 100644 index 00000000..8ff74371 --- /dev/null +++ b/doc/omlibdbi.html @@ -0,0 +1,126 @@ +<!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> +<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>Configuration Directives</b>:</p> +<ul> +<li><span style="font-weight: bold;">$ActionLibdbiDriverDirectory /path/to/dbd/drivers</span><br>This +is a global setting. It points libdbi to its driver directory. Usually, +you do not need to set it. If you installed libdbi-driver's at a +non-standard location, you may need to specify the directory here. If +you are unsure, do <span style="font-weight: bold;">not</span> use this configuration directive. Usually, everything works just fine.<strong></strong></li><li><strong>$ActionLibdbiDriver drivername</strong><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> +<li><span style="font-weight: bold;">$ActionLibdbiHost +hostname</span><br> +The host to connect to.</li> +<li><span style="font-weight: bold;">$ActionLibdbiUserName +user</span><br> +The user used to connect to the database.</li> +<li><span style="font-weight: bold;">$ActionlibdbiPassword</span><br> +That user's password.</li> +<li><span style="font-weight: bold;">$ActionlibdbiDBName +db</span><br> +The database that shall be written to.</li> +<li><span style="font-weight: bold;">selector +line: :omlibdbi:<span style="font-style: italic;">;template</span></span><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="15" 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 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/ommail.html b/doc/ommail.html new file mode 100644 index 00000000..62ded6d0 --- /dev/null +++ b/doc/ommail.html @@ -0,0 +1,128 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>mail output module - sending syslog messages via mail</title> + +</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 recipients email address. There is no default.</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>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/ommysql.html b/doc/ommysql.html new file mode 100644 index 00000000..79d913eb --- /dev/null +++ b/doc/ommysql.html @@ -0,0 +1,46 @@ +<!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> +<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>Configuration Directives</b>:</p> +<p>ommysql mostly uses the "old style" configuration, with almost everything on the +action line itself. A few newer features are being migrated to the new style-config +directive configuration system. +<ul> +<li><b>$ActionOmmysqlServerPort <port></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 directive unless +you know the server is running on a non-standard listen port. +<li>Action parameters: +<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><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 +$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 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..0952cc71 --- /dev/null +++ b/doc/omrelp.html @@ -0,0 +1,54 @@ +<!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> +<h1>RELP Output Module (omlibdbi)</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 the <a href="http://www.librelp.com">librelp</a> +is required for imrelp (it provides the core relp protocol +implementation).</p> +<p><b>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>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). Rsyslog's high-precision timestamp format is used, thus the +special "RSYSLOG_ForwardFormat" (case sensitive!) template is used.<br> +</p> +<textarea rows="15" cols="60">$ModLoad omrelp +# forward messages to the remote server "myserv" on +# port 2514 +*.* :omrelp:centralserv:2514;RSYSLOG_ForwardFormat +</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>
\ No newline at end of file diff --git a/doc/omsnmp.html b/doc/omsnmp.html new file mode 100644 index 00000000..31aaef24 --- /dev/null +++ b/doc/omsnmp.html @@ -0,0 +1,174 @@ +<!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> + +<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>$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/professional_support.html b/doc/professional_support.html index 7f5e8371..1a9e6524 100644 --- a/doc/professional_support.html +++ b/doc/professional_support.html @@ -53,5 +53,5 @@ 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> +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 index 8b777a73..f5fc194c 100644 --- a/doc/property_replacer.html +++ b/doc/property_replacer.html @@ -1,175 +1,323 @@ -<html> -<head> -<title>The Rsyslogd Property Replacer</title> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>The Rsyslogd Property Replacer</title></head> <body> <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> +<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> +<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%</code></b></blockquote> <h2>Available Properties</h2> -<p><b><code>propname</code></b> is the name of the property to access. It is case-sensitive. +<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> -<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>UxTradMsg</b></td><td>will disappear soon - do NOT use!</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)</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 a 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 +<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>uxtradmsg</b></td> +<td>will disappear soon - do NOT use!</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)</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 a textual form (e.g. +"syslog.info")</td> +</tr> +<tr> +<td><span style="font-weight: bold;">iut</span></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 height="24"><b>MSGID</b></td><td height="24">The contents of the MSGID field from IETF draft - draft-ietf-syslog-protocol</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> +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 height="24"><b>msgid</b></td> +<td height="24">The contents of the MSGID field from +IETF draft draft-ietf-syslog-protocol</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><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> -<tr><td><b>$MINUTE</b></td><td>The current minute (2-digit)</td></tr> +<td><b>$minute</b></td> +<td>The current minute (2-digit)</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> +<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>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> -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%"<br> -<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> -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).<br> +<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%"<br> +</p> +<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).<br> +</p> <h2>Property Options</h2> -<b><code>property options</code></b> are case-insensitive. Currently, the following options -are defined:</p> +<b><code>property options</code></b> are +case-insensitive. Currently, the following options are defined: +<p></p> <table> -<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>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><td><b>date-rfc3339</b></td><td>format as RFC 3339 date</td></tr> -<tr> - <td><b>escape-cc</b></td><td>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".<br> - Note: using this option requires that - <a href="../../rsyslog/doc/rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> - is set to off.</td> -</tr> -<tr> - <td><b>space-cc</b></td><td>replace control characters by spaces<br> - Note: using this option requires that - <a href="../../rsyslog/doc/rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> - is set to off.</td> -</tr> -<tr> - <td><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="../../rsyslog/doc/rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a> - is set to off.</td> +<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>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> +<td><b>date-rfc3339</b></td> +<td>format as RFC 3339 date</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> +</tbody> </table> - <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 syntax</a>, this is where you - actually use the property replacer.</li> +<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 +syntax</a>, this is where you actually use the property replacer.</li> </ul> - -</body> -</html> +</body></html> diff --git a/doc/queueWorkerLogic.dia b/doc/queueWorkerLogic.dia Binary files differnew file mode 100644 index 00000000..068ea50c --- /dev/null +++ b/doc/queueWorkerLogic.dia 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/queues.html b/doc/queues.html new file mode 100644 index 00000000..a2074d36 --- /dev/null +++ b/doc/queues.html @@ -0,0 +1,360 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head> +<meta http-equiv="Content-Language" content="de"> +<title>Understanding rsyslog queues</title></head> +<body> + +<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>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>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. </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 +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +outweigh </span>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>QueueWorkerTimeoutShutdown</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.</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 +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +resolution </span>for this scenario.</p> +<p>During +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +throtteling</span>, 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 +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +infinitely</span>, 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>Terminating Queues</h2> +<p>Terminating a process sounds easy, but can be complex. +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +Terminating </span>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> +<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> +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +Wherever </span>"<i><object></i>" was used above in the config file +statements, substitute "<i><object></i>" with either "MainMsg" or "Action". The +former will set main message queue +<span style="font-size: 12pt; line-height: 115%; font-family: 'Times New Roman',serif;" lang="EN-US"> +parameters</span>, 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> + +</body></html>
\ No newline at end of file diff --git a/doc/rainerscript.html b/doc/rainerscript.html new file mode 100644 index 00000000..ef0e41cb --- /dev/null +++ b/doc/rainerscript.html @@ -0,0 +1,63 @@ +<!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. +<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>
\ No newline at end of file diff --git a/doc/rsconf1_actionexeconlywhenpreviousissuspended.html b/doc/rsconf1_actionexeconlywhenpreviousissuspended.html index 3f18e243..d5cf8b14 100644 --- a/doc/rsconf1_actionexeconlywhenpreviousissuspended.html +++ b/doc/rsconf1_actionexeconlywhenpreviousissuspended.html @@ -9,14 +9,14 @@ <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 ActionExecOnlyIfPreviousIsSuspended. It may lead to "interesting" and undesired results (but you can try it if you like).</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>$ActionExecOnlyIfPreviousIsSuspended on +<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>$ActionExecOnlyIfPreviousIsSuspended off # to re-set it for the next selector </b></code></p> +<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> @@ -26,4 +26,4 @@ Copyright © 2007 by <a href="http://www.gerhards.net/rainer">Rainer Gerhard <a href="http://www.adiscon.com/">Adiscon</a>. Released under the GNU GPL version 2 or higher.</font></p> </body> -</html>
\ No newline at end of file +</html> diff --git a/doc/rsconf1_filecreatemode.html b/doc/rsconf1_filecreatemode.html index 7c6f1713..c8440864 100644 --- a/doc/rsconf1_filecreatemode.html +++ b/doc/rsconf1_filecreatemode.html @@ -30,6 +30,6 @@ index</a>] [<a href="http://www.rsyslog.com/">rsyslog site</a>]</p> <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> +version 3 or higher.</font></p> </body> </html> diff --git a/doc/rsconf1_markmessageperiod.html b/doc/rsconf1_markmessageperiod.html new file mode 100644 index 00000000..9b6590cd --- /dev/null +++ b/doc/rsconf1_markmessageperiod.html @@ -0,0 +1,30 @@ +<html> +<head> +<title>rsyslog.conf file</title> +</head> +<body> +<h2>$MarkMessagePeriod</h2> +<p><b>Type:</b> specific to immark input module</p> +<p><b>Default:</b> 1800 (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>
\ No newline at end of file diff --git a/doc/rsconf1_modload.html b/doc/rsconf1_modload.html index 37623a1f..a2b8087a 100644 --- a/doc/rsconf1_modload.html +++ b/doc/rsconf1_modload.html @@ -18,7 +18,7 @@ $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 MySQL # load MySQL functionality<br> +<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 diff --git a/doc/rscript_abnf.html b/doc/rscript_abnf.html new file mode 100644 index 00000000..278fb59c --- /dev/null +++ b/doc/rscript_abnf.html @@ -0,0 +1,41 @@ +<!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<br>msgvar := 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>] +[<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 index 495bc566..a3ec2f1f 100644 --- a/doc/rsyslog-example.conf +++ b/doc/rsyslog-example.conf @@ -34,7 +34,7 @@ $IncludeConfig /etc/rsyslog.d/ # whole directory (must contain t # $ModLoad - Dynamically loads a plug-in and activates it # -------- -$ModLoad MySQL # load MySQL functionality +$ModLoad ommysql # load MySQL functionality $ModLoad /rsyslog/modules/somemodule.so # load a module via absolute path diff --git a/doc/rsyslog_conf.html b/doc/rsyslog_conf.html index 25298d33..1073cf5e 100644 --- a/doc/rsyslog_conf.html +++ b/doc/rsyslog_conf.html @@ -1,540 +1,863 @@ -<html> -<head> -<title>rsyslog.conf file</title> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog.conf file</title></head> <body> <h1>rsyslog.conf configuration file</h1> -<p><b>This document is currently being enhanced. Please pardon its current -appearance.</b></p> -<p><b>Rsyslogd 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 a command line option.</p> +<p><b>This document is currently being enhanced. Please +pardon its current appearance.</b></p> +<p><b>Rsyslogd 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 a command line option.</p> <p><a href="http://wiki.rsyslog.com/index.php/Configuration_Samples"> Configuration file examples can be found in the rsyslog wiki</a>.</p> -<p>There is also one sample file provided together with the documentation set. -If you do not like to read, be sure to have at least a quick look at -<a href="rsyslog-example.conf">rsyslog-example.conf</a>. </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>There is also one sample file provided together with the +documentation set. If you do not like to read, be sure to have at least +a quick look at +<a href="rsyslog-example.conf">rsyslog-example.conf</a>. +</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> +<h2>Modules</h2> +<p>Rsyslog has a modular design. 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> +<ul> +<li><a href="omsnmp.html">omsnmp</a> - SNMP +trap output module</li> +<li><a href="omrelp.html">omrelp</a> - RELP +output module</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="imfile.html">imfile</a> +- input module for text files</li> +<li><a href="imrelp.html">imrelp</a> - RELP +input module</li> +<li>imudp - udp syslog message input</li> +<li><a href="imtcp.html">imtcp</a> - input +plugin for plain tcp syslog</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> +</ul> +<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> +<h2>Lines</h2> +Lines can be continued by specifying a backslash ("\") as the last +character of the line.<br> <h2>Global Directives</h2> -<p>All global directives need to be specified on a line by their own and must -start with a dollar-sign. Here is a list in alphabetical order. Follow links for -a description.</p> +<p>All global directives need to be specified on a line by their +own and must start with a dollar-sign. 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. Directives may change during that +process, we will NOT try hard to maintain backwards compatibility +(after all, v3 is still very early in development and quite +unstable...). So you have been warned ;)</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_actionexeconlywhenpreviousissuspended.html">$ActionExecOnlyWhenPreviousIsSuspended</a></li> - <li><a href="rsconf1_actionresumeinterval.html">$ActionResumeInterval</a></li> - <li><a href="rsconf1_allowedsender.html">$AllowedSender</a></li> - <li><a href="rsconf1_controlcharacterescapeprefix.html">$ControlCharacterEscapePrefix</a></li> - <li><a href="rsconf1_debugprintcfsyslinehandlerlist.html">$DebugPrintCFSyslineHandlerList</a></li> - <li><a href="rsconf1_debugprintmodulelist.html">$DebugPrintModuleList</a></li> - <li><a href="rsconf1_debugprinttemplatelist.html">$DebugPrintTemplateList</a></li> - <li><a href="rsconf1_dircreatemode.html">$DirCreateMode</a></li> - <li><a href="rsconf1_dirgroup.html">$DirGroup</a></li> - <li><a href="rsconf1_dirowner.html">$DirOwner</a></li> - <li><a href="rsconf1_dropmsgswithmaliciousdnsptrrecords.html">$DropMsgsWithMaliciousDnsPTRRecords</a></li> - <li><a href="rsconf1_droptrailinglfonreception.html">$DropTrailingLFOnReception</a></li> - <li><a href="rsconf1_dynafilecachesize.html">$DynaFileCacheSize</a></li> - <li><a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a></li> - <li><a href="rsconf1_failonchownfailure.html">$FailOnChownFailure</a></li> - <li><a href="rsconf1_filecreatemode.html">$FileCreateMode</a></li> - <li><a href="rsconf1_filegroup.html">$FileGroup</a></li> - <li><a href="rsconf1_fileowner.html">$FileOwner</a></li> - <li><a href="rsconf1_gssforwardservicename.html">$GssForwardServiceName</a></li> - <li><a href="rsconf1_gsslistenservicename.html">$GssListenServiceName</a></li> - <li><a href="rsconf1_gssmode.html">$GssMode</a></li> - <li><a href="rsconf1_includeconfig.html">$IncludeConfig</a></li> - <li><a href="rsconf1_mainmsgqueuesize.html">$MainMsgQueueSize</a></li> - <li><a href="rsconf1_moddir.html">$ModDir</a></li> - <li><a href="rsconf1_modload.html">$ModLoad</a></li> - <li><a href="rsconf1_repeatedmsgreduction.html">$RepeatedMsgReduction</a></li> - <li><a href="rsconf1_resetconfigvariables.html">$ResetConfigVariables</a></li> - <li><a href="rsconf1_umask.html">$UMASK</a></li> +<li><a href="rsconf1_actionexeconlywhenpreviousissuspended.html">$ActionExecOnlyWhenPreviousIsSuspended</a></li> +<li>$ActionExecOnlyOnceEveryInterval <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>$ActionFileDefaultTemplate [templateName] - sets a new +default template for file actions</li> +<li>$ActionFileEnableSync [on/<span style="font-weight: bold;">off</span>] - enables file +syncing capability of omfile</li> +<li>$ActionForwardDefaultTemplate [templateName] - sets a new +default template for UDP and plain TCP forwarding action</li> +<li>$ActionGSSForwardDefaultTemplate [templateName] - sets a +new default template for GSS-API forwarding action</li> +<li>$ActionQueueCheckpointInterval <number></li> +<li>$ActionQueueDequeueSlowdown <number> [number +is timeout in <i> micro</i>seconds (1000000us is 1sec!), +default 0 (no delay). Simple rate-limiting!]</li> +<li>$ActionQueueDiscardMark <number> [default +9750]</li> +<li>$ActionQueueDiscardSeverity <number> +[*numerical* severity! default 4 (warning)]</li> +<li>$ActionQueueFileName <name></li> +<li>$ActionQueueHighWaterMark <number> [default +8000]</li> +<li>$ActionQueueImmediateShutdown [on/<b>off</b>]</li> +<li>$ActionQueueSize <number></li> +<li>$ActionQueueLowWaterMark <number> [default +2000]</li> +<li>$ActionQueueMaxFileSize <size_nbr>, default 1m</li> +<li>$ActionQueueTimeoutActionCompletion <number> +[number is timeout in ms (1000ms is 1sec!), default 1000, 0 means +immediate!]</li> +<li>$ActionQueueTimeoutEnqueue <number> [number +is timeout in ms (1000ms is 1sec!), default 2000, 0 means indefinite]</li> +<li>$ActionQueueTimeoutShutdown <number> [number +is timeout in ms (1000ms is 1sec!), default 0 (indefinite)]</li> +<li>$ActionQueueWorkerTimeoutThreadShutdown +<number> [number is timeout in ms (1000ms is 1sec!), +default 60000 (1 minute)]</li> +<li>$ActionQueueType [FixedArray/LinkedList/<b>Direct</b>/Disk]</li> +<li>$ActionQueueSaveOnShutdown [on/<b>off</b>] +</li> +<li>$ActionQueueWorkerThreads <number>, num +worker threads, default 1, recommended 1</li> +<li>$ActionQueueWorkerThreadMinumumMessages +<number>, default 100</li> +<li><a href="rsconf1_actionresumeinterval.html">$ActionResumeInterval</a></li> +<li>$ActionResumeRetryCount <number> [default 0, +-1 means eternal]</li> +<li><a href="rsconf1_allowedsender.html">$AllowedSender</a></li> +<li><a href="rsconf1_controlcharacterescapeprefix.html">$ControlCharacterEscapePrefix</a></li> +<li><a href="rsconf1_debugprintcfsyslinehandlerlist.html">$DebugPrintCFSyslineHandlerList</a></li> + +<li><a href="rsconf1_debugprintmodulelist.html">$DebugPrintModuleList</a></li> +<li><a href="rsconf1_debugprinttemplatelist.html">$DebugPrintTemplateList</a></li> +<li><a href="rsconf1_dircreatemode.html">$DirCreateMode</a></li> +<li><a href="rsconf1_dirgroup.html">$DirGroup</a></li> +<li><a href="rsconf1_dirowner.html">$DirOwner</a></li> +<li><a href="rsconf1_dropmsgswithmaliciousdnsptrrecords.html">$DropMsgsWithMaliciousDnsPTRRecords</a></li> +<li><a href="rsconf1_droptrailinglfonreception.html">$DropTrailingLFOnReception</a></li> +<li><a href="rsconf1_dynafilecachesize.html">$DynaFileCacheSize</a></li> +<li><a href="rsconf1_escapecontrolcharactersonreceive.html">$EscapeControlCharactersOnReceive</a></li> +<li><a href="rsconf1_failonchownfailure.html">$FailOnChownFailure</a></li> +<li><a href="rsconf1_filecreatemode.html">$FileCreateMode</a></li> +<li><a href="rsconf1_filegroup.html">$FileGroup</a></li> +<li><a href="rsconf1_fileowner.html">$FileOwner</a></li> +<li><a href="rsconf1_gssforwardservicename.html">$GssForwardServiceName</a></li> +<li><a href="rsconf1_gsslistenservicename.html">$GssListenServiceName</a></li> +<li><a href="rsconf1_gssmode.html">$GssMode</a></li> +<li><a href="rsconf1_includeconfig.html">$IncludeConfig</a></li><li>MainMsgQueueCheckpointInterval <number></li> +<li>$MainMsgQueueDequeueSlowdown <number> [number +is timeout in <i> micro</i>seconds (1000000us is 1sec!), +default 0 (no delay). Simple rate-limiting!]</li> +<li>$MainMsgQueueDiscardMark <number> [default +9750]</li> +<li>$MainMsgQueueDiscardSeverity <severity> +[either a textual or numerical severity! default 4 (warning)]</li> +<li>$MainMsgQueueFileName <name></li> +<li>$MainMsgQueueHighWaterMark <number> [default +8000]</li> +<li>$MainMsgQueueImmediateShutdown [on/<b>off</b>]</li> +<li><a href="rsconf1_mainmsgqueuesize.html">$MainMsgQueueSize</a></li> +<li>$MainMsgQueueLowWaterMark <number> [default +2000]</li> +<li>$MainMsgQueueMaxFileSize <size_nbr>, default +1m</li> +<li>$MainMsgQueueTimeoutActionCompletion +<number> [number is timeout in ms (1000ms is 1sec!), +default +1000, 0 means immediate!]</li> +<li>$MainMsgQueueTimeoutEnqueue <number> [number +is timeout in ms (1000ms is 1sec!), default 2000, 0 means indefinite]</li> +<li>$MainMsgQueueTimeoutShutdown <number> [number +is timeout in ms (1000ms is 1sec!), default 0 (indefinite)]</li> +<li>$MainMsgQueueWorkerTimeoutThreadShutdown +<number> [number is timeout in ms (1000ms is 1sec!), +default 60000 (1 minute)]</li> +<li>$MainMsgQueueType [<b>FixedArray</b>/LinkedList/Direct/Disk]</li> +<li>$MainMsgQueueSaveOnShutdown [on/<b>off</b>] +</li> +<li>$MainMsgQueueWorkerThreads <number>, num +worker threads, default 1, recommended 1</li> +<li>$MainMsgQueueWorkerThreadMinumumMessages +<number>, default 100</li> +<li><a href="rsconf1_markmessageperiod.html">$MarkMessagePeriod</a> +(immark)</li> +<li><a href="rsconf1_moddir.html">$ModDir</a></li> +<li><a href="rsconf1_modload.html">$ModLoad</a></li> +<li><a href="rsconf1_repeatedmsgreduction.html">$RepeatedMsgReduction</a></li> +<li><a href="rsconf1_resetconfigvariables.html">$ResetConfigVariables</a></li> +<li>$WorkDirectory <name> (directory for spool +and other work files)</li> +<li>$UDPServerAddress <IP> (imudp) -- local IP +address (or name) the UDP listens should bind to</li> +<li>$UDPServerRun <port> (imudp) -- former +-r<port> option, default 514, start UDP server on this +port, "*" means all addresses</li> +<li><a href="rsconf1_umask.html">$UMASK</a></li> </ul> +<p><b>Where <size_nbr> is 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. Form it's +point of view, "1,,0.0.,.,0" also has the value 1000. </p> <h2>Basic Structure</h2> -<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>Rsyslogd -supports the classical, selector-based rule lines. They are still at the heart -of it and all actions are initiated via rule lines. A rule lines is any line not -starting with a $ or the comment sign (#). Lines starting with $ carry -rsyslog-specific directives.<p>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.<br> -<br> -Lines starting with a hash mark ("#'') and empty lines are ignored. - +<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. A rule lines is any line not starting with a $ or the +comment sign (#). Lines starting with $ carry rsyslog-specific +directives.</p> +<p>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.<br> +<br> +Lines starting with a hash mark ("#'') and empty lines are ignored. +</p> <h2>Templates</h2> -<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 +<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>A template consists of a template directive, a name, the actual template text -and optional options. A sample is:</p> -<blockquote><code>$template MyTemplateName,"\7Text %property% some more text\n",<options></code></blockquote> -<p>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 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> -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.<br> -<br> -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).<br> +<p>A template consists of a template directive, a name, the +actual template text and optional options. A sample is:</p> +<blockquote><code>$template MyTemplateName,"\7Text +%property% some more text\n",<options></code></blockquote> +<p>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 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 +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.<br> +<br> +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).<br> <br> Template options are case-insensitive. Currently defined are: </p> -<p><b>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> +<p><b>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>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>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 +<p><b>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>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> +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 TraditionalFormat,%timegenerated% %HOSTNAME% %syslogtag%%msg%\n"<br> -<br> -Properties can be accessed by the <a href="property_replacer.html">property replacer</a> -(see there for details).</p> -<p><b>Please note that as of 1.15.0, templates can also by 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 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> +$template TraditionalFormat,%timegenerated% %HOSTNAME% +%syslogtag%%msg%\n"<br> +<br> +Properties can be accessed by the <a href="property_replacer.html">property +replacer</a> (see there for details).</p> +<p><b>Please note that templates can also by +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 +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> +<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_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> +</ul> <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. Output channels define -the output definition, only. As of this build, they can only be used to write to -files - not pipes, ttys or whatever 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> +<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. +Output channels define the output definition, only. As of this build, +they can only be used to write to files - not pipes, ttys or whatever +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> +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> *.* $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 log rotation script 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> +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> <h2>Filter Conditions</h2> -<p>Rsyslog offers two different types "filter conditions":</p> +<p>Rsyslog offers four different types "filter conditions":</p> <ul> - <li>"traditional" severity and facility based selectors</li> - <li>property-based filters</li> +<li>BSD-style blocks</li> +<li>"traditional" severity and facility based selectors</li> +<li>property-based filters</li> +<li>expression-based filters</li> </ul> <h3>Blocks</h3> -<p>Rsyslogd supports BSD-style blocks inside rsyslog.conf. Each block of lines -is separated from the previous block by a program or hostname specification. A -block will only log messages corresponding to the most recent program and -hostname specifications given. Thus, a block which selects ‘ppp’ as the program, -directly followed by a block that selects messages from the hostname ‘dialhost’, -then the second block will only log messages from the ppp program on dialhost. +<p>Rsyslogd supports BSD-style blocks inside rsyslog.conf. Each +block of lines is separated from the previous block by a program or +hostname specification. A block will only log messages corresponding to +the most recent program and hostname specifications given. Thus, a +block which selects ‘ppp’ as the program, directly followed by a block +that selects messages from the hostname ‘dialhost’, then the second +block will only log messages from the ppp program on dialhost. </p> -<p>A program specification is a line beginning with ‘!prog’ and the following -blocks will be associated with calls to syslog from that specific program. A -program specification for ‘foo’ will also match any message logged by the kernel -with the prefix ‘foo: ’. Alternatively, a program specification ‘-foo’ causes the -following blocks to be applied to messages from any program but the one specified. - -A hostname specification of the form ‘+hostname’ and -the following blocks will be applied to messages received from the specified -hostname. Alternatively, a hostname specification ‘-hostname’ causes the -following blocks to be applied to messages from any host but the one specified. - -If the hostname is given as ‘@’, the local hostname will be used. (NOT YET -IMPLEMENTED) A program or hostname specification may be reset by giving the -program or hostname as ‘*’.</p> -<p>Please note that the "#!prog", "#+hostname" and "#-hostname" syntax available -in BSD syslogd is not supported by rsyslogd. By default, no hostname or program -is set.</p> +<p>A program specification is a line beginning with ‘!prog’ and +the following blocks will be associated with calls to syslog from that +specific program. A program specification for ‘foo’ will also match any +message logged by the kernel with the prefix ‘foo: ’. Alternatively, a +program specification ‘-foo’ causes the following blocks to be applied +to messages from any program but the one specified. A hostname +specification of the form ‘+hostname’ and the following blocks will be +applied to messages received from the specified hostname. +Alternatively, a hostname specification ‘-hostname’ causes the +following blocks to be applied to messages from any host but the one +specified. If the hostname is given as ‘@’, the local hostname will be +used. (NOT YET IMPLEMENTED) A program or hostname specification may be +reset by giving the program or hostname as ‘*’.</p> +<p>Please note that the "#!prog", "#+hostname" and "#-hostname" +syntax available in BSD syslogd is not supported by rsyslogd. By +default, no hostname or program is set.</p> <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 rsyslog(3). The names mentioned -below correspond to the similar LOG_-values in /usr/include/rsyslog.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>Please note that the upcoming next syslog-RFC specifies many more facilities. -Support for them will be added in a future version of rsyslog, which might -require changes to existing configuration files.<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 equation 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 equation sign, just use it intuitively.</p> +<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 rsyslog(3). The names mentioned below correspond to the +similar LOG_-values in /usr/include/rsyslog.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 equation 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 +equation 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. Currently, there is only -a single compare operation (contains) available, but additional operations will be added in the -future.</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 border="1" width="100%" id="table1"> - <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>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 even once they are - implemented, it can make very much sense (performance-wise) to use "startswith".</td> - </tr> - <tr> - <td>regex</td> - <td>Compares the property against the provided regular expression.</td> - </tr> +<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. Currently, there +is only a single compare operation (contains) available, but additional +operations will be added in the future.</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>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 even +once they are implemented, it can make very much sense +(performance-wise) to use "startswith".</p> +</td> +</tr> +<tr> +<td>regex</td> +<td>Compares the property against the provided POSIX +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>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>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><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> +<h3>Expression-Based Filters</h3> +Expression based filters allow +filtering on arbitrary complex expressions, which can include boolean, +arithmetic and string operations. Expression filters will evolve into a +full configuration scripting language. Unfortunately, their syntax will +slightly change during that process. So if you use them now, you need +to be prepared to change your configuration files some time later. +However, we try to implement the scripting facility as soon as possible +(also in respect to stage work needed). So the window of exposure is +probably not too long.<br> +<br> +Expression based filters are indicated by the keyword "if" in column 1 +of a new line. They have this format:<br> +<br> +if expr then action-part-of-selector-line<br> +<br> +"If" and "then" are fixed keywords that mus be present. "expr" is a +(potentially quite complex) expression. So the <a href="expression.h">expression documentation</a> for +details. "action-part-of-selector-line" is an action, just as you know +it (e.g. "/var/log/logfile" to write to that file).<br> +<br> +A few quick samples:<br> +<br> +<code> +*.* /var/log/file1 # the traditional way<br> +if $msg contains 'error' /var/log/errlog # the expression-based way<br> +</code> +<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> +Note that regular expressions are currently NOT +supported in expression-based filters. These will be added later when +function support is added to the expression engine (the reason is that +regular expressions will be a separate loadable module, which requires +some more prequisites before it can be implemented).<br> <h2>ACTIONS</h2> -<p>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.<br> -<br> -Templates can be used with all 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>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.<br> +<br> +Templates can be used with all 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 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>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 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 has to be specified with -full pathname, beginning with a slash "/''.<br> +<p>Typically messages are logged to real files. The file has to +be specified with full pathname, beginning with a slash "/''.<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 +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> +<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> +<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> +<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> +<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> +<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> +<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<br> +<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<br> administration needs.<br> <br> -<b>Please note that this version of rsyslogd by default does NOT forward messages -it has received from the network to another host. Specify the "-h" option to enable this.</b></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 WinSyslog). -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 border="1" width="100%" id="table2"> - <tr> - <td> - <p align="center"><b>z<number></b></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.</td> - </tr> - <tr> - <td> - <p align="center"><b>o</b></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...</td> - </tr> +<b>Please note that this version of rsyslogd by default does NOT +forward messages it has received from the network to another host. +Specify the "-h" option to enable this.</b></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 WinSyslog). 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>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>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: @@ -544,167 +867,182 @@ brackets also work with real host names and IPv4 addresses, too. 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> +<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 the login. -You may specify more than one user by separating them with commas (",''). If -they're logged in they get the message. Don't think a mail would be sent, that -might be too late.</p> +<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 the login. You may specify more than one user +by separating them with commas (",''). If they're logged in they get +the message. Don't think a mail would be sent, that might be too late.</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 ("*'').</p> +<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 ("*'').</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>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>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> +<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> +<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 MySQL</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> +<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>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> +<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> +<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>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> +<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> <h2>TEMPLATE NAME</h2> -<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>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> <h2>EXAMPLES</h2> -<p>Below are example for templates and selector lines. I hope they are -self-explanatory. If not, please see www.monitorware.com/rsyslog/ for advise.</p> +<p>Below are example for templates and selector lines. I hope +they are self-explanatory. If not, please see +www.monitorware.com/rsyslog/ for advise.</p> <h3>TEMPLATES</h3> -<p>Please note that the samples are split across multiple lines. A template MUST -NOT actually be split across multiple lines.<br> +<p>Please note that the samples are split across multiple lines. +A template MUST NOT actually be split across multiple lines.<br> <br> A template that resembles traditional syslogd file output:<br> -$template TraditionalFormat,"%timegenerated% %HOSTNAME%<br> -%syslogtag%%msg:::drop-last-lf%\n"<br> +$template TraditionalFormat,"%timegenerated% %HOSTNAME%<br> +%syslogtag%%msg:::drop-last-lf%\n"<br> <br> A template that tells you a little more about the message:<br> -$template precise,"%syslogpriority%,%syslogfacility%,%timegenerated%,%HOSTNAME%,<br> -%syslogtag%,%msg%\n"<br> +$template +precise,"%syslogpriority%,%syslogfacility%,%timegenerated%,%HOSTNAME%,<br> +%syslogtag%,%msg%\n"<br> <br> A template for RFC 3164 format:<br> -$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%"<br> +$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% +%syslogtag%%msg%"<br> <br> A template for the format traditonally used for user messages:<br> -$template usermsg," XXXX%syslogtag%%msg%\n\r"<br> +$template usermsg," XXXX%syslogtag%%msg%\n\r"<br> <br> And a template with the traditonal wall-message format:<br> -$template wallmsg,"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated%<br> +$template wallmsg,"\r\n\7Message from syslogd@%HOSTNAME% at +%timegenerated%<br> <br> A template that can be used for the database write (please note the SQL<br> template option)<br> -$template MySQLInsert,"insert iut, message, receivedat values<br> +$template MySQLInsert,"insert iut, message, receivedat values<br> ('%iut%', '%msg:::UPPERCASE%', '%timegenerated:::date-mysql%')<br> -into systemevents\r\n", SQL<br> +into systemevents\r\n", SQL<br> <br> -The following template emulates <a href="http://www.winsyslog.com/en/">WinSyslog</a> -format (it's an <a href="http://www.adiscon.com/en/">Adiscon</a> format, you do -not feel bad if you don't know it ;)). It's interesting to see how it takes -different parts out of the date stamps. What happens is that the date stamp is -split into the actual date and time and the these two are combined with just a -comma in between them.<br> +The following template emulates <a href="http://www.winsyslog.com/en/">WinSyslog</a> +format (it's an <a href="http://www.adiscon.com/en/">Adiscon</a> +format, you do not feel bad if you don't know it ;)). It's interesting +to see how it takes different parts out of the date stamps. What +happens is that the date stamp is split into the actual date and time +and the these two are combined with just a comma in between them.<br> <br> -$template WinSyslogFmt,"%HOSTNAME%,%timegenerated:1:10:date-rfc3339%,<br> +$template WinSyslogFmt,"%HOSTNAME%,%timegenerated:1:10:date-rfc3339%,<br> %timegenerated:12:19:date-rfc3339%,%timegenerated:1:10:date-rfc3339%,<br> %timegenerated:12:19:date-rfc3339%,%syslogfacility%,%syslogpriority%,<br> -%syslogtag%%msg%\n"</p> +%syslogtag%%msg%\n"</p> <h3>SELECTOR LINES</h3> <p># Store critical stuff in critical<br> #<br> *.=crit;kern.none /var/adm/critical<br> <br> -This will store all messages with the priority crit in the file /var/adm/critical, -except for any kernel message.<br> +This will store all messages with the priority crit in the file +/var/adm/critical, except for any kernel message.<br> <br> <br> # Kernel messages are first, stored in the kernel<br> @@ -718,20 +1056,21 @@ kern.crit @finlandia;RFC3164fmt<br> kern.crit /dev/console<br> kern.info;kern.!err /var/adm/kernel-info<br> <br> -The first rule direct any message that has the kernel facility to the file /var/adm/kernel.<br> +The first rule direct any message that has the kernel facility to the +file /var/adm/kernel.<br> <br> -The second statement directs all kernel messages of the priority crit and higher -to the remote host finlandia. This is useful, because if the host crashes and -the disks get irreparable errors you might not be able to read the stored -messages. If they're on a remote host, too, you still can try to find out the -reason for the crash.<br> +The second statement directs all kernel messages of the priority crit +and higher to the remote host finlandia. This is useful, because if the +host crashes and the disks get irreparable errors you might not be able +to read the stored messages. If they're on a remote host, too, you +still can try to find out the reason for the crash.<br> <br> -The third rule directs these messages to the actual console, so the person who -works on the machine will get them, too.<br> +The third rule directs these messages to the actual console, so the +person who works on the machine will get them, too.<br> <br> -The fourth line tells rsyslogd to save all kernel messages that come with -priorities from info up to warning in the file /var/adm/kernel-info. Everything -from err and higher is excluded.<br> +The fourth line tells rsyslogd to save all kernel messages that come +with priorities from info up to warning in the file +/var/adm/kernel-info. Everything from err and higher is excluded.<br> <br> <br> # The tcp wrapper loggs with mail.info, we display<br> @@ -739,25 +1078,26 @@ from err and higher is excluded.<br> #<br> mail.=info /dev/tty12<br> <br> -This directs all messages that uses mail.info (in source LOG_MAIL | LOG_INFO) to -/dev/tty12, the 12th console. For example the tcpwrapper tcpd(8) uses this as -it's default.<br> +This directs all messages that uses mail.info (in source LOG_MAIL | +LOG_INFO) to /dev/tty12, the 12th console. For example the tcpwrapper +tcpd(8) uses this as it's default.<br> <br> <br> # Store all mail concerning stuff in a file<br> #<br> mail.*;mail.!=info /var/adm/mail<br> <br> -This pattern matches all messages that come with the mail facility, except for -the info priority. These will be stored in the file /var/adm/mail.<br> +This pattern matches all messages that come with the mail facility, +except for the info priority. These will be stored in the file +/var/adm/mail.<br> <br> <br> # Log all mail.info and news.info messages to info<br> #<br> mail,news.=info /var/adm/info<br> <br> -This will extract all messages that come either with mail.info or with news.info -and store them in the file /var/adm/info.<br> +This will extract all messages that come either with mail.info or with +news.info and store them in the file /var/adm/info.<br> <br> <br> # Log info and notice messages to messages file<br> @@ -765,8 +1105,8 @@ and store them in the file /var/adm/info.<br> *.=info;*.=notice;\<br> mail.none /var/log/messages<br> <br> -This lets rsyslogd log all messages that come with either the info or the notice -facility into the file /var/log/messages, except for all<br> +This lets rsyslogd log all messages that come with either the info or +the notice facility into the file /var/log/messages, except for all<br> messages that use the mail facility.<br> <br> <br> @@ -775,17 +1115,17 @@ messages that use the mail facility.<br> *.=info;\<br> mail,news.none /var/log/messages<br> <br> -This statement causes rsyslogd to log all messages that come with the info -priority to the file /var/log/messages. But any message coming either with the -mail or the news facility will not be stored.<br> +This statement causes rsyslogd to log all messages that come with the +info priority to the file /var/log/messages. But any message coming +either with the mail or the news facility will not be stored.<br> <br> <br> # Emergency messages will be displayed using wall<br> #<br> *.=emerg *<br> <br> -This rule tells rsyslogd to write all emergency messages to all currently logged -in users. This is the wall action.<br> +This rule tells rsyslogd to write all emergency messages to all +currently logged in users. This is the wall action.<br> <br> <br> # Messages of the priority alert will be directed<br> @@ -793,37 +1133,38 @@ in users. This is the wall action.<br> #<br> *.alert root,rgerhards<br> <br> -This rule directs all messages with a priority of alert or higher to the -terminals of the operator, i.e. of the users ``root'' and ``rgerhards'' if -they're logged in.<br> +This rule directs all messages with a priority of alert or higher to +the terminals of the operator, i.e. of the users "root'' and +"rgerhards'' if they're logged in.<br> <br> <br> *.* @finlandia<br> <br> -This rule would redirect all messages to a remote host called finlandia. This is -useful especially in a cluster of machines where all syslog messages will be -stored on only one machine.<br> +This rule would redirect all messages to a remote host called +finlandia. This is useful especially in a cluster of machines where all +syslog messages will be stored on only one machine.<br> <br> -In the format shown above, UDP is used for transmitting the message. The -destination port is set to the default auf 514. Rsyslog is also capable of using -much more secure and reliable TCP sessions for message forwarding. Also, the -destination port can be specified. To select TCP, simply add one additional @ in -front of the host name (that is, @host is UPD, @@host is TCP). For example:<br> +In the format shown above, UDP is used for transmitting the message. +The destination port is set to the default auf 514. Rsyslog is also +capable of using much more secure and reliable TCP sessions for message +forwarding. Also, the destination port can be specified. To select TCP, +simply add one additional @ in front of the host name (that is, @host +is UPD, @@host is TCP). For example:<br> <br> <br> *.* @@finlandia<br> <br> -To specify the destination port on the remote machine, use a colon followed by -the port number after the machine name. The following forwards to port 1514 on -finlandia:<br> +To specify the destination port on the remote machine, use a colon +followed by the port number after the machine name. The following +forwards to port 1514 on finlandia:<br> <br> <br> *.* @@finlandia:1514<br> <br> -This syntax works both with TCP and UDP based syslog. However, you will probably -primarily need it for TCP, as there is no well-accepted port for this transport -(it is non-standard). For UDP, you can usually stick with the default auf 514, -but might want to modify it for security rea-<br> +This syntax works both with TCP and UDP based syslog. However, you will +probably primarily need it for TCP, as there is no well-accepted port +for this transport (it is non-standard). For UDP, you can usually stick +with the default auf 514, but might want to modify it for security rea-<br> sons. If you would like to do that, it's quite easy:<br> <br> <br> @@ -833,27 +1174,28 @@ sons. If you would like to do that, it's quite easy:<br> <br> *.* >dbhost,dbname,dbuser,dbpassword;dbtemplate<br> <br> -This rule writes all message to the database "dbname" hosted on "dbhost". The -login is done with user "dbuser" and password "dbpassword". The actual table -that is updated is specified within the template (which contains the insert -statement). The template is called "dbtemplate" in this case.</p> -<p>:msg,contains,"error" @errorServer</p> -<p>This rule forwards all messages that contain the word "error" in the msg part -to the server "errorServer". Forwarding is via UDP. Please note the colon in -fron</p> +This rule writes all message to the database "dbname" hosted on +"dbhost". The login is done with user "dbuser" and password +"dbpassword". The actual table that is updated is specified within the +template (which contains the insert statement). The template is called +"dbtemplate" in this case.</p> +<p>:msg,contains,"error" @errorServer</p> +<p>This rule forwards all messages that contain the word "error" +in the msg part to the server "errorServer". Forwarding is via UDP. +Please note the colon in fron</p> <h2>CONFIGURATION FILE SYNTAX DIFFERENCES</h2> -<p>Rsyslogd uses a slightly different syntax for its configuration file than the -original BSD sources. Originally all messages of a specific priority and above -were forwarded to the log file. The modifiers ``='', ``!'' and ``-'' were added -to make rsyslogd more flexible and to use it in a more intuitive manner.<br> -<br> -The original BSD syslogd doesn't understand spaces as separators between the -selector and the action field.<br> -<br> -When compared to syslogd from sysklogd package, rsyslogd offers additional -<a href="features.html">features</a> (like template and database support). For obvious reasons, the syntax for -defining such features is available -in rsyslogd, only.<br> - </p> -</body> -</html> +<p>Rsyslogd uses a slightly different syntax for its +configuration file than the original BSD sources. Originally all +messages of a specific priority and above were forwarded to the log +file. The modifiers "='', "!'' and "!-'' were added to make rsyslogd +more flexible and to use it in a more intuitive manner.<br> +<br> +The original BSD syslogd doesn't understand spaces as separators +between the selector and the action field.<br> +<br> +When compared to syslogd from sysklogd package, rsyslogd offers +additional +<a href="features.html">features</a> (like template +and database support). For obvious reasons, the syntax for defining +such features is available in rsyslogd, only.</p> +</body></html> diff --git a/doc/rsyslog_high_database_rate.html b/doc/rsyslog_high_database_rate.html new file mode 100644 index 00000000..158a4df6 --- /dev/null +++ b/doc/rsyslog_high_database_rate.html @@ -0,0 +1,177 @@ +<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> + +<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> + + + +</body> + +</html> diff --git a/doc/rsyslog_mysql.html b/doc/rsyslog_mysql.html index 53ee30cc..a5c72429 100644 --- a/doc/rsyslog_mysql.html +++ b/doc/rsyslog_mysql.html @@ -1,249 +1,262 @@ -<html><head> -<title>Writing syslog Data to MySQL</title> -<meta name="KEYWORDS" content="syslog, mysql, syslog to mysql, howto"> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>Writing syslog Data to MySQL</title> + +<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> (2005-08-02)</i></small></P> +<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.</i></p> +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>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> +<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.</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 +<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 MySQL</code></p> +<p><code>$ModLoad ommysql</code></p> </blockquote> -<p>directive at the begining of /etc/rsyslog.conf</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> +<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>*.* - >database-server,database-name,database-userid,database-password</code></p> +<p><code>*.* :ommysql:database-server,database-name,database-userid,database-password</code></p> </blockquote> -<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> +<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>*.* - >127.0.0.1,syslog,syslogwriter,topsecret</code></p> +<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> +<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.* - >127.0.0.1,syslog,syslogwriter,topsecret</code></p> +<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> +<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><b>This section needs updating. You can now solve the issue with failover -database servers. Read the <a href="rsyslog_conf.html">rsyslog.conf </a>doc on -that</b>.</p> -<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. While rsyslogd could retry until it finally succeeds, that would have -negative impact. Syslog messages keep coming in. If rsyslogd would be busy -retrying the database, it would not be able to process these messages. -Ultimately, this would lead to loss of newly arrived messages.</p> -<p>In most cases, rsyslogd is configured not only to write to the database but -to perform other actions as well. In the always-retry scenario, that would mean -no other actions would be carried out. As such, the design of rsyslogd is -limited to a single retry. If that does not succeed, the current message is will -not be written to the database and the MySQL database writer be suspended for a -short period of time. Obviously, this leads to the loss of the current message -as well as all messages received during the suspension period. But they are only -lost in regard to the database, all other actions are correctly carried out. -While not perfect, we consider this to be a better approach then the potential -loss of all messages in all actions.</p> -<p><b>In short: try to avoid database downtime if you do not want to experience -message loss.</b></p> -<p>Please note that this restriction is not rsyslogd specific. All approaches to -real-time database storage share this problem area.</p> +<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. 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 think about alternate approaches -involving non-real-time database writing (beyond the scope of this paper).</P> -<P>The method outlined in this paper provides an easy to setup and maintain -solution for most use cases, especially with low and medium syslog message -volume (or fast database servers).</P> +<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> +<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> +<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>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-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"> +<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> -</body> -</html> +</body></html> diff --git a/doc/rsyslog_ng_comparison.html b/doc/rsyslog_ng_comparison.html new file mode 100644 index 00000000..aac543a7 --- /dev/null +++ b/doc/rsyslog_ng_comparison.html @@ -0,0 +1,583 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog vs. syslog-ng - a comparison</title> + +</head> +<body> +<h1>rsyslog vs. syslog-ng</h1> +<p><small><i>Written by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> +(2008-04-08)</i></small></p> +<p>We have often been asked about a comparison sheet between +rsyslog and syslog-ng. Unfortunately, I do not know much about +syslog-ng, I did not even use it once. Also, there seems to be no +comprehensive feature sheet available for syslog-ng (that recently +changed, see below). So I started this +comparison, but it probably is not complete. For sure, I miss some +syslog-ng features. This is not an attempt to let rsyslog shine more +than it should. I just used the <a href="features.html">rsyslog +feature sheet</a> as a starting point, simply because it was +available. If you would like to add anything to the chart, or correct +it, please simply <a href="mailto:rgerhards@adiscon.com">drop +me a line</a>. I would love to see a real honest and up-to-date +comparison sheet, so please don't be shy ;)</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">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">UDP</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">TCP</td> +<td valign="top">yes</td> +<td valign="top">yes</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">no</td> +<td></td> +</tr> +<tr> +<td valign="top">RFC 3195/BEEP</td> +<td valign="top">yes (needs separate build process)</td> +<td valign="top">no</td> +<td></td> +</tr> +<tr> +<td valign="top">kernel log</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">file</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">mark message generator as an +optional input</td> +<td valign="top">yes</td> +<td valign="top">no (?)</td> +<td></td> +</tr> +<tr> +<td valign="top">Windows Event Log</td> +<td valign="top">via <a href="http://www.eventreporter.com">EventReporter</a> +or <a href="http://www.mwagent.com">MonitorWare Agent</a> +(both commercial software)</td> +<td valign="top">via separate Windows agent, paid +edition only</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">yes</td> +</tr> +<tr> +<td valign="top">support for GSS-API</td> +<td valign="top">yes</td> +<td valign="top">no (?)</td> +</tr> +<tr> +<td valign="top">ability to limit the allowed +network senders (syslog ACLs)</td> +<td valign="top">yes</td> +<td valign="top">yes (?)</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">no (?)</td> +</tr> +<tr> +<td valign="top">udp syslog</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">syslog over RELP<br> +truly reliable message delivery (<a href="http://rgerhards.blogspot.com/2008/04/on-unreliability-of-plain-tcp-syslog.html">Why +is plain tcp syslog not reliable?</a>)</td> +<td valign="top">yes</td> +<td valign="top">no</td> +</tr> +<tr> +<td valign="top">on the wire (zlib) message +compression</td> +<td valign="top">yes</td> +<td valign="top">no (?)</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">no</td> +</tr> +<tr> +<td valign="top">support for <a href="rsyslog_stunnel.html">ssl-protected +syslog</a> </td> +<td valign="top"><a href="rsyslog_stunnel.html">via +stunnel</a></td> +<td valign="top">via stunnel<br> +paid edition natively</td> +</tr> +<tr> +<td valign="top">support for IETF's new +syslog-protocol draft</td> +<td valign="top">yes</td> +<td valign="top">no</td> +</tr> +<tr> +<td valign="top">support for IPv6</td> +<td valign="top">yes</td> +<td valign="top">yes</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">yes</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">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for hostname</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for application</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for message contents</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +<td></td> +</tr> +<tr> +<td valign="top">Filtering for sending IP address</td> +<td valign="top">yes</td> +<td valign="top">yes</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">no</td> +</tr> +<tr> +<td>support for complex filters, using full boolean algebra +with and/or/not operators and parenthesis</td> +<td>yes</td> +<td>yes</td> +</tr> +<tr> +<td>Support for reusable filters: specify a filter once and +use it in multiple selector lines</td> +<td>no</td> +<td>yes</td> +</tr> +<tr> +<td>support for arbritrary complex arithmetic and string +expressions inside filters</td> +<td>yes</td> +<td>no</td> +</tr> +<tr> +<td valign="top">ability to use regular expressions +in filters</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">support for discarding messages +based on filters</td> +<td valign="top">yes</td> +<td valign="top">yes</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">no</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">yes (via libdibi)</td> +</tr> +<tr> +<td valign="top">PostgreSQL</td> +<td valign="top">yes (native ompgsql, <a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">yes (via libdibi)</td> +</tr> +<tr> +<td valign="top">Oracle</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">yes (via libdibi)</td> +</tr> +<tr> +<td valign="top">SQLite</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">yes (via libdibi)</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">no (?)</td> +</tr> +<tr> +<td valign="top">Sybase (Open TDS)</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">no (?)</td> +</tr> +<tr> +<td valign="top">Firebird/Interbase</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">no (?)</td> +</tr> +<tr> +<td valign="top">Ingres</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">no (?)</td> +</tr> +<tr> +<td valign="top">mSQL</td> +<td valign="top">yes (<a href="omlibdbi.html">omlibdbi</a>)</td> +<td valign="top">no (?)</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">paid edition only</td> +</tr> +<tr> +<td valign="top">ability to limit disk space used +by spool files</td> +<td valign="top">yes</td> +<td valign="top">yes</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">no</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">no</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">no</td> +</tr> +<tr> +<td valign="top">ability to configure backup +syslog/database servers </td> +<td valign="top">yes</td> +<td valign="top">no</td> +</tr> +<tr> +<td>Professional Support</td> +<td><a href="professional_support.html">yes</a></td> +<td>yes</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">clean but not backwards compatible</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">no</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">no</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">no</td> +</tr> +<tr> +<td valign="top">Support for third-party input +plugins</td> +<td valign="top">yes</td> +<td valign="top">no</td> +</tr> +<tr> +</tr> +<tr> +<td valign="top">Support for third-party output +plugins</td> +<td valign="top">yes</td> +<td valign="top">no</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">yes</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">yes</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">no (only via piped external process)</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">yes</td> +</tr> +<tr> +<td valign="top">ability to reformat message +contents and work with substrings</td> +<td valign="top">yes</td> +<td valign="top">I think yes</td> +</tr> +<tr> +<td valign="top">support for log files larger than +2gb</td> +<td valign="top">yes</td> +<td valign="top">yes</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">yes</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">? (but I think yes)</td> +</tr> +<tr> +<td valign="top">ability to execute shell scripts on +received messages</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">ability to pipe messages to a +continously running program</td> +<td valign="top">no</td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">massively multi-threaded for +tomorrow's multi-core machines</td> +<td valign="top">yes</td> +<td valign="top">no (only multithreaded with +database destinations)</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">yes (?)</td> +</tr> +<tr> +<td valign="top">supports multiple actions per +selector/filter condition</td> +<td valign="top">yes</td> +<td valign="top">yes</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"><a href="http://freshmeat.net/projects/php-syslog-ng/"> +php-syslog-ng</a></td> +</tr> +<tr> +<td valign="top">using text files as input source</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">rate-limiting output actions</td> +<td valign="top">yes</td> +<td valign="top">yes</td> +</tr> +<tr> +<td valign="top">discard low-priority messages under +system stress</td> +<td valign="top">yes</td> +<td valign="top">no (?)</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">yes (limited? +"stops accepting messages")</td> +</tr> +<tr> +<td valign="top">rewriting messages</td> +<td valign="top">yes</td> +<td valign="top">yes (at least I think so...)</td> +</tr> +<tr> +<td valign="top">output data into various formats</td> +<td valign="top">yes</td> +<td valign="top">yes (looks somewhat limited to me)</td> +</tr> +<tr> +<td valign="top">ability to control "message +repeated n times" generation</td> +<td valign="top">yes</td> +<td valign="top">no (?)</td> +</tr> +<tr> +<td valign="top">license</td> +<td valign="top">GPLv3 (GPLv2 for v2 branch)</td> +<td valign="top">GPL (paid edition is closed source)</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">many popular *nixes</td> +</tr> +<tr> +<td valign="top">DNS cache</td> +<td valign="top">no</td> +<td valign="top">yes</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>Balabit, the vendor of syslog-ng, has just recently done a +feature sheet. I have not yet been able to fully work through it. In +the mean time, you may want to read it in parallel. It is available at +<a href="http://www.balabit.com/network-security/syslog-ng/features/detailed/">Balabit's +site</a>.</p> +<p>This document is current as of 2008-08-15 and definitely +incomplete (I did not yet manage to complete it!).</p> +</body></html> diff --git a/doc/rsyslog_packages.html b/doc/rsyslog_packages.html index 38c43c93..80ba96c5 100644 --- a/doc/rsyslog_packages.html +++ b/doc/rsyslog_packages.html @@ -5,43 +5,72 @@ <body> <h1>rsyslog packages</h1> <p><b>Thanks to some volunteers, rsyslog is also available in package form on -some distributions.</b> All available packages are listed below. If you would +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 2007-07-06 by +<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> -<p>Red Hat has recently begun to build RPMs for rsyslog. The URL changes, but a -good place to look for them is at -<a href="http://freshmeat.net/projects/rsyslog/">freshmeat's rsyslog info page</a>. -Please note that the Red Hat RPMs do currently <b>not</b> include MySQL -functionality. If you need that, you need to compile from the source tarball -(which honestly is quite easy).</p> -<h2>BSD</h2> -<p>Give <a href="http://www.freshports.org/sysutils/rsyslog/"> -http://www.freshports.org/sysutils/rsyslog/</a> a try.</p> - -<h2>CentOS 4.3</h2> -<a href="http://www.se-community.com/~james/rsyslog/"> -http://www.se-community.com/~james/rsyslog/</a></p> -<p>Maintained by<b> James Bergamin.</b></p> - -<h2>openSUSE</h2> -<a href="http://download.opensuse.org/repositories/home:/darix/"> -http://download.opensuse.org/repositories/home:/darix/</a></p> -<p>Maintained by<b> darix</b></p> - -<h2>Almost any Linux</h2> -<p><b>Bennet Todd</b> maintains packages that should work on almost any Linux. +<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).</p> -<p>Please visit <a href="http://bent.latency.net/bent/"> +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.</p> +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_recording_pri.html b/doc/rsyslog_recording_pri.html index 48852ca2..1dcf00c7 100644 --- a/doc/rsyslog_recording_pri.html +++ b/doc/rsyslog_recording_pri.html @@ -1,5 +1,5 @@ <html><head> -<title>Writing syslog Data to MySQL</title> +<title>Recording the Priority of Syslog Messages</title> <meta name="KEYWORDS" content="syslog, mysql, syslog to mysql, howto"> </head> <body> 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/src/classes.dia b/doc/src/classes.dia Binary files differnew file mode 100644 index 00000000..70e91566 --- /dev/null +++ b/doc/src/classes.dia diff --git a/doc/status.html b/doc/status.html index cc197318..63a3f588 100644 --- a/doc/status.html +++ b/doc/status.html @@ -1,48 +1,53 @@ -<html> -<head> -<title>rsyslog status page</title> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog status page</title></head> <body> <h2>rsyslog status page</h2> -<p>This page reflects the status as of 2008-03-27.</p> +<p>This page reflects the status as of 2008-04-15.</p> <h2>Current Releases</h2> -p><b>development:</b> 3.12.4 - -<a href="http://www.rsyslog.com/Article195.phtml">change -log</a> - -<a href="http://www.rsyslog.com/Downloads-index-req-getit-lid-89.phtml">download</a></p> -<p><b><font color="#ff0000"><a href="v3compatibility.html">If you used version 2, be sure to read the rsyslog v3 -compatibility document!</a></font></b><br> -Documentation for 3.x is currently partly sparse. If you need -assistance, please -<a href="http://www.rsyslog.com/PNphpBB2.phtml">post in -the rsyslog forums</a>!</p> -<p><b>stable:</b> 2.0.4 - <a href="http://www.rsyslog.com/Article197.phtml">change log</a> - -<a href="http://www.rsyslog.com/Downloads-index-req-getit-lid-90.phtml">download</a></p> -<p> (<a href="version_naming.html">How are versions named?</a>)</p> + +<p><b>development:</b> 3.17.1 - +<a href="http://www.rsyslog.com/Article213.phtml">change log</a> - +<a href="http://www.rsyslog.com/Downloads-req-viewdownloaddetails-lid-98.phtml">download</a> + +<br><b>beta:</b> 3.15.1 - +<a href="http://www.rsyslog.com/Article210.phtml">change log</a> - +<a href="http://www.rsyslog.com/Downloads-req-viewdownloaddetails-lid-97.phtml">download</a></p> + +<p><b>v3 stable:</b> 3.14.2 - <a href="http://www.rsyslog.com/Article209.phtml">change log</a> - +<a href="http://www.rsyslog.com/Downloads-req-viewdownloaddetails-lid-96.phtml">download</a> + +<br><b>v2 stable:</b> 2.0.4 - <a href="http://www.rsyslog.com/Article197.phtml">change log</a> - +<a href="http://www.rsyslog.com/Downloads-index-req-getit-lid-90.phtml">download</a> +<br>v0 and v1 are deprecated and no longer supported. If you absolutely do not like to +upgrade, you may consider purchasing a +<a href="professional_support.html">commercial rsyslog support package</a>. Just let us point +out that it is really not a good idea to still run a v0 version. + +<p><a href="v3compatibility.html">If you updgrade from version 2, be sure to read the rsyslog v3 +compatibility document.</a></p> +<p>(<a href="version_naming.html">How are versions named?</a>)</p> + <h2>Platforms</h2> -<p>Thankfully, a number of folks have begin to build packages and help port -rsyslog to other platforms. As such, -<a href="http://wiki.rsyslog.com/index.php/Platforms">the platform list is now -maintained inside the rsyslog wiki</a>. Platform maintainers perhaps have posted -extra information there. If you do platform-specific work, feel free to add -information to the wiki.</p> -<p>Rsyslog is the default syslogd in Fedora 8.</p> +<p>Thankfully, a number of folks have begin to build packages and +help port rsyslog to other platforms. As such, +<a href="http://wiki.rsyslog.com/index.php/Platforms">the +platform list is now maintained inside the rsyslog wiki</a>. +Platform maintainers perhaps have posted extra information there. If +you do platform-specific work, feel free to add information to the wiki.</p> +<p>Rsyslog is the default syslogd in Fedora 8 and above.</p> <h2>Additional information</h2> <p><b>Currently supported features are listed on the <a href="features.html">rsyslog features page</a>.</b></p> <ul> - <li>The rsyslog home page is <a href="http://www.rsyslog.com">www.rsyslog.com</a>.</li> - <li>Mailing list info can be found at - <a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">http://lists.adiscon.com/rsyslog</a>.</li> - <li>The change log can be found at - <a href="http://www.rsyslog.com/Topic4.phtml"> - http://www.rsyslog.com/Topic4.phtml</a>. </li> - <li>Online documentation is available at - <a href="http://www.rsyslog.com/doc">http://www.rsyslog.com/doc</a>.</li> - <li>You may also find <a href="http://rgerhards.blogspot.com/">Rainer's blog</a> an interesting read.</li> +<li>The rsyslog home page is <a href="http://www.rsyslog.com">www.rsyslog.com</a>.</li> +<li>Mailing list info can be found at <a href="http://lists.adiscon.net/mailman/listinfo/rsyslog">http://lists.adiscon.com/rsyslog</a>.</li> +<li>The change log can be found at <a href="http://www.rsyslog.com/Topic4.phtml"> +http://www.rsyslog.com/Topic4.phtml</a>. </li> +<li>Online documentation is available at <a href="http://www.rsyslog.com/doc">http://www.rsyslog.com/doc</a>.</li> +<li>You may also find <a href="http://rgerhards.blogspot.com/">Rainer's blog</a> +an interesting read.</li> </ul> -<p>The project was initiated in 2004 by +<p>The project was initiated in 2003 and seriouosly begun in 2004 by <a href="http://www.gerhards.net/rainer">Rainer Gerhards</a> -and is currently being maintained by him. See the <a href="history.html">history -page</a> for more background information.</p> -</body> -</html> +and is currently being maintained by him. See the <a href="history.html">history page</a> for more +background information.</p> +</body></html> diff --git a/doc/syslog_parsing.html b/doc/syslog_parsing.html new file mode 100644 index 00000000..57da6657 --- /dev/null +++ b/doc/syslog_parsing.html @@ -0,0 +1,196 @@ +<!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>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. +<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 © 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/troubleshoot.html b/doc/troubleshoot.html new file mode 100644 index 00000000..b1e9c4ae --- /dev/null +++ b/doc/troubleshoot.html @@ -0,0 +1,38 @@ +<!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>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. +</ul> +<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>[<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 2 or higher.</font></p> +</body> +</html> + diff --git a/doc/v3compatibility.html b/doc/v3compatibility.html new file mode 100644 index 00000000..51619947 --- /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> +$MarkMessageInterval 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> +$UDPSeverAddress 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> +$UDPSeverAddress 192.0.2.1 # this MUST be before the $UDPServerRun +directive!<br> +$UDPServerRun 514<br> +$UDPSeverAddress * # 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/version_naming.html b/doc/version_naming.html index a685f5ff..8c1b9187 100644 --- a/doc/version_naming.html +++ b/doc/version_naming.html @@ -1,33 +1,130 @@ -<html> -<head> -<title>rsyslog bugs and annoyances</title> -</head> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html><head><title>rsyslog version naming</title></head> <body> <h1>Version Naming</h1> -<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://rgerhards.blogspot.com/2007/08/on-rsyslog-versions.html">blog +<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://rgerhards.blogspot.com/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> +<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>
\ No newline at end of file diff --git a/errmsg.c b/errmsg.c new file mode 100644 index 00000000..907046b9 --- /dev/null +++ b/errmsg.c @@ -0,0 +1,120 @@ +/* The errmsg object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c + * + * 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 <stdarg.h> +#include <errno.h> +#include <assert.h> + +#include "rsyslog.h" +#include "syslogd.h" +#include "obj.h" +#include "errmsg.h" +#include "sysvar.h" +#include "srUtils.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + + +/* TODO: restructure this code some time. Especially look if we need + * to check errno and, if so, how to do that in a clean way. + */ +static void __attribute__((format(printf, 2, 3))) +LogError(int __attribute__((unused)) 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 (errno == 0) { + snprintf(msg, sizeof(msg), "%s", buf); + } else { + rs_strerror_r(errno, errStr, sizeof(errStr)); + snprintf(msg, sizeof(msg), "%s: %s", buf, errStr); + } + msg[sizeof(msg)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + errno = 0; + logmsgInternal(LOG_SYSLOG|LOG_ERR, msg, ADDDATE); + + 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) + +/* vi:set ai: + */ diff --git a/errmsg.h b/errmsg.h new file mode 100644 index 00000000..12469581 --- /dev/null +++ b/errmsg.h @@ -0,0 +1,45 @@ +/* The errmsg object. It is used to emit error message inside 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. + */ +#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 { +} errmsg_t; + + +/* interfaces */ +BEGINinterface(errmsg) /* name must also be changed in ENDinterface macro! */ + void __attribute__((format(printf, 2, 3))) (*LogError)(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 */ @@ -6,35 +6,413 @@ * * Module begun 2007-11-30 by Rainer Gerhards * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * Copyright 2007, 2008 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 file is part of the rsyslog runtime library. * - * This program is distributed in the hope that it will be useful, + * 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 General Public License for more details. + * GNU Lesser 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. + * 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. */ -/* This is the syntax of an expression. I keep it as inline documentation - * as this enhances the chance that it is updates should there be a change. +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "template.h" +#include "expr.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmprg) +DEFobjCurrIf(var) +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(ctok) + + +/* ------------------------------ parser functions ------------------------------ */ +/* the following functions implement the parser. They are all static. For + * simplicity, the function names match their ABNF definition. The ABNF is defined + * in the doc set. See file expression.html for details. I do *not* reproduce it + * here in an effort to keep both files in sync. + * + * All functions receive the current expression object as parameter as well as the + * current tokenizer. * - * expr = (simple-string / template-string / function / property) [* expr ] - * simple-string = "'" chars "'" - * template-string = '"' template-as--1.19.11-and-below '"' - * ; string as used in previous $template directive - * function = function-name "(" expr ")" - * property = [list of property names] + * rgerhards, 2008-02-19 */ +/* forward definiton - thanks to recursive ABNF, we can not avoid at least one ;) */ +static rsRetVal expr(expr_t *pThis, ctok_t *tok); + + +static rsRetVal +terminal(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken = NULL; + var_t *pVar; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(ctok.GetToken(tok, &pToken)); + /* note: pToken is destructed in finalize_it */ + + switch(pToken->tok) { + case ctok_SIMPSTR: + dbgoprint((obj_t*) pThis, "simpstr\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHCONSTANT, pVar)); /* add to program */ + break; + case ctok_NUMBER: + dbgoprint((obj_t*) pThis, "number\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHCONSTANT, pVar)); /* add to program */ + break; + case ctok_FUNCTION: + dbgoprint((obj_t*) pThis, "function\n"); + // vm: call - well, need to implement that first + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + case ctok_MSGVAR: + dbgoprint((obj_t*) pThis, "MSGVAR\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHMSGVAR, pVar)); /* add to program */ + break; + case ctok_SYSVAR: + dbgoprint((obj_t*) pThis, "SYSVAR\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHSYSVAR, pVar)); /* add to program */ + break; + case ctok_LPAREN: + dbgoprint((obj_t*) pThis, "expr\n"); + CHKiRet(ctok_token.Destruct(&pToken)); /* "eat" processed token */ + CHKiRet(expr(pThis, tok)); + CHKiRet(ctok.GetToken(tok, &pToken)); /* get next one */ + if(pToken->tok != ctok_RPAREN) + ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); + break; + default: + dbgoprint((obj_t*) pThis, "invalid token %d\n", pToken->tok); + ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); + break; + } + +finalize_it: + if(pToken != NULL) { + ctok_token.Destruct(&pToken); /* "eat" processed token */ + } + + RETiRet; +} + +static rsRetVal +factor(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + int bWasNot; + int bWasUnaryMinus; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(ctok.GetToken(tok, &pToken)); + if(pToken->tok == ctok_NOT) { + dbgprintf("not\n"); + bWasNot = 1; + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); /* get new one for next check */ + } else { + bWasNot = 0; + } + + if(pToken->tok == ctok_MINUS) { + dbgprintf("unary minus\n"); + bWasUnaryMinus = 1; + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + } else { + bWasUnaryMinus = 0; + /* we could not process the token, so push it back */ + CHKiRet(ctok.UngetToken(tok, pToken)); + } + + CHKiRet(terminal(pThis, tok)); + + /* warning: the order if the two following ifs is important. Do not change them, this + * would change the semantics of the expression! + */ + if(bWasUnaryMinus) { + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_UNARY_MINUS, NULL)); /* add to program */ + } + + if(bWasNot == 1) { + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_NOT, NULL)); /* add to program */ + } + +finalize_it: + RETiRet; +} + + +static rsRetVal +term(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(factor(pThis, tok)); + + /* *(("*" / "/" / "%") factor) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_TIMES || pToken->tok == ctok_DIV || pToken->tok == ctok_MOD) { + dbgoprint((obj_t*) pThis, "/,*,%%\n"); + CHKiRet(factor(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, (opcode_t) pToken->tok, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + +static rsRetVal +val(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(term(pThis, tok)); + + /* *(("+" / "-") term) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_PLUS || pToken->tok == ctok_MINUS || pToken->tok == ctok_STRADD) { + dbgoprint((obj_t*) pThis, "+/-/&\n"); + CHKiRet(term(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, (opcode_t) pToken->tok, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + + +static rsRetVal +e_cmp(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(val(pThis, tok)); + + /* 0*1(cmp_op val) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + if(ctok_token.IsCmpOp(pToken)) { + dbgoprint((obj_t*) pThis, "cmp\n"); + CHKiRet(val(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, (opcode_t) pToken->tok, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + } else { + /* we could not process the token, so push it back */ + CHKiRet(ctok.UngetToken(tok, pToken)); + } + + +finalize_it: + RETiRet; +} + + +static rsRetVal +e_and(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(e_cmp(pThis, tok)); + + /* *("and" e_cmp) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_AND) { + dbgoprint((obj_t*) pThis, "and\n"); + CHKiRet(e_cmp(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_AND, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + + +static rsRetVal +expr(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(e_and(pThis, tok)); + + /* *("or" e_and) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_OR) { + dbgoprint((obj_t*) pThis, "found OR\n"); + CHKiRet(e_and(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_OR, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end parser functions ------------------------------ */ + + +/* ------------------------------ actual expr object functions ------------------------------ */ + +/* Standard-Constructor + * rgerhards, 2008-02-09 (a rainy Tenerife return flight day ;)) + */ +BEGINobjConstruct(expr) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(expr) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal exprConstructFinalize(expr_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, expr); + + RETiRet; +} + + +/* destructor for the expr object */ +BEGINobjDestruct(expr) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(expr) + if(pThis->pVmprg != NULL) + vmprg.Destruct(&pThis->pVmprg); +ENDobjDestruct(expr) + + +/* parse an expression object based on a given tokenizer + * rgerhards, 2008-02-19 + */ +rsRetVal +exprParse(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + /* first, we need to make sure we have a program where we can add to what we parse... */ + CHKiRet(vmprg.Construct(&pThis->pVmprg)); + CHKiRet(vmprg.ConstructFinalize(pThis->pVmprg)); + + /* happy parsing... */ + CHKiRet(expr(pThis, tok)); + dbgoprint((obj_t*) pThis, "successfully parsed/created expression\n"); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(expr) +CODESTARTobjQueryInterface(expr) + if(pIf->ifVersion != exprCURR_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 = exprConstruct; + pIf->ConstructFinalize = exprConstructFinalize; + pIf->Destruct = exprDestruct; + pIf->Parse = exprParse; +finalize_it: +ENDobjQueryInterface(expr) + + +/* Initialize the expr class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(expr, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmprg, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + CHKiRet(objUse(ctok, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, exprConstructFinalize); +ENDObjClassInit(expr) + /* vi:set ai: */ @@ -0,0 +1,56 @@ +/* The expr object. + * + * 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_EXPR_H +#define INCLUDED_EXPR_H + +#include "obj.h" +#include "ctok.h" +#include "vmprg.h" +#include "stringbuf.h" + +/* a node inside an expression tree */ +typedef struct exprNode_s { +} exprNode_t; + + +/* the expression object */ +typedef struct expr_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + vmprg_t *pVmprg; /* the expression in vmprg format - ready to execute */ +} expr_t; + + +/* interfaces */ +BEGINinterface(expr) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(expr); + rsRetVal (*Construct)(expr_t **ppThis); + rsRetVal (*ConstructFinalize)(expr_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(expr_t **ppThis); + rsRetVal (*Parse)(expr_t *pThis, ctok_t *ctok); +ENDinterface(expr) +#define exprCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(expr); + +#endif /* #ifndef INCLUDED_EXPR_H */ @@ -0,0 +1,40 @@ +/* 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 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 GLOBALS_H_INCLUDED +#define GLOBALS_H_INCLUDED + +#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */ + +extern uchar *glblModPath; /* module load path */ +extern uchar *pszWorkDir; +#define glblGetWorkDir() (pszWorkDir == NULL ? (uchar*) "" : pszWorkDir) + +#endif /* #ifndef GLOBALS_H_INCLUDED */ @@ -1,5 +1,26 @@ +/* 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" -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) #include "rsyslog.h" #include <stdio.h> #include <stdarg.h> @@ -29,8 +50,15 @@ #include "msg.h" #include "tcpsyslog.h" #include "module-template.h" +#include "obj.h" +#include "errmsg.h" #include "gss-misc.h" +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) static void display_status_(char *m, OM_uint32 code, int type) { @@ -40,13 +68,13 @@ static void display_status_(char *m, OM_uint32 code, int type) do { maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NO_OID, &msg_ctx, &msg); if (maj_stat != GSS_S_COMPLETE) { - logerrorSz("GSS-API error in gss_display_status called from <%s>\n", m); + errmsg.LogError(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'; - logerror(buf); + errmsg.LogError(NO_ERRCODE, "%s", buf); } if (msg.length != 0) gss_release_buffer(&min_stat, &msg); @@ -54,14 +82,14 @@ static void display_status_(char *m, OM_uint32 code, int type) } -void display_status(char *m, OM_uint32 maj_stat, OM_uint32 min_stat) +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); } -void display_ctx_flags(OM_uint32 flags) +static void display_ctx_flags(OM_uint32 flags) { if (flags & GSS_C_DELEG_FLAG) dbgprintf("GSS_C_DELEG_FLAG\n"); @@ -128,7 +156,7 @@ static int write_all(int fd, char *buf, unsigned int nbyte) } -int recv_token(int s, gss_buffer_t tok) +static int recv_token(int s, gss_buffer_t tok) { int ret; unsigned char lenbuf[4]; @@ -136,12 +164,12 @@ int recv_token(int s, gss_buffer_t tok) ret = read_all(s, (char *) lenbuf, 4); if (ret < 0) { - logerror("GSS-API error reading token length"); + errmsg.LogError(NO_ERRCODE, "GSS-API error reading token length"); return -1; } else if (!ret) { return 0; } else if (ret != 4) { - logerror("GSS-API error reading token length"); + errmsg.LogError(NO_ERRCODE, "GSS-API error reading token length"); return -1; } @@ -153,17 +181,17 @@ int recv_token(int s, gss_buffer_t tok) tok->value = (char *) malloc(tok->length ? tok->length : 1); if (tok->length && tok->value == NULL) { - logerror("Out of memory allocating token data\n"); + errmsg.LogError(NO_ERRCODE, "Out of memory allocating token data\n"); return -1; } ret = read_all(s, (char *) tok->value, tok->length); if (ret < 0) { - logerror("GSS-API error reading token data"); + errmsg.LogError(NO_ERRCODE, "GSS-API error reading token data"); free(tok->value); return -1; } else if (ret != (int) tok->length) { - logerror("GSS-API error reading token data"); + errmsg.LogError(NO_ERRCODE, "GSS-API error reading token data"); free(tok->value); return -1; } @@ -172,7 +200,7 @@ int recv_token(int s, gss_buffer_t tok) } -int send_token(int s, gss_buffer_t tok) +static int send_token(int s, gss_buffer_t tok) { int ret; unsigned char lenbuf[4]; @@ -188,23 +216,88 @@ int send_token(int s, gss_buffer_t tok) ret = write_all(s, (char *) lenbuf, 4); if (ret < 0) { - logerror("GSS-API error sending token length"); + errmsg.LogError(NO_ERRCODE, "GSS-API error sending token length"); return -1; } else if (ret != 4) { - logerror("GSS-API error sending token length"); + errmsg.LogError(NO_ERRCODE, "GSS-API error sending token length"); return -1; } ret = write_all(s, tok->value, tok->length); if (ret < 0) { - logerror("GSS-API error sending token data"); + errmsg.LogError(NO_ERRCODE, "GSS-API error sending token data"); return -1; } else if (ret != (int) tok->length) { - logerror("GSS-API error sending token data"); + errmsg.LogError(NO_ERRCODE, "GSS-API error sending token data"); return -1; } return 0; } -#endif /* #if defined(SYSLOG_INET) && defined(USE_GSSAPI) */ + +/* 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); +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)); +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 @@ -1,11 +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" -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); +/* 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/iminternal.c b/iminternal.c index 18b31603..60460a99 100644 --- a/iminternal.c +++ b/iminternal.c @@ -8,19 +8,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -48,11 +49,11 @@ static rsRetVal iminternalDestruct(iminternal_t *pThis) assert(pThis != NULL); if(pThis->pMsg != NULL) - MsgDestruct(pThis->pMsg); + msgDestruct(&pThis->pMsg); free(pThis); - return iRet; + RETiRet; } @@ -77,8 +78,8 @@ finalize_it: *ppThis = pThis; - return iRet; -}; + RETiRet; +} /* add a message to the linked list @@ -110,7 +111,7 @@ finalize_it: iminternalDestruct(pThis); } - return iRet; + RETiRet; } @@ -141,7 +142,7 @@ rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags) } finalize_it: - return iRet; + RETiRet; } /* tell the caller if we have any messages ready for processing. @@ -165,7 +166,7 @@ rsRetVal modInitIminternal(void) iRet = llInit(&llMsgs, iminternalDestruct, NULL, NULL); - return iRet; + RETiRet; } @@ -181,7 +182,7 @@ rsRetVal modExitIminternal(void) iRet = llDestroy(&llMsgs); - return iRet; + RETiRet; } /* diff --git a/iminternal.h b/iminternal.h index 0677f814..8dc0f171 100644 --- a/iminternal.h +++ b/iminternal.h @@ -6,19 +6,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ diff --git a/klogd.c b/klogd.c deleted file mode 100644 index d32610e9..00000000 --- a/klogd.c +++ /dev/null @@ -1,1200 +0,0 @@ -/* - klogd.c - main program for Linux kernel log daemon. - Copyright (c) 1995 Dr. G.W. Wettstein <greg@wind.rmcc.com> - - This file is part of the sysklogd package, a kernel and 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#include "config.h" - -#ifdef FEATURE_KLOGD -/* - * Steve Lord (lord@cray.com) 7th Nov 92 - * - * Modified to check for kernel info by Dr. G.W. Wettstein 02/17/93. - * - * Fri Mar 12 16:53:56 CST 1993: Dr. Wettstein - * Modified LogLine to use a newline as the line separator in - * the kernel message buffer. - * - * Added debugging code to dump the contents of the kernel message - * buffer at the start of the LogLine function. - * - * Thu Jul 29 11:40:32 CDT 1993: Dr. Wettstein - * Added syscalls to turn off logging of kernel messages to the - * console when klogd becomes responsible for kernel messages. - * - * klogd now catches SIGTERM and SIGKILL signals. Receipt of these - * signals cases the clean_up function to be called which shuts down - * kernel logging and re-enables logging of messages to the console. - * - * Sat Dec 11 11:54:22 CST 1993: Dr. Wettstein - * Added fixes to allow compilation with no complaints with -Wall. - * - * When the daemon catches a fatal signal (SIGTERM, SIGKILL) a - * message is output to the logfile advising that the daemon is - * going to terminate. - * - * Thu Jan 6 11:54:10 CST 1994: Dr. Wettstein - * Major re-write/re-organization of the code. - * - * Klogd now assigns kernel messages to priority levels when output - * to the syslog facility is requested. The priority level is - * determined by decoding the prioritization sequence which is - * tagged onto the start of the kernel messages. - * - * Added the following program options: -f arg -c arg -s -o -d - * - * The -f switch can be used to specify that output should - * be written to the named file. - * - * The -c switch is used to specify the level of kernel - * messages which are to be directed to the console. - * - * The -s switch causes the program to use the syscall - * interface to the kernel message facility. This can be - * used to override the presence of the /proc filesystem. - * - * The -o switch causes the program to operate in 'one-shot' - * mode. A single call will be made to read the complete - * kernel buffer. The contents of the buffer will be - * output and the program will terminate. - * - * The -d switch causes 'debug' mode to be activated. This - * will cause the daemon to generate LOTS of output to stderr. - * - * The buffer decomposition function (LogLine) was re-written to - * squash a bug which was causing only partial kernel messages to - * be written to the syslog facility. - * - * The signal handling code was modified to properly differentiate - * between the STOP and TSTP signals. - * - * Added pid saving when the daemon detaches into the background. Thank - * you to Juha Virtanen (jiivee@hut.fi) for providing this patch. - * - * Mon Feb 6 07:31:29 CST 1995: Dr. Wettstein - * Significant re-organization of the signal handling code. The - * signal handlers now only set variables. Not earth shaking by any - * means but aesthetically pleasing to the code purists in the group. - * - * Patch to make things more compliant with the file system standards. - * Thanks to Chris Metcalf for prompting this helpful change. - * - * The routines responsible for reading the kernel log sources now - * initialize the buffers before reading. I think that this will - * solve problems with non-terminated kernel messages producing - * output of the form: new old old old - * - * This may also help influence the occassional reports of klogd - * failing under significant load. I think that the jury may still - * be out on this one though. My thanks to Joerg Ahrens for initially - * tipping me off to the source of this problem. Also thanks to - * Michael O'Reilly for tipping me off to the best fix for this problem. - * And last but not least Mark Lord for prompting me to try this as - * a means of attacking the stability problem. - * - * Specifying a - as the arguement to the -f switch will cause output - * to be directed to stdout rather than a filename of -. Thanks to - * Randy Appleton for a patch which prompted me to do this. - * - * Wed Feb 22 15:37:37 CST 1995: Dr. Wettstein - * Added version information to logging startup messages. - * - * Wed Jul 26 18:57:23 MET DST 1995: Martin Schulze - * Added an commandline argument "-n" to avoid forking. This obsoletes - * the compiler define NO_FORK. It's more useful to have this as an - * argument as there are many binary versions and one doesn't need to - * recompile the daemon. - * - * 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. - * - * Fri Nov 17 15:05:43 CST 1995: Dr. Wettstein - * Added support for kernel address translation. This required moving - * some definitions and includes to the new klogd.h file. Some small - * code cleanups and modifications. - * - * Mon Nov 20 10:03:39 MET 1995 - * Added -v option to print the version and exit. - * - * Thu Jan 18 11:19:46 CST 1996: Dr. Wettstein - * Added suggested patches from beta-testers. These address two - * two problems. The first is segmentation faults which occur with - * the ELF libraries. This was caused by passing a null pointer to - * the strcmp function. - * - * Added a second patch to remove the pidfile as part of the - * termination cleanup sequence. This minimizes the potential for - * conflicting pidfiles causing immediate termination at boot time. - * - * Wed Aug 21 09:13:03 CDT 1996: Dr. Wettstein - * Added ability to reload static symbols and kernel module symbols - * under control of SIGUSR1 and SIGUSR2 signals. - * - * Added -p switch to select 'paranoid' behavior with respect to the - * loading of kernel module symbols. - * - * Informative line now printed whenever a state change occurs due - * to signal reception by the daemon. - * - * Added the -i and -I command line switches to signal the currently - * executing daemon. - * - * Tue Nov 19 10:15:36 PST 1996: Leland Olds <olds@eskimo.com> - * Corrected vulnerability to buffer overruns by rewriting LogLine - * routine. Obscenely long kernel messages will now be broken up - * into lines no longer than LOG_LINE_LENGTH. - * - * The last version of LogLine was vulnerable to buffer overruns: - * - Kernel messages longer than LOG_LINE_LENGTH caused a buffer - * overrun. - * - If a line was determined to be shorter than LOG_LINE_LENGTH, - * the routine "ExpandKadds" could cause the line grow by - * an unknown amount and overrun a buffer. - * I turned these routines into a little parsing state machine that - * should not have these problems. - * - * Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman - * Some more glibc patches made by <mdorman@debian.org>. - * - * Thu Aug 21 12:11:27 MET DST 1997: Martin Schulze <joey@infodrom.north.de> - * Fixed little mistake which prevented klogd from accepting a - * console log - * - * Fri Jan 9 00:39:52 CET 1998: Martin Schulze <joey@infodrom.north.de> - * Changed the behaviour of klogd when receiving a terminate - * signal. Now the program terminates immediately instead of - * completing the receipt of a kernel message, i.e the read() - * call. The old behaveiour could result in klogd being - * recognized as being undead, because it'll only die after a - * message has been received. - * - * Fri Jan 9 11:03:48 CET 1998: Martin Schulze <joey@infodrom.north.de> - * Corrected some code that caused klogd to dump core when - * receiving messages containing '%', some of them exist in - * 2.1.78. Thanks to Chu-yeon Park <kokids@doit.ajou.ac.kr> for - * informing me. - * - * Fri Jan 9 23:38:19 CET 1998: Florian La Roche <florian@knorke.saar.de> - * Added -x switch to omit EIP translation and System.map evaluation. - * - * Sun Jan 25 20:47:46 CET 1998: Martin Schulze <joey@infodrom.north.de> - * As the bug covering the %'s introduced a problem with - * unevaluated priorities I've worked out a real fix that strips - * %'s to an even number which is harmless for printf. - * - * Sat Oct 10 20:01:48 CEST 1998: Martin Schulze <joey@infodrom.north.de> - * Added support for TESTING define which will turn klogd into - * stdio-mode used for debugging. - * - * Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de> - * Modified System.map read function to try all possible map - * files until a file with matching version is found. Added support for - * Debian release. - * - * Mon Oct 12 13:01:27 MET DST 1998: Martin Schulze <joey@infodrom.north.de> - * Used unsigned long and strtoul() to resolve kernel oops symbols. - * - * Sun Jan 3 18:38:03 CET 1999: Martin Schulze <joey@infodrom.north.de> - * Shortened LOG_LINE_LENGTH in order to get long lines splitted - * up earlier and syslogd has a better chance concatenating them - * together again. - * - * Sat Aug 21 12:27:02 CEST 1999: Martin Schulze <joey@infodrom.north.de> - * Skip newline when reading in messages. - * - * Tue Sep 12 22:14:33 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> - * Don't feed a buffer directly to a printf-type routine, use - * "%s" as format string instead. Thanks to Jouko Pynnönen - * <jouko@solutions.fi> for pointing this out. - * - * Tue Sep 12 22:44:57 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> - * Commandline option `-2': When symbols are expanded, print the - * line twice. Once with addresses converted to symbols, once with the - * raw text. Allows external programs such as ksymoops do their own - * processing on the original data. Thanks to Keith Owens - * <kaos@ocs.com.au> for the patch. - * - * Mon Sep 18 09:32:27 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> - * Added patch to fix priority decoding after moving kernel - * messgages into "%s". Thanks to Solar Designer - * <solar@false.com> for the patch. - * - * Sun Mar 11 20:23:44 CET 2001: Martin Schulze <joey@infodrom.ffis.de> - * Stop LogLine() from being called with wrong argument when a - * former calculation failed already. Thanks to Thomas Roessler - * <roessler@does-not-exist.org> for providing a patch. - * - * Ignore zero bytes, no busy loop is entered anymore. Several - * people have submitted patches: Troels Walsted Hansen - * <troels@thule.no>, Wolfgang Oertl <Wolfgang.Oertl@uibk.ac.at> - * and Thomas Roessler. - */ - - -/* Includes. */ -#include <unistd.h> -#include <signal.h> -#include <errno.h> -#include <sys/fcntl.h> -#include <sys/stat.h> -#if !defined(__GLIBC__) -#include <linux/time.h> -#endif /* __GLIBC__ */ -#include <stdarg.h> -#include <paths.h> -#include <stdlib.h> -#include "klogd.h" -#include "ksyms.h" -#ifndef TESTING -#include "pidfile.h" -#endif - -#define __LIBRARY__ -#include <linux/unistd.h> -#if !defined(__GLIBC__) -# define __NR_ksyslog __NR_syslog -_syscall3(int,ksyslog,int, type, char *, buf, int, len); -#else -#include <sys/klog.h> -#define ksyslog klogctl -#endif -extern int writeSyslogV(int pri, const char *szFmt, va_list va); -extern int writeSyslog(int iPri, const char *szFmt, ...); - -#ifndef _PATH_KLOG -#define _PATH_KLOG "/proc/kmsg" -#endif - -#define LOG_BUFFER_SIZE 4096 -#define LOG_LINE_LENGTH 1000 - -#ifndef TESTING -#if defined(FSSTND) -static char *PidFile = _PATH_VARRUN "rklogd.pid"; -#else -static char *PidFile = "/etc/rklogd.pid"; -#endif -#endif - -static int kmsg, - change_state = 0, - terminate = 0, - caught_TSTP = 0, - reload_symbols = 0, - console_log_level = -1; - -static int use_syscall = 0, - one_shot = 0, - symbol_lookup = 1, - no_fork = 0; /* don't fork - don't run in daemon mode */ - -static char *symfile = (char *) 0, - log_buffer[LOG_BUFFER_SIZE]; - -static FILE *output_file = (FILE *) 0; - -static enum LOGSRC {none, proc, kernel} logsrc; - -int debugging = 0; -int symbols_twice = 0; - - -/* Function prototypes. */ -extern int ksyslog(int type, char *buf, int len); -static void CloseLogSrc(void); -extern void restart(int sig); -extern void stop_logging(int sig); -extern void stop_daemon(int sig); -extern void reload_daemon(int sig); -static void Terminate(void); -static void SignalDaemon(int); -static void ReloadSymbols(void); -static void ChangeLogging(void); -static enum LOGSRC GetKernelLogSrc(void); -static void LogLine(char *ptr, int len); -static void LogKernelLine(void); -static void LogProcLine(void); -extern int main(int argc, char *argv[]); - - -extern void Syslog(int priority, char *fmt, ...) __attribute__((format(printf,2, 3))); -extern void Syslog(int priority, char *fmt, ...) -{ - va_list ap; - char *argl; - - if(debugging) - { - fputs("Logging line:\n", stderr); - fprintf(stderr, "\tLine: %s\n", fmt); - fprintf(stderr, "\tPriority: %d\n", priority); - } - - /* Handle output to a file. */ - if ( output_file != (FILE *) 0 ) { - va_start(ap, fmt); - vfprintf(output_file, fmt, ap); - va_end(ap); - fputc('\n', output_file); - fflush(output_file); - if (!one_shot) - fsync(fileno(output_file)); - return; - } - - /* Output using syslog. */ - if (!strcmp(fmt, "%s")) { - va_start(ap, fmt); - argl = va_arg(ap, char *); - if (argl[0] == '<' && argl[1] && argl[2] == '>') { - switch ( argl[1] ) - { - case '0': - priority = LOG_EMERG; - break; - case '1': - priority = LOG_ALERT; - break; - case '2': - priority = LOG_CRIT; - break; - case '3': - priority = LOG_ERR; - break; - case '4': - priority = LOG_WARNING; - break; - case '5': - priority = LOG_NOTICE; - break; - case '6': - priority = LOG_INFO; - break; - case '7': - default: - priority = LOG_DEBUG; - } - argl += 3; - } - writeSyslog(priority, fmt, argl); - va_end(ap); -#ifdef TESTING - putchar('\n'); -#endif - return; - } - - va_start(ap, fmt); - writeSyslogV(priority, fmt, ap); - va_end(ap); -#ifdef TESTING - printf ("\n"); -#endif - - return; -} - - -static void CloseLogSrc(void) -{ - /* Turn on logging of messages to console, but only if we had the -c - * option -- rgerhards, 2007-08-01 - */ - if (console_log_level != -1) - ksyslog(7, NULL, 0); - - /* Shutdown the log sources. */ - switch ( logsrc ) - { - case kernel: - ksyslog(0, 0, 0); - Syslog(LOG_INFO, "Kernel logging (ksyslog) stopped."); - break; - case proc: - close(kmsg); - Syslog(LOG_INFO, "Kernel logging (proc) stopped."); - break; - case none: - break; - } - - if ( output_file != (FILE *) 0 ) - fflush(output_file); - return; -} - - -void restart(int __attribute__((unused)) sig) -{ - change_state = 1; - caught_TSTP = 0; - return; -} - - -void stop_logging(int __attribute__((unused)) sig) -{ - change_state = 1; - caught_TSTP = 1; - return; -} - - -void stop_daemon(int __attribute__((unused)) sig) -{ - change_state = 1; - terminate = 1; - return; -} - - -void reload_daemon(int sig) -{ - change_state = 1; - reload_symbols = 1; - - if ( sig == SIGUSR2 ) - { - ++reload_symbols; - } - - return; -} - - -static void Terminate(void) -{ - CloseLogSrc(); - Syslog(LOG_INFO, "Kernel log daemon terminating."); - sleep(1); - if ( output_file != (FILE *) 0 ) - fclose(output_file); - closelog(); -#ifndef TESTING - (void) remove_pid(PidFile); -#endif - exit(1); -} - -static void SignalDaemon(int sig) -{ -#ifndef TESTING - auto int pid = check_pid(PidFile); - - kill(pid, sig); -#else - kill(getpid(), sig); -#endif - return; -} - - -static void ReloadSymbols(void) -{ - if (symbol_lookup) { - if ( reload_symbols > 1 ) - InitKsyms(symfile); - InitMsyms(); - } - reload_symbols = change_state = 0; - return; -} - - -static void ChangeLogging(void) -{ - /* Terminate kernel logging. */ - if ( terminate == 1 ) - Terminate(); - - /* Indicate that something is happening. */ - Syslog(LOG_INFO, "rklogd %s, ---------- state change ----------\n", \ - VERSION); - - /* Reload symbols. */ - if ( reload_symbols > 0 ) - { - ReloadSymbols(); - return; - } - - /* Stop kernel logging. */ - if ( caught_TSTP == 1 ) - { - CloseLogSrc(); - logsrc = none; - change_state = 0; - return; - } - - /* - * The rest of this function is responsible for restarting - * kernel logging after it was stopped. - * - * In the following section we make a decision based on the - * kernel log state as to what is causing us to restart. Somewhat - * groady but it keeps us from creating another static variable. - */ - if ( logsrc != none ) - { - Syslog(LOG_INFO, "Kernel logging re-started after SIGSTOP."); - change_state = 0; - return; - } - - /* Restart logging. */ - logsrc = GetKernelLogSrc(); - change_state = 0; - return; -} - - -static enum LOGSRC GetKernelLogSrc(void) -{ - auto struct stat sb; - - /* Set level of kernel console messaging.. */ - if ( (console_log_level != -1) && - (ksyslog(8, NULL, console_log_level) < 0) && - (errno == EINVAL) ) - { - /* - * An invalid arguement error probably indicates that - * a pre-0.14 kernel is being run. At this point we - * issue an error message and simply shut-off console - * logging completely. - */ - Syslog(LOG_WARNING, "Cannot set console log level - disabling " - "console output."); - } - - /* - * First do a stat to determine whether or not the proc based - * file system is available to get kernel messages from. - */ - if ( use_syscall || - ((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT)) ) - { - /* Initialize kernel logging. */ - ksyslog(1, NULL, 0); -#ifdef DEBRELEASE - Syslog(LOG_INFO, "rklogd %s#%s, log source = ksyslog " - "started.", VERSION, DEBRELEASE); -#else - Syslog(LOG_INFO, "rklogd %s, log source = ksyslog " - "started.", VERSION); -#endif - return(kernel); - } - -#ifndef TESTING - if ( (kmsg = open(_PATH_KLOG, O_RDONLY)) < 0 ) - { - fprintf(stderr, "rklogd: Cannot open proc file system, " \ - "%d - %s.\n", errno, strerror(errno)); - ksyslog(7, NULL, 0); - exit(1); - } -#else - kmsg = fileno(stdin); -#endif - -#ifdef DEBRELEASE - Syslog(LOG_INFO, "rklogd %ss#%s, log source = %s started.", \ - VERSION, DEBRELEASE, _PATH_KLOG); -#else - Syslog(LOG_INFO, "rklogd %s, log source = %s started.", \ - VERSION, _PATH_KLOG); -#endif - return(proc); -} - - -/* - * Copy characters from ptr to line until a char in the delim - * string is encountered or until min( space, len ) chars have - * been copied. - * - * Returns the actual number of chars copied. - */ -static int copyin( char *line, int space, - const char *ptr, int len, - const char *delim ) -{ - auto int i; - auto int count; - - count = len < space ? len : space; - - for(i=0; i<count && !strchr(delim, *ptr); i++ ) { - *line++ = *ptr++; - } - - return( i ); -} - -/* - * Messages are separated by "\n". Messages longer than - * LOG_LINE_LENGTH are broken up. - * - * Kernel symbols show up in the input buffer as : "[<aaaaaa>]", - * where "aaaaaa" is the address. These are replaced with - * "[symbolname+offset/size]" in the output line - symbolname, - * offset, and size come from the kernel symbol table. - * - * If a kernel symbol happens to fall at the end of a message close - * in length to LOG_LINE_LENGTH, the symbol will not be expanded. - * (This should never happen, since the kernel should never generate - * messages that long. - * - * To preserve the original addresses, lines containing kernel symbols - * are output twice. Once with the symbols converted and again with the - * original text. Just in case somebody wants to run their own Oops - * analysis on the syslog, e.g. ksymoops. - */ -static void LogLine(char *ptr, int len) -{ - enum parse_state_enum { - PARSING_TEXT, - PARSING_SYMSTART, /* at < */ - PARSING_SYMBOL, - PARSING_SYMEND /* at ] */ - }; - - static char line_buff[LOG_LINE_LENGTH]; - - static char *line =line_buff; - static enum parse_state_enum parse_state = PARSING_TEXT; - static int space = sizeof(line_buff)-1; - - static char *sym_start; /* points at the '<' of a symbol */ - - auto int delta = 0; /* number of chars copied */ - auto int symbols_expanded = 0; /* 1 if symbols were expanded */ - auto int skip_symbol_lookup = 0; /* skip symbol lookup on this pass */ - auto char *save_ptr = ptr; /* save start of input line */ - auto int save_len = len; /* save length at start of input line */ - - while( len > 0 ) - { - if( space == 0 ) /* line buffer is full */ - { - /* - ** Line too long. Start a new line. - */ - *line = 0; /* force null terminator */ - - if ( debugging ) - { - fputs("Line buffer full:\n", stderr); - fprintf(stderr, "\tLine: %s\n", line); - } - - Syslog( LOG_INFO, "%s", line_buff ); - line = line_buff; - space = sizeof(line_buff)-1; - parse_state = PARSING_TEXT; - symbols_expanded = 0; - skip_symbol_lookup = 0; - save_ptr = ptr; - save_len = len; - } - - switch( parse_state ) - { - case PARSING_TEXT: - delta = copyin( line, space, ptr, len, "\n[" ); - line += delta; - ptr += delta; - space -= delta; - len -= delta; - - if( space == 0 || len == 0 ) - { - break; /* full line_buff or end of input buffer */ - } - - if( *ptr == '\0' ) /* zero byte */ - { - ptr++; /* skip zero byte */ - space -= 1; - len -= 1; - - break; - } - - if( *ptr == '\n' ) /* newline */ - { - ptr++; /* skip newline */ - space -= 1; - len -= 1; - - *line = 0; /* force null terminator */ - Syslog( LOG_INFO, "%s", line_buff ); - line = line_buff; - space = sizeof(line_buff)-1; - if (symbols_twice) { - if (symbols_expanded) { - /* reprint this line without symbol lookup */ - symbols_expanded = 0; - skip_symbol_lookup = 1; - ptr = save_ptr; - len = save_len; - } - else - { - skip_symbol_lookup = 0; - save_ptr = ptr; - save_len = len; - } - } - break; - } - if( *ptr == '[' ) /* possible kernel symbol */ - { - *line++ = *ptr++; - space -= 1; - len -= 1; - if (!skip_symbol_lookup) - parse_state = PARSING_SYMSTART; /* at < */ - break; - } - /* Now that line_buff is no longer fed to *printf as format - * string, '%'s are no longer "dangerous". - */ - break; - - case PARSING_SYMSTART: - if( *ptr != '<' ) - { - parse_state = PARSING_TEXT; /* not a symbol */ - break; - } - - /* - ** Save this character for now. If this turns out to - ** be a valid symbol, this char will be replaced later. - ** If not, we'll just leave it there. - */ - - sym_start = line; /* this will point at the '<' */ - - *line++ = *ptr++; - space -= 1; - len -= 1; - parse_state = PARSING_SYMBOL; /* symbol... */ - break; - - case PARSING_SYMBOL: - delta = copyin( line, space, ptr, len, ">\n[" ); - line += delta; - ptr += delta; - space -= delta; - len -= delta; - if( space == 0 || len == 0 ) - { - break; /* full line_buff or end of input buffer */ - } - if( *ptr != '>' ) - { - parse_state = PARSING_TEXT; - break; - } - - *line++ = *ptr++; /* copy the '>' */ - space -= 1; - len -= 1; - - parse_state = PARSING_SYMEND; - - break; - - case PARSING_SYMEND: - if( *ptr != ']' ) - { - parse_state = PARSING_TEXT; /* not a symbol */ - break; - } - - /* - ** It's really a symbol! Replace address with the - ** symbol text. - */ - { - auto int sym_space; - - unsigned long value; - auto struct symbol sym; - auto char *symbol; - - *(line-1) = 0; /* null terminate the address string */ - value = strtoul(sym_start+1, (char **) 0, 16); - *(line-1) = '>'; /* put back delim */ - - if ( !symbol_lookup || (symbol = LookupSymbol(value, &sym)) == (char *)0 ) - { - parse_state = PARSING_TEXT; - break; - } - - /* - ** verify there is room in the line buffer - */ - sym_space = space + ( line - sym_start ); - if( (unsigned) sym_space < strlen(symbol) + 30 ) /*(30 should be overkill)*/ - { - parse_state = PARSING_TEXT; /* not enough space */ - break; - } - - delta = sprintf( sym_start, "%s+%d/%d]", - symbol, sym.offset, sym.size ); - - space = sym_space + delta; - line = sym_start + delta; - symbols_expanded = 1; - } - ptr++; - len--; - parse_state = PARSING_TEXT; - break; - - default: /* Can't get here! */ - parse_state = PARSING_TEXT; - - } - } - - return; -} - - -static void LogKernelLine(void) -{ - auto int rdcnt; - - /* - * Zero-fill the log buffer. This should cure a multitude of - * problems with klogd logging the tail end of the message buffer - * which will contain old messages. Then read the kernel log - * messages into this fresh buffer. - */ - memset(log_buffer, '\0', sizeof(log_buffer)); - if ( (rdcnt = ksyslog(2, log_buffer, sizeof(log_buffer)-1)) < 0 ) - { - if ( errno == EINTR ) - return; - fprintf(stderr, "rklogd: Error return from sys_sycall: " \ - "%d - %s\n", errno, strerror(errno)); - } - else - LogLine(log_buffer, rdcnt); - return; -} - - -static void LogProcLine(void) -{ - auto int rdcnt; - - /* - * Zero-fill the log buffer. This should cure a multitude of - * problems with klogd logging the tail end of the message buffer - * which will contain old messages. Then read the kernel messages - * from the message pseudo-file into this fresh buffer. - */ - memset(log_buffer, '\0', sizeof(log_buffer)); - if ( (rdcnt = read(kmsg, log_buffer, sizeof(log_buffer)-1)) < 0 ) - { - if ( errno == EINTR ) - return; - Syslog(LOG_ERR, "Cannot read proc file system: %d - %s.", \ - errno, strerror(errno)); - } - else - LogLine(log_buffer, rdcnt); - - return; -} - - -/* helper routine to spit out an error message and terminate - * klogd when setting a signal error fails. - */ -void sigactionErrAbort() -{ - fprintf(stderr, "rklogd: could net set a signal handler - terminating. Error: %s\n", - strerror(errno)); - exit(1); -} - - -int main(int argc, char *argv[]) -{ - int ch, - use_output = 0; - - char *log_level = (char *) 0, - *output = (char *) 0; - struct sigaction sigAct; - -#ifndef TESTING - chdir ("/"); -#endif - /* Parse the command-line. */ - while ((ch = getopt(argc, argv, "c:df:iIk:nopsvx2")) != EOF) - switch((char)ch) - { - case '2': /* Print lines with symbols twice. */ - symbols_twice = 1; - break; - case 'c': /* Set console message level. */ - log_level = optarg; - break; - case 'd': /* Activity debug mode. */ - debugging = 1; - break; - case 'f': /* Define an output file. */ - output = optarg; - use_output++; - break; - case 'i': /* Reload module symbols. */ - SignalDaemon(SIGUSR1); - return(0); - case 'I': - SignalDaemon(SIGUSR2); - return(0); - case 'k': /* Kernel symbol file. */ - symfile = optarg; - break; - case 'n': /* don't fork */ - no_fork++; - break; - case 'o': /* One-shot mode. */ - one_shot = 1; - break; - case 'p': - SetParanoiaLevel(1); /* Load symbols on oops. */ - break; - case 's': /* Use syscall interface. */ - use_syscall = 1; - break; - case 'v': - printf("rklogd %s\n", VERSION); - exit (1); - case 'x': - symbol_lookup = 0; - break; - } - - - /* Set console logging level. */ - if ( log_level != (char *) 0 ) - { - if ( (strlen(log_level) > 1) || \ - (strchr("12345678", *log_level) == (char *) 0) ) - { - fprintf(stderr, "rklogd: Invalid console logging " - "level <%s> specified.\n", log_level); - return(1); - } - console_log_level = *log_level - '0'; - } - - -#ifndef TESTING - /* - * The following code allows klogd to auto-background itself. - * What happens is that the program forks and the parent quits. - * The child closes all its open file descriptors, and issues a - * call to setsid to establish itself as an independent session - * immune from control signals. - * - * fork() is only called if it should run in daemon mode, fork is - * not disabled with the command line argument and there's no - * such process running. - */ - if ( (!one_shot) && (!no_fork) ) - { - if (!check_pid(PidFile)) - { - if ( fork() == 0 ) - { - auto int fl; - int num_fds = getdtablesize(); - - /* This is the child closing its file descriptors. */ - for (fl= 0; fl <= num_fds; ++fl) - { - if ( fileno(stdout) == fl && use_output ) - if ( strcmp(output, "-") == 0 ) - continue; - close(fl); - } - - setsid(); - } - else - exit(0); - } - else - { - fputs("rklogd: Already running.\n", stderr); - exit(1); - } - } - - - /* tuck my process id away */ - if (!check_pid(PidFile)) - { - if (!write_pid(PidFile)) - Terminate(); - } - else - { - fputs("rklogd: Already running.\n", stderr); - Terminate(); - } -#endif - - /* Signal setups. - * Please note that the "original" klogd in sysklogd tries to - * handle SIGKILL and SIGSTOP. That does not work - but as the - * original klogd had no error checking, nobody ever noticed. We - * do now have error checking and consequently those ever-failing - * calls are now removed. - */ - sigemptyset(&sigAct.sa_mask); - sigAct.sa_flags = 0; - - /* first, set all signals to ignore - * In this loop, we try blindly to ignore all signals. I am leaving - * intentionally out all error checking. If we can ignore the signal, - * that's nice, but if we can't ... well, so be it ;) - * RGerhards, 2007-06-15 - */ - sigAct.sa_handler = SIG_IGN; - for (ch= 1; ch < NSIG ; ++ch) - { - if(ch != SIGKILL && ch != SIGSTOP) - sigaction(ch, &sigAct, NULL); - } - - /* Now specific handlers (one after another) */ - sigAct.sa_handler = stop_daemon; - if(sigaction(SIGINT, &sigAct, NULL) != 0) sigactionErrAbort(); - if(sigaction(SIGTERM, &sigAct, NULL) != 0) sigactionErrAbort(); - if(sigaction(SIGHUP, &sigAct, NULL) != 0) sigactionErrAbort(); - - sigAct.sa_handler = stop_daemon; - if(sigaction(SIGTSTP, &sigAct, NULL) != 0) sigactionErrAbort(); - - sigAct.sa_handler = restart; - if(sigaction(SIGCONT, &sigAct, NULL) != 0) sigactionErrAbort(); - - sigAct.sa_handler = reload_daemon; - if(sigaction(SIGUSR1, &sigAct, NULL) != 0) sigactionErrAbort(); - if(sigaction(SIGUSR2, &sigAct, NULL) != 0) sigactionErrAbort(); - - - /* Open outputs. */ - if ( use_output ) - { - if ( strcmp(output, "-") == 0 ) - output_file = stdout; - else if ( (output_file = fopen(output, "w")) == (FILE *) 0 ) - { - fprintf(stderr, "rklogd: Cannot open output file " \ - "%s - %s\n", output, strerror(errno)); - return(1); - } - } - - /* Handle one-shot logging. */ - if ( one_shot ) - { - if (symbol_lookup) { - symbol_lookup = (InitKsyms(symfile) == 1); - symbol_lookup |= InitMsyms(); - if (symbol_lookup == 0) { - Syslog(LOG_WARNING, "cannot find any symbols, turning off symbol lookups\n"); - } - } - if ( (logsrc = GetKernelLogSrc()) == kernel ) - LogKernelLine(); - else - LogProcLine(); - Terminate(); - } - - /* Determine where kernel logging information is to come from. */ -#if defined(KLOGD_DELAY) - sleep(KLOGD_DELAY); -#endif - logsrc = GetKernelLogSrc(); - if (symbol_lookup) { - symbol_lookup = (InitKsyms(symfile) == 1); - symbol_lookup |= InitMsyms(); - if (symbol_lookup == 0) { - Syslog(LOG_WARNING, "cannot find any symbols, turning off symbol lookups\n"); - } - } - - /* The main loop. */ - /* The main loop will be broken by a signal handler which set the - * terminate variable. That is then cheked in ChangeLogging(), which - * will then terminate klogd. - * RGerhards, 2007-06-15 - */ - while(1) - { - if ( change_state ) - ChangeLogging(); - switch ( logsrc ) - { - case kernel: - LogKernelLine(); - break; - case proc: - LogProcLine(); - break; - case none: - pause(); - break; - } - } -} -#else /* #ifdef FEATURE_KLOGD */ -#include <stdio.h> -int main() -{ - fprintf(stderr, "FEATURE_KLOGD was disabled during make, so rklogd is not available.\n"); - return(1); -} -#endif /* #ifdef WITH_KLOGD */ -/* - * Local variables: - * c-indent-level: 8 - * c-basic-offset: 8 - * tab-width: 8 - * End: - * vi:set ai: - */ diff --git a/klogd.h b/klogd.h deleted file mode 100644 index 1343f401..00000000 --- a/klogd.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - klogd.h - main header file for Linux kernel log daemon. - Copyright (c) 1995 Dr. G.W. Wettstein <greg@wind.rmcc.com> - - This file is part of the sysklogd package, a kernel and 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -/* - * Symbols and definitions needed by klogd. - * - * Thu Nov 16 12:45:06 CST 1995: Dr. Wettstein - * Initial version. - */ - -/* Useful include files. */ -#include <stdio.h> -#include <syslog.h> -#include <string.h> -#include <stdarg.h> -#undef syslog -#undef vsyslog - -/* Function prototypes. */ -extern int InitKsyms(char *); -extern int InitMsyms(void); -extern char * ExpandKadds(char *, char *); -extern void SetParanoiaLevel(int); -extern void Syslog(int priority, char *fmt, ...); -extern void vsyslog(int pri, const char *fmt, va_list ap); -extern void openlog(const char *ident, int logstat, int logfac); diff --git a/ksym_mod.c b/ksym_mod.c deleted file mode 100644 index d84140e0..00000000 --- a/ksym_mod.c +++ /dev/null @@ -1,703 +0,0 @@ -#include "config.h" - -#ifdef FEATURE_KLOGD -/* - ksym_mod.c - functions for building symbol lookup tables for klogd - Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> - Copyright (c) 1996 Enjellic Systems Development - - This file is part of the sysklogd package, a kernel and 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -/* - * This file implements functions which are useful for building - * a symbol lookup table based on the in kernel symbol table - * maintained by the Linux kernel. - * - * Proper logging of kernel panics generated by loadable modules - * tends to be difficult. Since the modules are loaded dynamically - * their addresses are not known at kernel load time. A general - * protection fault (Oops) cannot be properly deciphered with - * classic methods using the static symbol map produced at link time. - * - * One solution to this problem is to have klogd attempt to translate - * addresses from module when the fault occurs. By referencing the - * the kernel symbol table proper resolution of these symbols is made - * possible. - * - * At least that is the plan. - * - * Wed Aug 21 09:20:09 CDT 1996: Dr. Wettstein - * The situation where no module support has been compiled into a - * kernel is now detected. An informative message is output indicating - * that the kernel has no loadable module support whenever kernel - * module symbols are loaded. - * - * An informative message is printed indicating the number of kernel - * modules and the number of symbols loaded from these modules. - * - * Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman - * Some more glibc patches made by <mdorman@debian.org>. - * - * Sat Jan 10 15:00:18 CET 1998: Martin Schulze <joey@infodrom.north.de> - * Fixed problem with klogd not being able to be built on a kernel - * newer than 2.1.18. It was caused by modified structures - * inside the kernel that were included. I have worked in a - * patch from Alessandro Suardi <asuardi@uninetcom.it>. - * - * Sun Jan 25 20:57:34 CET 1998: Martin Schulze <joey@infodrom.north.de> - * Another patch for Linux/alpha by Christopher C Chimelis - * <chris@classnet.med.miami.edu>. - * - * Thu Mar 19 23:39:29 CET 1998: Manuel Rodrigues <pmanuel@cindy.fe.up.pt> - * Changed lseek() to llseek() in order to support > 2GB address - * space which provided by kernels > 2.1.70. - * - * Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de> - * Removed <sys/module.h> as it's no longer part of recent glibc - * versions. Added prototyp for llseek() which has been - * forgotton in <unistd.h> from glibc. Added more log - * information if problems occurred while reading a system map - * file, by submission from Mark Simon Phillips <M.S.Phillips@nortel.co.uk>. - * - * Sun Jan 3 18:38:03 CET 1999: Martin Schulze <joey@infodrom.north.de> - * Corrected return value of AddModule if /dev/kmem can't be - * loaded. This will prevent klogd from segfaulting if /dev/kmem - * is not available. Patch from Topi Miettinen <tom@medialab.sonera.net>. - * - * Tue Sep 12 23:11:13 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> - * Changed llseek() to lseek64() in order to skip a libc warning. - */ - - -/* Includes. */ -#include <stdlib.h> -#include <unistd.h> -#include <signal.h> -#include <errno.h> -#include <sys/fcntl.h> -#include <sys/stat.h> -#if !defined(__GLIBC__) -#include <linux/time.h> -#include <linux/module.h> -#else /* __GLIBC__ */ -#include "module.h" -extern __off64_t lseek64 __P ((int __fd, __off64_t __offset, int __whence)); -extern int get_kernel_syms __P ((struct kernel_sym *__table)); -#endif /* __GLIBC__ */ -#include <stdarg.h> -#include <paths.h> -#include <linux/version.h> - -#include "klogd.h" -#include "ksyms.h" - - -#if !defined(__GLIBC__) -/* - * The following bit uses some kernel/library magic to product what - * looks like a function call to user level code. This function is - * actually a system call in disguise. The purpose of the getsyms - * call is to return a current copy of the in-kernel symbol table. - */ -#define __LIBRARY__ -#include <linux/unistd.h> -#define __NR_getsyms __NR_get_kernel_syms -_syscall1(int, getsyms, struct kernel_sym *, syms); -#undef __LIBRARY__ -extern int getsyms(struct kernel_sym *); -#else /* __GLIBC__ */ -#define getsyms get_kernel_syms -#endif /* __GLIBC__ */ - -/* Variables static to this module. */ -struct sym_table -{ - unsigned long value; - char *name; -}; - -struct Module -{ - struct sym_table *sym_array; - int num_syms; - - char *name; - struct module module; -#if LINUX_VERSION_CODE >= 0x20112 - struct module_info module_info; -#endif -}; - -static int num_modules = 0; -struct Module *sym_array_modules = (struct Module *) 0; - -static int have_modules = 0; - -#if defined(TEST) -static int debugging = 1; -#else -extern int debugging; -#endif - - -/* Function prototypes. */ -static void FreeModules(void); -static int AddSymbol(struct Module *mp, unsigned long, char *); -static int AddModule(unsigned long, char *); -static int symsort(const void *, const void *); - - -/************************************************************************** - * Function: InitMsyms - * - * Purpose: This function is responsible for building a symbol - * table which can be used to resolve addresses for - * loadable modules. - * - * Arguements: Void - * - * Return: A boolean return value is assumed. - * - * A false value indicates that something went wrong. - * - * True if loading is successful. - **************************************************************************/ - -extern int InitMsyms() - -{ - auto int rtn, - tmp; - - auto struct kernel_sym *ksym_table, - *p; - - - /* Initialize the kernel module symbol table. */ - FreeModules(); - - - /* - * The system call which returns the kernel symbol table has - * essentialy two modes of operation. Called with a null pointer - * the system call returns the number of symbols defined in the - * the table. - * - * The second mode of operation is to pass a valid pointer to - * the call which will then load the current symbol table into - * the memory provided. - * - * Returning the symbol table is essentially an all or nothing - * proposition so we need to pre-allocate enough memory for the - * complete table regardless of how many symbols we need. - * - * Bummer. - */ - if ( (rtn = getsyms((struct kernel_sym *) 0)) < 0 ) - { - if ( errno == ENOSYS ) - Syslog(LOG_INFO, "No module symbols loaded - " - "kernel modules not enabled.\n"); - else - Syslog(LOG_ERR, "Error loading kernel symbols " \ - "- %s\n", strerror(errno)); - return(0); - } - if ( debugging ) - fprintf(stderr, "Loading kernel module symbols - " - "Size of table: %d\n", rtn); - - ksym_table = (struct kernel_sym *) malloc(rtn * \ - sizeof(struct kernel_sym)); - if ( ksym_table == (struct kernel_sym *) 0 ) - { - Syslog(LOG_WARNING, " Failed memory allocation for kernel " \ - "symbol table.\n"); - return(0); - } - if ( (rtn = getsyms(ksym_table)) < 0 ) - { - Syslog(LOG_WARNING, "Error reading kernel symbols - %s\n", \ - strerror(errno)); - return(0); - } - - - /* - * Build a symbol table compatible with the other one used by - * klogd. - */ - tmp = rtn; - p = ksym_table; - while ( tmp-- ) - { - if ( !AddModule(p->value, p->name) ) - { - Syslog(LOG_WARNING, "Error adding kernel module table " - "entry.\n"); - free(ksym_table); - return(0); - } - ++p; - } - - /* Sort the symbol tables in each module. */ - for (rtn = tmp= 0; tmp < num_modules; ++tmp) - { - rtn += sym_array_modules[tmp].num_syms; - if ( sym_array_modules[tmp].num_syms < 2 ) - continue; - qsort(sym_array_modules[tmp].sym_array, \ - sym_array_modules[tmp].num_syms, \ - sizeof(struct sym_table), symsort); - } - - if ( rtn == 0 ) - Syslog(LOG_INFO, "No module symbols loaded."); - else - Syslog(LOG_INFO, "Loaded %d %s from %d module%s", rtn, \ - (rtn == 1) ? "symbol" : "symbols", \ - num_modules, (num_modules == 1) ? "." : "s."); - free(ksym_table); - return(1); -} - - -static int symsort(p1, p2) - - const void *p1; - - const void *p2; - -{ - auto const struct sym_table *sym1 = p1, - *sym2 = p2; - - if ( sym1->value < sym2->value ) - return(-1); - if ( sym1->value == sym2->value ) - return(0); - return(1); -} - - -/************************************************************************** - * Function: FreeModules - * - * Purpose: This function is used to free all memory which has been - * allocated for the modules and their symbols. - * - * Arguements: None specified. - * - * Return: void - **************************************************************************/ - -static void FreeModules() - -{ - auto int nmods, - nsyms; - - auto struct Module *mp; - - - /* Check to see if the module symbol tables need to be cleared. */ - have_modules = 0; - if ( num_modules == 0 ) - return; - - - for (nmods= 0; nmods < num_modules; ++nmods) - { - mp = &sym_array_modules[nmods]; - if ( mp->num_syms == 0 ) - continue; - - for (nsyms= 0; nsyms < mp->num_syms; ++nsyms) - free(mp->sym_array[nsyms].name); - free(mp->sym_array); - } - - free(sym_array_modules); - sym_array_modules = (struct Module *) 0; - num_modules = 0; - return; -} - - -/************************************************************************** - * Function: AddModule - * - * Purpose: This function is responsible for adding a module to - * the list of currently loaded modules. - * - * Arguements: (unsigned long) address, (char *) symbol - * - * address:-> The address of the module. - * - * symbol:-> The name of the module. - * - * Return: int - **************************************************************************/ - -static int AddModule(address, symbol) - - unsigned long address; - - char *symbol; - -{ - auto int memfd; - - auto struct Module *mp; - - - /* Return if we have loaded the modules. */ - if ( have_modules ) - return(1); - - /* - * The following section of code is responsible for determining - * whether or not we are done reading the list of modules. - */ - if ( symbol[0] == '#' ) - { - - if ( symbol[1] == '\0' ) - { - /* - * A symbol which consists of a # sign only - * signifies a a resident kernel segment. When we - * hit one of these we are done reading the - * module list. - */ - have_modules = 1; - return(1); - } - /* Allocate space for the module. */ - sym_array_modules = (struct Module *) \ - realloc(sym_array_modules, \ - (num_modules+1) * sizeof(struct Module)); - if ( sym_array_modules == (struct Module *) 0 ) - { - Syslog(LOG_WARNING, "Cannot allocate Module array.\n"); - return(0); - } - mp = &sym_array_modules[num_modules]; - - if ( (memfd = open("/dev/kmem", O_RDONLY)) < 0 ) - { - Syslog(LOG_WARNING, "Error opening /dev/kmem\n"); - return(0); - } - if ( lseek64(memfd, address, SEEK_SET) < 0 ) - { - Syslog(LOG_WARNING, "Error seeking in /dev/kmem\n"); - Syslog(LOG_WARNING, "Symbol %s, value %08x\n", symbol, address); - return(0); - } - if ( read(memfd, \ - (char *)&sym_array_modules[num_modules].module, \ - sizeof(struct module)) < 0 ) - { - Syslog(LOG_WARNING, "Error reading module " - "descriptor.\n"); - return(0); - } - close(memfd); - - /* Save the module name. */ - mp->name = (char *) malloc(strlen(&symbol[1]) + 1); - if ( mp->name == (char *) 0 ) - return(0); - strcpy(mp->name, &symbol[1]); - - mp->num_syms = 0; - mp->sym_array = (struct sym_table *) 0; - ++num_modules; - return(1); - } - else - { - if (num_modules > 0) - mp = &sym_array_modules[num_modules - 1]; - else - mp = &sym_array_modules[0]; - AddSymbol(mp, address, symbol); - } - - - return(1); -} - - -/************************************************************************** - * Function: AddSymbol - * - * Purpose: This function is responsible for adding a symbol name - * and its address to the symbol table. - * - * Arguements: (struct Module *) mp, (unsigned long) address, (char *) symbol - * - * mp:-> A pointer to the module which the symbol is - * to be added to. - * - * address:-> The address of the symbol. - * - * symbol:-> The name of the symbol. - * - * Return: int - * - * A boolean value is assumed. True if the addition is - * successful. False if not. - **************************************************************************/ - -static int AddSymbol(mp, address, symbol) - - struct Module *mp; - - unsigned long address; - - char *symbol; - -{ - auto int tmp; - - - /* Allocate space for the symbol table entry. */ - mp->sym_array = (struct sym_table *) realloc(mp->sym_array, \ - (mp->num_syms+1) * sizeof(struct sym_table)); - if ( mp->sym_array == (struct sym_table *) 0 ) - return(0); - - /* Then the space for the symbol. */ - tmp = strlen(symbol); - tmp += (strlen(mp->name) + 1); - mp->sym_array[mp->num_syms].name = (char *) malloc(tmp + 1); - if ( mp->sym_array[mp->num_syms].name == (char *) 0 ) - return(0); - memset(mp->sym_array[mp->num_syms].name, '\0', tmp + 1); - - /* Stuff interesting information into the module. */ - mp->sym_array[mp->num_syms].value = address; - strcpy(mp->sym_array[mp->num_syms].name, mp->name); - strcat(mp->sym_array[mp->num_syms].name, ":"); - strcat(mp->sym_array[mp->num_syms].name, symbol); - ++mp->num_syms; - - return(1); -} - - -/************************************************************************** - * Function: LookupModuleSymbol - * - * Purpose: Find the symbol which is related to the given address from - * a kernel module. - * - * Arguements: (long int) value, (struct symbol *) sym - * - * value:-> The address to be located. - * - * sym:-> A pointer to a structure which will be - * loaded with the symbol's parameters. - * - * Return: (char *) - * - * If a match cannot be found a diagnostic string is printed. - * If a match is found the pointer to the symbolic name most - * closely matching the address is returned. - **************************************************************************/ - -extern char * LookupModuleSymbol(value, sym) - - unsigned long value; - - struct symbol *sym; - -{ - auto int nmod, - nsym; - - auto struct sym_table *last; - - auto struct Module *mp; - - - sym->size = 0; - sym->offset = 0; - if ( num_modules == 0 ) - return((char *) 0); - - for(nmod= 0; nmod < num_modules; ++nmod) - { - mp = &sym_array_modules[nmod]; - - /* - * Run through the list of symbols in this module and - * see if the address can be resolved. - */ - for(nsym= 1, last = &mp->sym_array[0]; - nsym < mp->num_syms; - ++nsym) - { - if ( mp->sym_array[nsym].value > value ) - { - sym->offset = value - last->value; - sym->size = mp->sym_array[nsym].value - \ - last->value; - return(last->name); - } - last = &mp->sym_array[nsym]; - } - - - /* - * At this stage of the game we still cannot give up the - * ghost. There is the possibility that the address is - * from a module which has no symbols registered with - * the kernel. The solution is to compare the address - * against the starting address and extant of the module - * If it is in this range we can at least return the - * name of the module. - */ -#if LINUX_VERSION_CODE < 0x20112 - if ( (void *) value >= mp->module.addr && - (void *) value <= (mp->module.addr + \ - mp->module.size * 4096) ) -#else - if ( value >= mp->module_info.addr && - value <= (mp->module_info.addr + \ - mp->module.size * 4096) ) -#endif - { - /* - * A special case needs to be checked for. The above - * conditional tells us that we are within the - * extant of this module but symbol lookup has - * failed. - * - * We need to check to see if any symbols have - * been defined in this module. If there have been - * symbols defined the assumption must be made that - * the faulting address lies somewhere beyond the - * last symbol. About the only thing we can do - * at this point is use an offset from this - * symbol. - */ - if ( mp->num_syms > 0 ) - { - last = &mp->sym_array[mp->num_syms - 1]; -#if LINUX_VERSION_CODE < 0x20112 - sym->size = (int) mp->module.addr + \ - (mp->module.size * 4096) - value; -#else - sym->size = (int) mp->module_info.addr + \ - (mp->module.size * 4096) - value; -#endif - sym->offset = value - last->value; - return(last->name); - } - - /* - * There were no symbols defined for this module. - * Return the module name and the offset of the - * faulting address in the module. - */ - sym->size = mp->module.size * 4096; -#if LINUX_VERSION_CODE < 0x20112 - sym->offset = (void *) value - mp->module.addr; -#else - sym->offset = value - mp->module_info.addr; -#endif - return(mp->name); - } - } - - /* It has been a hopeless exercise. */ - return((char *) 0); -} - - -/* - * Setting the -DTEST define enables the following code fragment to - * be compiled. This produces a small standalone program which will - * dump the current kernel symbol table. - */ -#if defined(TEST) - -#include <stdarg.h> - - -extern int main(int, char **); - - -int main(argc, argv) - - int argc; - - char *argv[]; - -{ - auto int lp, syms; - - - if ( !InitMsyms() ) - { - fprintf(stderr, "Cannot load module symbols.\n"); - return(1); - } - - printf("Number of modules: %d\n\n", num_modules); - - for(lp= 0; lp < num_modules; ++lp) - { - printf("Module #%d = %s, Number of symbols = %d\n", lp + 1, \ - sym_array_modules[lp].name, \ - sym_array_modules[lp].num_syms); - - for (syms= 0; syms < sym_array_modules[lp].num_syms; ++syms) - { - printf("\tSymbol #%d\n", syms + 1); - printf("\tName: %s\n", \ - sym_array_modules[lp].sym_array[syms].name); - printf("\tAddress: %lx\n\n", \ - sym_array_modules[lp].sym_array[syms].value); - } - } - - FreeModules(); - return(0); -} - -extern void Syslog(int priority, char *fmt, ...) - -{ - va_list ap; - - va_start(ap, fmt); - fprintf(stdout, "Pr: %d, ", priority); - vfprintf(stdout, fmt, ap); - va_end(ap); - fputc('\n', stdout); - - return; -} - -#endif -#endif /* #ifdef FEATURE_KLOGD */ diff --git a/ksyms.h b/ksyms.h deleted file mode 100644 index 4e70ba05..00000000 --- a/ksyms.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - ksym.h - Definitions for symbol table utilities. - Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> - Copyright (c) 1996 Enjellic Systems Development - - This file is part of the sysklogd package, a kernel and 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -/* Variables, structures and type definitions static to this module. */ - -struct symbol -{ - char *name; - int size; - int offset; -}; - - -/* Function prototypes. */ -extern char * LookupSymbol(unsigned long, struct symbol *); -extern char * LookupModuleSymbol(unsigned long int, struct symbol *); diff --git a/liblogging-stub.h b/liblogging-stub.h index 644762c1..03315f08 100755..100644 --- a/liblogging-stub.h +++ b/liblogging-stub.h @@ -1,5 +1,24 @@ /* This is a (now *very slim*) stub for some liblogging
* code we use in rsyslog.
+ *
+ * Copyright (C) 2004, 2007 by 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 __LIB3195_LIBLOGGINGSTUB_H_INCLUDED__
#define __LIB3195_LIBLOGGINGSTUB_H_INCLUDED__ 1
diff --git a/linkedlist.c b/linkedlist.c index 27d6db36..383cf488 100644 --- a/linkedlist.c +++ b/linkedlist.c @@ -11,21 +11,22 @@ * * File begun on 2007-07-31 by RGerhards * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * Copyright (C) 2007, 2008 by 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -80,7 +81,7 @@ static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt) free(pElt); pList->iNumElts--; /* one less */ - return iRet; + RETiRet; } @@ -103,11 +104,11 @@ rsRetVal llDestroy(linkedList_t *pThis) */ llDestroyElt(pThis, pEltPrev); } - + /* now clean up the pointers */ pThis->pRoot = NULL; pThis->pLast = NULL; - return iRet; + RETiRet; } /* llDestroyRootElt - destroy the root element but otherwise @@ -135,7 +136,7 @@ rsRetVal llDestroyRootElt(linkedList_t *pThis) CHKiRet(llDestroyElt(pThis, pPrev)); finalize_it: - return iRet; + RETiRet; } @@ -167,7 +168,7 @@ rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppU *ppElt = pElt; - return iRet; + RETiRet; } @@ -205,7 +206,7 @@ static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData) finalize_it: *ppThis = pThis; - return iRet; + RETiRet; } @@ -228,7 +229,7 @@ rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData) pThis->pLast = pElt; finalize_it: - return iRet; + RETiRet; } @@ -268,7 +269,7 @@ static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t CHKiRet(llDestroyElt(pThis, pElt)); finalize_it: - return iRet; + RETiRet; } /* find a user element based on the provided key - this is the @@ -306,7 +307,7 @@ static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llEl } else iRet = RS_RET_NOT_FOUND; - return iRet; + RETiRet; } @@ -324,7 +325,7 @@ rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData) *ppData = pElt->pData; finalize_it: - return iRet; + RETiRet; } @@ -346,7 +347,7 @@ rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey) CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev)); finalize_it: - return iRet; + RETiRet; } @@ -361,7 +362,7 @@ rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt) *piCnt = pThis->iNumElts; - return iRet; + RETiRet; } @@ -405,7 +406,7 @@ rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* iRet = iRetLL; finalize_it: - return iRet; + RETiRet; } diff --git a/linkedlist.h b/linkedlist.h index e4de9c1c..98fb76a5 100644 --- a/linkedlist.h +++ b/linkedlist.h @@ -2,19 +2,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ diff --git a/module-template.h b/module-template.h index a5ece4fb..94fa1914 100644 --- a/module-template.h +++ b/module-template.h @@ -6,31 +6,63 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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)(); + #define DEF_OMOD_STATIC_DATA \ - static rsRetVal (*omsdRegCFSLineHdlr)(); + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_IMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_LMOD_STATIC_DATA \ + 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 + */ +#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_LIB \ + DEF_LMOD_STATIC_DATA \ + MODULE_TYPE(eMOD_LIB) /* 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 @@ -76,12 +108,13 @@ static rsRetVal createInstance(instanceData **ppData)\ #define CODESTARTcreateInstance \ if((pData = calloc(1, sizeof(instanceData))) == NULL) {\ *ppData = NULL;\ + ENDfunc \ return RS_RET_OUT_OF_MEMORY;\ } #define ENDcreateInstance \ *ppData = pData;\ - return iRet;\ + RETiRet;\ } /* freeInstance() @@ -105,7 +138,7 @@ static rsRetVal freeInstance(void* pModData)\ #define ENDfreeInstance \ if(pData != NULL)\ free(pData); /* we need to free this in any case */\ - return iRet;\ + RETiRet;\ } /* isCompatibleWithFeature() @@ -113,12 +146,13 @@ static rsRetVal freeInstance(void* pModData)\ #define BEGINisCompatibleWithFeature \ static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\ {\ - rsRetVal iRet = RS_RET_INCOMPATIBLE; + rsRetVal iRet = RS_RET_INCOMPATIBLE; \ + BEGINfunc #define CODESTARTisCompatibleWithFeature #define ENDisCompatibleWithFeature \ - return iRet;\ + RETiRet;\ } /* doAction() @@ -132,7 +166,7 @@ static rsRetVal doAction(uchar __attribute__((unused)) **ppString, unsigned __at /* ppString may be NULL if the output module requested no strings */ #define ENDdoAction \ - return iRet;\ + RETiRet;\ } @@ -150,71 +184,7 @@ static rsRetVal dbgPrintInstInfo(void *pModData)\ pData = (instanceData*) pModData; #define ENDdbgPrintInstInfo \ - return iRet;\ -} - - -/* needUDPSocket() - * Talks back to syslogd if the global UDP syslog socket is needed for - * sending. Returns 0 if not, 1 if needed. This interface hopefully goes - * away at some time, because it is kind of a hack. However, currently - * there is no way around it, so we need to support it. - * rgerhards, 2007-07-26 - */ -#define BEGINneedUDPSocket \ -static rsRetVal needUDPSocket(void *pModData)\ -{\ - rsRetVal iRet = RS_RET_FALSE;\ - instanceData *pData = NULL; - -#define CODESTARTneedUDPSocket \ - pData = (instanceData*) pModData; - -#define ENDneedUDPSocket \ - return iRet;\ -} - - -/* onSelectReadyWrite() - * Extra comments: - * This is called when select() returned with a writable file descriptor - * for this module. The fd was most probably obtained by getWriteFDForSelect() - * before. - */ -#define BEGINonSelectReadyWrite \ -static rsRetVal onSelectReadyWrite(void *pModData)\ -{\ - rsRetVal iRet = RS_RET_NONE;\ - instanceData *pData = NULL; - -#define CODESTARTonSelectReadyWrite \ - pData = (instanceData*) pModData; - -#define ENDonSelectReadyWrite \ - return iRet;\ -} - - -/* getWriteFDForSelect() - * Extra comments: - * Gets writefd for select call. Must only be returned when the selector must - * be written to. If the module has no such fds, it must return RS_RET_NONE. - * In this case, the default implementation is sufficient. - * This interface will probably go away over time, but we need it now to - * continue modularization. - */ -#define BEGINgetWriteFDForSelect \ -static rsRetVal getWriteFDForSelect(void *pModData, short __attribute__((unused)) *fd)\ -{\ - rsRetVal iRet = RS_RET_NONE;\ - instanceData *pData = NULL; - -#define CODESTARTgetWriteFDForSelect \ - assert(fd != NULL);\ - pData = (instanceData*) pModData; - -#define ENDgetWriteFDForSelect \ - return iRet;\ + RETiRet;\ } @@ -259,12 +229,13 @@ finalize_it:\ OMSRdestruct(*ppOMSR);\ *ppOMSR = NULL;\ }\ - if(pData != NULL)\ + if(pData != NULL) {\ freeInstance(pData);\ + } \ } #define ENDparseSelectorAct \ - return iRet;\ + RETiRet;\ } @@ -286,7 +257,7 @@ static rsRetVal tryResume(instanceData __attribute__((unused)) *pData)\ assert(pData != NULL); #define ENDtryResume \ - return iRet;\ + RETiRet;\ } @@ -300,50 +271,79 @@ static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\ DEFiRet; #define CODESTARTqueryEtryPt \ - if((name == NULL) || (pEtryPoint == NULL))\ + if((name == NULL) || (pEtryPoint == NULL)) {\ + ENDfunc \ return RS_RET_PARAM_ERROR;\ + } \ *pEtryPoint = NULL; #define ENDqueryEtryPt \ if(iRet == RS_RET_OK)\ - iRet = (*pEtryPoint == NULL) ? RS_RET_NOT_FOUND : RS_RET_OK;\ - return iRet;\ + 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;\ + } + /* 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 \ - if(!strcmp((char*) name, "doAction")) {\ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "doAction")) {\ *pEtryPoint = doAction;\ - } else if(!strcmp((char*) name, "parseSelectorAct")) {\ - *pEtryPoint = parseSelectorAct;\ - } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ - *pEtryPoint = isCompatibleWithFeature;\ } else if(!strcmp((char*) name, "dbgPrintInstInfo")) {\ *pEtryPoint = dbgPrintInstInfo;\ } else if(!strcmp((char*) name, "freeInstance")) {\ *pEtryPoint = freeInstance;\ - } else if(!strcmp((char*) name, "modExit")) {\ - *pEtryPoint = modExit;\ - } else if(!strcmp((char*) name, "getWriteFDForSelect")) {\ - *pEtryPoint = getWriteFDForSelect;\ - } else if(!strcmp((char*) name, "onSelectReadyWrite")) {\ - *pEtryPoint = onSelectReadyWrite;\ - } else if(!strcmp((char*) name, "needUDPSocket")) {\ - *pEtryPoint = needUDPSocket;\ + } else if(!strcmp((char*) name, "parseSelectorAct")) {\ + *pEtryPoint = parseSelectorAct;\ + } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ + *pEtryPoint = isCompatibleWithFeature;\ } else if(!strcmp((char*) name, "tryResume")) {\ *pEtryPoint = tryResume;\ - } else if(!strcmp((char*) name, "modGetID")) {\ - *pEtryPoint = modGetID;\ } +/* 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 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 /* 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 alwaysy only + * 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 @@ -366,19 +366,25 @@ static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\ * 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 (**)()))\ +rsRetVal modInit##uniqName(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t __attribute__((unused)) *pModInfo)\ {\ - DEFiRet; + DEFiRet; \ + rsRetVal (*pObjGetObjInterface)(obj_if_t *pIf); #define CODESTARTmodInit \ assert(pHostQueryEtryPt != NULL);\ - if((pQueryEtryPt == NULL) || (ipIFVersProvided == NULL))\ - return RS_RET_PARAM_ERROR; + 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)); #define ENDmodInit \ finalize_it:\ *pQueryEtryPt = queryEtryPt;\ - return iRet;\ + RETiRet;\ } @@ -391,7 +397,7 @@ finalize_it:\ /* 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-globale data structures and registrations. + * 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. @@ -408,9 +414,68 @@ static rsRetVal modExit(void)\ #define CODESTARTmodExit #define ENDmodExit \ - return iRet;\ + 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;\ +} + + /* * vi:set ai: */ diff --git a/module.h b/module.h deleted file mode 100644 index cb8e243f..00000000 --- a/module.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Module definitions for klogd's module support */ -struct kernel_sym -{ - unsigned long value; - char name[60]; -}; - -struct module_symbol -{ - unsigned long value; - const char *name; -}; - -struct module_ref -{ - struct module *dep; /* "parent" pointer */ - struct module *ref; /* "child" pointer */ - struct module_ref *next_ref; -}; - -struct module_info -{ - unsigned long addr; - unsigned long size; - unsigned long flags; - long usecount; -}; - - -typedef struct { volatile int counter; } atomic_t; - -struct module -{ - unsigned long size_of_struct; /* == sizeof(module) */ - struct module *next; - const char *name; - unsigned long size; - - union - { - atomic_t usecount; - long pad; - } uc; /* Needs to keep its size - so says rth */ - - unsigned long flags; /* AUTOCLEAN et al */ - - unsigned nsyms; - unsigned ndeps; - - struct module_symbol *syms; - struct module_ref *deps; - struct module_ref *refs; - int (*init)(void); - void (*cleanup)(void); - const struct exception_table_entry *ex_table_start; - const struct exception_table_entry *ex_table_end; -#ifdef __alpha__ - unsigned long gp; -#endif -}; - @@ -1,24 +1,32 @@ /* modules.c * This is the implementation of syslogd modules object. - * This object handles plug-ins and buil-in modules of all kind. + * 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 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 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. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -31,6 +39,9 @@ #include <time.h> #include <assert.h> #include <errno.h> +#ifdef OS_BSD +# include "libgen.h" +#endif #include <dlfcn.h> /* TODO: replace this with the libtools equivalent! */ @@ -40,10 +51,117 @@ #include "syslogd.h" #include "cfsysline.h" #include "modules.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) static modInfo_t *pLoadedModules = NULL; /* list of currently-loaded modules */ static modInfo_t *pLoadedModulesLast = NULL; /* tail-pointer */ -static int bCfsyslineInitialized = 0; + +/* config settings */ +uchar *pModDir = NULL; /* read-only after startup */ + + +#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 @@ -70,22 +188,29 @@ static rsRetVal moduleConstruct(modInfo_t **pThis) */ static void moduleDestruct(modInfo_t *pThis) { + assert(pThis != NULL); if(pThis->pszName != NULL) free(pThis->pszName); - if(pThis->pModHdlr != NULL) + if(pThis->pModHdlr != NULL) { +# ifdef VALGRIND +# warning "dlclose disabled for valgrind" +# else dlclose(pThis->pModHdlr); +# endif + } + free(pThis); } -/* The followind function is the queryEntryPoint for host-based entry points. +/* 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 */ -rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) +static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) { DEFiRet; @@ -94,11 +219,23 @@ rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) if(!strcmp((char*) name, "regCfSysLineHdlr")) { *pEtryPoint = regCfSysLineHdlr; + } else if(!strcmp((char*) name, "objGetObjInterface")) { + *pEtryPoint = objGetObjInterface; + } else { + *pEtryPoint = NULL; /* to be on the safe side */ + ABORT_FINALIZE(RS_RET_ENTRY_POINT_NOT_FOUND); } - if(iRet == RS_RET_OK) - iRet = (*pEtryPoint == NULL) ? RS_RET_NOT_FOUND : RS_RET_OK; - return iRet; +finalize_it: + RETiRet; +} + + +/* get the name of a module + */ +static uchar *modGetName(modInfo_t *pThis) +{ + return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); } @@ -108,23 +245,16 @@ rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) * rgerhards, 2007-07-24 * TODO: the actual state name is not yet pulled */ -uchar *modGetStateName(modInfo_t *pThis) +static uchar *modGetStateName(modInfo_t *pThis) { return(modGetName(pThis)); } -/* get the name of a module - */ -uchar *modGetName(modInfo_t *pThis) -{ - return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); -} - - /* Add a module to the loaded module linked list */ -static inline void addModToList(modInfo_t *pThis) +static inline void +addModToList(modInfo_t *pThis) { assert(pThis != NULL); @@ -132,6 +262,7 @@ static inline void addModToList(modInfo_t *pThis) pLoadedModules = pLoadedModulesLast = pThis; } else { /* there already exist entries */ + pThis->pPrev = pLoadedModulesLast; pLoadedModulesLast->pNext = pThis; pLoadedModulesLast = pThis; } @@ -145,7 +276,7 @@ static inline void addModToList(modInfo_t *pThis) * returned - then, the list is empty. * rgerhards, 2007-07-23 */ -modInfo_t *modGetNxt(modInfo_t *pThis) +static modInfo_t *GetNxt(modInfo_t *pThis) { modInfo_t *pNew; @@ -158,14 +289,20 @@ modInfo_t *modGetNxt(modInfo_t *pThis) } -/* this function is like modGetNxt(), but it returns pointers to - * output modules only. As we currently deal just with output modules, +/* this function is like GetNxt(), but it returns pointers to + * modules of specific type only. As we currently deal just with output modules, * it is a dummy, to be filled with real code later. * rgerhards, 2007-07-24 */ -modInfo_t *omodGetNxt(modInfo_t *pThis) +static modInfo_t *GetNxtType(modInfo_t *pThis, eModType_t rqtdType) { - return(modGetNxt(pThis)); + modInfo_t *pMod = pThis; + + do { + pMod = GetNxt(pMod); + } while(!(pMod == NULL || pMod->eType == rqtdType)); /* warning: do ... while() */ + + return pMod; } @@ -177,112 +314,90 @@ modInfo_t *omodGetNxt(modInfo_t *pThis) * 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) +static rsRetVal +modPrepareUnload(modInfo_t *pThis) { DEFiRet; void *pModCookie; assert(pThis != NULL); - /* WARNING - the current code does NOT work and causes an abort - this is acceptable right now - * as I am DEVELOPING the working code and will NOT release until it is there. If you use a - * CVS snapshot, be aware of this limitation. For now, you can just remove everything up to - * (but not including) the END DEVEL comment. That will do the trick. rgerhards, 2007-11-21 - */ + 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)); - /* END DEVEL */ - finalize_it: - return iRet; + RETiRet; } /* Add an already-loaded module to the module linked list. This function does * everything needed to fully initialize the module. */ -rsRetVal doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)()), uchar *name, void *pModHdlr) +static rsRetVal +doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*), uchar *name, void *pModHdlr) { DEFiRet; - modInfo_t *pNew; + modInfo_t *pNew = NULL; + rsRetVal (*modGetType)(eModType_t *pType); assert(modInit != NULL); - if(bCfsyslineInitialized == 0) { - /* we need to initialize the cfsysline subsystem first */ - CHKiRet(cfsyslineInit()); - bCfsyslineInitialized = 1; + if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) { + pNew = NULL; + ABORT_FINALIZE(iRet); } - if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) - return iRet; + CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew)); - if((iRet = (*modInit)(1, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - - if(pNew->iIFVers != 1) { - moduleDestruct(pNew); - return RS_RET_MISSING_INTERFACE; + 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((iRet = (*modGetType)(&pNew->eType)) != RS_RET_OK); + dbgprintf("module of type %d being loaded.\n", pNew->eType); + /* OK, we know we can successfully work with the module. So we now fill the - * rest of the data elements. + * rest of the data elements. First we load the interfaces common to all + * module types. */ - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature", - &pNew->isCompatibleWithFeature)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo", - &pNew->dbgPrintInstInfo)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"getWriteFDForSelect", &pNew->getWriteFDForSelect)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"onSelectReadyWrite", &pNew->onSelectReadyWrite)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"needUDPSocket", &pNew->needUDPSocket)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; - } - if((iRet = (*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)) != RS_RET_OK) { - moduleDestruct(pNew); - return iRet; + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)); + + /* ... 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)); + 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*)"isCompatibleWithFeature", &pNew->isCompatibleWithFeature)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)); + break; + case eMOD_LIB: + break; } pNew->pszName = (uchar*) strdup((char*)name); /* we do not care if strdup() fails, we can accept that */ pNew->pModHdlr = pModHdlr; - pNew->eType = eMOD_OUT; /* TODO: take this from module */ /* TODO: take this from module */ if(pModHdlr == NULL) pNew->eLinkType = eMOD_LINK_STATIC; @@ -293,18 +408,24 @@ rsRetVal doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)()) addModToList(pNew); finalize_it: - return iRet; + if(iRet != RS_RET_OK) { + if(pNew != NULL) + moduleDestruct(pNew); + } + + 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! */ -void modPrintList(void) +static void modPrintList(void) { modInfo_t *pMod; - pMod = modGetNxt(NULL); + pMod = GetNxt(NULL); while(pMod != NULL) { dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ", (char*) modGetName(pMod), pMod->iIFVers); @@ -316,8 +437,8 @@ void modPrintList(void) case eMOD_IN: dbgprintf("input"); break; - case eMOD_FILTER: - dbgprintf("filter"); + case eMOD_LIB: + dbgprintf("library"); break; } dbgprintf(" module.\n"); @@ -328,70 +449,354 @@ void modPrintList(void) dbgprintf("\tdbgPrintInstInfo: 0x%lx\n", (unsigned long) pMod->dbgPrintInstInfo); dbgprintf("\tfreeInstance: 0x%lx\n", (unsigned long) pMod->freeInstance); dbgprintf("\n"); - pMod = modGetNxt(pMod); /* done, go next */ + pMod = GetNxt(pMod); /* done, go next */ } } -/* unload all modules and free module linked list - * rgerhards, 2007-08-09 +/* 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 */ -rsRetVal modUnloadAndDestructAll(void) +static rsRetVal +modUnlinkAndDestroy(modInfo_t **ppThis) { DEFiRet; - modInfo_t *pMod; - modInfo_t *pModPrev; + modInfo_t *pThis; - pMod = modGetNxt(NULL); - while(pMod != NULL) { - pModPrev = pMod; - pMod = modGetNxt(pModPrev); /* get next */ - /* now we can destroy the previous module */ - dbgprintf("Unloading module %s\n", modGetName(pModPrev)); - modPrepareUnload(pModPrev); - moduleDestruct(pModPrev); + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + /* 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; } - return iRet; + 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: + RETiRet; } -rsRetVal modUnloadAndDestructDynamic(void) +/* 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 *pMod; - modInfo_t *pModPrev; + 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); + } + /* Note: if the module was successfully unloaded, it has updated the + * pModCurr pointer to the next module. So we do NOT need to advance + * to the next module on successful unload. + */ + } else { + pModCurr = GetNxt(pModCurr); + } + } + +# ifdef DEBUG + if(pLoadedModules != NULL) { + dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n"); + modUsrPrintAll(); + } +# endif - pLoadedModulesLast = NULL; + RETiRet; +} - pMod = modGetNxt(NULL); - while(pMod != NULL) { - pModPrev = pMod; - pMod = modGetNxt(pModPrev); /* get next */ - /* now we can destroy the previous module */ - if(pModPrev->eLinkType != eMOD_LINK_STATIC) { - dbgprintf("Unloading module %s\n", modGetName(pModPrev)); - modPrepareUnload(pModPrev); - moduleDestruct(pModPrev); - } else { - pLoadedModulesLast = pModPrev; + +/* 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. + */ +static rsRetVal +Load(uchar *pModName) +{ + DEFiRet; + + size_t iPathLen, iModNameLen; + uchar szPath[PATH_MAX]; + uchar *pModNameCmp; + int bHasExtension; + void *pModHdlr, *pModInit; + modInfo_t *pModInfo; + + assert(pModName != NULL); + dbgprintf("Requested to load module '%s'\n", pModName); + + iModNameLen = strlen((char *) pModName); + if(iModNameLen > 3 && !strcmp((char *) pModName + iModNameLen - 3, ".so")) { + iModNameLen -= 3; + bHasExtension = TRUE; + } else + bHasExtension = FALSE; + + 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' already loaded\n", pModName); + ABORT_FINALIZE(RS_RET_OK); } + pModInfo = GetNxt(pModInfo); } - /* Note: the last modules pNext pointer is now invalid - * (except if the last module was not touched, what is highly - * unlikely. We simply fix this be setting it to NULL. After all, - * it is the last module ;). This bug had some severe effects in - * v3, but none in v2 because in v2 the list was never again - * traversed before a new one was added. But even in v2 it may cause - * a segfault if the number of loaded modules changed between HUPs. - * rgerhards, 2008-02-26 - */ - if(pLoadedModulesLast != NULL) - pLoadedModulesLast->pNext = NULL; + /* now build our load module name */ + if(*pModName == '/') { + *szPath = '\0'; /* we do not need to append the path - its already in the module name */ + iPathLen = 0; + } else { + *szPath = '\0'; + strncat((char *) szPath, (pModDir == NULL) ? _PATH_MODDIR : (char*) pModDir, sizeof(szPath) - 1); + iPathLen = strlen((char*) szPath); + if((szPath[iPathLen - 1] != '/')) { + if((iPathLen <= sizeof(szPath) - 2)) { + szPath[iPathLen++] = '/'; + szPath[iPathLen] = '\0'; + } else { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); + } + } + } + + /* ... add actual name ... */ + strncat((char *) szPath, (char *) pModName, sizeof(szPath) - 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 + */ + /* ... so now add the extension */ + strncat((char *) szPath, ".so", sizeof(szPath) - strlen((char*) szPath) - 1); + iPathLen += 3; + } + + if(iPathLen + strlen((char*) pModName) >= sizeof(szPath)) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); + } + + /* complete load path constructed, so ... GO! */ + dbgprintf("loading module '%s'\n", szPath); + if(!(pModHdlr = dlopen((char *) szPath, RTLD_NOW))) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlopen: %s\n", szPath, dlerror()); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN); + } + if(!(pModInit = dlsym(pModHdlr, "modInit"))) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlsym: %s\n", szPath, dlerror()); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_NO_INIT); + } + if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr)) != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', rsyslog error %d\n", szPath, iRet); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED); + } - return iRet; +finalize_it: + RETiRet; } -/* - * vi:set ai: + + +/* 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); + +# 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->GetNxtType = GetNxtType; + pIf->GetName = modGetName; + pIf->GetStateName = modGetStateName; + pIf->PrintList = modPrintList; + 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: */ @@ -1,29 +1,33 @@ /* modules.h - * Definition for build-in and plug-ins module handler. * - * The following definitions are to be used for modularization. Currently, - * the code is NOT complete. I am just adding pieces to it as I - * go along in designing the interface. - * rgerhards, 2007-07-19 + * 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 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -31,35 +35,55 @@ #define MODULES_H_INCLUDED 1 #include "objomsr.h" +#include "threads.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 + */ +#define CURR_MOD_IF_VERSION 4 typedef enum eModType_ { eMOD_IN, /* input module */ eMOD_OUT, /* output module */ - eMOD_FILTER /* filter module (not know yet if we will once have such at all...) */ + eMOD_LIB /* library module - this module provides one or many interfaces */ } 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_DYNAMIC_LOADED, /* dynalink module, currently loaded */ + eMOD_LINK_ALL /* special: all linkage types, e.g. for unload */ } eModLinkType_t; -typedef struct moduleInfo { - struct moduleInfo *pNext; /* support for creating a linked module list */ +typedef 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; uchar* pszName; /* printable module name, e.g. for dbgprintf */ + unsigned uRefCnt; /* reference count for this module; 0 -> may be unloaded */ /* 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 (*getWriteFDForSelect)(void*,short *);/* called before termination or module unload */ - rsRetVal (*onSelectReadyWrite)(void*);/* called when fd is writeable after select() */ - rsRetVal (*needUDPSocket)(void*);/* called when fd is writeable after select() */ 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 */ @@ -72,11 +96,12 @@ typedef struct moduleInfo { * can allocate instance memory in this call. */ rsRetVal (*createInstance)(); + /* TODO: pass pointer to msg submit function to IM rger, 2007-12-14 */ union { struct {/* data for input modules */ - /* input modules come after output modules are finished, I am - * currently not really thinking about them. rgerhards, 2007-07-19 - */ + rsRetVal (*runInput)(thrdInfo_t*); /* function to gather input and submit to queue */ + rsRetVal (*willRun)(void); /* function to gather input and submit to queue */ + rsRetVal (*afterRun)(thrdInfo_t*); /* function to gather input and submit to queue */ } im; struct {/* data for output modules */ /* below: perform the configured action @@ -84,18 +109,40 @@ typedef struct moduleInfo { rsRetVal (*doAction)(uchar**, unsigned, void*); rsRetVal (*parseSelectorAct)(uchar**, void**,omodStringRequest_t**); } om; + struct { /* data for library modules */ + } fm; } 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). In + * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 + */ + modUsr_t *pModUsrRoot; +# endif } modInfo_t; +/* interfaces */ +BEGINinterface(module) /* name must also be changed in ENDinterface macro! */ + modInfo_t *(*GetNxt)(modInfo_t *pThis); + modInfo_t *(*GetNxtType)(modInfo_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); + rsRetVal (*Load)(uchar *name); + rsRetVal (*SetModDir)(uchar *name); +ENDinterface(module) +#define moduleCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + /* prototypes */ -rsRetVal doModInit(rsRetVal (*modInit)(), uchar *name, void *pModHdlr); -modInfo_t *omodGetNxt(modInfo_t *pThis); -uchar *modGetName(modInfo_t *pThis); -uchar *modGetStateName(modInfo_t *pThis); -void modPrintList(void); -rsRetVal modUnloadAndDestructAll(void); -rsRetVal modUnloadAndDestructDynamic(void); +PROTOTYPEObj(module); + +/* TODO: remove them below (means move the config init code) -- rgerhards, 2008-02-19 */ +extern uchar *pModDir; /* read-only after startup */ + #endif /* #ifndef MODULES_H_INCLUDED */ /* @@ -9,36 +9,47 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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> #define SYSLOG_NAMES -#include <sys/syslog.h> #include <string.h> #include <assert.h> #include <ctype.h> +#include "rsyslog.h" #include "syslogd.h" #include "srUtils.h" +#include "stringbuf.h" #include "template.h" #include "msg.h" +#include "var.h" +#include "datetime.h" +#include "regexp.h" +#include "atomic.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) +DEFobjCurrIf(datetime) +DEFobjCurrIf(regexp) static syslogCODE rs_prioritynames[] = { @@ -57,17 +68,22 @@ static syslogCODE rs_prioritynames[] = { NULL, -1 } }; +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif static syslogCODE rs_facilitynames[] = { { "auth", LOG_AUTH }, { "authpriv", LOG_AUTHPRIV }, { "cron", LOG_CRON }, { "daemon", LOG_DAEMON }, - { "ftp", LOG_FTP }, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif { "kern", LOG_KERN }, { "lpr", LOG_LPR }, { "mail", LOG_MAIL }, - { "mark", INTERNAL_MARK }, /* INTERNAL */ + //{ "mark", INTERNAL_MARK }, /* INTERNAL */ { "news", LOG_NEWS }, { "security", LOG_AUTH }, /* DEPRECATED */ { "syslog", LOG_SYSLOG }, @@ -84,6 +100,9 @@ static syslogCODE rs_facilitynames[] = { NULL, -1 } }; +/* some forward declarations */ +static int getAPPNAMELen(msg_t *pM); + /* The following functions will support advanced output module * multithreading, once this is implemented. Currently, we * include them as hooks only. The idea is that we need to guard @@ -96,96 +115,224 @@ static syslogCODE rs_facilitynames[] = * for "set" methods, as these are called during input. Only "get" * functions that modify important structures have them. * rgerhards, 2007-07-20 + * We now support locked and non-locked operations, depending on + * the configuration of rsyslog. To support this, we use function + * pointers. Initially, we start in non-locked mode. There, all + * locking operations call into dummy functions. When locking is + * enabled, the function pointers are changed to functions doing + * actual work. We also introduced another MsgPrepareEnqueue() function + * which initializes the locking structures, if needed. This is + * necessary because internal messages during config file startup + * processing are always created in non-locking mode. So we can + * not initialize locking structures during constructions. We now + * postpone this until when the message is fully constructed and + * enqueued. Then we know the status of locking. This has a nice + * side effect, and that is that during the initial creation of + * the Msg object no locking needs to be done, which results in better + * performance. -- rgerhards, 2008-01-05 + */ +static void (*funcLock)(msg_t *pMsg); +static void (*funcUnlock)(msg_t *pMsg); +static void (*funcDeleteMutex)(msg_t *pMsg); +void (*funcMsgPrepareEnqueue)(msg_t *pMsg); +#if 1 /* This is a debug aid */ +#define MsgLock(pMsg) funcLock(pMsg) +#define MsgUnlock(pMsg) funcUnlock(pMsg) +#else +#define MsgLock(pMsg) {dbgprintf("line %d\n - ", __LINE__); funcLock(pMsg);; } +#define MsgUnlock(pMsg) {dbgprintf("line %d - ", __LINE__); funcUnlock(pMsg); } +#endif + +/* the next function is a dummy to be used by the looking functions + * when the class is not yet running in an environment where locking + * is necessary. Please note that the need to lock can (and will) change + * during a single run. Typically, this is depending on the operation mode + * of the message queues (which is operator-configurable). -- rgerhards, 2008-01-05 + */ +static void MsgLockingDummy(msg_t __attribute__((unused)) *pMsg) +{ + /* empty be design */ +} + + +/* The following function prepares a message for enqueue into the queue. This is + * where a message may be accessed by multiple threads. This implementation here + * is the version for multiple concurrent acces. It initializes the locking + * structures. + * TODO: change to an iRet interface! -- rgerhards, 2008-07-14 + */ +static void MsgPrepareEnqueueLockingCase(msg_t *pThis) +{ + int iErr; + BEGINfunc + assert(pThis != NULL); + iErr = pthread_mutexattr_init(&pThis->mutAttr); + if(iErr != 0) { + dbgprintf("error initializing mutex attribute in %s:%d, trying to continue\n", + __FILE__, __LINE__); + } + iErr = pthread_mutexattr_settype(&pThis->mutAttr, PTHREAD_MUTEX_RECURSIVE); + if(iErr != 0) { + dbgprintf("ERROR setting mutex attribute to recursive in %s:%d, trying to continue " + "but we will probably either abort or hang soon\n", + __FILE__, __LINE__); + /* TODO: it makes very little sense to continue here, + * but it requires an iRet interface to gracefully shut + * down. We should do that over time. -- rgerhards, 2008-07-14 + */ + } + pthread_mutex_init(&pThis->mut, &pThis->mutAttr); + + /* we do no longer need the attribute. According to the + * POSIX spec, we can destroy it without affecting the + * initialized mutex (that used the attribute). + * rgerhards, 2008-07-14 + */ + pthread_mutexattr_destroy(&pThis->mutAttr); + ENDfunc +} + + +/* ... and now the locking and unlocking implementations: */ +static void MsgLockLockingCase(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); */ + assert(pThis != NULL); + pthread_mutex_lock(&pThis->mut); +} + +static void MsgUnlockLockingCase(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); */ + assert(pThis != NULL); + pthread_mutex_unlock(&pThis->mut); +} + +/* delete the mutex object on message destruction (locking case) */ -#define MsgLock(pMsg) -#define MsgUnlock(pMsg) +static void MsgDeleteMutexLockingCase(msg_t *pThis) +{ + assert(pThis != NULL); + pthread_mutex_destroy(&pThis->mut); +} + +/* enable multiple concurrent access on the message object + * This works on a class-wide basis and can bot be undone. + * That is, if it is once enabled, it can not be disabled during + * the same run. When this function is called, no other thread + * must manipulate message objects. Then we would have race conditions, + * but guarding against this is counter-productive because it + * would cost additional time. Plus, it would be a programming error. + * rgerhards, 2008-01-05 + */ +rsRetVal MsgEnableThreadSafety(void) +{ + DEFiRet; + funcLock = MsgLockLockingCase; + funcUnlock = MsgUnlockLockingCase; + funcMsgPrepareEnqueue = MsgPrepareEnqueueLockingCase; + funcDeleteMutex = MsgDeleteMutexLockingCase; + RETiRet; +} + +/* end locking functions */ /* "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()". + * via "msgDestruct()". */ -msg_t* MsgConstruct(void) +rsRetVal msgConstruct(msg_t **ppThis) { + DEFiRet; msg_t *pM; - if((pM = calloc(1, sizeof(msg_t))) != NULL) - { /* initialize members that are non-zero */ - pM->iRefCount = 1; - pM->iSyslogVers = -1; - pM->iSeverity = -1; - pM->iFacility = -1; - getCurrTime(&(pM->tRcvdAt)); - } + assert(ppThis != NULL); + if((pM = calloc(1, sizeof(msg_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - /* DEV debugging only! dbgprintf("MsgConstruct\t0x%x, ref 1\n", (int)pM);*/ + /* initialize members that are non-zero */ + pM->iRefCount = 1; + pM->iSeverity = -1; + pM->iFacility = -1; + datetime.getCurrTime(&(pM->tRcvdAt)); + objConstructSetObjInfo(pM); - return(pM); + /* DEV debugging only! dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);*/ + + *ppThis = pM; + +finalize_it: + RETiRet; } -/* Destructor for a msg "object". Must be called to dispose - * of a msg object. - */ -void MsgDestruct(msg_t * pM) -{ - assert(pM != NULL); - /* DEV Debugging only ! dbgprintf("MsgDestruct\t0x%x, Ref now: %d\n", (int)pM, pM->iRefCount - 1); */ - if(--pM->iRefCount == 0) +BEGINobjDestruct(msg) /* be sure to specify the object type also in END and CODESTART macros! */ + int currRefCount; +CODESTARTobjDestruct(msg) + /* DEV Debugging only ! dbgprintf("msgDestruct\t0x%lx, Ref now: %d\n", (unsigned long)pM, pM->iRefCount - 1); */ +# ifdef DO_HAVE_ATOMICS + currRefCount = ATOMIC_DEC_AND_FETCH(pThis->iRefCount); +# else + currRefCount = --pThis->iRefCount; +# endif + if(currRefCount == 0) { - /* DEV Debugging Only! dbgprintf("MsgDestruct\t0x%x, RefCount now 0, doing DESTROY\n", (int)pM); */ - if(pM->pszUxTradMsg != NULL) - free(pM->pszUxTradMsg); - if(pM->pszRawMsg != NULL) - free(pM->pszRawMsg); - if(pM->pszTAG != NULL) - free(pM->pszTAG); - if(pM->pszHOSTNAME != NULL) - free(pM->pszHOSTNAME); - if(pM->pszRcvFrom != NULL) - free(pM->pszRcvFrom); - if(pM->pszMSG != NULL) - free(pM->pszMSG); - if(pM->pszFacility != NULL) - free(pM->pszFacility); - if(pM->pszFacilityStr != NULL) - free(pM->pszFacilityStr); - if(pM->pszSeverity != NULL) - free(pM->pszSeverity); - if(pM->pszSeverityStr != NULL) - free(pM->pszSeverityStr); - if(pM->pszRcvdAt3164 != NULL) - free(pM->pszRcvdAt3164); - if(pM->pszRcvdAt3339 != NULL) - free(pM->pszRcvdAt3339); - if(pM->pszRcvdAt_MySQL != NULL) - free(pM->pszRcvdAt_MySQL); - if(pM->pszRcvdAt_PgSQL != NULL) - free(pM->pszRcvdAt_PgSQL); - if(pM->pszTIMESTAMP3164 != NULL) - free(pM->pszTIMESTAMP3164); - if(pM->pszTIMESTAMP3339 != NULL) - free(pM->pszTIMESTAMP3339); - if(pM->pszTIMESTAMP_MySQL != NULL) - free(pM->pszTIMESTAMP_MySQL); - if(pM->pszTIMESTAMP_PgSQL != NULL) - free(pM->pszTIMESTAMP_PgSQL); - if(pM->pszPRI != NULL) - free(pM->pszPRI); - if(pM->pCSProgName != NULL) - rsCStrDestruct(pM->pCSProgName); - if(pM->pCSStrucData != NULL) - rsCStrDestruct(pM->pCSStrucData); - if(pM->pCSAPPNAME != NULL) - rsCStrDestruct(pM->pCSAPPNAME); - if(pM->pCSPROCID != NULL) - rsCStrDestruct(pM->pCSPROCID); - if(pM->pCSMSGID != NULL) - rsCStrDestruct(pM->pCSMSGID); - free(pM); + /* DEV Debugging Only! dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", (unsigned long)pThis); */ + if(pThis->pszUxTradMsg != NULL) + free(pThis->pszUxTradMsg); + if(pThis->pszRawMsg != NULL) + free(pThis->pszRawMsg); + if(pThis->pszTAG != NULL) + free(pThis->pszTAG); + if(pThis->pszHOSTNAME != NULL) + free(pThis->pszHOSTNAME); + if(pThis->pszRcvFrom != NULL) + free(pThis->pszRcvFrom); + if(pThis->pszMSG != NULL) + free(pThis->pszMSG); + if(pThis->pszFacility != NULL) + free(pThis->pszFacility); + if(pThis->pszFacilityStr != NULL) + free(pThis->pszFacilityStr); + if(pThis->pszSeverity != NULL) + free(pThis->pszSeverity); + if(pThis->pszSeverityStr != NULL) + free(pThis->pszSeverityStr); + if(pThis->pszRcvdAt3164 != NULL) + free(pThis->pszRcvdAt3164); + if(pThis->pszRcvdAt3339 != NULL) + free(pThis->pszRcvdAt3339); + if(pThis->pszRcvdAt_MySQL != NULL) + free(pThis->pszRcvdAt_MySQL); + if(pThis->pszRcvdAt_PgSQL != NULL) + free(pThis->pszRcvdAt_PgSQL); + if(pThis->pszTIMESTAMP3164 != NULL) + free(pThis->pszTIMESTAMP3164); + if(pThis->pszTIMESTAMP3339 != NULL) + free(pThis->pszTIMESTAMP3339); + if(pThis->pszTIMESTAMP_MySQL != NULL) + free(pThis->pszTIMESTAMP_MySQL); + if(pThis->pszTIMESTAMP_PgSQL != NULL) + free(pThis->pszTIMESTAMP_PgSQL); + if(pThis->pszPRI != NULL) + free(pThis->pszPRI); + if(pThis->pCSProgName != NULL) + rsCStrDestruct(&pThis->pCSProgName); + 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); + funcDeleteMutex(pThis); + } else { + pThis = NULL; /* tell framework not to destructing the object! */ } -} +ENDobjDestruct(msg) /* The macros below are used in MsgDup(). I use macros @@ -195,7 +342,7 @@ void MsgDestruct(msg_t * pM) #define tmpCOPYSZ(name) \ if(pOld->psz##name != NULL) { \ if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\ - MsgDestruct(pNew);\ + msgDestruct(&pNew);\ return NULL;\ }\ pNew->iLen##name = pOld->iLen##name;\ @@ -208,7 +355,7 @@ void MsgDestruct(msg_t * pM) #define tmpCOPYCSTR(name) \ if(pOld->pCS##name != NULL) {\ if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\ - MsgDestruct(pNew);\ + msgDestruct(&pNew);\ return NULL;\ }\ } @@ -226,15 +373,13 @@ msg_t* MsgDup(msg_t* pOld) assert(pOld != NULL); - if((pNew = (msg_t*) calloc(1, sizeof(msg_t))) == NULL) { - glblHadMemShortage = 1; + BEGINfunc + if(msgConstruct(&pNew) != RS_RET_OK) { return NULL; } /* now copy the message properties */ pNew->iRefCount = 1; - pNew->iSyslogVers = pOld->iSyslogVers; - pNew->bParseHOSTNAME = pOld->bParseHOSTNAME; pNew->iSeverity = pOld->iSeverity; pNew->iFacility = pOld->iFacility; pNew->bParseHOSTNAME = pOld->bParseHOSTNAME; @@ -264,12 +409,59 @@ msg_t* MsgDup(msg_t* pOld) * 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 bParseHOSTNAME, 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) +{ + DEFiRet; + + assert(pThis != NULL); + assert(pStrm != NULL); + + 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, tRcvdAt, SYSLOGTIME); + objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME); + + objSerializePTR(pStrm, pszRawMsg, PSZ); + objSerializePTR(pStrm, pszMSG, PSZ); + objSerializePTR(pStrm, pszUxTradMsg, PSZ); + objSerializePTR(pStrm, pszTAG, PSZ); + objSerializePTR(pStrm, pszHOSTNAME, PSZ); + objSerializePTR(pStrm, pszRcvFrom, PSZ); + + objSerializePTR(pStrm, pCSStrucData, CSTR); + objSerializePTR(pStrm, pCSAPPNAME, CSTR); + objSerializePTR(pStrm, pCSPROCID, CSTR); + objSerializePTR(pStrm, pCSMSGID, CSTR); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + /* 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. @@ -280,9 +472,13 @@ msg_t* MsgDup(msg_t* pOld) msg_t *MsgAddRef(msg_t *pM) { assert(pM != NULL); - MsgLock(); - pM->iRefCount++; - MsgUnlock(); +# ifdef DO_HAVE_ATOMICS + ATOMIC_INC(pM->iRefCount); +# 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); } @@ -301,7 +497,7 @@ msg_t *MsgAddRef(msg_t *pM) static rsRetVal aquirePROCIDFromTAG(msg_t *pM) { register int i; - int iRet; + DEFiRet; assert(pM != NULL); if(pM->pCSPROCID != NULL) @@ -320,12 +516,10 @@ static rsRetVal aquirePROCIDFromTAG(msg_t *pM) ++i; /* skip '[' */ /* now obtain the PROCID string... */ - if((pM->pCSPROCID = rsCStrConstruct()) == NULL) - return RS_RET_OBJ_CREATION_FAILED; /* best we can do... */ + CHKiRet(rsCStrConstruct(&pM->pCSPROCID)); rsCStrSetAllocIncrement(pM->pCSPROCID, 16); while((i < pM->iLenTAG) && (pM->pszTAG[i] != ']')) { - if((iRet = rsCStrAppendChar(pM->pCSPROCID, pM->pszTAG[i])) != RS_RET_OK) - return iRet; + CHKiRet(rsCStrAppendChar(pM->pCSPROCID, pM->pszTAG[i])); ++i; } @@ -335,16 +529,15 @@ static rsRetVal aquirePROCIDFromTAG(msg_t *pM) * the buffer and simply return. Note that this is NOT an error * case! */ - rsCStrDestruct(pM->pCSPROCID); - pM->pCSPROCID = NULL; - return RS_RET_OK; + rsCStrDestruct(&pM->pCSPROCID); + FINALIZE; } /* OK, finaally we could obtain a PROCID. So let's use it ;) */ - if((iRet = rsCStrFinish(pM->pCSPROCID)) != RS_RET_OK) - return iRet; + CHKiRet(rsCStrFinish(pM->pCSPROCID)); - return RS_RET_OK; +finalize_it: + RETiRet; } @@ -366,29 +559,27 @@ static rsRetVal aquirePROCIDFromTAG(msg_t *pM) */ static rsRetVal aquireProgramName(msg_t *pM) { + DEFiRet; register int i; - int iRet; assert(pM != NULL); if(pM->pCSProgName == NULL) { /* ok, we do not yet have it. So let's parse the TAG * to obtain it. */ - if((pM->pCSProgName = rsCStrConstruct()) == NULL) - return RS_RET_OBJ_CREATION_FAILED; /* best we can do... */ + CHKiRet(rsCStrConstruct(&pM->pCSProgName)); rsCStrSetAllocIncrement(pM->pCSProgName, 33); for( i = 0 ; (i < pM->iLenTAG) && isprint((int) pM->pszTAG[i]) && (pM->pszTAG[i] != '\0') && (pM->pszTAG[i] != ':') && (pM->pszTAG[i] != '[') && (pM->pszTAG[i] != '/') ; ++i) { - if((iRet = rsCStrAppendChar(pM->pCSProgName, pM->pszTAG[i])) != RS_RET_OK) - return iRet; + CHKiRet(rsCStrAppendChar(pM->pCSProgName, pM->pszTAG[i])); } - if((iRet = rsCStrFinish(pM->pCSProgName)) != RS_RET_OK) - return iRet; + CHKiRet(rsCStrFinish(pM->pCSProgName)); } - return RS_RET_OK; +finalize_it: + RETiRet; } @@ -482,21 +673,25 @@ char *getMSG(msg_t *pM) /* Get PRI value in text form */ char *getPRI(msg_t *pM) { + int pri; + if(pM == NULL) return ""; - MsgLock(); + MsgLock(pM); if(pM->pszPRI == NULL) { - /* OK, we need to construct it... - * we use a 5 byte buffer - as of - * RFC 3164, it can't be longer. Should it - * still be, snprintf will truncate... + /* OK, we need to construct it... we use a 5 byte buffer - as of + * RFC 3164, it can't be longer. Should it still be, snprintf will truncate... + * Note that we do not use the LOG_MAKEPRI macro. This macro + * is a simple add of the two values under FreeBSD 7. So we implement + * the logic in our own code. This is a change from a bug + * report. -- rgerhards, 2008-07-14 */ + pri = pM->iFacility * 8 + pM->iSeverity; if((pM->pszPRI = malloc(5)) == NULL) return ""; - pM->iLenPRI = snprintf((char*)pM->pszPRI, 5, "%d", - LOG_MAKEPRI(pM->iFacility, pM->iSeverity)); + pM->iLenPRI = snprintf((char*)pM->pszPRI, 5, "%d", pri); } - MsgUnlock(); + MsgUnlock(pM); return (char*)pM->pszPRI; } @@ -517,64 +712,64 @@ char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) switch(eFmt) { case tplFmtDefault: - MsgLock(); + MsgLock(pM); if(pM->pszTIMESTAMP3164 == NULL) { if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszTIMESTAMP3164); case tplFmtMySQLDate: - MsgLock(); + MsgLock(pM); if(pM->pszTIMESTAMP_MySQL == NULL) { if((pM->pszTIMESTAMP_MySQL = malloc(15)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL, 15); + datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL, 15); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszTIMESTAMP_MySQL); case tplFmtPgSQLDate: - MsgLock(); + MsgLock(pM); if(pM->pszTIMESTAMP_PgSQL == NULL) { if((pM->pszTIMESTAMP_PgSQL = malloc(21)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL, 21); + datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL, 21); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszTIMESTAMP_PgSQL); case tplFmtRFC3164Date: - MsgLock(); + MsgLock(pM); if(pM->pszTIMESTAMP3164 == NULL) { if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszTIMESTAMP3164); case tplFmtRFC3339Date: - MsgLock(); + MsgLock(pM); if(pM->pszTIMESTAMP3339 == NULL) { if((pM->pszTIMESTAMP3339 = malloc(33)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; /* TODO: check this: can it cause a free() of constant memory?) */ } - formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339, 33); + datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339, 33); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszTIMESTAMP3339); } return "INVALID eFmt OPTION!"; @@ -587,64 +782,64 @@ char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) switch(eFmt) { case tplFmtDefault: - MsgLock(); + MsgLock(pM); if(pM->pszRcvdAt3164 == NULL) { if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszRcvdAt3164); case tplFmtMySQLDate: - MsgLock(); + MsgLock(pM); if(pM->pszRcvdAt_MySQL == NULL) { if((pM->pszRcvdAt_MySQL = malloc(15)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL, 15); + datetime.formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL, 15); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszRcvdAt_MySQL); case tplFmtPgSQLDate: - MsgLock(); + MsgLock(pM); if(pM->pszRcvdAt_PgSQL == NULL) { if((pM->pszRcvdAt_PgSQL = malloc(21)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL, 21); + datetime.formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL, 21); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszRcvdAt_PgSQL); case tplFmtRFC3164Date: - MsgLock(); + MsgLock(pM); if(pM->pszRcvdAt3164 == NULL) { if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszRcvdAt3164); case tplFmtRFC3339Date: - MsgLock(); + MsgLock(pM); if(pM->pszRcvdAt3339 == NULL) { if((pM->pszRcvdAt3339 = malloc(33)) == NULL) { glblHadMemShortage = 1; - MsgUnlock(); + MsgUnlock(pM); return ""; } - formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339, 33); + datetime.formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339, 33); } - MsgUnlock(); + MsgUnlock(pM); return(pM->pszRcvdAt3339); } return "INVALID eFmt OPTION!"; @@ -656,14 +851,14 @@ char *getSeverity(msg_t *pM) if(pM == NULL) return ""; - MsgLock(); + MsgLock(pM); if(pM->pszSeverity == NULL) { /* we use a 2 byte buffer - can only be one digit */ - if((pM->pszSeverity = malloc(2)) == NULL) { MsgUnlock() ; return ""; } + if((pM->pszSeverity = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } pM->iLenSeverity = snprintf((char*)pM->pszSeverity, 2, "%d", pM->iSeverity); } - MsgUnlock(); + MsgUnlock(pM); return((char*)pM->pszSeverity); } @@ -677,7 +872,7 @@ char *getSeverityStr(msg_t *pM) if(pM == NULL) return ""; - MsgLock(); + MsgLock(pM); if(pM->pszSeverityStr == NULL) { for(c = rs_prioritynames, val = pM->iSeverity; c->c_name; c++) if(c->c_val == val) { @@ -686,15 +881,15 @@ char *getSeverityStr(msg_t *pM) } if(name == NULL) { /* we use a 2 byte buffer - can only be one digit */ - if((pM->pszSeverityStr = malloc(2)) == NULL) { MsgUnlock() ; return ""; } + if((pM->pszSeverityStr = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } pM->iLenSeverityStr = snprintf((char*)pM->pszSeverityStr, 2, "%d", pM->iSeverity); } else { - if((pM->pszSeverityStr = (uchar*) strdup(name)) == NULL) { MsgUnlock() ; return ""; } + if((pM->pszSeverityStr = (uchar*) strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } pM->iLenSeverityStr = strlen((char*)name); } } - MsgUnlock(); + MsgUnlock(pM); return((char*)pM->pszSeverityStr); } @@ -703,17 +898,17 @@ char *getFacility(msg_t *pM) if(pM == NULL) return ""; - MsgLock(); + MsgLock(pM); if(pM->pszFacility == NULL) { /* we use a 12 byte buffer - as of * syslog-protocol, facility can go * up to 2^32 -1 */ - if((pM->pszFacility = malloc(12)) == NULL) { MsgUnlock() ; return ""; } + if((pM->pszFacility = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } pM->iLenFacility = snprintf((char*)pM->pszFacility, 12, "%d", pM->iFacility); } - MsgUnlock(); + MsgUnlock(pM); return((char*)pM->pszFacility); } @@ -726,7 +921,7 @@ char *getFacilityStr(msg_t *pM) if(pM == NULL) return ""; - MsgLock(); + MsgLock(pM); if(pM->pszFacilityStr == NULL) { for(c = rs_facilitynames, val = pM->iFacility << 3; c->c_name; c++) if(c->c_val == val) { @@ -738,93 +933,88 @@ char *getFacilityStr(msg_t *pM) * syslog-protocol, facility can go * up to 2^32 -1 */ - if((pM->pszFacilityStr = malloc(12)) == NULL) { MsgUnlock() ; return ""; } + if((pM->pszFacilityStr = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } pM->iLenFacilityStr = snprintf((char*)pM->pszFacilityStr, 12, "%d", val >> 3); } else { - if((pM->pszFacilityStr = (uchar*)strdup(name)) == NULL) { MsgUnlock() ; return ""; } + if((pM->pszFacilityStr = (uchar*)strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } pM->iLenFacilityStr = strlen((char*)name); } } - MsgUnlock(); + MsgUnlock(pM); return((char*)pM->pszFacilityStr); } +/* 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; +} + + /* rgerhards 2004-11-24: set APP-NAME in msg object + * TODO: revisit msg locking code! */ rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME) { + DEFiRet; assert(pMsg != NULL); if(pMsg->pCSAPPNAME == NULL) { /* we need to obtain the object first */ - if((pMsg->pCSAPPNAME = rsCStrConstruct()) == NULL) - return RS_RET_OBJ_CREATION_FAILED; /* best we can do... */ + CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME)); rsCStrSetAllocIncrement(pMsg->pCSAPPNAME, 128); } /* if we reach this point, we have the object */ - return rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME); -} - - -/* 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. - */ -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, getProgramName(pM)); - } -} + iRet = rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME); - -/* rgerhards, 2005-11-24 - */ -int getAPPNAMELen(msg_t *pM) -{ - assert(pM != NULL); - MsgLock(); - if(pM->pCSAPPNAME == NULL) - tryEmulateAPPNAME(pM); - MsgUnlock(); - return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); +finalize_it: + RETiRet; } +static void tryEmulateAPPNAME(msg_t *pM); /* forward reference */ /* rgerhards, 2005-11-24 */ char *getAPPNAME(msg_t *pM) { assert(pM != NULL); - MsgLock(); + MsgLock(pM); if(pM->pCSAPPNAME == NULL) tryEmulateAPPNAME(pM); - MsgUnlock(); + MsgUnlock(pM); return (pM->pCSAPPNAME == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSAPPNAME); } - - /* rgerhards 2004-11-24: set PROCID in msg object */ rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID) { - assert(pMsg != NULL); + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); if(pMsg->pCSPROCID == NULL) { /* we need to obtain the object first */ - if((pMsg->pCSPROCID = rsCStrConstruct()) == NULL) - return RS_RET_OBJ_CREATION_FAILED; /* best we can do... */ + CHKiRet(rsCStrConstruct(&pMsg->pCSPROCID)); rsCStrSetAllocIncrement(pMsg->pCSPROCID, 128); } /* if we reach this point, we have the object */ - return rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID); + iRet = rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID); + +finalize_it: + RETiRet; } /* rgerhards, 2005-11-24 @@ -832,10 +1022,10 @@ rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID) int getPROCIDLen(msg_t *pM) { assert(pM != NULL); - MsgLock(); + MsgLock(pM); if(pM->pCSPROCID == NULL) aquirePROCIDFromTAG(pM); - MsgUnlock(); + MsgUnlock(pM); return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID); } @@ -844,12 +1034,15 @@ int getPROCIDLen(msg_t *pM) */ char *getPROCID(msg_t *pM) { - assert(pM != NULL); - MsgLock(); + char* pszRet; + + ISOBJ_TYPE_assert(pM, msg); + MsgLock(pM); if(pM->pCSPROCID == NULL) aquirePROCIDFromTAG(pM); - MsgUnlock(); - return (pM->pCSPROCID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSPROCID); + pszRet = (pM->pCSPROCID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSPROCID); + MsgUnlock(pM); + return pszRet; } @@ -857,15 +1050,18 @@ char *getPROCID(msg_t *pM) */ rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID) { - assert(pMsg != NULL); + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); if(pMsg->pCSMSGID == NULL) { /* we need to obtain the object first */ - if((pMsg->pCSMSGID = rsCStrConstruct()) == NULL) - return RS_RET_OBJ_CREATION_FAILED; /* best we can do... */ + CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID)); rsCStrSetAllocIncrement(pMsg->pCSMSGID, 128); } /* if we reach this point, we have the object */ - return rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID); + iRet = rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID); + +finalize_it: + RETiRet; } /* rgerhards, 2005-11-24 @@ -966,17 +1162,17 @@ char *getTAG(msg_t *pM) { char *ret; - MsgLock(); if(pM == NULL) ret = ""; else { + MsgLock(pM); tryEmulateTAG(pM); if(pM->pszTAG == NULL) ret = ""; else ret = (char*) pM->pszTAG; + MsgUnlock(pM); } - MsgUnlock(); return(ret); } @@ -1020,15 +1216,18 @@ char *getRcvFrom(msg_t *pM) */ rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData) { - assert(pMsg != NULL); + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); if(pMsg->pCSStrucData == NULL) { /* we need to obtain the object first */ - if((pMsg->pCSStrucData = rsCStrConstruct()) == NULL) - return RS_RET_OBJ_CREATION_FAILED; /* best we can do... */ + CHKiRet(rsCStrConstruct(&pMsg->pCSStrucData)); rsCStrSetAllocIncrement(pMsg->pCSStrucData, 128); } /* if we reach this point, we have the object */ - return rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData); + iRet = rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData); + +finalize_it: + RETiRet; } /* get the length of the "STRUCTURED-DATA" sz string @@ -1060,13 +1259,13 @@ int getProgramNameLen(msg_t *pM) int iRet; assert(pM != NULL); - MsgLock(); + MsgLock(pM); if((iRet = aquireProgramName(pM)) != RS_RET_OK) { dbgprintf("error %d returned by aquireProgramName() in getProgramNameLen()\n", iRet); - MsgUnlock(); + MsgUnlock(pM); return 0; /* best we can do (consistent wiht what getProgramName() returns) */ } - MsgUnlock(); + MsgUnlock(pM); return (pM->pCSProgName == NULL) ? 0 : rsCStrLen(pM->pCSProgName); } @@ -1075,21 +1274,100 @@ int getProgramNameLen(msg_t *pM) /* get the "programname" as sz string * rgerhards, 2005-10-19 */ -char *getProgramName(msg_t *pM) +char *getProgramName(msg_t *pM) /* this is the non-locking version for internal use */ +{ + int iRet; + char *pszRet; + + assert(pM != NULL); + MsgLock(pM); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); + pszRet = ""; /* best we can do */ + } else { + pszRet = (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); + } + + MsgUnlock(pM); + return pszRet; +} +/* The code below was an approach without PTHREAD_MUTEX_RECURSIVE + * However, it turned out to be quite complex. So far, we use recursive + * locking, which is OK from a performance point of view, especially as + * we do not anticipate that multithreading msg objects is used often. + * However, we may re-think about using non-recursive locking and I leave this + * code in here to conserve the idea. -- rgerhards, 2008-01-05 + */ +#if 0 +static char *getProgramNameNoLock(msg_t *pM) /* this is the non-locking version for internal use */ { int iRet; assert(pM != NULL); - MsgLock(); if((iRet = aquireProgramName(pM)) != RS_RET_OK) { dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); - MsgUnlock(); return ""; /* best we can do */ } - MsgUnlock(); return (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); } +char *getProgramName(msg_t *pM) /* this is the external callable version */ +{ + char *pszRet; + + MsgLock(pM); + pszRet = getProgramNameNoLock(pM); + MsgUnlock(pM); + return pszRet; +} +/* an alternative approach has been: */ +/* The macro below is used to generate external function definitions + * for such functions that may also be called internally (and thus have + * both a locking and non-locking implementation. Over time, we could + * reconsider how we handle that. -- rgerhards, 2008-01-05 + */ +#define EXT_LOCKED_FUNC(fName, ret) \ +ret fName(msg_t *pM) \ +{ \ + ret valRet; \ + MsgLock(pM); \ + valRet = fName##NoLock(pM); \ + MsgUnlock(pM); \ + return(valRet); \ +} +EXT_LOCKED_FUNC(getProgramName, char*) +/* in this approach, the external function is provided by the macro and + * needs not to be writen. + */ +#endif /* #if 0 -- saved code */ + + +/* 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. + */ +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, getProgramName(pM)); + } +} + + +/* rgerhards, 2005-11-24 + */ +static int getAPPNAMELen(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pCSAPPNAME == NULL) + tryEmulateAPPNAME(pM); + return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); +} /* rgerhards 2004-11-16: set pszRcvFrom in msg object @@ -1255,7 +1533,7 @@ static uchar *getNOW(eNOWType eNow) return NULL; } - getCurrTime(&t); + datetime.getCurrTime(&t); switch(eNow) { case NOW_NOW: snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); @@ -1326,7 +1604,7 @@ static uchar *getNOW(eNOWType eNow) * rgerhards 2005-09-15 */ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, - rsCStrObj *pCSPropName, unsigned short *pbMustBeFreed) + cstr_t *pCSPropName, unsigned short *pbMustBeFreed) { uchar *pName; char *pRes; /* result pointer */ @@ -1357,18 +1635,17 @@ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, pRes = getMSG(pMsg); } else if(!strcmp((char*) pName, "rawmsg")) { pRes = getRawMsg(pMsg); - } else if(!strcmp((char*) pName, "UxTradMsg")) { + } else if(!strcmp((char*) pName, "uxtradmsg")) { pRes = getUxTradMsg(pMsg); - } else if(!strcmp((char*) pName, "FROMHOST")) { + } else if(!strcmp((char*) pName, "fromhost")) { pRes = getRcvFrom(pMsg); - } else if(!strcmp((char*) pName, "source") - || !strcmp((char*) pName, "HOSTNAME")) { + } else if(!strcmp((char*) pName, "source") || !strcmp((char*) pName, "hostname")) { pRes = getHOSTNAME(pMsg); } else if(!strcmp((char*) pName, "syslogtag")) { pRes = getTAG(pMsg); - } else if(!strcmp((char*) pName, "PRI")) { + } else if(!strcmp((char*) pName, "pri")) { pRes = getPRI(pMsg); - } else if(!strcmp((char*) pName, "PRI-text")) { + } else if(!strcmp((char*) pName, "pri-text")) { pBuf = malloc(20 * sizeof(char)); if(pBuf == NULL) { *pbMustBeFreed = 0; @@ -1390,57 +1667,57 @@ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, } else if(!strcmp((char*) pName, "timegenerated")) { pRes = getTimeGenerated(pMsg, pTpe->data.field.eDateFormat); } else if(!strcmp((char*) pName, "timereported") - || !strcmp((char*) pName, "TIMESTAMP")) { + || !strcmp((char*) pName, "timestamp")) { pRes = getTimeReported(pMsg, pTpe->data.field.eDateFormat); } else if(!strcmp((char*) pName, "programname")) { pRes = getProgramName(pMsg); - } else if(!strcmp((char*) pName, "PROTOCOL-VERSION")) { + } else if(!strcmp((char*) pName, "protocol-version")) { pRes = getProtocolVersionString(pMsg); - } else if(!strcmp((char*) pName, "STRUCTURED-DATA")) { + } else if(!strcmp((char*) pName, "structured-data")) { pRes = getStructuredData(pMsg); - } else if(!strcmp((char*) pName, "APP-NAME")) { + } else if(!strcmp((char*) pName, "app-name")) { pRes = getAPPNAME(pMsg); - } else if(!strcmp((char*) pName, "PROCID")) { + } else if(!strcmp((char*) pName, "procid")) { pRes = getPROCID(pMsg); - } else if(!strcmp((char*) pName, "MSGID")) { + } else if(!strcmp((char*) pName, "msgid")) { pRes = getMSGID(pMsg); /* here start system properties (those, that do not relate to the message itself */ - } else if(!strcmp((char*) pName, "$NOW")) { + } else if(!strcmp((char*) pName, "$now")) { if((pRes = (char*) getNOW(NOW_NOW)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$YEAR")) { + } else if(!strcmp((char*) pName, "$year")) { if((pRes = (char*) getNOW(NOW_YEAR)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$MONTH")) { + } else if(!strcmp((char*) pName, "$month")) { if((pRes = (char*) getNOW(NOW_MONTH)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$DAY")) { + } else if(!strcmp((char*) pName, "$day")) { if((pRes = (char*) getNOW(NOW_DAY)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$HOUR")) { + } else if(!strcmp((char*) pName, "$hour")) { if((pRes = (char*) getNOW(NOW_HOUR)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$HHOUR")) { + } else if(!strcmp((char*) pName, "$hhour")) { if((pRes = (char*) getNOW(NOW_HHOUR)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$QHOUR")) { + } else if(!strcmp((char*) pName, "$qhour")) { if((pRes = (char*) getNOW(NOW_QHOUR)) == NULL) { return "***OUT OF MEMORY***"; } else *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$MINUTE")) { + } else if(!strcmp((char*) pName, "$minute")) { if((pRes = (char*) getNOW(NOW_MINUTE)) == NULL) { return "***OUT OF MEMORY***"; } else @@ -1449,6 +1726,7 @@ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, /* there is no point in continuing, we may even otherwise render the * error message unreadable. rgerhards, 2007-07-10 */ + dbgprintf("invalid property name: '%s'\n", pName); return "**INVALID PROPERTY NAME**"; } @@ -1484,7 +1762,7 @@ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, ++iCurrFld; } } - dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, iCurrFld); + dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, (int) iCurrFld); if(iCurrFld == pTpe->data.field.iToPos) { /* field found, now extract it */ @@ -1566,45 +1844,85 @@ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, if (pTpe->data.field.has_regex != 0) { if (pTpe->data.field.has_regex == 2) /* Could not compile regex before! */ - return - "**NO MATCH** **BAD REGULAR EXPRESSION**"; + return "**NO MATCH** **BAD REGULAR EXPRESSION**"; - dbgprintf("debug: String to match for regex is: %s\n", - pRes); + dbgprintf("debug: String to match for regex is: %s\n", pRes); - if (0 != regexec(&pTpe->data.field.re, pRes, nmatch, - pmatch, 0)) { - /* we got no match! */ - return "**NO MATCH**"; - } else { - /* Match! */ - /* I need to malloc pB */ - int iLenBuf; - char *pB; + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + if (0 != regexp.regexec(&pTpe->data.field.re, pRes, nmatch, pmatch, 0)) { + /* we got no match! */ + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + return "**NO MATCH**"; + } else { + /* Match! */ + /* I need to malloc pB */ + int iLenBuf; + char *pB; + + iLenBuf = pmatch[0].rm_eo - pmatch[0].rm_so; + pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); + + if (pB == NULL) { + if (*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY ALLOCATING pBuf**"; + } - iLenBuf = pmatch[0].rm_eo - pmatch[0].rm_so; - pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); + /* Lets copy the matched substring to the buffer */ + memcpy(pB, pRes + pmatch[0].rm_so, iLenBuf); + pB[iLenBuf] = '\0';/* terminate string, did not happen before */ - if (pB == NULL) { if (*pbMustBeFreed == 1) free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY ALLOCATING pBuf**"; + pRes = pB; + *pbMustBeFreed = 1; } - - /* Lets copy the matched substring to the buffer */ - memcpy(pB, pRes + pmatch[0].rm_so, iLenBuf); - pB[iLenBuf] = '\0';/* terminate string, did not happen before */ - - if (*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); - pRes = pB; - *pbMustBeFreed = 1; + *pbMustBeFreed = 0; + } + return "***REGEXP NOT AVAILABLE***"; } } #endif /* #ifdef FEATURE_REGEXP */ } + /* now check if we need to do our "SP if first char is non-space" hack logic */ + if(*pRes && pTpe->data.field.options.bSPIffNo1stSP) { + char *pB; + uchar cFirst = *pRes; + + /* here, we always destruct the buffer and return a new one */ + pB = (char *) malloc(2 * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + pRes = pB; + *pbMustBeFreed = 1; + + if(cFirst == ' ') { + /* if we have a SP, we must return an empty string */ + *pRes = '\0'; /* empty */ + } else { + /* if it is no SP, we need to return one */ + *pRes = ' '; + *(pRes+1) = '\0'; + } + } + if(*pRes) { /* case conversations (should go after substring, because so we are able to * work on the smallest possible buffer). @@ -1884,6 +2202,149 @@ char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, } +/* The returns a message variable suitable for use with RainerScript. Most importantly, this means + * that the value is returned in a var_t object. The var_t is constructed inside this function and + * MUST be freed by the caller. + * rgerhards, 2008-02-25 + */ +rsRetVal +msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar) +{ + DEFiRet; + var_t *pVar; + uchar *pszProp = NULL; + cstr_t *pstrProp; + unsigned short bMustBeFreed = 0; + + ISOBJ_TYPE_assert(pThis, msg); + ASSERT(pstrPropName != NULL); + ASSERT(ppVar != NULL); + + /* make sure we have a var_t instance */ + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + /* always call MsgGetProp() without a template specifier */ + pszProp = (uchar*) MsgGetProp(pThis, NULL, pstrPropName, &bMustBeFreed); + + /* now create a string object out of it and hand that over to the var */ + CHKiRet(rsCStrConstructFromszStr(&pstrProp, pszProp)); + CHKiRet(var.SetString(pVar, pstrProp)); + + /* finally store var */ + *ppVar = pVar; + +finalize_it: + if(bMustBeFreed) + free(pszProp); + + RETiRet; +} + + +/* 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) +{ + 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("pszRawMsg")) { + MsgSetRawMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszMSG")) { + MsgSetMSG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszUxTradMsg")) { + MsgSetUxTradMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszTAG")) { + MsgSetTAG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszRcvFrom")) { + MsgSetHOSTNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszHOSTNAME")) { + MsgSetRcvFrom(pThis, (char*) rsCStrGetSzStrNoNULL(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("tRcvdAt")) { + memcpy(&pThis->tRcvdAt, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } else if(isProp("tTIMESTAMP")) { + memcpy(&pThis->tTIMESTAMP, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } + + RETiRet; +} +#undef isProp + + +/* This is a construction finalizer that must be called after all properties + * have been set. It does some final work on the message object. After this + * is done, the object is considered ready for full processing. + * rgerhards, 2008-07-08 + */ +static rsRetVal msgConstructFinalizer(msg_t *pThis) +{ + MsgPrepareEnqueue(pThis); + return RS_RET_OK; +} + + +/* get the severity - this is an entry point that + * satisfies the base object class getSeverity semantics. + * rgerhards, 2008-01-14 + */ +static rsRetVal +MsgGetSeverity(obj_t *pThis, int *piSeverity) +{ + ISOBJ_TYPE_assert(pThis, msg); + assert(piSeverity != NULL); + *piSeverity = ((msg_t*) pThis)->iSeverity; + return RS_RET_OK; +} + + +/* 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(var, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize); + OBJSetMethodHandler(objMethod_SETPROPERTY, MsgSetProperty); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, msgConstructFinalizer); + OBJSetMethodHandler(objMethod_GETSEVERITY, MsgGetSeverity); + /* initially, we have no need to lock message objects */ + funcLock = MsgLockingDummy; + funcUnlock = MsgLockingDummy; + funcDeleteMutex = MsgLockingDummy; + funcMsgPrepareEnqueue = MsgLockingDummy; +ENDObjClassInit(msg) + /* * vi:set ai: */ @@ -5,25 +5,30 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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 "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 "obj.h" #include "syslogd-types.h" #include "template.h" @@ -43,10 +48,10 @@ * called each time a "copy" is stored somewhere. */ struct msg { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + pthread_mutexattr_t mutAttr; + pthread_mutex_t mut; int iRefCount; /* reference counter (0 = unused) */ - short iSyslogVers; /* version of syslog protocol - * 0 - RFC 3164 - * 1 - RFC draft-protocol-08 */ short bParseHOSTNAME; /* should the hostname be parsed from the message? */ /* background: the hostname is not present on "regular" messages * received via UNIX domain sockets from the same machine. However, @@ -54,12 +59,14 @@ struct msg { * sockets. All in all, the parser would need parse templates, that would * resolve all these issues... rgerhards, 2005-10-06 */ + 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. */ short iSeverity; /* the severity 0..7 */ uchar *pszSeverity; /* severity as string... */ int iLenSeverity; /* ... and its length. */ uchar *pszSeverityStr; /* severity name... */ int iLenSeverityStr; /* ... and its length. */ - int iFacility; /* Facility code (up to 2^32-1) */ + short iFacility; /* Facility code 0 .. 23*/ uchar *pszFacility; /* Facility as string... */ int iLenFacility; /* ... and its length. */ uchar *pszFacilityStr; /* facility name... */ @@ -81,12 +88,12 @@ struct msg { int iLenHOSTNAME; /* Length of HOSTNAME */ uchar *pszRcvFrom; /* System message was received from */ int iLenRcvFrom; /* Length of pszRcvFrom */ - int iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */ - rsCStrObj *pCSProgName; /* the (BSD) program name */ - rsCStrObj *pCSStrucData;/* STRUCTURED-DATA */ - rsCStrObj *pCSAPPNAME; /* APP-NAME */ - rsCStrObj *pCSPROCID; /* PROCID */ - rsCStrObj *pCSMSGID; /* MSGID */ + short iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */ + cstr_t *pCSProgName; /* the (BSD) program name */ + cstr_t *pCSStrucData;/* STRUCTURED-DATA */ + cstr_t *pCSAPPNAME; /* APP-NAME */ + cstr_t *pCSPROCID; /* PROCID */ + cstr_t *pCSMSGID; /* MSGID */ struct syslogTime tRcvdAt;/* time the message entered this program */ char *pszRcvdAt3164; /* time as RFC3164 formatted string (always 15 charcters) */ char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 charcters at most) */ @@ -103,9 +110,10 @@ typedef struct msg msg_t; /* new name */ /* function prototypes */ +PROTOTYPEObjClassInit(msg); char* getProgramName(msg_t*); -msg_t* MsgConstruct(void); -void MsgDestruct(msg_t * pM); +rsRetVal msgConstruct(msg_t **ppThis); +rsRetVal msgDestruct(msg_t **ppM); msg_t* MsgDup(msg_t* pOld); msg_t *MsgAddRef(msg_t *pM); void setProtocolVersion(msg_t *pM, int iNewVersion); @@ -124,7 +132,6 @@ char *getSeverityStr(msg_t *pM); char *getFacility(msg_t *pM); char *getFacilityStr(msg_t *pM); rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME); -int getAPPNAMELen(msg_t *pM); char *getAPPNAME(msg_t *pM); rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID); int getPROCIDLen(msg_t *pM); @@ -132,6 +139,7 @@ char *getPROCID(msg_t *pM); rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID); void MsgAssignTAG(msg_t *pMsg, uchar *pBuf); void MsgSetTAG(msg_t *pMsg, char* pszTAG); +rsRetVal MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl); char *getTAG(msg_t *pM); int getHOSTNAMELen(msg_t *pM); char *getHOSTNAME(msg_t *pM); @@ -149,8 +157,19 @@ void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg); void moveHOSTNAMEtoTAG(msg_t *pM); char *getMSGID(msg_t *pM); char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, - rsCStrObj *pCSPropName, unsigned short *pbMustBeFreed); + cstr_t *pCSPropName, unsigned short *pbMustBeFreed); char *textpri(char *pRes, size_t pResLen, int pri); +rsRetVal msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar); +rsRetVal MsgEnableThreadSafety(void); + +/* The MsgPrepareEnqueue() function is a macro for performance reasons. + * It needs one global variable to work. This is acceptable, as it gains + * us quite some performance and is fully abstracted using this header file. + * The important thing is that no other module is permitted to actually + * access that global variable! -- rgerhards, 2008-01-05 + */ +extern void (*funcMsgPrepareEnqueue)(msg_t *pMsg); +#define MsgPrepareEnqueue(pMsg) funcMsgPrepareEnqueue(pMsg) #endif /* #ifndef MSG_H_INCLUDED */ /* @@ -7,28 +7,32 @@ * 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. * + * 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 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 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. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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 SYSLOG_INET - #include "rsyslog.h" #include <stdio.h> #include <stdarg.h> @@ -39,11 +43,588 @@ #include <signal.h> #include <ctype.h> #include <netdb.h> +#include <fnmatch.h> +#include <fcntl.h> +#include <unistd.h> #include "syslogd.h" #include "syslogd-types.h" +#include "module-template.h" +#include "parse.h" +#include "srUtils.h" +#include "obj.h" +#include "errmsg.h" #include "net.h" +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + +/* 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) */ + + +/* 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; +} + + +/* 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 *)(sa)) +#define SIN6(sa) ((struct sockaddr_in6 *)(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) { + glblHadMemShortage = 1; + 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; + + 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); + } +} + + +/* 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(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(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(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(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 (DisableDNS) { + errmsg.LogError(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(NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); + + if (ACLAddHostnameOnFail) { + errmsg.LogError(NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + FINALIZE; + } else { + errmsg.LogError(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) { + glblHadMemShortage = 1; + 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) { + glblHadMemShortage = 1; + 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 (struct sockaddr_in)); + + 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) { + glblHadMemShortage = 1; + 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(NO_ERRCODE, "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(NO_ERRCODE, "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(NO_ERRCODE, "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(NO_ERRCODE, "Error %d adding allowed sender entry " + "- ignoring.", iRet); + } else { + errmsg.LogError(NO_ERRCODE, "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, something else otherwise. + * contributed 1007-07-16 by mildew@gmail.com + */ +static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost) +{ + assert(pAllow != NULL); + assert(pFrom != NULL); + + if(F_ISSET(pAllow->flags, ADDR_NAME)) { + 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 otherwise. + * rgerhards, 2005-09-26 + */ +static int isAllowedSender(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost) +{ + struct AllowedSenders *pAllow; + struct AllowedSenders *pAllowRoot; + + assert(pFrom != NULL); + + if(setAllowRoot(&pAllowRoot, pszType) != RS_RET_OK) + return 0; /* if something went wrong, we denie 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) { + if (MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost)) + return 1; + } + return 0; +} + + /* The following #ifdef sequence is a small compatibility * layer. It tries to work around the different availality * levels of SO_BSDCOMPAT on linuxes... @@ -52,19 +633,20 @@ * It still needs to be a bit better adapted to rsyslog. * rgerhards 2005-09-19 */ -#ifndef BSD #include <sys/utsname.h> -int should_use_so_bsdcompat(void) +static int +should_use_so_bsdcompat(void) { +#ifndef OS_BSD static int init_done; static int so_bsdcompat_is_obsolete; if (!init_done) { - struct utsname utsname; + struct utsname myutsname; unsigned int version, patchlevel; init_done = 1; - if (uname(&utsname) < 0) { + if (uname(&myutsname) < 0) { char errStr[1024]; dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr))); return 1; @@ -72,9 +654,9 @@ int should_use_so_bsdcompat(void) /* 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(utsname.release, "%u.%u", &version, &patchlevel) != 2) { + if (sscanf(myutsname.release, "%u.%u", &version, &patchlevel) != 2) { dbgprintf("uname: unexpected release '%s'\r\n", - utsname.release); + myutsname.release); return 1; } /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5 @@ -83,10 +665,10 @@ int should_use_so_bsdcompat(void) so_bsdcompat_is_obsolete = 1; } return !so_bsdcompat_is_obsolete; +#else /* #ifndef OS_BSD */ + return 1; +#endif /* #ifndef OS_BSD */ } -#else /* #ifndef BSD */ -#define should_use_so_bsdcompat() 1 -#endif /* #ifndef BSD */ #ifndef SO_BSDCOMPAT /* this shall prevent compiler errors due to undfined name */ #define SO_BSDCOMPAT 0 @@ -105,8 +687,8 @@ int should_use_so_bsdcompat(void) * we should abort. For this, the return value tells the caller if the * message should be processed (1) or discarded (0). */ -/* TODO: after the bughunt, make this function static - rgerhards, 2007-09-18 */ -rsRetVal gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) +static rsRetVal +gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) { DEFiRet; int error; @@ -117,7 +699,7 @@ rsRetVal gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) assert(f != NULL); assert(pszHostFQDN != NULL); - error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f), + error = mygetnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f), ip, sizeof ip, NULL, 0, NI_NUMERICHOST); if (error) { @@ -131,7 +713,7 @@ rsRetVal gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) sigaddset(&nmask, SIGHUP); pthread_sigmask(SIG_BLOCK, &nmask, &omask); - error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *) f), + error = mygetnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *) f), (char*)pszHostFQDN, NI_MAXHOST, NULL, 0, NI_NAMEREQD); if (error == 0) { @@ -160,7 +742,7 @@ rsRetVal gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) "Malicious PTR record, message dropped " "IP = \"%s\" HOST = \"%s\"", ip, pszHostFQDN); - logerror((char*)szErrMsg); + errmsg.LogError(NO_ERRCODE, "%s", szErrMsg); pthread_sigmask(SIG_SETMASK, &omask, NULL); ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY); } @@ -175,7 +757,7 @@ rsRetVal gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) "Malicious PTR record (message accepted, but used IP " "instead of PTR name: IP = \"%s\" HOST = \"%s\"", ip, pszHostFQDN); - logerror((char*)szErrMsg); + errmsg.LogError(NO_ERRCODE, "%s", szErrMsg); error = 1; /* that will trigger using IP address below. */ } @@ -190,7 +772,50 @@ rsRetVal gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) } finalize_it: - return iRet; + RETiRet; +} + + + +/* 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*) &sa; + port = ntohs(ipv4->sin_port); + break; + case PF_INET6: + szFamily = "IPv6"; + ipv6 = (struct sockaddr_in6*) &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); } @@ -280,10 +905,291 @@ rsRetVal cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN } finalize_it: - return iRet; + RETiRet; } -#endif /* #ifdef SYSLOG_INET */ -/* - * vi:set ai: + +/* 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 = family; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res); + if(error) { + errmsg.LogError(NO_ERRCODE, "%s", gai_strerror(error)); + errmsg.LogError(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(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(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(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(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(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(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(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(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); +} + + +/* 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->should_use_so_bsdcompat = should_use_so_bsdcompat; + pIf->getLocalHostname = getLocalHostname; +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(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)); + + /* 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: */ @@ -2,19 +2,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -24,6 +25,7 @@ #ifdef SYSLOG_INET #include <netinet/in.h> +#include <sys/socket.h> /* this is needed on HP UX -- rgerhards, 2008-03-04 */ #define F_SET(where, flag) (where)|=(flag) #define F_ISSET(where, flag) ((where)&(flag))==(flag) @@ -32,10 +34,10 @@ #define ADDR_NAME 0x01 /* address is hostname wildcard) */ #define ADDR_PRI6 0x02 /* use IPv6 address prior to IPv4 when resolving */ -#ifdef BSD -#ifndef _KERNEL -#define s6_addr32 __u6_addr.__u6_addr32 -#endif +#ifdef OS_BSD +# ifndef _KERNEL +# define s6_addr32 __u6_addr.__u6_addr32 +# endif #endif struct NetAddr { @@ -46,17 +48,25 @@ struct NetAddr { } addr; }; -#ifndef BSD - int should_use_so_bsdcompat(void); -#else -# define should_use_so_bsdcompat() 1 -#endif /* #ifndef BSD */ - #ifndef SO_BSDCOMPAT /* this shall prevent compiler errors due to undfined 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 @@ -69,20 +79,37 @@ static inline size_t SALEN(struct sockaddr *sa) { } #endif -rsRetVal cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN); +struct AllowedSenders { + struct NetAddr allowedSender; /* ip address allowed */ + uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */ + struct AllowedSenders *pNext; +}; -/* 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 +/* interfaces */ +BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*cvthname)(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN); + /* 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); + rsRetVal (*getLocalHostname)(uchar**); + int (*should_use_so_bsdcompat)(void); + /* data memebers - 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) */ +ENDinterface(net) +#define netCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(net); + +/* the name of our library binary */ +#define LM_NET_FILENAME "lmnet" #endif /* #ifdef SYSLOG_INET */ #endif /* #ifndef INCLUDED_NET_H */ diff --git a/obj-types.h b/obj-types.h new file mode 100644 index 00000000..4cd45153 --- /dev/null +++ b/obj-types.h @@ -0,0 +1,405 @@ +/* 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 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 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 + */ +typedef 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! */ +} interface_t; + + +typedef 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; +} objInfo_t; + + +typedef struct obj { /* 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 */ +} obj_t; + + +/* 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); \ + ASSERT((unsigned) ((obj_t*) (pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + ASSERT(!strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)); \ + } 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 + +#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 **ppThis) \ + { \ + DEFiRet; \ + int iCancelStateSave; \ + OBJ##_t *pThis; + +#define CODESTARTobjDestruct(OBJ) \ + ASSERT(ppThis != NULL); \ + pThis = *ppThis; \ + ISOBJ_TYPE_assert(pThis, OBJ); \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + +#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; \ + } \ + pthread_setcancelstate(iCancelStateSave, 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 *pThis) \ + { \ + DEFiRet; \ + +#define CODESTARTobjDebugPrint(obj) \ + ASSERT(pThis != NULL); \ + ISOBJ_TYPE_assert(pThis, obj); \ + +#define ENDobjDebugPrint(obj) \ + RETiRet; \ + } + +/* ------------------------------ object loader system ------------------------------ * + * The following code is the early beginning of a dynamic object loader system. The + * root idea is that all objects will become dynamically loadable libraries over time, + * 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. + * + * To facilitate moving to this system, I begin to implement some hooks, which + * allows to use interfaces today (when the rest of the infrastructure is not yet + * there). This is in the hope that it will ease migration to the full-fledged system + * once we are ready to work on that. + * rgerhards, 2008-02-21 + */ + +/* this defines the QueryInterface print entry point. Over time, it should be + * present in all objects. + */ +//#define PROTOTYPEObjQueryInterface(obj) rsRetVal obj##QueryInterface(obj##_if_t *pThis) +#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, MYLIB, FILENAME) \ + obj.UseObj(__FILE__, (uchar*)#objName, MYLIB, (uchar*)FILENAME, (void*) &objName) +*/ +#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 */ @@ -0,0 +1,1342 @@ +/* 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 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 <ctype.h> +#include <assert.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" + +/* static data */ +DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */ +DEFobjCurrIf(var) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */ + + +/* 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 = strlen((char*)pszID); + pThis->pszName = (uchar*)strdup((char*)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); + + if(pThis->pszName != 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); + if(pThis->pszName != NULL) { + 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(strmWriteChar(pStrm, COOKIE_OBJLINE)); + CHKiRet(strmWrite(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '1')); + + /* object type, version and string length */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWrite(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)); + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteLong(pStrm, objGetVersion(pObj))); + + /* record trailer */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(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(strmRecordBegin(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(strmRecordBegin(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 = strlen((char*) pszBuf); + vType = VARTYPE_STR; + break; + case PROPTYPE_SHORT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_LONG: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT64: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) 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(strmWriteChar(pStrm, COOKIE_PROPLINE)); + /* name */ + CHKiRet(strmWrite(pStrm, pszPropName, strlen((char*)pszPropName))); + CHKiRet(strmWriteChar(pStrm, ':')); + /* type */ + CHKiRet(strmWriteLong(pStrm, (int) vType)); + CHKiRet(strmWriteChar(pStrm, ':')); + /* length */ + CHKiRet(strmWriteLong(pStrm, lenBuf)); + CHKiRet(strmWriteChar(pStrm, ':')); + + /* data */ + CHKiRet(strmWrite(pStrm, (uchar*) pszBuf, lenBuf)); + + /* trailer */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(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(strmWriteChar(pStrm, COOKIE_ENDLINE)); + CHKiRet(strmWrite(pStrm, (uchar*) "End\n", sizeof("END\n") - 1)); + CHKiRet(strmWriteChar(pStrm, COOKIE_BLANKLINE)); + CHKiRet(strmWriteChar(pStrm, '\n')); + + CHKiRet(strmRecordEnd(pStrm)); + +finalize_it: + RETiRet; +} + + +/* define a helper to make code below a bit cleaner (and quicker to write) */ +#define NEXTC CHKiRet(strmReadChar(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(rsCStrConstruct(&pStr)); + + NEXTC; + while(c != ':') { + CHKiRet(rsCStrAppendChar(pStr, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pStr)); + + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK && pStr != NULL) + rsCStrDestruct(&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(rsCStrConstruct(&pCStr)); + + NEXTC; + for(i = 0 ; i < iLen ; ++i) { + CHKiRet(rsCStrAppendChar(pCStr, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pCStr)); + + /* check terminator */ + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + *ppCStr = pCStr; + +finalize_it: + if(iRet != RS_RET_OK && pCStr != NULL) + rsCStrDestruct(&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. + */ +static rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm) +{ + DEFiRet; + number_t i; + number_t iLen; + uchar c; + + 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(strmUnreadChar(pStrm, c)); + ABORT_FINALIZE(RS_RET_NO_PROPLINE); + } + + /* get the property name first */ + CHKiRet(rsCStrConstruct(&pProp->pcsName)); + + NEXTC; + while(c != ':') { + CHKiRet(rsCStrAppendChar(pProp->pcsName, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pProp->pcsName)); + + /* property type */ + CHKiRet(objDeserializeNumber(&i, pStrm)); + pProp->varType = i; + + /* size (needed for strings) */ + CHKiRet(objDeserializeNumber(&iLen, pStrm)); + + /* 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; + } + + /* 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: + 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(strmUnreadChar(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, objInfo_t *pObjInfo, strm_t *pStrm) +{ + DEFiRet; + var_t *pVar = NULL; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + ASSERT(pObjInfo != NULL); + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + iRet = objDeserializeProperty(pVar, pStrm); + while(iRet == RS_RET_OK) { + CHKiRet(pObjInfo->objMethods[objMethod_SETPROPERTY](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; /* after all, 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, strlen((char*)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, 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, 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, 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, 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; + + if(pThis->pszName != NULL) + free(pThis->pszName); + + pThis->pszName = (uchar*) strdup((char*) pszName); + + if(pThis->pszName == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + +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 0 +RUNLOG_VAR("%d", i); +if(arrObjInfo[i] != NULL) { +RUNLOG_VAR("%p", arrObjInfo[i]->pszID); +RUNLOG_VAR("%s", arrObjInfo[i]->pszID); +} +#endif + 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 + && !strcmp((char*)arrObjInfo[i]->pszID, (char*)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(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 + && !strcmp((char*)arrObjInfo[i]->pszID, (char*)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); */ + + 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)); + /* 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: + 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; + + + dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); + + if(pObjFile == NULL) + FINALIZE; /* if it is not a lodable module, we do not need to do anything... */ + + if(pIf->ifIsLoaded == 0) { + ABORT_FINALIZE(RS_RET_OK); /* we are already set */ /* TODO: flag an error? */ + } + if(pIf->ifIsLoaded == 2) { + pIf->ifIsLoaded = 0; /* clean up */ + ABORT_FINALIZE(RS_RET_OK); /* we had a load error and can not continue */ + } + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + + /* if we reach this point, we have a valid pObjInfo */ + //if(pObjInfo->pModInfo != NULL) { /* NULL means core module */ + module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */ + + pIf->ifIsLoaded = 0; /* indicated "no longer valid" */ + +finalize_it: + 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(var, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + + /* TODO: implement the class exits! */ +#if 0 + errmsgClassInit(pModInfo); + cfsyslineInit(pModInfo); + varClassInit(pModInfo); +#endif + 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) +{ + DEFiRet; + int i; + + /* 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; + } + + /* request objects we use */ + CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */ + + /* init classes we use (limit to as few as possible!) */ + CHKiRet(errmsgClassInit(pModInfo)); + CHKiRet(cfsyslineInit()); + CHKiRet(varClassInit(pModInfo)); + CHKiRet(moduleClassInit(pModInfo)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +finalize_it: + RETiRet; +} + +/* vi:set ai: + */ @@ -0,0 +1,124 @@ +/* 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 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 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 *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) \ + ASSERT(((obj_t*) (pThis))->pObjInfo == NULL); \ + ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \ + ((obj_t*) (pThis))->iObjCooCKiE = 0xBADEFEE +#else +# define objConstructSetObjInfo(pThis) ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ +#endif +#define objDestruct(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DESTRUCT])(&pThis) +#define objSerialize(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_SERIALIZE]) +#define objGetSeverity(pThis, piSever) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_GETSEVERITY])(pThis, piSever) +#define objDebugPrint(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DEBUGPRINT])(pThis) + +#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); + +#endif /* #ifndef OBJ_H_INCLUDED */ @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -91,7 +92,7 @@ rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries) abort_it: *ppThis = pThis; - return iRet; + RETiRet; } /* set a template name and option to the object. Index must be given. The pTplName must be @@ -2,19 +2,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ diff --git a/omdiscard.c b/omdiscard.c index bcd9ba21..f13144e8 100644 --- a/omdiscard.c +++ b/omdiscard.c @@ -8,19 +8,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -36,6 +37,8 @@ #include "omdiscard.h" #include "module-template.h" +MODULE_TYPE_OUTPUT + /* internal structures */ DEF_OMOD_STATIC_DATA @@ -68,6 +71,7 @@ ENDtryResume BEGINdoAction CODESTARTdoAction + dbgprintf("\n"); iRet = RS_RET_DISCARDMSG; ENDdoAction @@ -96,21 +100,6 @@ CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - BEGINmodExit CODESTARTmodExit ENDmodExit @@ -124,7 +113,7 @@ ENDqueryEtryPt BEGINmodInit(Discard) CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr ENDmodInit /* diff --git a/omdiscard.h b/omdiscard.h index 113845f2..116308a4 100644 --- a/omdiscard.h +++ b/omdiscard.h @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -25,7 +26,7 @@ #define OMDISCARD_H_INCLUDED 1 /* prototypes */ -rsRetVal modInitDiscard(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)())); +rsRetVal modInitDiscard(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); #endif /* #ifndef OMDISCARD_H_INCLUDED */ /* @@ -12,21 +12,22 @@ * 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 Rainer Gerhards and Adiscon GmbH. + * Copyright 2007, 2008 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -51,10 +52,14 @@ #include "omfile.h" #include "cfsysline.h" #include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) /* The following structure is a dynafile name cache entry. */ @@ -76,6 +81,8 @@ static uid_t fileGID; /* GID to be used for newly created files */ static uid_t dirUID; /* UID to be used for newly created directories */ static uid_t dirGID; /* GID to be used for newly created directories */ static int bCreateDirs; /* auto-create directories for dynaFiles: 0 - no, 1 - yes */ +static int bEnableSync = 0;/* enable syncing of files (no dash in front of pathname in conf): 0 - no, 1 - yes */ +static uchar *pszTplName = NULL; /* name of the default template to use */ /* end globals for default values */ typedef struct _instanceData { @@ -87,7 +94,6 @@ typedef struct _instanceData { eTypeCONSOLE, eTypePIPE } fileType; - struct template *pTpl; /* pointer to template object */ char bDynamicName; /* 0 - static name, 1 - dynamic name (with properties) */ int fCreateMode; /* file creation mode for open() */ int fDirCreateMode; /* creation mode for mkdir() */ @@ -154,14 +160,14 @@ rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal) snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), "DynaFileCacheSize must be greater 0 (%d given), changed to 1.", iNewVal); errno = 0; - logerror((char*) errMsg); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); iRet = RS_RET_VAL_OUT_OF_RANGE; iNewVal = 1; } else if(iNewVal > 10000) { snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), "DynaFileCacheSize maximum is 10,000 (%d given), changed to 10,000.", iNewVal); errno = 0; - logerror((char*) errMsg); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); iRet = RS_RET_VAL_OUT_OF_RANGE; iNewVal = 10000; } @@ -169,7 +175,7 @@ rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal) iDynaFileCacheSize = iNewVal; dbgprintf("DynaFileCacheSize changed to %d.\n", iNewVal); - return iRet; + RETiRet; } @@ -214,8 +220,8 @@ static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringR snprintf(errMsg, sizeof(errMsg)/sizeof(char), "outchannel '%s' not found - ignoring action line", szBuf); - logerror(errMsg); - return RS_RET_NOT_FOUND; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_NOT_FOUND); } /* check if there is a file name in the outchannel... */ @@ -225,8 +231,8 @@ static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringR snprintf(errMsg, sizeof(errMsg)/sizeof(char), "outchannel '%s' has no file name template - ignoring action line", szBuf); - logerror(errMsg); - return RS_RET_ERR; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_ERR); } /* OK, we finally got a correct template. So let's use it... */ @@ -237,9 +243,12 @@ static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringR */ pData->f_sizeLimitCmd = (char*) pOch->cmdOnSizeLimit; - iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, (uchar*) " TradFmt"); +RUNLOG_VAR("%p", pszTplName); + iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName); - return(iRet); +finalize_it: + RETiRet; } @@ -255,7 +264,7 @@ int resolveFileSizeLimit(instanceData *pData) uchar *pCmd; uchar *p; off_t actualFileSize; - assert(pData != NULL); + ASSERT(pData != NULL); if(pData->f_sizeLimitCmd == NULL) return 1; /* nothing we can do in this case... */ @@ -307,14 +316,16 @@ int resolveFileSizeLimit(instanceData *pData) * 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 free()ed and 0 if not. + * if the entry should be d_free()ed and 0 if not. */ static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) { - assert(pCache != NULL); + ASSERT(pCache != NULL); + + BEGINfunc; if(pCache[iEntry] == NULL) - return; + FINALIZE; dbgprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry, pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName); @@ -324,14 +335,17 @@ static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int b */ if(pCache[iEntry]->pName != NULL) { close(pCache[iEntry]->fd); - free(pCache[iEntry]->pName); + d_free(pCache[iEntry]->pName); pCache[iEntry]->pName = NULL; } if(bFreeEntry) { - free(pCache[iEntry]); + d_free(pCache[iEntry]); pCache[iEntry] = NULL; } + +finalize_it: + ENDfunc; } @@ -340,14 +354,16 @@ static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int b static void dynaFileFreeCache(instanceData *pData) { register int i; - assert(pData != NULL); + ASSERT(pData != NULL); + BEGINfunc; for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { dynaFileDelCacheEntry(pData->dynCache, i, 1); } if(pData->dynCache != NULL) - free(pData->dynCache); + d_free(pData->dynCache); + ENDfunc; } @@ -416,8 +432,8 @@ static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsg int iFirstFree; dynaFileCacheEntry **pCache; - assert(pData != NULL); - assert(newFileName != NULL); + ASSERT(pData != NULL); + ASSERT(newFileName != NULL); pCache = pData->dynCache; @@ -488,7 +504,7 @@ static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsg if(iMsgOpts & INTERNAL_MSG) dbgprintf("Could not open dynaFile, discarding message\n"); else - logerrorSz("Could not open dynamic file '%s' - discarding message", (char*)newFileName); + errmsg.LogError(NO_ERRCODE, "Could not open dynamic file '%s' - discarding message", (char*)newFileName); dynaFileDelCacheEntry(pCache, iFirstFree, 1); pData->iCurrElt = -1; return -1; @@ -514,14 +530,14 @@ static rsRetVal writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pDa off_t actualFileSize; DEFiRet; - assert(pData != NULL); + 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) { if(prepareDynFile(pData, ppString[1], iMsgOpts) != 0) - return RS_RET_ERR; + ABORT_FINALIZE(RS_RET_ERR); } /* create the message based on format specified */ @@ -545,14 +561,14 @@ again: "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation", pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize); errno = 0; - logerror(errMsg); - return RS_RET_DISABLE_ACTION; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); } else { snprintf(errMsg, sizeof(errMsg), "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation", pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize); errno = 0; - logerror(errMsg); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); } } } @@ -563,14 +579,14 @@ again: /* If a named pipe is full, just ignore it for now - mrn 24 May 96 */ if (pData->fileType == eTypePIPE && e == EAGAIN) - return RS_RET_OK; + ABORT_FINALIZE(RS_RET_OK); /* If the filesystem is filled up, just ignore * it for now and continue writing when possible * based on patch for sysklogd by Martin Schulze on 2007-05-24 */ if (pData->fileType == eTypeFILE && e == ENOSPC) - return RS_RET_OK; + ABORT_FINALIZE(RS_RET_OK); (void) close(pData->fd); /* @@ -586,7 +602,7 @@ again: pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_NOCTTY); if (pData->fd < 0) { iRet = RS_RET_DISABLE_ACTION; - logerror((char*) pData->f_fname); + errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); } else { untty(); goto again; @@ -594,11 +610,14 @@ again: } else { iRet = RS_RET_DISABLE_ACTION; errno = e; - logerror((char*) pData->f_fname); + errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); } - } else if (pData->bSyncFile) + } else if (pData->bSyncFile) { fsync(pData->fd); - return(iRet); + } + +finalize_it: + RETiRet; } @@ -617,21 +636,6 @@ CODESTARTfreeInstance ENDfreeInstance -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - BEGINtryResume CODESTARTtryResume ENDtryResume @@ -654,20 +658,24 @@ CODESTARTparseSelectorAct * the code further changes. -- rgerhards, 2007-07-25 */ if(*p == '$' || *p == '?' || *p == '|' || *p == '/' || *p == '-') { - if((iRet = createInstance(&pData)) != RS_RET_OK) - return iRet; + 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; } - if (*p == '-') { + if(*p == '-') { pData->bSyncFile = 0; p++; - } else - pData->bSyncFile = 1; + } else { + pData->bSyncFile = bEnableSync ? 1 : 0; + } pData->f_sizeLimit = 0; /* default value, use outchannels to configure! */ @@ -696,7 +704,8 @@ CODESTARTparseSelectorAct */ CODE_STD_STRING_REQUESTparseSelectorAct(2) ++p; /* eat '?' */ - if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)) + if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName)) != RS_RET_OK) break; /* "filename" is actually a template name, we need this as string 1. So let's add it @@ -741,7 +750,8 @@ CODESTARTparseSelectorAct * to use is specified. So we need to scan for the first coma first * and then look at the rest of the line. */ - if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)) + if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName)) != RS_RET_OK) break; @@ -764,7 +774,7 @@ CODESTARTparseSelectorAct if ( pData->fd < 0 ){ pData->fd = -1; dbgprintf("Error opening log file: %s\n", pData->f_fname); - logerror((char*) pData->f_fname); + errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); break; } if (isatty(pData->fd)) { @@ -796,6 +806,11 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a fCreateMode = 0644; fDirCreateMode = 0644; bCreateDirs = 1; + bEnableSync = 0; + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } return RS_RET_OK; } @@ -803,6 +818,8 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a BEGINmodExit CODESTARTmodExit + if(pszTplName != NULL) + free(pszTplName); ENDmodExit @@ -814,8 +831,9 @@ ENDqueryEtryPt BEGINmodInit(File) CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"dynafilecachesize", 0, eCmdHdlrInt, (void*) setDynaFileCacheSize, NULL, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirowner", 0, eCmdHdlrUID, NULL, &dirUID, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirgroup", 0, eCmdHdlrGID, NULL, &dirGID, STD_LOADABLE_MODULE_ID)); @@ -825,6 +843,8 @@ CODEmodInit_QueryRegCFSLineHdlr CHKiRet(omsdRegCFSLineHdlr((uchar *)"filecreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fCreateMode, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"createdirs", 0, eCmdHdlrBinary, NULL, &bCreateDirs, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"failonchownfailure", 0, eCmdHdlrBinary, NULL, &bFailOnChown, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionfileenablesync", 0, eCmdHdlrBinary, NULL, &bEnableSync, STD_LOADABLE_MODULE_ID)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionfiledefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit /* @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -25,7 +26,7 @@ #define OMFILE_H_INCLUDED 1 /* prototypes */ -rsRetVal modInitFile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)())); +rsRetVal modInitFile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); #endif /* #ifndef OMFILE_H_INCLUDED */ /* @@ -12,19 +12,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -46,11 +47,7 @@ #ifdef USE_NETZIP #include <zlib.h> #endif -#ifdef USE_PTHREADS #include <pthread.h> -#else -#include <fcntl.h> -#endif #include "syslogd.h" #include "syslogd-types.h" #include "srUtils.h" @@ -59,60 +56,58 @@ #include "template.h" #include "msg.h" #include "tcpsyslog.h" +#include "tcpclt.h" #include "cfsysline.h" #include "module-template.h" +#include "errmsg.h" -#ifdef SYSLOG_INET -#define INET_SUSPEND_TIME 60 /* equal to 1 minute - * rgerhards, 2005-07-26: This was 3 minutes. As the - * same timer is used for tcp based syslog, we have - * reduced it. However, it might actually be worth - * thinking about a buffered tcp sender, which would be - * a much better alternative. When that happens, this - * time here can be re-adjusted to 3 minutes (or, - * even better, made configurable). - */ -#define INET_RETRY_MAX 30 /* maximum of retries for gethostbyname() */ - /* was 10, changed to 30 because we reduced INET_SUSPEND_TIME by one third. So - * this "fixes" some of implications of it (see comment on INET_SUSPEND_TIME). - * rgerhards, 2005-07-26 - */ -#endif +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) +DEFobjCurrIf(tcpclt) typedef struct _instanceData { - char f_hname[MAXHOSTNAMELEN+1]; + char *f_hname; short sock; /* file descriptor */ + int *pSockArray; /* sockets to use for UDP */ enum { /* TODO: we shoud revisit these definitions */ eDestFORW, eDestFORW_SUSP, eDestFORW_UNKN } eDestState; - int iRtryCnt; struct addrinfo *f_addr; int compressionLevel; /* 0 - no compression, else level for zlib */ char *port; int protocol; - TCPFRAMINGMODE tcp_framing; # define FORW_UDP 0 # define FORW_TCP 1 /* following fields for TCP-based delivery */ - enum TCPSendStatus { - TCP_SEND_NOTCONNECTED = 0, - TCP_SEND_CONNECTING = 1, - TCP_SEND_READY = 2 - } status; - char *savedMsg; - int savedMsgLen; /* length of savedMsg in octets */ time_t ttSuspend; /* time selector was suspended */ -# ifdef USE_PTHREADS - pthread_mutex_t mtxTCPSend; -# endif + tcpclt_t *pTCPClt; /* our tcpclt object */ } instanceData; +/* config data */ +static uchar *pszTplName = NULL; /* name of the default template to use */ + + +/* 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 @@ -139,15 +134,20 @@ CODESTARTfreeInstance free(pData->port); break; } -# ifdef USE_PTHREADS - /* delete any mutex objects, if present */ - if(pData->protocol == FORW_TCP) { - pthread_mutex_destroy(&pData->mtxTCPSend); - } -# endif + /* final cleanup */ if(pData->sock >= 0) close(pData->sock); + if(pData->pSockArray != NULL) + net.closeUDPListenSockets(pData->pSockArray); + + if(pData->protocol == FORW_TCP) { + tcpclt.Destruct(&pData->pTCPClt); + } + + if(pData->f_hname != NULL) + free(pData->f_hname); + ENDfreeInstance @@ -156,66 +156,56 @@ CODESTARTdbgPrintInstInfo printf("%s", pData->f_hname); ENDdbgPrintInstInfo -/* CODE FOR SENDING TCP MESSAGES */ -/* set send status - * rgerhards, 2005-10-24 +/* Send a message via UDP + * rgehards, 2007-12-20 */ -static void TCPSendSetStatus(instanceData *pData, enum TCPSendStatus iNewState) +static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len) { - assert(pData != NULL); - assert(pData->protocol == FORW_TCP); - assert( (iNewState == TCP_SEND_NOTCONNECTED) - || (iNewState == TCP_SEND_CONNECTING) - || (iNewState == TCP_SEND_READY)); - - /* there can potentially be a race condition, so guard by mutex */ -# ifdef USE_PTHREADS - pthread_mutex_lock(&pData->mtxTCPSend); -# endif - pData->status = iNewState; -# ifdef USE_PTHREADS - pthread_mutex_unlock(&pData->mtxTCPSend); -# endif -} + DEFiRet; + struct addrinfo *r; + int i; + unsigned lsent = 0; + int bSendSuccess; + 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 = FALSE; + 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 = TRUE; + 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 == FALSE) { + dbgprintf("error forwarding via udp, suspending\n"); + iRet = RS_RET_SUSPENDED; + } + } -/* get send status - * rgerhards, 2005-10-24 - */ -static enum TCPSendStatus TCPSendGetStatus(instanceData *pData) -{ - enum TCPSendStatus eState; - assert(pData != NULL); - assert(pData->protocol == FORW_TCP); - - /* there can potentially be a race condition, so guard by mutex */ -# ifdef USE_PTHREADS - pthread_mutex_lock(&pData->mtxTCPSend); -# endif - eState = pData->status; -# ifdef USE_PTHREADS - pthread_mutex_unlock(&pData->mtxTCPSend); -# endif - - return eState; + RETiRet; } +/* CODE FOR SENDING TCP MESSAGES */ -/* 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); -} /* Send a frame via plain TCP protocol * rgerhards, 2007-12-28 @@ -246,12 +236,12 @@ static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len) * For the time being, we ignore this... * rgerhards, 2005-10-25 */ - dbgprintf("message not completely (tcp)send, ignoring %ld\n", lenSend); + dbgprintf("message not completely (tcp)send, ignoring %ld\n", (long) lenSend); usleep(1000); /* experimental - might be benefitial in this situation */ /* TODO: we need to revisit this code -- rgerhards, 2007-12-28 */ } - return iRet; + RETiRet; } @@ -261,12 +251,13 @@ static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len) */ static rsRetVal TCPSendPrepRetry(void *pvData) { + DEFiRet; instanceData *pData = (instanceData *) pvData; assert(pData != NULL); close(pData->sock); pData->sock = -1; - return RS_RET_OK; + RETiRet; } @@ -280,15 +271,14 @@ static rsRetVal TCPSendInit(void *pvData) assert(pData != NULL); if(pData->sock < 0) { - if((pData->sock = TCPSendCreateSocket(pData->f_addr)) < 0) + if((pData->sock = tcpclt.CreateSocket(pData->f_addr)) < 0) iRet = RS_RET_TCP_SOCKCREATE_ERR; } - return iRet; + RETiRet; } - /* try to resume connection if it is not ready * rgerhards, 2007-08-02 */ @@ -320,7 +310,6 @@ static rsRetVal doTryResume(instanceData *pData) getFwdSyslogPt(pData), &hints, &res)) == 0) { dbgprintf("%s found, resuming.\n", pData->f_hname); pData->f_addr = res; - pData->iRtryCnt = 0; pData->eDestState = eDestFORW; } else { iRet = RS_RET_SUSPENDED; @@ -333,7 +322,7 @@ static rsRetVal doTryResume(instanceData *pData) break; } - return iRet; + RETiRet; } @@ -345,10 +334,6 @@ ENDtryResume BEGINdoAction char *psz; /* temporary buffering */ register unsigned l; - struct addrinfo *r; - int i; - unsigned lsent = 0; - int bSendSuccess; CODESTARTdoAction switch (pData->eDestState) { case eDestFORW_SUSP: @@ -364,108 +349,75 @@ CODESTARTdoAction case eDestFORW: dbgprintf(" %s:%s/%s\n", pData->f_hname, getFwdSyslogPt(pData), pData->protocol == FORW_UDP ? "udp" : "tcp"); - if ( 0) // TODO: think about this strcmp(getHOSTNAME(f->f_pMsg), LocalHostName) && NoHops ) - /* what we need to do is get the hostname as an additonal string (during parseSe..). Then, - * we can compare that string to LocalHostName. That way, we do not need to access the - * msgobject, and everything is clean. The question remains, though, if that functionality - * here actually makes sense or not. If we really need it, it might make more sense to compare - * the target IP address to the IP addresses of the local machene - that is a far better way of - * handling things than to relay on the error-prone hostname property. - * rgerhards, 2007-07-27 - */ - dbgprintf("Not sending message to remote.\n"); - else { - pData->ttSuspend = time(NULL); - psz = (char*) ppString[0]; - l = strlen((char*) psz); - if (l > MAXLINE) - l = MAXLINE; + /* with UDP, check if the socket is there and, if not, alloc + * it. TODO: there should be a better place for that code. + * rgerhards, 2007-12-26 + */ + if(pData->protocol == FORW_UDP) { + if(pData->pSockArray == NULL) { + pData->pSockArray = net.create_udp_socket((uchar*)pData->f_hname, NULL, 0); + } + } + pData->ttSuspend = time(NULL); + psz = (char*) ppString[0]; + l = strlen((char*) psz); + if (l > MAXLINE) + l = MAXLINE; # 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 > MIN_SIZE_FOR_COMPRESS)) { - Bytef out[MAXLINE+MAXLINE/100+12] = "z"; - uLongf destLen = sizeof(out) / sizeof(Bytef); - uLong srcLen = l; - int ret; - 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; + /* 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 > MIN_SIZE_FOR_COMPRESS)) { + Bytef out[MAXLINE+MAXLINE/100+12] = "z"; + uLongf destLen = sizeof(out) / sizeof(Bytef); + uLong srcLen = l; + int ret; + 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 */ - if(finet != 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 = FALSE; - for (r = pData->f_addr; r; r = r->ai_next) { - for (i = 0; i < *finet; i++) { - lsent = sendto(finet[i+1], psz, l, 0, - r->ai_addr, r->ai_addrlen); - if (lsent == l) { - bSendSuccess = TRUE; - break; - } else { - int eno = errno; - char errStr[1024]; - dbgprintf("sendto() error: %d = %s.\n", - eno, rs_strerror_r(eno, errStr, sizeof(errStr))); - } - } - if (lsent == l && !send_to_all) - break; - } - /* finished looping */ - if (bSendSuccess == FALSE) { - dbgprintf("error forwarding via udp, suspending\n"); - iRet = RS_RET_SUSPENDED; - } - } - } else { - int ret; - ret = TCPSend(pData, psz, l, pData->tcp_framing, TCPSendInit, TCPSendFrame, TCPSendPrepRetry); - if(ret != RS_RET_OK) { - /* error! */ - dbgprintf("error forwarding via tcp, suspending\n"); - pData->eDestState = eDestFORW_SUSP; - iRet = RS_RET_SUSPENDED; - } + if(pData->protocol == FORW_UDP) { + /* forward via UDP */ + CHKiRet(UDPSend(pData, psz, l)); + } else { + /* forward via TCP */ + rsRetVal ret; + ret = tcpclt.Send(pData->pTCPClt, pData, psz, l); + if(ret != RS_RET_OK) { + /* error! */ + dbgprintf("error forwarding via tcp, suspending\n"); + pData->eDestState = eDestFORW_SUSP; + iRet = RS_RET_SUSPENDED; } } break; } +finalize_it: ENDdoAction @@ -475,6 +427,7 @@ BEGINparseSelectorAct int error; int bErr; struct addrinfo hints, *res; + TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING; CODESTARTparseSelectorAct CODE_STD_STRING_REQUESTparseSelectorAct(1) if(*p == '@') { @@ -484,10 +437,6 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) if(*p == '@') { /* indicator for TCP! */ pData->protocol = FORW_TCP; ++p; /* eat this '@', too */ - /* in this case, we also need a mutex... */ -# ifdef USE_PTHREADS - pthread_mutex_init(&pData->mtxTCPSend, 0); -# endif } else { pData->protocol = FORW_UDP; } @@ -519,20 +468,20 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) ++p; /* eat */ pData->compressionLevel = iLevel; } else { - logerrorInt("Invalid compression level '%c' specified in " + errmsg.LogError(NO_ERRCODE, "Invalid compression level '%c' specified in " "forwardig action - NOT turning on compression.", *p); } # else - logerror("Compression requested, but rsyslogd is not compiled " + errmsg.LogError(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 */ - pData->tcp_framing = TCP_FRAMING_OCTET_COUNTING; + tcp_framing = TCP_FRAMING_OCTET_COUNTING; } else { /* invalid option! Just skip it... */ - logerrorInt("Invalid option %c in forwarding action - ignoring.", *p); + errmsg.LogError(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 @@ -548,7 +497,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* we probably have end of string - leave it for the rest * of the code to handle it (but warn the user) */ - logerror("Option block not terminated in forwarding action."); + errmsg.LogError(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 @@ -576,7 +525,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* SKIP AND COUNT */; pData->port = malloc(i + 1); if(pData->port == NULL) { - logerror("Could not get memory to store syslog forwarding port, " + errmsg.LogError(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(). @@ -589,30 +538,22 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* 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; - logerror("invalid selector line (port), probably not doing " - "what was intended"); - } - } - ++p; - } - + while(*p && *p != ';' && *p != '#' && !isspace((int) *p)) + ++p; /*JUST SKIP*/ + /* TODO: make this if go away! */ - if(*p == ';') { + if(*p == ';' || *p == '#' || isspace(*p)) { + uchar cTmp = *p; *p = '\0'; /* trick to obtain hostname (later)! */ - strcpy(pData->f_hname, (char*) q); - *p = ';'; - } else - strcpy(pData->f_hname, (char*) q); + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = cTmp; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } /* process template */ - if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " StdFwdFmt")) - != RS_RET_OK) - goto finalize_it; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName)); /* first set the pData->eDestState */ memset(&hints, 0, sizeof(hints)); @@ -622,19 +563,26 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) hints.ai_socktype = pData->protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM; if( (error = getaddrinfo(pData->f_hname, getFwdSyslogPt(pData), &hints, &res)) != 0) { pData->eDestState = eDestFORW_UNKN; - pData->iRtryCnt = INET_RETRY_MAX; pData->ttSuspend = time(NULL); } else { pData->eDestState = eDestFORW; pData->f_addr = res; } - - /* - * Otherwise the host might be unknown due to an + /* Otherwise the host might be unknown due to an * inaccessible nameserver (perhaps on the same * host). We try to get the ip number later, like * FORW_SUSP. */ + if(pData->protocol == FORW_TCP) { + /* create our tcpclt */ + CHKiRet(tcpclt.Construct(&pData->pTCPClt)); + /* 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, tcp_framing)); + } + } else { iRet = RS_RET_CONFLINE_UNPROCESSED; } @@ -646,44 +594,17 @@ CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct -BEGINneedUDPSocket -CODESTARTneedUDPSocket - iRet = RS_RET_TRUE; -ENDneedUDPSocket - - -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite - dbgprintf("tcp send socket %d ready for writing.\n", pData->sock); - TCPSendSetStatus(pData, TCP_SEND_READY); - /* Send stored message (if any) */ - if(pData->savedMsg != NULL) { - if(TCPSend(pData, pData->savedMsg, pData->savedMsgLen, pData->tcp_framing, - TCPSendInit, TCPSendFrame, TCPSendPrepRetry) != RS_RET_OK) { - /* error! */ - pData->eDestState = eDestFORW_SUSP; - errno = 0; - logerror("error forwarding via tcp, suspending..."); - } - free(pData->savedMsg); - pData->savedMsg = NULL; - } -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect - if( (pData->eDestState == eDestFORW) - && (pData->protocol == FORW_TCP) - && TCPSendGetStatus(pData) == TCP_SEND_CONNECTING) { - *fd = pData->sock; - iRet = RS_RET_OK; - } -ENDgetWriteFDForSelect - - BEGINmodExit CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(tcpclt, LM_TCPCLT_FILENAME); + + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } ENDmodExit @@ -693,13 +614,32 @@ CODEqueryEtryPt_STD_OMOD_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) +{ + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } + + return RS_RET_OK; +} + + BEGINmodInit(Fwd) CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); + + CHKiRet(regCfSysLineHdlr((uchar *)"actionforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit #endif /* #ifdef SYSLOG_INET */ -/* - * vi:set ai: +/* vim:set ai: */ @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -25,7 +26,7 @@ #define OMFWD_H_INCLUDED 1 /* prototypes */ -rsRetVal modInitFwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)())); +rsRetVal modInitFwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); #endif /* #ifndef OMFWD_H_INCLUDED */ /* @@ -14,19 +14,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -42,10 +43,14 @@ #include "srUtils.h" #include "omshell.h" #include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) typedef struct _instanceData { uchar progName[MAXFNAME]; /* program to execute */ @@ -87,7 +92,7 @@ CODESTARTdoAction */ dbgprintf("\n"); if(execProg((uchar*) pData->progName, 1, ppString[0]) == 0) - logerrorSz("Executing program '%s' failed", (char*)pData->progName); + errmsg.LogError(NO_ERRCODE, "Executing program '%s' failed", (char*)pData->progName); ENDdoAction @@ -108,7 +113,8 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) 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); + iRet = cflineParseFileName(p, (uchar*) pData->progName, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (uchar*)"RSYSLOG_TraditionalFileFormat"); break; default: iRet = RS_RET_CONFLINE_UNPROCESSED; @@ -119,21 +125,6 @@ CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - BEGINmodExit CODESTARTmodExit ENDmodExit @@ -147,8 +138,9 @@ ENDqueryEtryPt BEGINmodInit(Shell) CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); ENDmodInit /* @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -25,7 +26,7 @@ #define ACTSHELL_H_INCLUDED 1 /* prototypes */ -rsRetVal modInitShell(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)())); +rsRetVal modInitShell(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); #endif /* #ifndef ACTSHELL_H_INCLUDED */ /* @@ -13,19 +13,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -57,10 +58,21 @@ #include "syslogd.h" #include "omusrmsg.h" #include "module-template.h" +#include "errmsg.h" + + +/* portability: */ +#ifndef _PATH_DEV +# define _PATH_DEV "/dev/" +#endif + + +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) typedef struct _instanceData { int bIsWall; /* 1- is wall, 0 - individual users */ @@ -86,16 +98,11 @@ CODESTARTfreeInstance ENDfreeInstance -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - BEGINdbgPrintInstInfo register int i; CODESTARTdbgPrintInstInfo for (i = 0; i < MAXUNAMES && *pData->uname[i]; i++) - printf("%s, ", pData->uname[i]); + dbgprintf("%s, ", pData->uname[i]); ENDdbgPrintInstInfo @@ -112,13 +119,13 @@ static void endtty() * BSD because they are not available there. We only emulate what we actually * need! rgerhards 2005-03-18 */ -#ifdef BSD +#ifdef OS_BSD static FILE *BSD_uf = NULL; void setutent(void) { assert(BSD_uf == NULL); if ((BSD_uf = fopen(_PATH_UTMP, "r")) == NULL) { - logerror(_PATH_UTMP); + errmsg.LogError(NO_ERRCODE, "%s", _PATH_UTMP); return; } } @@ -138,7 +145,7 @@ void endutent(void) fclose(BSD_uf); BSD_uf = NULL; } -#endif +#endif /* #ifdef OS_BSD */ /* @@ -202,7 +209,7 @@ static rsRetVal wallmsg(uchar* pMsg, instanceData *pData) /* is this slot used? */ if (ut.ut_name[0] == '\0') continue; -#ifndef BSD +#ifndef OS_BSD if (ut.ut_type != USER_PROCESS) continue; #endif @@ -322,16 +329,6 @@ CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - BEGINmodExit CODESTARTmodExit ENDmodExit @@ -345,8 +342,9 @@ ENDqueryEtryPt BEGINmodInit(UsrMsg) CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); ENDmodInit /* @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -25,7 +26,7 @@ #define OMUSRMSG_H_INCLUDED 1 /* prototypes */ -rsRetVal modInitUsrMsg(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)())); +rsRetVal modInitUsrMsg(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); #endif /* #ifndef OMUSRMSG_H_INCLUDED */ /* diff --git a/outchannel.c b/outchannel.c index 0427a4a4..d013ea08 100644 --- a/outchannel.c +++ b/outchannel.c @@ -6,13 +6,28 @@ * Please see syslogd.c for license information. * This code is placed under the GPL. * begun 2005-06-21 rgerhards + * + * Copyright (C) 2005 by 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 __FreeBSD__ -#define BSD -#endif - #include "rsyslog.h" #include <stdio.h> #include <unistd.h> @@ -76,12 +91,12 @@ static void skip_Comma(char **pp) /* 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. - * returns: 0 - ok, 1 - failure */ -static int get_Field(uchar **pp, uchar **pField) +static rsRetVal get_Field(uchar **pp, uchar **pField) { + DEFiRet; register uchar *p; - rsCStrObj *pStrB; + cstr_t *pStrB = NULL; assert(pp != NULL); assert(*pp != NULL); @@ -90,21 +105,25 @@ static int get_Field(uchar **pp, uchar **pField) skip_Comma((char**)pp); p = *pp; - if((pStrB = rsCStrConstruct()) == NULL) - return 1; + CHKiRet(rsCStrConstruct(&pStrB)); rsCStrSetAllocIncrement(pStrB, 32); /* copy the field */ while(*p && *p != ' ' && *p != ',') { - rsCStrAppendChar(pStrB, *p++); + CHKiRet(rsCStrAppendChar(pStrB, *p++)); } *pp = p; - rsCStrFinish(pStrB); - if(rsCStrConvSzStrAndDestruct(pStrB, pField, 0) != RS_RET_OK) - return 1; + CHKiRet(rsCStrFinish(pStrB)); + CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, pField, 0)); - return 0; +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrB != NULL) + rsCStrDestruct(&pStrB); + } + + RETiRet; } @@ -141,12 +160,12 @@ static int get_off_t(uchar **pp, off_t *pOff_t) * current position to the end of line and returns it * to the caller. Leading white space is removed, but * not trailing. - * returns: 0 - ok, 1 - failure */ -static inline int get_restOfLine(uchar **pp, uchar **pBuf) +static inline rsRetVal get_restOfLine(uchar **pp, uchar **pBuf) { + DEFiRet; register uchar *p; - rsCStrObj *pStrB; + cstr_t *pStrB = NULL; assert(pp != NULL); assert(*pp != NULL); @@ -155,21 +174,25 @@ static inline int get_restOfLine(uchar **pp, uchar **pBuf) skip_Comma((char**)pp); p = *pp; - if((pStrB = rsCStrConstruct()) == NULL) - return 1; + CHKiRet(rsCStrConstruct(&pStrB)); rsCStrSetAllocIncrement(pStrB, 32); /* copy the field */ while(*p) { - rsCStrAppendChar(pStrB, *p++); + CHKiRet(rsCStrAppendChar(pStrB, *p++)); } *pp = p; - rsCStrFinish(pStrB); - if(rsCStrConvSzStrAndDestruct(pStrB, pBuf, 0) != RS_RET_OK) - return 1; + CHKiRet(rsCStrFinish(pStrB)); + CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, pBuf, 0)); - return 0; +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrB != NULL) + rsCStrDestruct(&pStrB); + } + + RETiRet; } @@ -271,11 +294,10 @@ void ochPrintList(void) 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", pOch->uSizeLimit); + 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: +/* vi:set ai: */ diff --git a/outchannel.h b/outchannel.h index 50d58bbb..a9298492 100644 --- a/outchannel.h +++ b/outchannel.h @@ -1,7 +1,25 @@ /* This is the header for the output channel code of rsyslog. * Please see syslogd.c for license information. - * This code is placed under the GPL. * begun 2005-06-21 rgerhards + * + * Copyright(C) 2005 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. */ struct outchannel { struct outchannel *pNext; @@ -5,7 +5,23 @@ * * Copyright 2005 * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. - * This code is placed under the GPL. + * + * 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" @@ -42,7 +58,7 @@ rsRetVal rsParsDestruct(rsParsObj *pThis) rsCHECKVALIDOBJECT(pThis, OIDrsPars); if(pThis->pCStr != NULL) - rsCStrDestruct (pThis->pCStr); + rsCStrDestruct(&pThis->pCStr); RSFREEOBJ(pThis); return RS_RET_OK; } @@ -73,37 +89,37 @@ rsRetVal rsParsConstruct(rsParsObj **ppThis) */ rsRetVal rsParsConstructFromSz(rsParsObj **ppThis, unsigned char *psz) { + DEFiRet; rsParsObj *pThis; - rsCStrObj *pCS; - rsRetVal iRet; + cstr_t *pCS; assert(ppThis != NULL); assert(psz != NULL); /* create string for parser */ - if((iRet = rsCStrConstructFromszStr(&pCS, psz)) != RS_RET_OK) - return(iRet); + CHKiRet(rsCStrConstructFromszStr(&pCS, psz)); /* create parser */ if((iRet = rsParsConstruct(&pThis)) != RS_RET_OK) { - rsCStrDestruct (pCS); - return(iRet); + rsCStrDestruct(&pCS); + FINALIZE; } /* assign string to parser */ if((iRet = rsParsAssignString(pThis, pCS)) != RS_RET_OK) { rsParsDestruct(pThis); - return(iRet); + FINALIZE; } - *ppThis = pThis; - return RS_RET_OK; + +finalize_it: + RETiRet; } /** * Assign the to-be-parsed string. */ -rsRetVal rsParsAssignString(rsParsObj *pThis, rsCStrObj *pCStr) +rsRetVal rsParsAssignString(rsParsObj *pThis, cstr_t *pCStr) { rsCHECKVALIDOBJECT(pThis, OIDrsPars); rsCHECKVALIDOBJECT(pCStr, OIDrsCStr); @@ -163,7 +179,7 @@ rsRetVal parsInt(rsParsObj *pThis, int* pInt) rsRetVal parsSkipAfterChar(rsParsObj *pThis, char c) { register unsigned char *pC; - rsRetVal iRet; + DEFiRet; rsCHECKVALIDOBJECT(pThis, OIDrsPars); @@ -187,7 +203,7 @@ rsRetVal parsSkipAfterChar(rsParsObj *pThis, char c) iRet = RS_RET_NOT_FOUND; } - return iRet; + RETiRet; } /* Skip whitespace. Often used to trim parsable entries. @@ -219,32 +235,28 @@ rsRetVal parsSkipWhitespace(rsParsObj *pThis) * 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, rsCStrObj **ppCStr, char cDelim, int bTrimLeading, int bTrimTrailing) +rsRetVal parsDelimCStr(rsParsObj *pThis, cstr_t **ppCStr, char cDelim, int bTrimLeading, int bTrimTrailing, int bConvLower) { + DEFiRet; register unsigned char *pC; - rsCStrObj *pCStr; - rsRetVal iRet; + cstr_t *pCStr = NULL; rsCHECKVALIDOBJECT(pThis, OIDrsPars); - if((pCStr = rsCStrConstruct()) == NULL) - return RS_RET_OUT_OF_MEMORY; + CHKiRet(rsCStrConstruct(&pCStr)); if(bTrimLeading) parsSkipWhitespace(pThis); pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; - while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) - && *pC != cDelim) { - if((iRet = rsCStrAppendChar(pCStr, *pC)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); - } + while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) && *pC != cDelim) { + CHKiRet(rsCStrAppendChar(pCStr, bConvLower ? tolower(*pC) : *pC)); ++pThis->iCurrPos; ++pC; } @@ -256,22 +268,22 @@ rsRetVal parsDelimCStr(rsParsObj *pThis, rsCStrObj **ppCStr, char cDelim, int bT /* We got the string, now take it and see if we need to * remove anything at its end. */ - if((iRet = rsCStrFinish(pCStr)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); - } + CHKiRet(rsCStrFinish(pCStr)); if(bTrimTrailing) { - if((iRet = rsCStrTrimTrailingWhiteSpace(pCStr)) - != RS_RET_OK) { - rsCStrDestruct (pCStr); - return iRet; - } + CHKiRet(rsCStrTrimTrailingWhiteSpace(pCStr)); } /* done! */ *ppCStr = pCStr; - return RS_RET_OK; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pCStr != NULL) + rsCStrDestruct(&pCStr); + } + + RETiRet; } /* Parse a quoted string ("-some-data") from the given position. @@ -289,21 +301,19 @@ rsRetVal parsDelimCStr(rsParsObj *pThis, rsCStrObj **ppCStr, char cDelim, int bT * does NOT include the quotes. * rgerhards, 2005-09-19 */ -rsRetVal parsQuotedCStr(rsParsObj *pThis, rsCStrObj **ppCStr) +rsRetVal parsQuotedCStr(rsParsObj *pThis, cstr_t **ppCStr) { register unsigned char *pC; - rsCStrObj *pCStr; - rsRetVal iRet; + cstr_t *pCStr = NULL; + DEFiRet; rsCHECKVALIDOBJECT(pThis, OIDrsPars); - if((iRet = parsSkipAfterChar(pThis, '"')) != RS_RET_OK) - return iRet; + CHKiRet(parsSkipAfterChar(pThis, '"')); pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; /* OK, we most probably can obtain a value... */ - if((pCStr = rsCStrConstruct()) == NULL) - return RS_RET_OUT_OF_MEMORY; + CHKiRet(rsCStrConstruct(&pCStr)); while(pThis->iCurrPos < rsCStrLen(pThis->pCStr)) { if(*pC == '"') { @@ -316,16 +326,10 @@ rsRetVal parsQuotedCStr(rsParsObj *pThis, rsCStrObj **ppCStr) * to the output buffer (but do not rely on this, * we might later introduce other things, like \007! */ - if((iRet = rsCStrAppendChar(pCStr, *pC)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); - } + CHKiRet(rsCStrAppendChar(pCStr, *pC)); } } else { /* regular character */ - if((iRet = rsCStrAppendChar(pCStr, *pC)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); - } + CHKiRet(rsCStrAppendChar(pCStr, *pC)); } ++pThis->iCurrPos; ++pC; @@ -335,19 +339,23 @@ rsRetVal parsQuotedCStr(rsParsObj *pThis, rsCStrObj **ppCStr) ++pThis->iCurrPos; /* 'eat' trailing quote */ } else { /* error - improperly quoted string! */ - rsCStrDestruct (pCStr); - return RS_RET_MISSING_TRAIL_QUOTE; + rsCStrDestruct(&pCStr); + ABORT_FINALIZE(RS_RET_MISSING_TRAIL_QUOTE); } /* We got the string, let's finish it... */ - if((iRet = rsCStrFinish(pCStr)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); - } + CHKiRet(rsCStrFinish(pCStr)); /* done! */ *ppCStr = pCStr; - return RS_RET_OK; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pCStr != NULL) + rsCStrDestruct(&pCStr); + } + + RETiRet; } /* @@ -365,15 +373,14 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) uchar *pszIP; uchar *pszTmp; struct addrinfo hints, *res = NULL; - rsCStrObj *pCStr; - rsRetVal iRet; + cstr_t *pCStr; + DEFiRet; rsCHECKVALIDOBJECT(pThis, OIDrsPars); assert(pIP != NULL); assert(pBits != NULL); - if((pCStr = rsCStrConstruct()) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + CHKiRet(rsCStrConstruct(&pCStr)); parsSkipWhitespace(pThis); pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; @@ -384,8 +391,8 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) while(pThis->iCurrPos < rsCStrLen(pThis->pCStr) && *pC != '/' && *pC != ',' && !isspace((int)*pC)) { if((iRet = rsCStrAppendChar(pCStr, *pC)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); + rsCStrDestruct (&pCStr); + FINALIZE; } ++pThis->iCurrPos; ++pC; @@ -393,8 +400,8 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) /* We got the string, let's finish it... */ if((iRet = rsCStrFinish(pCStr)) != RS_RET_OK) { - rsCStrDestruct (pCStr); - return(iRet); + rsCStrDestruct (&pCStr); + FINALIZE; } /* now we have the string and must check/convert it to @@ -408,7 +415,7 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) pszTmp = (uchar*)strchr ((char*)pszIP, ']'); if (pszTmp == NULL) { free (pszIP); - return RS_RET_INVALID_IP; + ABORT_FINALIZE(RS_RET_INVALID_IP); } *pszTmp = '\0'; @@ -433,7 +440,7 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) default: free (pszIP); free (*pIP); - return RS_RET_ERR; + ABORT_FINALIZE(RS_RET_ERR); } if(*pC == '/') { @@ -442,7 +449,7 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) if((iRet = parsInt(pThis, pBits)) != RS_RET_OK) { free (pszIP); free (*pIP); - return(iRet); + FINALIZE; } /* we need to refresh pointer (changed by parsInt()) */ pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; @@ -472,7 +479,7 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) default: free (pszIP); free (*pIP); - return RS_RET_ERR; + ABORT_FINALIZE(RS_RET_ERR); } if(*pC == '/') { @@ -481,7 +488,7 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) if((iRet = parsInt(pThis, pBits)) != RS_RET_OK) { free (pszIP); free (*pIP); - return(iRet); + FINALIZE; } /* we need to refresh pointer (changed by parsInt()) */ pC = rsCStrGetBufBeg(pThis->pCStr) + pThis->iCurrPos; @@ -502,7 +509,7 @@ rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits) iRet = RS_RET_OK; finalize_it: - return iRet; + RETiRet; } #endif /* #ifdef SYSLOG_INET */ @@ -23,6 +23,25 @@ * necessary buffer space. * * begun 2005-09-09 rgerhards + * + * Copyright (C) 2005 by 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 _PARSE_H_INCLUDED__ #define _PARSE_H_INCLUDED__ 1 @@ -37,7 +56,7 @@ struct rsParsObject #ifndef NDEBUG rsObjID OID; /**< object ID */ #endif - rsCStrObj *pCStr; /**< pointer to the string object we are parsing */ + cstr_t *pCStr; /**< pointer to the string object we are parsing */ int iCurrPos; /**< current parsing position (char offset) */ }; typedef struct rsParsObject rsParsObj; @@ -52,7 +71,7 @@ int rsParsGetParsePointer(rsParsObj *pThis); * Construct a rsPars object. */ rsRetVal rsParsConstruct(rsParsObj **ppThis); -rsRetVal rsParsAssignString(rsParsObj *pThis, rsCStrObj *pCStr); +rsRetVal rsParsAssignString(rsParsObj *pThis, cstr_t *pCStr); /* parse an integer. The parse pointer is advanced */ rsRetVal parsInt(rsParsObj *pThis, int* pInt); @@ -72,10 +91,10 @@ rsRetVal parsSkipWhitespace(rsParsObj *pThis); * Output: * ppCStr Pointer to the parsed string */ -rsRetVal parsDelimCStr(rsParsObj *pThis, rsCStrObj **ppCStr, char cDelim, int bTrimLeading, int bTrimTrailing); +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, rsCStrObj **ppCStr); +rsRetVal parsQuotedCStr(rsParsObj *pThis, cstr_t **ppCStr); rsRetVal rsParsConstructFromSz(rsParsObj **ppThis, unsigned char *psz); rsRetVal rsParsDestruct(rsParsObj *pThis); int parsIsAtEndOfParseString(rsParsObj *pThis); @@ -2,21 +2,22 @@ pidfile.c - interact with pidfiles Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE> - This file is part of the sysklogd package, a kernel and 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 + * 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" @@ -39,20 +40,7 @@ #include <fcntl.h> #endif - -static char *rs_strerror_r(int errnum, char *buf, size_t buflen) { -#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 - return buf; -} - +#include "srUtils.h" /* read_pid * @@ -123,7 +111,7 @@ int write_pid (char *pidfile) * 2006-02-16 rgerhards */ -#ifndef __sun +#if HAVE_FLOCK if (flock(fd, LOCK_EX|LOCK_NB) == -1) { fscanf(f, "%d", &pid); fclose(f); @@ -142,7 +130,7 @@ int write_pid (char *pidfile) } fflush(f); -#ifndef __sun +#if HAVE_FLOCK if (flock(fd, LOCK_UN) == -1) { char errStr[1024]; rs_strerror_r(errno, errStr, sizeof(errStr)); @@ -2,21 +2,22 @@ pidfile.h - interact with pidfiles Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE> - This file is part of the sysklogd package, a kernel and 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. + * 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 diff --git a/plugins/imfile/Makefile.am b/plugins/imfile/Makefile.am new file mode 100644 index 00000000..23b64d1b --- /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) +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..2df1aaf7 --- /dev/null +++ b/plugins/imfile/imfile.c @@ -0,0 +1,524 @@ +/* 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 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" /* 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 "syslogd.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 "datetime.h" + +MODULE_TYPE_INPUT /* must be present for input modules, do not remove */ + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA /* must be present, starts static data */ +DEFobjCurrIf(errmsg) +DEFobjCurrIf(datetime) + +typedef struct fileInfo_s { + uchar *pszFileName; + uchar *pszTag; + uchar *pszStateFile; /* file in which state between runs is to be stored */ + int iFacility; + int iSeverity; + strm_t *pStrm; /* its stream (NULL if not assigned) */ +} fileInfo_t; + + +/* config variables */ +static uchar *pszFileName = NULL; +static uchar *pszFileTag = NULL; +static uchar *pszStateFile = NULL; +static int iPollInterval = 10; /* number of seconds to sleep when there was no file activity */ +static int iFacility = 128; /* local0 */ +static int iSeverity = 5; /* notice, as of rfc 3164 */ + +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]; + + +/* 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); + MsgSetUxTradMsg(pMsg, (char*)rsCStrGetSzStr(cstrLine)); + MsgSetRawMsg(pMsg, (char*)rsCStrGetSzStr(cstrLine)); + MsgSetMSG(pMsg, (char*)rsCStrGetSzStr(cstrLine)); + MsgSetHOSTNAME(pMsg, (char*)LocalHostName); + MsgSetTAG(pMsg, (char*)pInfo->pszTag); + pMsg->iFacility = LOG_FAC(pInfo->iFacility); + pMsg->iSeverity = LOG_PRI(pInfo->iSeverity); + pMsg->bParseHOSTNAME = 0; + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + CHKiRet(submitMsg(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*) glblGetWorkDir(), (char*)pThis->pszStateFile); + + /* check if the file exists */ + if(stat((char*) pszSFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + /* currently no object! dbgoprint((obj_t*) pThis, "clean startup, no .si file found\n"); */ + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + /* currently no object! dbgoprint((obj_t*) pThis, "error %d trying to access .si file\n", errno); */ + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + + /* If we reach this point, we have a .si file */ + + CHKiRet(strmConstruct(&psSF)); + CHKiRet(strmSettOperationsMode(psSF, STREAMMODE_READ)); + CHKiRet(strmSetsType(psSF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psSF, pszSFNam, lenSFNam)); + CHKiRet(strmConstructFinalize(psSF)); + + /* read back in the object */ + CHKiRet(obj.Deserialize(&pThis->pStrm, (uchar*) "strm", psSF, NULL, pThis)); + + CHKiRet(strmSeekCurrOffs(pThis->pStrm)); + + /* OK, we could successfully read the file, so we now can request that it be deleted. + * If we need it again, it will be written on the next shutdown. + */ + psSF->bDeleteOnClose = 1; + +finalize_it: + if(psSF != NULL) + strmDestruct(&psSF); + + if(iRet != RS_RET_OK) { + CHKiRet(strmConstruct(&pThis->pStrm)); + CHKiRet(strmSettOperationsMode(pThis->pStrm, STREAMMODE_READ)); + CHKiRet(strmSetsType(pThis->pStrm, STREAMTYPE_FILE_MONITOR)); + CHKiRet(strmSetFName(pThis->pStrm, pThis->pszFileName, strlen((char*) pThis->pszFileName))); + CHKiRet(strmConstructFinalize(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 */ +static rsRetVal pollFile(fileInfo_t *pThis, int *pbHadFileData) +{ + cstr_t *pCStr = NULL; + 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(1) { + CHKiRet(strmReadLine(pThis->pStrm, &pCStr)); + *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!) */ + } + +finalize_it: + /*EMPTY - just to keep the compiler happy, do NOT remove*/; + /* Note: the problem above is that pthread:cleanup_pop() is a macro which + * evaluates to something like "} while(0);". So the code would become + * "finalize_it: }", that is a label without a statement. The C standard does + * not permit this. So we add an empty statement "finalize_it: ; }" and + * everybody is happy. Note that without the ;, an error is reported only + * on some platforms/compiler versions. -- rgerhards, 2008-08-15 + */ + pthread_cleanup_pop(0); + + if(pCStr != NULL) { + rsCStrDestruct(&pCStr); + } + + RETiRet; +} + + +/* 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. + */ +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ +static void +inputModuleCleanup(void __attribute__((unused)) *arg) +{ + BEGINfunc +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + + + /* so far not needed */ + + + +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + ENDfunc +} +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + +/* 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. + */ +BEGINrunInput + int i; + int bHadFileData; /* were there at least one file with data during this run? */ +CODESTARTrunInput + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + pthread_cleanup_push(inputModuleCleanup, NULL); + while(1) { /* endless loop - do NOT break; out of it! */ + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + do { + bHadFileData = 0; + for(i = 0 ; i < iFilPtr ; ++i) { + pollFile(&files[i], &bHadFileData); + } + } while(iFilPtr > 1 && bHadFileData == 1); /* waring: 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 + */ + srSleep(iPollInterval, 10); + + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + } + /*NOTREACHED*/ + + pthread_cleanup_pop(0); /* just for completeness, but never called... */ + RETiRet; /* use it to make sure the housekeeping is done! */ +ENDrunInput + /* 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 + if(iFilPtr == 0) { + errmsg.LogError(NO_ERRCODE, "No files configured to be monitored"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + +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) */ + + ASSERT(pInfo != NULL); + + /* TODO: create a function persistObj in obj.c? */ + CHKiRet(strmConstruct(&psSF)); + CHKiRet(strmSetDir(psSF, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); + CHKiRet(strmSettOperationsMode(psSF, STREAMMODE_WRITE)); + CHKiRet(strmSetiAddtlOpenFlags(psSF, O_TRUNC)); + CHKiRet(strmSetsType(psSF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psSF, pInfo->pszStateFile, strlen((char*) pInfo->pszStateFile))); + CHKiRet(strmConstructFinalize(psSF)); + + CHKiRet(strmSerialize(pInfo->pStrm, psSF)); + + CHKiRet(strmDestruct(&psSF)); + +finalize_it: + if(psSF != NULL) + strmDestruct(&psSF); + + 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]); + strmDestruct(&(files[i].pStrm)); + } + } +ENDafterRun + + +/* 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(datetime, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_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; + + if(pszFileName != NULL) { + free(pszFileName); + pszFileName = NULL; + } + + if(pszFileTag != NULL) { + free(pszFileTag); + pszFileTag = NULL; + } + + if(pszStateFile != NULL) { + free(pszFileTag); + pszFileTag = NULL; + } + + + /* set defaults... */ + iPollInterval = 10; + iFacility = 128; /* local0 */ + iSeverity = 5; /* notice, as of rfc 3164 */ + + RETiRet; +} + + +/* add a new monitor */ +static rsRetVal addMonitor(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + fileInfo_t *pThis; + + free(pNewVal); /* we do not need it, but we must free it! */ + + if(iFilPtr < MAX_INPUT_FILES) { + pThis = &files[iFilPtr]; + /* TODO: check for strdup() NULL return */ + if(pszFileName == NULL) { + errmsg.LogError(NO_ERRCODE, "imfile error: no file name given, file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } else { + pThis->pszFileName = (uchar*) strdup((char*) pszFileName); + } + + if(pszFileTag == NULL) { + errmsg.LogError(NO_ERRCODE, "imfile error: no tag value given , file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } else { + pThis->pszTag = (uchar*) strdup((char*) pszFileTag); + } + + if(pszStateFile == NULL) { + errmsg.LogError(NO_ERRCODE, "imfile error: not state file name given, file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } else { + pThis->pszStateFile = (uchar*) strdup((char*) pszStateFile); + } + + pThis->iSeverity = iSeverity; + pThis->iFacility = iFacility; + } else { + errmsg.LogError(NO_ERRCODE, "Too many file monitors configured - ignoring this one"); + ABORT_FINALIZE(RS_RET_OUT_OF_DESRIPTORS); + } + + CHKiRet(resetConfigVariables((uchar*) "dummy", (void*) pThis)); /* values are both dummies */ + +finalize_it: + if(iRet == RS_RET_OK) + ++iFilPtr; /* we got a new file to monitor */ + + RETiRet; +} + +/* 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(datetime, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilename", 0, eCmdHdlrGetWord, + NULL, &pszFileName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfiletag", 0, eCmdHdlrGetWord, + NULL, &pszFileTag, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilestatefile", 0, eCmdHdlrGetWord, + NULL, &pszStateFile, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfileseverity", 0, eCmdHdlrSeverity, + NULL, &iSeverity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilefacility", 0, eCmdHdlrFacility, + NULL, &iFacility, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilepollinterval", 0, eCmdHdlrInt, + NULL, &iPollInterval, STD_LOADABLE_MODULE_ID)); + /* that command ads a new file! */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrunfilemonitor", 0, eCmdHdlrGetWord, + addMonitor, 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/imgssapi/.cvsignore b/plugins/imgssapi/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/imgssapi/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/imgssapi/Makefile.am b/plugins/imgssapi/Makefile.am new file mode 100644 index 00000000..42a243f4 --- /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) +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..f2b00d9d --- /dev/null +++ b/plugins/imgssapi/imgssapi.c @@ -0,0 +1,697 @@ +/* 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 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 "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "srUtils.h" +#include "gss-misc.h" +#include "tcpsrv.h" +#include "tcps_sess.h" +#include "errmsg.h" + + +MODULE_TYPE_INPUT + +/* 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 int TCPSessGSSInit(void); +static void TCPSessGSSClose(tcps_sess_t* pSess); +static int TCPSessGSSRecv(tcps_sess_t *pSess, void *buf, size_t buf_len); +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(net) + +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; + +/* 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 */ +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.isAllowedSender((uchar*)"TCP", addr, (char*)fromHostFQDN)) + allowedMethods |= ALLOWEDMETHOD_TCP; + if((pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) && + net.isAllowedSender((uchar*)"GSS", addr, (char*)fromHostFQDN)) + 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 int* +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + int *pRet = NULL; + gsssrv_t *pGSrv; + + 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(NO_ERRCODE, "GSS-API initialization failed\n"); + pGSrv->allowedMethods &= ~(ALLOWEDMETHOD_GSS); + } + } + if(pGSrv->allowedMethods) { + /* fallback to plain TCP */ + if((pRet = tcpsrv.create_tcp_socket(pSrv)) != NULL) { + dbgprintf("Opened %d syslog TCP port(s).\n", *pRet); + } + } + } + + return pRet; +} + + +static int +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf) +{ + int state; + int allowedMethods; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + allowedMethods = pGSess->allowedMethods; + if(allowedMethods & ALLOWEDMETHOD_GSS) + state = TCPSessGSSRecv(pSess, buf, lenBuf); + else + state = recv(pSess->sock, buf, lenBuf, 0); + return state; +} + + +/* end callbacks */ + +static rsRetVal +addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + 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)); + tcpsrv.configureTCPListen(pOurTcpsrv, (char *) pNewVal); + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); + } + +finalize_it: + 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. + */ + char buf[MAXLINE]; + int ret = 0; + + dbgprintf("GSS-API Trying to accept TCP session %p\n", pSess); + + fdSess = pSess->sock; // TODO: method access! + if (allowedMethods & ALLOWEDMETHOD_TCP) { + int len; + fd_set fds; + struct timeval tv; + + do { + FD_ZERO(&fds); + FD_SET(fdSess, &fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = select(fdSess + 1, &fds, NULL, NULL, &tv); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + errmsg.LogError(NO_ERRCODE, "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(NO_ERRCODE, "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(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 + } + } + + 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(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(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(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; +} + + +/* returns: number of bytes read or -1 on error + * Replaces recv() for gssapi connections. + */ +int TCPSessGSSRecv(tcps_sess_t *pSess, void *buf, size_t buf_len) +{ + 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, len; + gss_sess_t *pGSess; + + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + fdSess = pSess->sock; + if ((state = gssutil.recv_token(fdSess, &xmit_buf)) <= 0) + return state; + + 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; + } + return (-1); + } + if (xmit_buf.value) { + free(xmit_buf.value); + xmit_buf.value = 0; + } + + len = msg_buf.length < buf_len ? msg_buf.length : buf_len; + memcpy(buf, msg_buf.value, len); + gss_release_buffer(&min_stat, &msg_buf); + + return len; +} + + +/* 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; +} + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + iRet = tcpsrv.Run(pOurTcpsrv); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + if(pOurTcpsrv == 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(net, LM_NET_FILENAME); +ENDmodExit + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"TCP"); + net.clearAllowedSenders((uchar*)"GSS"); +ENDafterRun + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_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(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/imklog/.cvsignore b/plugins/imklog/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/imklog/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/imklog/Makefile.am b/plugins/imklog/Makefile.am new file mode 100644 index 00000000..246b3306 --- /dev/null +++ b/plugins/imklog/Makefile.am @@ -0,0 +1,16 @@ +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 += linux.c module.h ksym.c ksyms.h ksym_mod.c +endif + +imklog_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_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..39b644c0 --- /dev/null +++ b/plugins/imklog/bsd.c @@ -0,0 +1,181 @@ +/* klog for BSD, based on the FreeBSD syslogd implementation. + * + * This contains OS-specific functionality to read the BSD + * kernel log. For a general overview, see head comment in + * imklog.c. + * + * Copyright (C) 2008 by Rainer Gerhards for the modifications of + * the original FreeBSD sources. + * + * I would like to express my gratitude to those folks which + * layed an important foundation for rsyslog to build on. + * + * 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. + * + * This file is based on earlier work included in the FreeBSD sources. We + * integrated it into the rsyslog project. The copyright below applies, and + * I also reproduce the original license under which we aquired the code: + * + * Copyright (c) 1983, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * If you would like to use the code under the BSD license, you should + * aquire your own copy of BSD's syslogd, from which we have taken it. The + * code in this file is modified and may only be used under the terms of + * the GPLv3+ as specified above. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#include "rsyslog.h" +#include "imklog.h" + +/* globals */ +static int fklog = -1; /* /dev/klog */ + +#ifndef _PATH_KLOG +# define _PATH_KLOG "/dev/klog" +#endif + +/* open the kernel log - will be called inside the willRun() imklog + * entry point. -- rgerhards, 2008-04-09 + */ +rsRetVal +klogWillRun(void) +{ + DEFiRet; + + fklog = open(_PATH_KLOG, O_RDONLY, 0); + if (fklog < 0) { + dbgprintf("can't open %s (%d)\n", _PATH_KLOG, errno); + iRet = RS_RET_ERR; // TODO: better error code + } + + RETiRet; +} + + +/* Read /dev/klog while data are available, split into lines. + * Contrary to standard BSD syslogd, we do a blocking read. We can + * afford this as imklog is running on its own threads. So if we have + * a single file, it really doesn't matter if we wait inside a 1-file + * select or the read() directly. + */ +static void +readklog(void) +{ + char *p, *q, line[MAXLINE + 1]; + int len, i; + + len = 0; + for (;;) { + dbgprintf("----------imklog waiting for kernel log line\n"); + i = read(fklog, line + len, MAXLINE - 1 - len); + if (i > 0) { + line[i + len] = '\0'; + } else { + if (i < 0 && errno != EINTR && errno != EAGAIN) { + imklogLogIntMsg(LOG_ERR, + "imklog error %d reading kernel log - shutting down imklog", + errno); + fklog = -1; + } + break; + } + + for (p = line; (q = strchr(p, '\n')) != NULL; p = q + 1) { + *q = '\0'; + Syslog(LOG_INFO, (uchar*) p); + } + len = strlen(p); + if (len >= MAXLINE - 1) { + Syslog(LOG_INFO, (uchar*)p); + len = 0; + } + if (len > 0) + memmove(line, p, len + 1); + } + if (len > 0) + Syslog(LOG_INFO, (uchar*)line); +} + + +/* 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; + readklog(); + 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..f7aee5b1 --- /dev/null +++ b/plugins/imklog/imklog.c @@ -0,0 +1,267 @@ +/* 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. + * + * Copyright (C) 2008 by 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 <string.h> +#include <stdarg.h> +#include <ctype.h> + +#include "syslogd.h" +#include "cfsysline.h" +#include "obj.h" +#include "msg.h" +#include "module-template.h" +#include "datetime.h" +#include "imklog.h" + +MODULE_TYPE_INPUT + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(datetime) + +/* configuration settings TODO: move to instance data? */ +int dbgPrintSymbols = 0; /* this one is extern so the helpers can access it! */ +int symbols_twice = 0; +int use_syscall = 0; +int symbol_lookup = 0; /* on recent kernels > 2.6, the kernel does this */ +int bPermitNonKernel = 0; /* permit logging of messages not having LOG_KERN facility */ +int iFacilIntMsg; /* the facility to use for internal messages (set by driver) */ +/* TODO: configuration for the following directives must be implemented. It + * was not done yet because we either do not yet have a config handler for + * that type or I thought it was acceptable to push it to a later stage when + * I gained more handson experience with the input module interface (and the + * changes resulting from that). -- rgerhards, 2007-12-20 + */ +char *symfile = NULL; +int console_log_level = -1; + + +/* 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) +{ + DEFiRet; + msg_t *pMsg; + + assert(msg != NULL); + assert(pszTag != NULL); + + CHKiRet(msgConstruct(&pMsg)); + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + MsgSetUxTradMsg(pMsg, (char*)msg); + MsgSetRawMsg(pMsg, (char*)msg); + MsgSetMSG(pMsg, (char*)msg); + MsgSetHOSTNAME(pMsg, (char*)LocalHostName); + MsgSetTAG(pMsg, (char*)pszTag); + pMsg->iFacility = LOG_FAC(iFacility); + pMsg->iSeverity = LOG_PRI(iSeverity); + pMsg->bParseHOSTNAME = 0; + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + CHKiRet(submitMsg(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 */ + uchar *pLogMsg; + + va_start(ap, fmt); + vsnprintf((char*)msgBuf, sizeof(msgBuf) / sizeof(char), fmt, ap); + pLogMsg = msgBuf; + va_end(ap); + + iRet = enqMsg((uchar*)pLogMsg, (uchar*) ((iFacilIntMsg == LOG_KERN) ? "kernel:" : "imklog:"), + iFacilIntMsg, LOG_PRI(priority)); + + RETiRet; +} + + +/* log a kernel message + * rgerhards, 2008-04-14 + */ +rsRetVal Syslog(int priority, uchar *pMsg) +{ + DEFiRet; + rsRetVal localRet; + + /* Output using syslog */ + 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(bPermitNonKernel == 0 && LOG_FAC(priority) != LOG_KERN) + FINALIZE; /* silently ignore */ + + iRet = enqMsg((uchar*)pMsg, (uchar*) "kernel:", LOG_FAC(priority), LOG_PRI(priority)); + +finalize_it: + RETiRet; +} + + +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()); + } +finalize_it: +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + iRet = klogWillRun(); +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + iRet = klogAfterRun(); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release objects we used */ + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + dbgPrintSymbols = 0; + symbols_twice = 0; + use_syscall = 0; + symfile = NULL; + symbol_lookup = 0; + bPermitNonKernel = 0; + 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)); + + iFacilIntMsg = klogFacilIntMsg(); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"debugprintkernelsymbols", 0, eCmdHdlrBinary, NULL, &dbgPrintSymbols, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbollookup", 0, eCmdHdlrBinary, NULL, &symbol_lookup, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbolstwice", 0, eCmdHdlrBinary, NULL, &symbols_twice, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogusesyscallinterface", 0, eCmdHdlrBinary, NULL, &use_syscall, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogpermitnonkernelfacility", 0, eCmdHdlrBinary, NULL, &bPermitNonKernel, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"kloginternalmsgfacility", 0, eCmdHdlrFacility, NULL, &iFacilIntMsg, STD_LOADABLE_MODULE_ID)); + 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..a37ecc9e --- /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-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 IMKLOG_H_INCLUDED +#define IMKLOG_H_INCLUDED 1 + +#include "rsyslog.h" +#include "syslogd.h" + +/* 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(void); +rsRetVal klogWillRun(void); +rsRetVal klogAfterRun(void); +int klogFacilIntMsg(void); + +/* the following data members may be accessed by the "drivers" + * I admit this is not the cleanest way to doing things, but I honestly + * believe it is appropriate for the job that needs to be done. + * rgerhards, 2008-04-09 + */ +extern int symbols_twice; +extern int use_syscall; +extern int symbol_lookup; +extern char *symfile; +extern int console_log_level; +extern int dbgPrintSymbols; + +/* 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); + +/* prototypes */ +extern int InitKsyms(char *); +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/ksym.c b/plugins/imklog/ksym.c index 8dff9ac7..f636a7bb 100644 --- a/ksym.c +++ b/plugins/imklog/ksym.c @@ -1,26 +1,25 @@ -#include "config.h" - -#ifdef FEATURE_KLOGD -/* - ksym.c - functions for kernel address->symbol translation - Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> - Copyright (c) 1996 Enjellic Systems Development - - This file is part of the sysklogd package, a kernel and 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., 675 Mass Ave, Cambridge, MA 02139, USA. +/* ksym.c - functions for kernel address->symbol translation + * Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> + * Copyright (c) 1996 Enjellic Systems Development + * Copyright (c) 1998-2007 Martin Schulze <joey@infodrom.org> + * Copyright (C) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * 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. */ /* @@ -112,23 +111,20 @@ /* Includes. */ +#include "config.h" +#include <stdio.h> #include <stdlib.h> #include <sys/utsname.h> #include <ctype.h> -#include "klogd.h" +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include "imklog.h" #include "ksyms.h" - -#define VERBOSE_DEBUGGING 0 +#include "module.h" -/* Variables static to this module. */ -struct sym_table -{ - unsigned long value; - char *name; -}; - -static int num_syms = 0; +int num_syms = 0; static int i_am_paranoid = 0; static char vstring[12]; static struct sym_table *sym_array = (struct sym_table *) 0; @@ -137,29 +133,19 @@ static char *system_maps[] = { "/boot/System.map", "/System.map", -#if defined(TEST) - "./System.map", -#endif - (char *) 0 + NULL }; -#if defined(TEST) -int debugging; -#else -extern int debugging; -#endif - - /* Function prototypes. */ -static char * FindSymbolFile(void); +static char *FindSymbolFile(void); static int AddSymbol(unsigned long, char*); static void FreeSymbols(void); static int CheckVersion(char *); static int CheckMapVersion(char *); -/************************************************************************** +/************************************************************************* * Function: InitKsyms * * Purpose: This function is responsible for initializing and loading @@ -176,11 +162,7 @@ static int CheckMapVersion(char *); * A boolean style context is returned. The return value will * be true if initialization was successful. False if not. **************************************************************************/ - -extern int InitKsyms(mapfile) - - char *mapfile; - +extern int InitKsyms(char *mapfile) { auto char type, sym[512]; @@ -191,68 +173,53 @@ extern int InitKsyms(mapfile) auto FILE *sym_file; + BEGINfunc /* Check and make sure that we are starting with a clean slate. */ if ( num_syms > 0 ) FreeSymbols(); - /* - * Search for and open the file containing the kernel symbols. - */ - if ( mapfile != (char *) 0 ) - { - if ( (sym_file = fopen(mapfile, "r")) == (FILE *) 0 ) + /* Search for and open the file containing the kernel symbols. */ + if ( mapfile != NULL ) { + if ( (sym_file = fopen(mapfile, "r")) == NULL ) { - Syslog(LOG_WARNING, "Cannot open map file: %s.", \ - mapfile); + imklogLogIntMsg(LOG_WARNING, "Cannot open map file: %s.", mapfile); return(0); } - } - else - { - if ( (mapfile = FindSymbolFile()) == (char *) 0 ) - { - Syslog(LOG_WARNING, "Cannot find map file."); - if ( debugging ) - fputs("Cannot find map file.\n", stderr); + } else { + if ( (mapfile = FindSymbolFile()) == NULL ) { + imklogLogIntMsg(LOG_WARNING, "Cannot find map file."); + dbgprintf("Cannot find map file.\n"); return(0); } - if ( (sym_file = fopen(mapfile, "r")) == (FILE *) 0 ) - { - Syslog(LOG_WARNING, "Cannot open map file."); - if ( debugging ) - fputs("Cannot open map file.\n", stderr); + if ( (sym_file = fopen(mapfile, "r")) == NULL ) { + imklogLogIntMsg(LOG_WARNING, "Cannot open map file."); + dbgprintf("Cannot open map file.\n"); return(0); } } - /* - * Read the kernel symbol table file and add entries for each + /* Read the kernel symbol table file and add entries for each * line. I suspect that the use of fscanf is not really in vogue * but it was quick and dirty and IMHO suitable for fixed format * data such as this. If anybody doesn't agree with this please * e-mail me a diff containing a parser with suitable political * correctness -- GW. */ - while ( !feof(sym_file) ) - { - if ( fscanf(sym_file, "%lx %c %s\n", &address, &type, sym) - != 3 ) - { - Syslog(LOG_ERR, "Error in symbol table input (#1)."); + while ( !feof(sym_file) ) { + if ( fscanf(sym_file, "%lx %c %s\n", &address, &type, sym) != 3 ) { + imklogLogIntMsg(LOG_ERR, "Error in symbol table input (#1)."); fclose(sym_file); return(0); } - if ( VERBOSE_DEBUGGING && debugging ) - fprintf(stderr, "Address: %lx, Type: %c, Symbol: %s\n", - address, type, sym); + if(dbgPrintSymbols) + dbgprintf("Address: %lx, Type: %c, Symbol: %s\n", address, type, sym); - if ( AddSymbol(address, sym) == 0 ) - { - Syslog(LOG_ERR, "Error adding symbol - %s.", sym); + if ( AddSymbol(address, sym) == 0 ) { + imklogLogIntMsg(LOG_ERR, "Error adding symbol - %s.", sym); fclose(sym_file); return(0); } @@ -262,29 +229,34 @@ extern int InitKsyms(mapfile) } - Syslog(LOG_INFO, "Loaded %d symbols from %s.", num_syms, mapfile); - switch ( version ) - { + imklogLogIntMsg(LOG_INFO, "Loaded %d symbols from %s.", num_syms, mapfile); + switch(version) { case -1: - Syslog(LOG_WARNING, "Symbols do not match kernel version."); + imklogLogIntMsg(LOG_WARNING, "Symbols do not match kernel version."); num_syms = 0; break; case 0: - Syslog(LOG_WARNING, "Cannot verify that symbols match " \ - "kernel version."); + imklogLogIntMsg(LOG_WARNING, "Cannot verify that symbols match kernel version."); break; case 1: - Syslog(LOG_INFO, "Symbols match kernel version %s.", vstring); + imklogLogIntMsg(LOG_INFO, "Symbols match kernel version %s.", vstring); break; } fclose(sym_file); + ENDfunc return(1); } +extern void DeinitKsyms(void) +{ + FreeSymbols(); +} + + /************************************************************************** * Function: FindSymbolFile * @@ -317,57 +289,44 @@ extern int InitKsyms(mapfile) * caller which points to the name of the file containing * the symbol table to be used. **************************************************************************/ - -static char * FindSymbolFile() - +static char *FindSymbolFile(void) { - auto char *file = (char *) 0, + auto char *file = NULL, **mf = system_maps; - auto struct utsname utsname; - static char symfile[100]; + static char mysymfile[100]; + auto FILE *sym_file = NULL; + BEGINfunc - auto FILE *sym_file = (FILE *) 0; - - if ( uname(&utsname) < 0 ) - { - Syslog(LOG_ERR, "Cannot get kernel version information."); + if(uname(&utsname) < 0) { + imklogLogIntMsg(LOG_ERR, "Cannot get kernel version information."); return(0); } - if ( debugging ) - fputs("Searching for symbol map.\n", stderr); + dbgprintf("Searching for symbol map.\n"); - for (mf = system_maps; *mf != (char *) 0 && file == (char *) 0; ++mf) - { - - sprintf (symfile, "%s-%s", *mf, utsname.release); - if ( debugging ) - fprintf(stderr, "Trying %s.\n", symfile); - if ( (sym_file = fopen(symfile, "r")) != (FILE *) 0 ) { - if (CheckMapVersion(symfile) == 1) - file = symfile; + for(mf = system_maps; *mf != NULL && file == NULL; ++mf) { + snprintf(mysymfile, sizeof(mysymfile), "%s-%s", *mf, utsname.release); + dbgprintf("Trying %s.\n", mysymfile); + if((sym_file = fopen(mysymfile, "r")) != NULL) { + if(CheckMapVersion(mysymfile) == 1) + file = mysymfile; fclose(sym_file); } - if (sym_file == (FILE *) 0 || file == (char *) 0) { - sprintf (symfile, "%s", *mf); - if ( debugging ) - fprintf(stderr, "Trying %s.\n", symfile); - if ( (sym_file = fopen(symfile, "r")) != (FILE *) 0 ) { - if (CheckMapVersion(symfile) == 1) - file = symfile; + if(sym_file == NULL || file == NULL) { + sprintf (mysymfile, "%s", *mf); + dbgprintf("Trying %s.\n", mysymfile); + if((sym_file = fopen(mysymfile, "r")) != NULL ) { + if (CheckMapVersion(mysymfile) == 1) + file = mysymfile; fclose(sym_file); } } - } - /* - * At this stage of the game we are at the end of the symbol - * tables. - */ - if ( debugging ) - fprintf(stderr, "End of search list encountered.\n"); + /* At this stage of the game we are at the end of the symbol tables. */ + dbgprintf("End of search list encountered.\n"); + ENDfunc return(file); } @@ -408,22 +367,14 @@ static char * FindSymbolFile() * 1:-> The executing kernel is of the same version * as the version string. **************************************************************************/ - -static int CheckVersion(version) - - char *version; - - +static int CheckVersion(char *version) { auto int vnum, major, minor, patch; - -#ifndef TESTING int kvnum; auto struct utsname utsname; -#endif static char *prefix = { "Version_" }; @@ -437,8 +388,7 @@ static int CheckVersion(version) return(0); - /* - * Since the symbol looks like a kernel version we can start + /* Since the symbol looks like a kernel version we can start * things out by decoding the version string into its component * parts. */ @@ -446,34 +396,24 @@ static int CheckVersion(version) patch = vnum & 0x000000FF; minor = (vnum >> 8) & 0x000000FF; major = (vnum >> 16) & 0x000000FF; - if ( debugging ) - fprintf(stderr, "Version string = %s, Major = %d, " \ - "Minor = %d, Patch = %d.\n", version + - strlen(prefix), major, minor, \ - patch); + dbgprintf("Version string = %s, Major = %d, Minor = %d, Patch = %d.\n", version + + strlen(prefix), major, minor, patch); sprintf(vstring, "%d.%d.%d", major, minor, patch); -#ifndef TESTING - /* - * We should now have the version string in the vstring variable in + /* We should now have the version string in the vstring variable in * the same format that it is stored in by the kernel. We now * ask the kernel for its version information and compare the two * values to determine if our system map matches the kernel * version level. */ - if ( uname(&utsname) < 0 ) - { - Syslog(LOG_ERR, "Cannot get kernel version information."); + if ( uname(&utsname) < 0 ) { + imklogLogIntMsg(LOG_ERR, "Cannot get kernel version information."); return(0); } - if ( debugging ) - fprintf(stderr, "Comparing kernel %s with symbol table %s.\n",\ - utsname.release, vstring); - - if ( sscanf (utsname.release, "%d.%d.%d", &major, &minor, &patch) < 3 ) - { - Syslog(LOG_ERR, "Kernel send bogus release string `%s'.", - utsname.release); + dbgprintf("Comparing kernel %s with symbol table %s.\n", utsname.release, vstring); + + if ( sscanf (utsname.release, "%d.%d.%d", &major, &minor, &patch) < 3 ) { + imklogLogIntMsg(LOG_ERR, "Kernel send bogus release string `%s'.", utsname.release); return(0); } @@ -485,7 +425,6 @@ static int CheckVersion(version) return(-1); /* Success. */ -#endif return(1); } @@ -514,12 +453,7 @@ static int CheckVersion(version) * 1:-> The executing kernel is of the same version * as the version of the map file. **************************************************************************/ - -static int CheckMapVersion(fname) - - char *fname; - - +static int CheckMapVersion(char *fname) { int version; FILE *sym_file; @@ -527,48 +461,36 @@ static int CheckMapVersion(fname) auto char type, sym[512]; - if ( (sym_file = fopen(fname, "r")) != (FILE *) 0 ) { + if ( (sym_file = fopen(fname, "r")) != NULL ) { /* * At this point a map file was successfully opened. We * now need to search this file and look for version * information. */ - Syslog(LOG_INFO, "Inspecting %s", fname); + imklogLogIntMsg(LOG_INFO, "Inspecting %s", fname); version = 0; - while ( !feof(sym_file) && (version == 0) ) - { - if ( fscanf(sym_file, "%lx %c %s\n", &address, \ - &type, sym) != 3 ) - { - Syslog(LOG_ERR, "Error in symbol table input (#2)."); + while ( !feof(sym_file) && (version == 0) ) { + if ( fscanf(sym_file, "%lx %c %s\n", &address, &type, sym) != 3 ) { + imklogLogIntMsg(LOG_ERR, "Error in symbol table input (#2)."); fclose(sym_file); return(0); } - if ( VERBOSE_DEBUGGING && debugging ) - fprintf(stderr, "Address: %lx, Type: %c, " \ - "Symbol: %s\n", address, type, sym); - + if(dbgPrintSymbols) + dbgprintf("Address: %lx, Type: %c, Symbol: %s\n", address, type, sym); version = CheckVersion(sym); } fclose(sym_file); - switch ( version ) - { + switch ( version ) { case -1: - Syslog(LOG_ERR, "Symbol table has incorrect " \ - "version number.\n"); + imklogLogIntMsg(LOG_ERR, "Symbol table has incorrect version number.\n"); break; - case 0: - if ( debugging ) - fprintf(stderr, "No version information " \ - "found.\n"); + dbgprintf("No version information found.\n"); break; case 1: - if ( debugging ) - fprintf(stderr, "Found table with " \ - "matching version number.\n"); + dbgprintf("Found table with matching version number.\n"); break; } @@ -592,24 +514,17 @@ static int CheckMapVersion(fname) * A boolean value is assumed. True if the addition is * successful. False if not. **************************************************************************/ - -static int AddSymbol(address, symbol) - - unsigned long address; - - char *symbol; - +static int AddSymbol(unsigned long address, char *symbol) { /* Allocate the the symbol table entry. */ - sym_array = (struct sym_table *) realloc(sym_array, (num_syms+1) * \ + sym_array = (struct sym_table *) realloc(sym_array, (num_syms+1) * sizeof(struct sym_table)); if ( sym_array == (struct sym_table *) 0 ) return(0); /* Then the space for the symbol. */ - sym_array[num_syms].name = (char *) malloc(strlen(symbol)*sizeof(char)\ - + 1); - if ( sym_array[num_syms].name == (char *) 0 ) + sym_array[num_syms].name = (char *) malloc(strlen(symbol)*sizeof(char) + 1); + if ( sym_array[num_syms].name == NULL ) return(0); sym_array[num_syms].value = address; @@ -638,46 +553,55 @@ static int AddSymbol(address, symbol) * If a match is found the pointer to the symbolic name most * closely matching the address is returned. **************************************************************************/ - -char * LookupSymbol(value, sym) - - unsigned long value; - - struct symbol *sym; - +char * LookupSymbol(unsigned long value, struct symbol *sym) { - auto int lp; - - auto char *last; + auto int lp; + + auto char *last; + auto char *name; + + struct symbol ksym, msym; + + if (!sym_array) + return(NULL); + + last = sym_array[0].name; + ksym.offset = 0; + ksym.size = 0; + if ( value < sym_array[0].value ) + return(NULL); + + for(lp = 0; lp <= num_syms; ++lp) { + if ( sym_array[lp].value > value ) { + ksym.offset = value - sym_array[lp-1].value; + ksym.size = sym_array[lp].value - \ + sym_array[lp-1].value; + break; + } + last = sym_array[lp].name; + } - if (!sym_array) - return((char *) 0); + name = LookupModuleSymbol(value, &msym); - last = sym_array[0].name; - sym->offset = 0; - sym->size = 0; - if ( value < sym_array[0].value ) - return((char *) 0); - - for(lp= 0; lp <= num_syms; ++lp) - { - if ( sym_array[lp].value > value ) - { - sym->offset = value - sym_array[lp-1].value; - sym->size = sym_array[lp].value - \ - sym_array[lp-1].value; - return(last); - } - last = sym_array[lp].name; - } + if ( ksym.offset == 0 && msym.offset == 0 ) { + return(NULL); + } + + if ( ksym.offset == 0 || msym.offset < 0 || + (ksym.offset > 0 && ksym.offset < msym.offset) ) { + sym->offset = ksym.offset; + sym->size = ksym.size; + return(last); + } else { + sym->offset = msym.offset; + sym->size = msym.size; + return(name); + } - if ( (last = LookupModuleSymbol(value, sym)) != (char *) 0 ) - return(last); - return((char *) 0); + return(NULL); } - /************************************************************************** * Function: FreeSymbols * @@ -690,9 +614,7 @@ char * LookupSymbol(value, sym) * * Return: void **************************************************************************/ - -static void FreeSymbols() - +static void FreeSymbols(void) { auto int lp; @@ -726,25 +648,19 @@ static void FreeSymbols() * * Return: void **************************************************************************/ - -extern char * ExpandKadds(line, el) - - char *line; - - char *el; - +extern char *ExpandKadds(char *line, char *el) { auto char dlm, *kp, *sl = line, *elp = el, *symbol; - char num[15]; auto unsigned long int value; - auto struct symbol sym; + sym.offset = 0; + sym.size = 0; /* * This is as handy a place to put this as anyplace. @@ -764,8 +680,8 @@ extern char * ExpandKadds(line, el) * open for patches. */ if ( i_am_paranoid && - (strstr(line, "Oops:") != (char *) 0) && !InitMsyms() ) - Syslog(LOG_WARNING, "Cannot load kernel module symbols.\n"); + (strstr(line, "Oops:") != NULL) && !InitMsyms() ) + imklogLogIntMsg(LOG_WARNING, "Cannot load kernel module symbols.\n"); /* @@ -773,12 +689,10 @@ extern char * ExpandKadds(line, el) * messages in this line. */ if ( (num_syms == 0) || - (kp = strstr(line, "[<")) == (char *) 0 ) - { + (kp = strstr(line, "[<")) == NULL ) { #ifdef __sparc__ if (num_syms) { - /* - * On SPARC, register dumps do not have the [< >] characters in it. + /* On SPARC, register dumps do not have the [< >] characters in it. */ static struct sparc_tests { char *str; @@ -858,14 +772,12 @@ extern char * ExpandKadds(line, el) } /* Loop through and expand all kernel messages. */ - do - { + do { while ( sl < kp+1 ) *elp++ = *sl++; /* Now poised at a kernel delimiter. */ - if ( (kp = strstr(sl, ">]")) == (char *) 0 ) - { + if ( (kp = strstr(sl, ">]")) == NULL ) { strcpy(el, sl); return(el); } @@ -873,34 +785,29 @@ extern char * ExpandKadds(line, el) strncpy(num,sl+1,kp-sl-1); num[kp-sl-1] = '\0'; value = strtoul(num, (char **) 0, 16); - if ( (symbol = LookupSymbol(value, &sym)) == (char *) 0 ) + if ( (symbol = LookupSymbol(value, &sym)) == NULL ) symbol = sl; strcat(elp, symbol); elp += strlen(symbol); - if ( debugging ) - fprintf(stderr, "Symbol: %s = %lx = %s, %x/%d\n", \ - sl+1, value, \ - (sym.size==0) ? symbol+1 : symbol, \ - sym.offset, sym.size); + dbgprintf("Symbol: %s = %lx = %s, %x/%d\n", sl+1, value, + (sym.size==0) ? symbol+1 : symbol, sym.offset, sym.size); value = 2; - if ( sym.size != 0 ) - { + if ( sym.size != 0 ) { --value; ++kp; - elp += sprintf(elp, "+%x/%d", sym.offset, sym.size); + elp += sprintf(elp, "+0x%x/0x%02x", sym.offset, sym.size); } strncat(elp, kp, value); elp += value; sl = kp + value; - if ( (kp = strstr(sl, "[<")) == (char *) 0 ) + if ( (kp = strstr(sl, "[<")) == NULL ) strcat(elp, sl); } - while ( kp != (char *) 0); + while ( kp != NULL); - if ( debugging ) - fprintf(stderr, "Expanded line: %s\n", el); + dbgprintf("Expanded line: %s\n", el); return(el); } @@ -918,68 +825,9 @@ extern char * ExpandKadds(line, el) * present when resolving kernel exceptions. * Return: void **************************************************************************/ - -extern void SetParanoiaLevel(level) - - int level; - +extern void SetParanoiaLevel(int level) { i_am_paranoid = level; return; } - -/* - * Setting the -DTEST define enables the following code fragment to - * be compiled. This produces a small standalone program which will - * echo the standard input of the process to stdout while translating - * all numeric kernel addresses into their symbolic equivalent. - */ -#if defined(TEST) - -#include <stdarg.h> - -extern int main(int, char **); - - -extern int main(int argc, char *argv[]) -{ - auto char line[1024], eline[2048]; - - debugging = 1; - - - if ( !InitKsyms((char *) 0) ) - { - fputs("ksym: Error loading system map.\n", stderr); - return(1); - } - - while ( !feof(stdin) ) - { - fgets(line, sizeof(line), stdin); - if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0'; /* Trash NL char */ - memset(eline, '\0', sizeof(eline)); - ExpandKadds(line, eline); - fprintf(stdout, "%s\n", eline); - } - - - return(0); -} - -extern void Syslog(int priority, char *fmt, ...) - -{ - va_list ap; - - va_start(ap, fmt); - fprintf(stdout, "Pr: %d, ", priority); - vfprintf(stdout, fmt, ap); - va_end(ap); - fputc('\n', stdout); - - return; -} -#endif -#endif /* #ifdef FEATURE_KLOGD */ diff --git a/plugins/imklog/ksym_mod.c b/plugins/imklog/ksym_mod.c new file mode 100644 index 00000000..6e48e89e --- /dev/null +++ b/plugins/imklog/ksym_mod.c @@ -0,0 +1,482 @@ +/* + * ksym_mod.c - functions for building symbol lookup tables for klogd + * Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> + * Copyright (c) 1996 Enjellic Systems Development + * Copyright (c) 1998-2007 Martin Schulze <joey@infodrom.org> + * Copyright (C) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * 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. +*/ + +/* + * This file implements functions which are useful for building + * a symbol lookup table based on the in kernel symbol table + * maintained by the Linux kernel. + * + * Proper logging of kernel panics generated by loadable modules + * tends to be difficult. Since the modules are loaded dynamically + * their addresses are not known at kernel load time. A general + * protection fault (Oops) cannot be properly deciphered with + * classic methods using the static symbol map produced at link time. + * + * One solution to this problem is to have klogd attempt to translate + * addresses from module when the fault occurs. By referencing the + * the kernel symbol table proper resolution of these symbols is made + * possible. + * + * At least that is the plan. + * + * Wed Aug 21 09:20:09 CDT 1996: Dr. Wettstein + * The situation where no module support has been compiled into a + * kernel is now detected. An informative message is output indicating + * that the kernel has no loadable module support whenever kernel + * module symbols are loaded. + * + * An informative message is printed indicating the number of kernel + * modules and the number of symbols loaded from these modules. + * + * Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman + * Some more glibc patches made by <mdorman@debian.org>. + * + * Sat Jan 10 15:00:18 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Fixed problem with klogd not being able to be built on a kernel + * newer than 2.1.18. It was caused by modified structures + * inside the kernel that were included. I have worked in a + * patch from Alessandro Suardi <asuardi@uninetcom.it>. + * + * Sun Jan 25 20:57:34 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Another patch for Linux/alpha by Christopher C Chimelis + * <chris@classnet.med.miami.edu>. + * + * Thu Mar 19 23:39:29 CET 1998: Manuel Rodrigues <pmanuel@cindy.fe.up.pt> + * Changed lseek() to llseek() in order to support > 2GB address + * space which provided by kernels > 2.1.70. + * + * Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Removed <sys/module.h> as it's no longer part of recent glibc + * versions. Added prototyp for llseek() which has been + * forgotton in <unistd.h> from glibc. Added more log + * information if problems occurred while reading a system map + * file, by submission from Mark Simon Phillips <M.S.Phillips@nortel.co.uk>. + * + * Sun Jan 3 18:38:03 CET 1999: Martin Schulze <joey@infodrom.north.de> + * Corrected return value of AddModule if /dev/kmem can't be + * loaded. This will prevent klogd from segfaulting if /dev/kmem + * is not available. Patch from Topi Miettinen <tom@medialab.sonera.net>. + * + * Tue Sep 12 23:11:13 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Changed llseek() to lseek64() in order to skip a libc warning. + */ + + +/* Includes. */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#if !defined(__GLIBC__) +#include <linux/time.h> +#include <linux/module.h> +#else /* __GLIBC__ */ +#include "module.h" +#endif /* __GLIBC__ */ +#include <stdarg.h> +#include <paths.h> +#include <linux/version.h> + +#include "rsyslog.h" +#include "imklog.h" +#include "ksyms.h" + +#define KSYMS "/proc/kallsyms" + +static int num_modules = 0; +struct Module *sym_array_modules = (struct Module *) 0; + +static int have_modules = 0; + + +/* Function prototypes. */ +static void FreeModules(void); +static int AddSymbol(const char *); +struct Module *AddModule(const char *); +static int symsort(const void *, const void *); + +/* Imported from ksym.c */ +extern int num_syms; + + +/************************************************************************** + * Function: InitMsyms + * + * Purpose: This function is responsible for building a symbol + * table which can be used to resolve addresses for + * loadable modules. + * + * Arguements: Void + * + * Return: A boolean return value is assumed. + * + * A false value indicates that something went wrong. + * + * True if loading is successful. + **************************************************************************/ +extern int InitMsyms(void) +{ + + auto int rtn, + tmp; + FILE *ksyms; + char buf[128]; + char *p; + + /* Initialize the kernel module symbol table. */ + FreeModules(); + + ksyms = fopen(KSYMS, "r"); + + if ( ksyms == NULL ) { + if ( errno == ENOENT ) + imklogLogIntMsg(LOG_INFO, "No module symbols loaded - " + "kernel modules not enabled.\n"); + else + imklogLogIntMsg(LOG_ERR, "Error loading kernel symbols " \ + "- %s\n", strerror(errno)); + return(0); + } + + dbgprintf("Loading kernel module symbols - Source: %s\n", KSYMS); + + while ( fgets(buf, sizeof(buf), ksyms) != NULL ) { + if (num_syms > 0 && index(buf, '[') == NULL) + continue; + + p = index(buf, ' '); + + if ( p == NULL ) + continue; + + if ( buf[strlen(buf)-1] == '\n' ) + buf[strlen(buf)-1] = '\0'; + /* overlong lines will be ignored above */ + + AddSymbol(buf); + } + + if(ksyms != NULL) + fclose(ksyms); + + have_modules = 1; + + /* Sort the symbol tables in each module. */ + for (rtn = tmp = 0; tmp < num_modules; ++tmp) { + rtn += sym_array_modules[tmp].num_syms; + if ( sym_array_modules[tmp].num_syms < 2 ) + continue; + qsort(sym_array_modules[tmp].sym_array, \ + sym_array_modules[tmp].num_syms, \ + sizeof(struct sym_table), symsort); + } + + if ( rtn == 0 ) + imklogLogIntMsg(LOG_INFO, "No module symbols loaded."); + else + imklogLogIntMsg(LOG_INFO, "Loaded %d %s from %d module%s", rtn, \ + (rtn == 1) ? "symbol" : "symbols", \ + num_modules, (num_modules == 1) ? "." : "s."); + + return(1); +} + + +static int symsort(const void *p1, const void *p2) +{ + auto const struct sym_table *sym1 = p1, + *sym2 = p2; + + if ( sym1->value < sym2->value ) + return(-1); + if ( sym1->value == sym2->value ) + return(0); + return(1); +} + + +extern void DeinitMsyms(void) +{ + FreeModules(); +} + + +/************************************************************************** + * Function: FreeModules + * + * Purpose: This function is used to free all memory which has been + * allocated for the modules and their symbols. + * + * Arguements: None specified. + * + * Return: void + **************************************************************************/ +static void FreeModules() +{ + auto int nmods, + nsyms; + auto struct Module *mp; + + /* Check to see if the module symbol tables need to be cleared. */ + have_modules = 0; + if ( num_modules == 0 ) + return; + + if ( sym_array_modules == NULL ) + return; + + for (nmods = 0; nmods < num_modules; ++nmods) { + mp = &sym_array_modules[nmods]; + if ( mp->num_syms == 0 ) + continue; + + for (nsyms= 0; nsyms < mp->num_syms; ++nsyms) + free(mp->sym_array[nsyms].name); + free(mp->sym_array); + if ( mp->name != NULL ) + free(mp->name); + } + + free(sym_array_modules); + sym_array_modules = (struct Module *) 0; + num_modules = 0; + return; +} + + +/************************************************************************** + * Function: AddModule + * + * Purpose: This function is responsible for adding a module to + * the list of currently loaded modules. + * + * Arguments: (const char *) module + * + * module:-> The name of the module. + * + * Return: struct Module * + **************************************************************************/ + +struct Module *AddModule(module) + const char *module; +{ + struct Module *mp; + + if ( num_modules == 0 ) { + sym_array_modules = (struct Module *)malloc(sizeof(struct Module)); + + if ( sym_array_modules == NULL ) + { + imklogLogIntMsg(LOG_WARNING, "Cannot allocate Module array.\n"); + return NULL; + } + mp = sym_array_modules; + } else { + /* Allocate space for the module. */ + mp = (struct Module *) \ + realloc(sym_array_modules, \ + (num_modules+1) * sizeof(struct Module)); + + if ( mp == NULL ) + { + imklogLogIntMsg(LOG_WARNING, "Cannot allocate Module array.\n"); + return NULL; + } + + sym_array_modules = mp; + mp = &sym_array_modules[num_modules]; + } + + num_modules++; + mp->sym_array = NULL; + mp->num_syms = 0; + + if ( module != NULL ) + mp->name = strdup(module); + else + mp->name = NULL; + + return mp; +} + + +/************************************************************************** + * Function: AddSymbol + * + * Purpose: This function is responsible for adding a symbol name + * and its address to the symbol table. + * + * Arguements: const char * + * + * Return: int + * + * A boolean value is assumed. True if the addition is + * successful. False if not. + **************************************************************************/ +static int AddSymbol(line) + const char *line; +{ + char *module; + unsigned long address; + char *p; + static char *lastmodule = NULL; + struct Module *mp; + + module = index(line, '['); + + if ( module != NULL ) { + p = index(module, ']'); + if ( p != NULL ) + *p = '\0'; + p = module++; + while ( isspace(*(--p)) ) + /*SKIP*/; + *(++p) = '\0'; + } + + p = index(line, ' '); + + if ( p == NULL ) + return(0); + + *p = '\0'; + + address = strtoul(line, (char **) 0, 16); + + p += 3; + + if ( num_modules == 0 || + ( lastmodule == NULL && module != NULL ) || + ( module == NULL && lastmodule != NULL) || + ( module != NULL && strcmp(module, lastmodule))) { + mp = AddModule(module); + + if ( mp == NULL ) + return(0); + } else + mp = &sym_array_modules[num_modules-1]; + + lastmodule = mp->name; + + /* Allocate space for the symbol table entry. */ + mp->sym_array = (struct sym_table *) realloc(mp->sym_array, \ + (mp->num_syms+1) * sizeof(struct sym_table)); + + if ( mp->sym_array == (struct sym_table *) 0 ) + return(0); + + mp->sym_array[mp->num_syms].name = strdup(p); + if ( mp->sym_array[mp->num_syms].name == (char *) 0 ) + return(0); + + /* Stuff interesting information into the module. */ + mp->sym_array[mp->num_syms].value = address; + ++mp->num_syms; + + return(1); +} + + + +/************************************************************************** + * Function: LookupModuleSymbol + * + * Purpose: Find the symbol which is related to the given address from + * a kernel module. + * + * Arguements: (long int) value, (struct symbol *) sym + * + * value:-> The address to be located. + * + * sym:-> A pointer to a structure which will be + * loaded with the symbol's parameters. + * + * Return: (char *) + * + * If a match cannot be found a diagnostic string is printed. + * If a match is found the pointer to the symbolic name most + * closely matching the address is returned. + **************************************************************************/ +extern char * LookupModuleSymbol(value, sym) + unsigned long value; + struct symbol *sym; +{ + auto int nmod, + nsym; + auto struct sym_table *last; + auto struct Module *mp; + static char ret[100]; + + sym->size = 0; + sym->offset = 0; + if ( num_modules == 0 ) + return((char *) 0); + + for (nmod = 0; nmod < num_modules; ++nmod) { + mp = &sym_array_modules[nmod]; + + /* + * Run through the list of symbols in this module and + * see if the address can be resolved. + */ + for(nsym = 1, last = &mp->sym_array[0]; + nsym < mp->num_syms; + ++nsym) { + if ( mp->sym_array[nsym].value > value ) + { + if ( sym->size == 0 || + (value - last->value) < sym->offset || + ( (sym->offset == (value - last->value)) && + (mp->sym_array[nsym].value-last->value) < sym->size ) ) + { + sym->offset = value - last->value; + sym->size = mp->sym_array[nsym].value - \ + last->value; + ret[sizeof(ret)-1] = '\0'; + if ( mp->name == NULL ) + snprintf(ret, sizeof(ret)-1, + "%s", last->name); + else + snprintf(ret, sizeof(ret)-1, + "%s:%s", mp->name, last->name); + } + break; + } + last = &mp->sym_array[nsym]; + } + } + + if ( sym->size > 0 ) + return(ret); + + /* It has been a hopeless exercise. */ + return((char *) 0); +} diff --git a/plugins/imklog/ksyms.h b/plugins/imklog/ksyms.h new file mode 100644 index 00000000..b5362ff3 --- /dev/null +++ b/plugins/imklog/ksyms.h @@ -0,0 +1,37 @@ +/* ksym.h - Definitions for symbol table utilities. + * Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> + * Copyright (c) 1996 Enjellic Systems Development + * Copyright (c) 2004-7 Martin Schulze <joey@infodrom.org> + * Copyright (c) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * 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. + */ + +/* Variables, structures and type definitions static to this module. */ + +struct symbol +{ + char *name; + int size; + int offset; +}; + + +/* Function prototypes. */ +extern char * LookupSymbol(unsigned long, struct symbol *); +extern char * LookupModuleSymbol(unsigned long int, struct symbol *); diff --git a/plugins/imklog/linux.c b/plugins/imklog/linux.c new file mode 100644 index 00000000..faf20134 --- /dev/null +++ b/plugins/imklog/linux.c @@ -0,0 +1,544 @@ +/* klog for linux, based on the FreeBSD syslogd implementation. + * + * This contains OS-specific functionality to read the BSD + * kernel log. For a general overview, see head comment in + * imklog.c. + * + * This file heavily borrows from the klogd daemon provided by + * the sysklogd project. Many thanks for this piece of software. + * + * 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 <assert.h> +#include <signal.h> +#include <string.h> +#include <pthread.h> +#include "syslogd.h" +#include "cfsysline.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "imklog.h" + + +/* Includes. */ +#include <unistd.h> +#include <errno.h> +#include <sys/fcntl.h> +#include <sys/stat.h> + +#if HAVE_TIME_H +# include <time.h> +#endif + +#include <stdarg.h> +#include <paths.h> +#include "ksyms.h" + +#define __LIBRARY__ +#include <unistd.h> + + +#if !defined(__GLIBC__) +# define __NR_ksyslog __NR_syslog +_syscall3(int,ksyslog,int, type, char *, buf, int, len); +#else +#include <sys/klog.h> +#define ksyslog klogctl +#endif + + + +#ifndef _PATH_KLOG +#define _PATH_KLOG "/proc/kmsg" +#endif + +#define LOG_BUFFER_SIZE 4096 +#define LOG_LINE_LENGTH 1000 + +static int kmsg; +static char log_buffer[LOG_BUFFER_SIZE]; + +static enum LOGSRC {none, proc, kernel} logsrc; + + +/* Function prototypes. */ +extern int ksyslog(int type, char *buf, int len); + + +static void CloseLogSrc(void) +{ + /* Turn on logging of messages to console, but only if we had the -c + * option -- rgerhards, 2007-08-01 + */ + if (console_log_level != -1) + ksyslog(7, NULL, 0); + + /* Shutdown the log sources. */ + switch ( logsrc ) + { + case kernel: + ksyslog(0, 0, 0); + imklogLogIntMsg(LOG_INFO, "Kernel logging (ksyslog) stopped."); + break; + case proc: + close(kmsg); + imklogLogIntMsg(LOG_INFO, "Kernel logging (proc) stopped."); + break; + case none: + break; + } + + return; +} + + +static enum LOGSRC GetKernelLogSrc(void) +{ + auto struct stat sb; + + /* Set level of kernel console messaging.. */ + if ( (console_log_level != -1) && + (ksyslog(8, NULL, console_log_level) < 0) && + (errno == EINVAL) ) + { + /* + * An invalid arguement error probably indicates that + * a pre-0.14 kernel is being run. At this point we + * issue an error message and simply shut-off console + * logging completely. + */ + imklogLogIntMsg(LOG_WARNING, "Cannot set console log level - disabling " + "console output."); + } + + /* + * First do a stat to determine whether or not the proc based + * file system is available to get kernel messages from. + */ + if ( use_syscall || + ((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT)) ) + { + /* Initialize kernel logging. */ + ksyslog(1, NULL, 0); + imklogLogIntMsg(LOG_INFO, "imklog %s, log source = ksyslog " + "started.", VERSION); + return(kernel); + } + + if ( (kmsg = open(_PATH_KLOG, O_RDONLY)) < 0 ) + { + char sz[512]; + snprintf(sz, sizeof(sz), "imklog: Cannot open proc file system, %d - %s.\n", errno, strerror(errno)); + logmsgInternal(LOG_SYSLOG|LOG_ERR, sz, ADDDATE); + ksyslog(7, NULL, 0); /* TODO: check this, implement more */ + return(none); + } + + imklogLogIntMsg(LOG_INFO, "imklog %s, log source = %s started.", VERSION, _PATH_KLOG); + return(proc); +} + + +/* Copy characters from ptr to line until a char in the delim + * string is encountered or until min( space, len ) chars have + * been copied. + * + * Returns the actual number of chars copied. + */ +static int copyin( uchar *line, int space, + const char *ptr, int len, + const char *delim ) +{ + auto int i; + auto int count; + + count = len < space ? len : space; + + for(i=0; i<count && !strchr(delim, *ptr); i++ ) { + *line++ = *ptr++; + } + + return(i); +} + +/* + * Messages are separated by "\n". Messages longer than + * LOG_LINE_LENGTH are broken up. + * + * Kernel symbols show up in the input buffer as : "[<aaaaaa>]", + * where "aaaaaa" is the address. These are replaced with + * "[symbolname+offset/size]" in the output line - symbolname, + * offset, and size come from the kernel symbol table. + * + * If a kernel symbol happens to fall at the end of a message close + * in length to LOG_LINE_LENGTH, the symbol will not be expanded. + * (This should never happen, since the kernel should never generate + * messages that long. + * + * To preserve the original addresses, lines containing kernel symbols + * are output twice. Once with the symbols converted and again with the + * original text. Just in case somebody wants to run their own Oops + * analysis on the syslog, e.g. ksymoops. + */ +static void LogLine(char *ptr, int len) +{ + enum parse_state_enum { + PARSING_TEXT, + PARSING_SYMSTART, /* at < */ + PARSING_SYMBOL, + PARSING_SYMEND /* at ] */ + }; + + static uchar line_buff[LOG_LINE_LENGTH]; + + static uchar *line =line_buff; + static enum parse_state_enum parse_state = PARSING_TEXT; + static int space = sizeof(line_buff)-1; + + static uchar *sym_start; /* points at the '<' of a symbol */ + + auto int delta = 0; /* number of chars copied */ + auto int symbols_expanded = 0; /* 1 if symbols were expanded */ + auto int skip_symbol_lookup = 0; /* skip symbol lookup on this pass */ + auto char *save_ptr = ptr; /* save start of input line */ + auto int save_len = len; /* save length at start of input line */ + + while( len > 0 ) + { + if( space == 0 ) /* line buffer is full */ + { + /* + ** Line too long. Start a new line. + */ + *line = 0; /* force null terminator */ + + //dbgprintf("Line buffer full:\n"); + //dbgprintf("\tLine: %s\n", line); + + Syslog(LOG_INFO, line_buff); + line = line_buff; + space = sizeof(line_buff)-1; + parse_state = PARSING_TEXT; + symbols_expanded = 0; + skip_symbol_lookup = 0; + save_ptr = ptr; + save_len = len; + } + + switch( parse_state ) + { + case PARSING_TEXT: + delta = copyin(line, space, ptr, len, "\n[" ); + line += delta; + ptr += delta; + space -= delta; + len -= delta; + + if( space == 0 || len == 0 ) + { + break; /* full line_buff or end of input buffer */ + } + + if( *ptr == '\0' ) /* zero byte */ + { + ptr++; /* skip zero byte */ + space -= 1; + len -= 1; + + break; + } + + if( *ptr == '\n' ) /* newline */ + { + ptr++; /* skip newline */ + space -= 1; + len -= 1; + + *line = 0; /* force null terminator */ + Syslog(LOG_INFO, line_buff); + line = line_buff; + space = sizeof(line_buff)-1; + if (symbols_twice) { + if (symbols_expanded) { + /* reprint this line without symbol lookup */ + symbols_expanded = 0; + skip_symbol_lookup = 1; + ptr = save_ptr; + len = save_len; + } + else + { + skip_symbol_lookup = 0; + save_ptr = ptr; + save_len = len; + } + } + break; + } + if( *ptr == '[' ) /* possible kernel symbol */ + { + *line++ = *ptr++; + space -= 1; + len -= 1; + if (!skip_symbol_lookup) + parse_state = PARSING_SYMSTART; /* at < */ + break; + } + /* Now that line_buff is no longer fed to *printf as format + * string, '%'s are no longer "dangerous". + */ + break; + + case PARSING_SYMSTART: + if( *ptr != '<' ) + { + parse_state = PARSING_TEXT; /* not a symbol */ + break; + } + + /* + ** Save this character for now. If this turns out to + ** be a valid symbol, this char will be replaced later. + ** If not, we'll just leave it there. + */ + + sym_start = line; /* this will point at the '<' */ + + *line++ = *ptr++; + space -= 1; + len -= 1; + parse_state = PARSING_SYMBOL; /* symbol... */ + break; + + case PARSING_SYMBOL: + delta = copyin( line, space, ptr, len, ">\n[" ); + line += delta; + ptr += delta; + space -= delta; + len -= delta; + if( space == 0 || len == 0 ) + { + break; /* full line_buff or end of input buffer */ + } + if( *ptr != '>' ) + { + parse_state = PARSING_TEXT; + break; + } + + *line++ = *ptr++; /* copy the '>' */ + space -= 1; + len -= 1; + + parse_state = PARSING_SYMEND; + + break; + + case PARSING_SYMEND: + if( *ptr != ']' ) + { + parse_state = PARSING_TEXT; /* not a symbol */ + break; + } + + /* + ** It's really a symbol! Replace address with the + ** symbol text. + */ + { + auto int sym_space; + + unsigned long value; + auto struct symbol sym; + auto char *symbol; + + *(line-1) = 0; /* null terminate the address string */ + value = strtoul((char*)(sym_start+1), (char **) 0, 16); + *(line-1) = '>'; /* put back delim */ + + if ( !symbol_lookup || (symbol = LookupSymbol(value, &sym)) == (char *)0 ) + { + parse_state = PARSING_TEXT; + break; + } + + /* + ** verify there is room in the line buffer + */ + sym_space = space + ( line - sym_start ); + if( (unsigned) sym_space < strlen(symbol) + 30 ) /*(30 should be overkill)*/ + { + parse_state = PARSING_TEXT; /* not enough space */ + break; + } + + // TODO: sprintf!!!! + delta = sprintf( (char*) sym_start, "%s+%d/%d]", + symbol, sym.offset, sym.size ); + + space = sym_space + delta; + line = sym_start + delta; + symbols_expanded = 1; + } + ptr++; + len--; + parse_state = PARSING_TEXT; + break; + + default: /* Can't get here! */ + parse_state = PARSING_TEXT; + + } + } + + return; +} + + +static void LogKernelLine(void) +{ + auto int rdcnt; + + /* + * Zero-fill the log buffer. This should cure a multitude of + * problems with klogd logging the tail end of the message buffer + * which will contain old messages. Then read the kernel log + * messages into this fresh buffer. + */ + memset(log_buffer, '\0', sizeof(log_buffer)); + if ( (rdcnt = ksyslog(2, log_buffer, sizeof(log_buffer)-1)) < 0 ) + { + char sz[512]; + if(errno == EINTR) + return; + snprintf(sz, sizeof(sz), "imklog: Error return from sys_sycall: %d - %s\n", errno, strerror(errno)); + logmsgInternal(LOG_SYSLOG|LOG_ERR, sz, ADDDATE); + } + else + LogLine(log_buffer, rdcnt); + return; +} + + +static void LogProcLine(void) +{ + auto int rdcnt; + + /* + * Zero-fill the log buffer. This should cure a multitude of + * problems with klogd logging the tail end of the message buffer + * which will contain old messages. Then read the kernel messages + * from the message pseudo-file into this fresh buffer. + */ + memset(log_buffer, '\0', sizeof(log_buffer)); + if ( (rdcnt = read(kmsg, log_buffer, sizeof(log_buffer)-1)) < 0 ) { + if ( errno == EINTR ) + return; + imklogLogIntMsg(LOG_ERR, "Cannot read proc file system: %d - %s.", errno, strerror(errno)); + } else { + LogLine(log_buffer, rdcnt); + } + + return; +} + + +/* to be called in the module's WillRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(void) +{ + DEFiRet; + switch(logsrc) { + case kernel: + LogKernelLine(); + break; + case proc: + LogProcLine(); + break; + case none: + /* TODO: We need to handle this case here somewhat more intelligent + * This is now at least partly done - code should never reach this point + * as willRun() already checked for the "none" status -- rgerhards, 2007-12-17 + */ + pause(); + break; + } + RETiRet; +} + + +/* to be called in the module's WillRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogWillRun(void) +{ + DEFiRet; + /* Initialize this module. If that fails, we tell the engine we don't like to run */ + /* Determine where kernel logging information is to come from. */ + logsrc = GetKernelLogSrc(); + if(logsrc == none) { + iRet = RS_RET_NO_KERNEL_LOGSRC; + } else { + if (symbol_lookup) { + symbol_lookup = (InitKsyms(symfile) == 1); + symbol_lookup |= InitMsyms(); + if (symbol_lookup == 0) { + imklogLogIntMsg(LOG_WARNING, "cannot find any symbols, turning off symbol lookups"); + } + } + } + + RETiRet; +} + + +/* to be called in the module's AfterRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogAfterRun(void) +{ + DEFiRet; + /* cleanup here */ + if(logsrc != none) + CloseLogSrc(); + + DeinitKsyms(); + DeinitMsyms(); + + RETiRet; +} + + +/* provide the (system-specific) default facility for internal messages + * rgerhards, 2008-04-14 + */ +int +klogFacilIntMsg(void) +{ + return LOG_KERN; +} + + +/* vi:set ai: + */ diff --git a/plugins/imklog/module.h b/plugins/imklog/module.h new file mode 100644 index 00000000..38a26fea --- /dev/null +++ b/plugins/imklog/module.h @@ -0,0 +1,35 @@ +/* module.h - Miscellaneous module definitions + * Copyright (c) 1996 Richard Henderson <rth@tamu.edu> + * Copyright (c) 2004-7 Martin Schulze <joey@infodrom.org> + * Copyright (c) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * 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. + */ +struct sym_table +{ + unsigned long value; + char *name; +}; + +struct Module +{ + struct sym_table *sym_array; + int num_syms; + + char *name; +}; diff --git a/plugins/immark/.cvsignore b/plugins/immark/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/immark/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/immark/Makefile.am b/plugins/immark/Makefile.am new file mode 100644 index 00000000..3dc0e408 --- /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 = -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..0bc31995 --- /dev/null +++ b/plugins/immark/immark.c @@ -0,0 +1,125 @@ +/* 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 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 <assert.h> +#include <signal.h> +#include <string.h> +#include <pthread.h> +#include "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" + +MODULE_TYPE_INPUT + +/* defines */ +#define DEFAULT_MARK_PERIOD (20 * 60) + +/* Module static data */ +DEF_IMOD_STATIC_DATA +static int iMarkMessagePeriod = DEFAULT_MARK_PERIOD; + +/* 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) { + /* we do not need to handle the RS_RET_TERMINATE_NOW case any + * special because we just need to terminate. This may be different + * if a cleanup is needed. But for now, we can just use CHKiRet(). + * rgerhards, 2007-12-17 + */ + CHKiRet(thrdSleep(pThrd, iMarkMessagePeriod, 0)); /* seconds, micro seconds */ + logmsgInternal(LOG_INFO, "-- MARK --", ADDDATE|MARK); + } +finalize_it: + return iRet; +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + /* We set the global MarkInterval to what is configured here -- rgerhards, 2008-07-15 */ + MarkInterval = iMarkMessagePeriod; + if(iMarkMessagePeriod == 0) + iRet = RS_RET_NO_RUN; +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_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(omsdRegCFSLineHdlr((uchar *)"markmessageperiod", 0, eCmdHdlrInt, NULL, &iMarkMessagePeriod, STD_LOADABLE_MODULE_ID)); + 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..db98978b --- /dev/null +++ b/plugins/immark/immark.h @@ -0,0 +1,35 @@ +/* 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 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 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/imrelp/.cvsignore b/plugins/imrelp/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/imrelp/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/imrelp/Makefile.am b/plugins/imrelp/Makefile.am new file mode 100644 index 00000000..53c9322c --- /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) +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..b7308016 --- /dev/null +++ b/plugins/imrelp/imrelp.c @@ -0,0 +1,190 @@ +/* imrelp.c + * + * This is the implementation of the RELP input module. + * + * File begun on 2008-03-13 by RGerhards + * + * 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 <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 <librelp.h> +#include "rsyslog.h" +#include "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" + +MODULE_TYPE_INPUT + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(net) + +/* Module static data */ +static relpEngine_t *pRelpEngine; /* our relp engine */ + + +/* config settings */ +static int iTCPSessMax = 200; /* max number of sessions */ + + +/* ------------------------------ callbacks ------------------------------ */ +#if 0 +/* 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.isAllowedSender(net.pAllowedSenders_TCP, addr, fromHostFQDN); +} + +#endif // #if 0 + +/* 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 + */ +static relpRetVal +onSyslogRcv(uchar *pHostname, uchar __attribute__((unused)) *pIP, uchar *pMsg, size_t lenMsg) +{ + DEFiRet; + parseAndSubmitMessage((char*)pHostname, (char*)pMsg, lenMsg, MSG_PARSE_HOSTNAME, + NOFLAG, eFLOWCTL_LIGHT_DELAY); + + RETiRet; +} + + +/* ------------------------------ end callbacks ------------------------------ */ + + +static rsRetVal addListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + if(pRelpEngine == NULL) { + CHKiRet(relpEngineConstruct(&pRelpEngine)); + CHKiRet(relpEngineSetDbgprint(pRelpEngine, dbgprintf)); + CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar*) "syslog", eRelpCmdState_Required)); + CHKiRet(relpEngineSetSyslogRcv(pRelpEngine, onSyslogRcv)); + } + + CHKiRet(relpEngineAddListner(pRelpEngine, pNewVal)); + + free(pNewVal); /* we do no longer need it */ + +finalize_it: + RETiRet; +} + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + /* TODO: we must be careful to start the listener here. Currently, tcpsrv.c seems to + * do that in ConstructFinalize + */ + iRet = relpEngineRun(pRelpEngine); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first apply some config settings */ + //net.PrintAllowedSenders(2); /* TCP */ + if(pRelpEngine == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ +#if 0 + if(net.pAllowedSenders_TCP != NULL) { + net.clearAllowedSenders(net.pAllowedSenders_TCP); + net.pAllowedSenders_TCP = NULL; + } +#endif +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pRelpEngine != NULL) + iRet = relpEngineDestruct(&pRelpEngine); + + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iTCPSessMax = 200; + 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 + pRelpEngine = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpserverrun", 0, eCmdHdlrGetWord, + addListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpmaxsessions", 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/imtcp/.cvsignore b/plugins/imtcp/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/imtcp/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/imtcp/Makefile.am b/plugins/imtcp/Makefile.am new file mode 100644 index 00000000..fe43cd98 --- /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) +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..9b4d49f5 --- /dev/null +++ b/plugins/imtcp/imtcp.c @@ -0,0 +1,213 @@ +/* imtcp.c + * This is the implementation of the TCP input module. + * + * File begun on 2007-12-21 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. + */ + +#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 "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "tcpsrv.h" + +MODULE_TYPE_INPUT + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(net) + +/* Module static data */ +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ + +/* config settings */ +static int iTCPSessMax = 200; /* max number of sessions */ + + +/* 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.isAllowedSender((uchar*) "TCP", addr, fromHostFQDN); +} + + +static int* +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + ISOBJ_TYPE_assert(pSrv, tcpsrv); + return tcpsrv.create_tcp_socket(pSrv); +} + + +static int +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf) +{ + int state; + assert(pSess != NULL); + + state = recv(pSess->sock, buf, lenBuf, 0); + return state; +} + +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 ------------------------------ */ + + +static rsRetVal addTCPListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + if(pOurTcpsrv == NULL) { + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + 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)); + tcpsrv.configureTCPListen(pOurTcpsrv, (char *) pNewVal); + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); + } + +finalize_it: + RETiRet; +} + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + /* TODO: we must be careful to start the listener here. Currently, tcpsrv.c seems to + * do that in ConstructFinalize + */ + iRet = tcpsrv.Run(pOurTcpsrv); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first apply some config settings */ + net.PrintAllowedSenders(2); /* TCP */ + if(pOurTcpsrv == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((char*)"TCP"); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pOurTcpsrv != NULL) + iRet = tcpsrv.Destruct(&pOurTcpsrv); + + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); + objRelease(tcps_sess, LM_TCPSRV_FILENAME); + objRelease(tcpsrv, LM_TCPSRV_FILENAME); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iTCPSessMax = 200; + 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 + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(tcps_sess, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(tcpsrv, LM_TCPSRV_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverrun", 0, eCmdHdlrGetWord, + addTCPListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpmaxsessions", 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/imtemplate/Makefile.am b/plugins/imtemplate/Makefile.am new file mode 100644 index 00000000..a9221817 --- /dev/null +++ b/plugins/imtemplate/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imtemplate.la + +imtemplate_la_SOURCES = imtemplate.c +imtemplate_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imtemplate_la_LDFLAGS = -module -avoid-version +imtemplate_la_LIBADD = diff --git a/plugins/imtemplate/imtemplate.c b/plugins/imtemplate/imtemplate.c new file mode 100644 index 00000000..6d29c4f1 --- /dev/null +++ b/plugins/imtemplate/imtemplate.c @@ -0,0 +1,459 @@ +/* imtemplate.c + * + * This is NOT a real input module but a (copy)-template to create one. Please + * do NOT edit this file directly. Rather, copy it, together with the rest of + * the directory, to a new location ./plugins/im<yourname>, then replace + * all references to imtemplate in Makefile.am to im<yourname>. Be sure to + * fix the copyright notices to gain proper credit ;) Any derived version, + * however, needs to be placed under GPLv3 (see GPLv3 for details). If you + * do not like that policy, do not use this template or any of the header + * files. The rsyslog project greatly appreciates module contributions, so + * please consider contributing your work - even if you may think it only + * server a single very special purpose. It has turned out that at least some + * folks have similiar special purposes ;) + * + * IMPORTANT + * The comments in this file are actually the interface specification. I decided + * not to put it into a separate file as it is much simpler to keep it up to + * date when it is part of the actual template module. + * + * NAMING + * All input modules shall be named im<something>. While this is not a hard + * requirement, it helps keeping track of things. + * + * Global variables and functions should have a prefix - use as somewhat + * longer one to prevent conflicts with rsyslog itself and other modules + * (OK, hopefully I'll have some more precise advise in the future...). + * + * INCLUDE MODULE IN THE MAIN MAKE SCRIPT + * If the module shall be provided as part of rsyslog (or simply as a build aid, + * you need to add it to the main autoconf files). To do so, you need to edit + * Makefile.am and configure.ac in the main directory. Search for imtemplate + * and copy/modify the relevant code for your plugin. + * + * DEBUGGING + * While you develop your code, you may want to add + * --enable-debug --enable-rtinst + * to your ./configure settings. These enable extra run-time checks, which cost + * a lot of performance but can help detect some of the most frequently made + * bugs. These settings will also provide you with a nice stack dump if something + * goes really wrong. + * + * MORE SAMPLES + * Remember that rsyslog ships with a number of input modules (./plugins/im*). It + * is always a good idea to have a look at them before starting your own. imudp + * may be a good, relatively trivial, sample. + * + * -------------------------------------------------------------------------------- + * + * This template was cretead on 2008-02-01 by Rainer Gerhards. + * + * 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" /* this is for autotools and always must be the first include */ +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> /* do NOT remove: will soon be done by the module generation macros */ +#include "rsyslog.h" /* error codes etc... */ +#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 */ + +MODULE_TYPE_INPUT /* must be present for input modules, do not remove */ + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA /* must be present, starts static data */ + +/* Here, define whatever static data is needed. Is it suggested that static variables only are + * used (not externally visible). If you need externally visible variables, make sure you use a + * prefix in order not to conflict with other modules or rsyslogd itself (also see comment + * at file header). + */ +/* static int imtemplateWhateverVar = 0; */ + +/* config settings */ + + +/* You may add any functions that you feel are useful for your needs. No specific restrictions + * apply, but we suggest that you use the "iRet" call order, which enables you to use debug + * support for your own functions and which also makes it easy to communicate exceptions back + * to the upstream caller (rsyslog framework, for example. + * + * The function below is a sample of how one of your functions may look like. Again, the sample + * below is *not* needed to be present in order to meet the interface requirements. + * + * Be sure to use static functions (suggested) or prefixes to prevent name conflicts -- see file + * header for more information. + */ +static rsRetVal /* rsRetVal is our generic error-reporting return type */ +imtemplateMyFunc(int iMyParam) +{ + DEFiRet; /* define iRet, the return code and other plumbing */ + /* define your local variables here */ + + /* code whatever you need to code here. The "iRet" system can be helpful: + * + * CHKiRet(function(param1, param2, ...)); + * calls a function and checks if it returns RS_RET_OK. If so, work + * proceeds. If some other code is returned, the function is aborted + * and control transferred to finalize_it (which you need to define) + * + * CHKiRet_Hdlr(function(param1, param2, ...)) + * much like CHKiRet, but allows you to specify your own code that is + * executed if the function does not return RS_RET_OK, e.g.: + * CHKiRet_Hdlr(function(a, b)) { + * ... some error handling here ... + * } + * control is not transferred to finalize_it, except if you use one + * of the relevant macros (described below) + * + * FINALIZE + * immediately transfers control to finalize_it, using the current + * value of iRet, e.g. + * if(bDone) + * FINALIZE; + * + * ABORT_FINALIZE(retcode) + * just like FINALIZE, except that iRet is set to the provided error + * code before control is transferred, e.g. + * if((ptr = malloc(20)) == NULL) + * ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + * + * In order for all this to work, you need to define finalize_it, e.g. + * + * finalize_it: + * RETiRet; + * + * RETiRet does some housekeeping and then does a "return iRet" to transfer + * control back to the caller. There shall only be one function exit and + * it shall be via RETiRet, preferrably at the end of the function code. + * + */ + +finalize_it: + /* clean up anything that needs to be cleaned up if processing did not + * go well, for example: + */ + if(iRet != RS_RET_OK) { + /* cleanup, e.g. + * free(somePtr); + */ + } + + RETiRet; +} + + +/* 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. + */ +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ +static void +inputModuleCleanup(void *arg) +{ + BEGINfunc +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + + + /* your code here */ + + + +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + ENDfunc +} +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + +/* 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! + * + * So how is it terminated? When it is time to terminate, rsyslog actually cancels + * the threads. This may sound scary, but is not. There is a cancel cleanup handler + * defined (the function directly above). See comments there for specifics. + * + * runInput is always called on a single thread. If the module neees multiple threads, + * it is free to create them. HOWEVER, it must make sure that any threads created + * are killed and joined in the cancel cleanup handler. + */ +BEGINrunInput + /* define any local variables you need here */ +CODESTARTrunInput + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + pthread_cleanup_push(inputModuleCleanup, NULL); + while(1) { /* endless loop - do NOT break; out of it! */ + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + /* your code here */ + + /* All rsyslog objects (see other modules, e.g. msg.c) are available + * to your here. Some useful things are: + * + * errmsg.LogError(NO_ERRCODE, format-string, ... params ...); + * logs an error message as syslogd, just as printf, e.g. + * errmsg.LogError(NO_ERRCODE, "Error %d occured during %s", 1, "test"); + * + * There are several ways how a message can be enqueued. This part of the + * interface is currently underspecified. Have a look at the function definitions + * in syslogd.c (sorry, folks...). + * + * If you received a full syslog message that must be decoded by a message + * parser, parseAndSubmitMessage() is the way to go. It's not just a funny name + * but also a quite some legacy. Consequently, its interface is, ummm, not + * well designed. + * parseAndSubmitMessage((char*)fromHost, (char*) pRcvBuf, lenRcvd, bParseHost); + * fromHost + * is the host that we received the message from (a string) + * pRcvBuf + * is the received (to-be-decoded) message + * lenRcvd + * is the length of the received message. Please note that pRcvBuf is + * NOT a standard C-string. Most importantly it is NOT expected to be + * \0-terminated. Thus the lenght is vitally imporant (if it is wrong, + * rsyslogd will probably segfault). + * bParseHost + * is a boolean (0-no, 1-yes). It tells the parser whether or not + * a hostname should be parsed from the message. This is important + * for sources that are known not to provide a hostname. + * Use define MSG_PARSE_HOSTNAME and MSG_DONT_PARSE_HOSTNAME + * + * Another, more elaborate, way is to create the message object ourselves and + * pass it to the rule engine. That way is more appropriate if the message + * does not need to be parsed, for example when reading text (log) files. In that way, + * we can set the message properties as of our liking. This is how it works: + * + msg_t *pMsg; + CHKiRet(msgConstruct(&pMsg)); + MsgSetUxTradMsg(pMsg, msg); + MsgSetRawMsg(pMsg, msg); + MsgSetHOSTNAME(pMsg, LocalHostName); + MsgSetTAG(pMsg, "rsyslogd:"); + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + pMsg->bParseHOSTNAME = 0; + getCurrTime(&(pMsg->tTIMESTAMP)); / * use the current time! * / + flags |= INTERNAL_MSG; + logmsg(pMsg, flags); / * some time, CHKiRet() will work here, too [today NOT!] * / + * + * Note that UxTradMsg is a wild construct. For the time being, set it to + * the raw message text. I am hard thinking at dropping that beast at all... + * + * This example probably does not set all message properties (but the ones + * that are of practical importance). If you need all, check msg.h. Use + * method access functions whereever possible, unfortunately not all structure + * members are currently exposed in that clean way - so you sometimes need + * to access them directly (it goes without saying that we will fix that + * over time ;)). + */ + + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + } + /*NOTREACHED*/ + + pthread_cleanup_pop(0); /* just for completeness, but never called... */ + RETiRet; /* use it to make sure the housekeeping is done! */ +ENDrunInput + /* 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. If you do not return anything + * specific, RS_RET_OK is automatically returned (as in all functions). + */ +BEGINwillRun + /* place any variables needed here */ +CODESTARTwillRun + + /* ... your code here ... */ + + /* Just to give you an idea, here are some samples (from the actual imudp module: + * + if(udpLstnSocks == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + + if((pRcvBuf = malloc(MAXLINE * sizeof(char))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + * + */ +finalize_it: +ENDwillRun + + +/* This function is called by the framework after runInput() has been terminated. It + * shall free any resources and prepare the module for unload. + * + * So it is important that runInput() keeps track of what needs to be cleaned up. + * Objects to think about are files (must be closed), network connections, threads (must + * be stopped and joined) and memory (must be freed). Of course, there are a myriad + * of other things, so use your own judgement what you need to do. + * + * Another important chore of this function is to persist whatever state the module + * needs to persist. Unfortunately, there is currently no standard way of doing that. + * Future version of the module interface will probably support it, but that doesn't + * help you right at the moment. In general, it is suggested that anything that needs + * to be persisted is saved in a file, whose name and location is passed in by a + * module-specific config directive. + */ +BEGINafterRun + /* place any variables needed here */ +CODESTARTafterRun + + /* ... do cleanup here ... */ + + /* if you have a string config variable, remember to free its content: + * + if(pszStr != NULL) { + free(pszStr); + pszStr = NULL; + } + */ +ENDafterRun + + +/* 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 +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_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; + + /* if you have string variables in you config settings, you need to do this: + if(pszStr != NULL) { + free(pszStr); + pszStr = NULL; + } + * Note that it is vitally important that the pointer is set to NULL, because + * otherwise the framework handler will try to free it a second time when + * a new value is set! + */ + + + /* ... your code here ... */ + + + RETiRet; +} + + +/* 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 = 1; /* interface spec version this module is written to (currently always 1) */ +CODEmodInit_QueryRegCFSLineHdlr + /* register config file handlers + * For details, see cfsysline.c/.h. The config file is automatically handled. In general, + * a pointer to a variable receiving the value and the config directive is to be supplied. + * A custom function pointer can only be provided, which then is called when the config + * directive appears. Limit this to cases where it is absolutely necessary. The + * STD_LOADABLE_MODULE_ID is a value that identifies the module. It is use to automatically + * unregister the module's config file handlers upon module unload. Do NOT use any other + * value for this parameter! Available Syntaxes (supported types) can be seen in cfsysline.h, + * the ecslCmdHdrlType enum has all that are currently defined. + * + * Config file directives should always be along the lines of + * + * $Input<moduleobject>ObjObjName + * + * An example would be $InputImtemplateRetriesMax. This is currently not enforced, + * but when we get to our new config file format and reader, this becomes quite + * important. + * + * Please note that config directives must be provided in lower case. The engine + * makes the mapping (what currently means case-insensitive comparison). The dollar + * sign is NOT part of the directive and thus not specified. + * + * Some samples: + * + * A hypothetical integer variable: + * CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputimtemplatemessagenumber", 0, eCmdHdlrInt, + NULL, &intVariable, STD_LOADABLE_MODULE_ID)); + * + * and a hypothetical string variable: + * CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputimtemplatemessagetext", 0, eCmdHdlrGetWord, + * NULL, &pszBindAddr, STD_LOADABLE_MODULE_ID)); + */ + + /* whenever config variables exist, they should be resettable via $ResetConfigVariables. + * The following line adds our handler for that. Note that if you do not have any config + * variables at all (unlikely, I think...), you can remove this handler. + */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + + /* ... do whatever else you need to do, but keep it brief ... */ + +ENDmodInit +/* + * vim:set ai: + */ diff --git a/plugins/imudp/.cvsignore b/plugins/imudp/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/imudp/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/imudp/Makefile.am b/plugins/imudp/Makefile.am new file mode 100644 index 00000000..53fdad16 --- /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) +imudp_la_LDFLAGS = -module -avoid-version +imudp_la_LIBADD = diff --git a/plugins/imudp/imudp.c b/plugins/imudp/imudp.c new file mode 100644 index 00000000..3cdd8dd6 --- /dev/null +++ b/plugins/imudp/imudp.c @@ -0,0 +1,308 @@ +/* imudp.c + * This is the implementation of the UDP input module. + * + * 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) + * + * 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 <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#include "rsyslog.h" +#include "syslogd.h" +#include "net.h" +#include "cfsysline.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" + +MODULE_TYPE_INPUT + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) + +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 int *udpLstnSocks = NULL; /* Internet datagram sockets, first element is nbr of elements + * read-only after init(), but beware of restart! */ +static uchar *pszBindAddr = NULL; /* IP to bind socket to */ +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 + */ + +/* config settings */ + + +/* This function is called when a new listener shall be added. It takes + * the configured parameters, tries to bind the socket and, if that + * succeeds, adds it to the list of existing listen sockets. + * rgerhards, 2007-12-27 + */ +static rsRetVal addListner(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + uchar *bindAddr; + int *newSocks; + int *tmpSocks; + int iSrc, iDst; + + /* 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(pszBindAddr == NULL) + bindAddr = NULL; + else if(pszBindAddr[0] == '*' && pszBindAddr[1] == '\0') + bindAddr = NULL; + else + bindAddr = pszBindAddr; + + dbgprintf("Trying to open syslog UDP ports at %s:%s.\n", + (bindAddr == NULL) ? (uchar*)"*" : bindAddr, pNewVal); + + newSocks = net.create_udp_socket(bindAddr, (pNewVal == NULL || *pNewVal == '\0') ? (uchar*) "514" : pNewVal, 1); + if(newSocks != NULL) { + /* we now need to add the new sockets to the existing set */ + if(udpLstnSocks == NULL) { + /* esay, we can just replace it */ + udpLstnSocks = newSocks; + } else { + /* we need to add them */ + if((tmpSocks = malloc(sizeof(int) * 1 + newSocks[0] + udpLstnSocks[0])) == NULL) { + dbgprintf("out of memory trying to allocate udp listen socket array\n"); + /* in this case, we discard the new sockets but continue with what we + * already have + */ + free(newSocks); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } else { + /* ready to copy */ + iDst = 1; + for(iSrc = 1 ; iSrc <= udpLstnSocks[0] ; ++iSrc) + tmpSocks[iDst++] = udpLstnSocks[iSrc]; + for(iSrc = 1 ; iSrc <= newSocks[0] ; ++iSrc) + tmpSocks[iDst++] = newSocks[iSrc]; + tmpSocks[0] = udpLstnSocks[0] + newSocks[0]; + free(newSocks); + free(udpLstnSocks); + udpLstnSocks = tmpSocks; + } + } + } + +finalize_it: + free(pNewVal); /* in any case, this is no longer needed */ + + RETiRet; +} + + +/* This function is called to gather input. + */ +BEGINrunInput + int maxfds; + int nfds; + int i; + fd_set readfds; + struct sockaddr_storage frominet; + socklen_t socklen; + uchar fromHost[NI_MAXHOST]; + uchar fromHostFQDN[NI_MAXHOST]; + ssize_t l; +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) { + /* 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 (&readfds); + + /* Add the UDP listen sockets to the list of read descriptors. + */ + if(udpLstnSocks != NULL) { + for (i = 0; i < *udpLstnSocks; i++) { + if (udpLstnSocks[i+1] != -1) { + if(Debug) + net.debugListenInfo(udpLstnSocks[i+1], "UDP"); + FD_SET(udpLstnSocks[i+1], &readfds); + if(udpLstnSocks[i+1]>maxfds) maxfds=udpLstnSocks[i+1]; + } + } + } + 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(udpLstnSocks != NULL) { + for (i = 0; nfds && i < *udpLstnSocks; i++) { + if (FD_ISSET(udpLstnSocks[i+1], &readfds)) { + socklen = sizeof(frominet); + l = recvfrom(udpLstnSocks[i+1], (char*) pRcvBuf, MAXLINE - 1, 0, + (struct sockaddr *)&frominet, &socklen); + if (l > 0) { + if(net.cvthname(&frominet, fromHost, fromHostFQDN) == RS_RET_OK) { + dbgprintf("Message from inetd socket: #%d, host: %s\n", + udpLstnSocks[i+1], fromHost); + /* 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). + * rgerhards, 2005-09-26 + */ + if(net.isAllowedSender((uchar*) "UDP", + (struct sockaddr *)&frominet, (char*)fromHostFQDN)) { + parseAndSubmitMessage((char*)fromHost, (char*) pRcvBuf, l, + MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_NO_DELAY); + } else { + dbgprintf("%s is not an allowed sender\n", (char*)fromHostFQDN); + if(option_DisallowWarning) { + time_t tt; + + time(&tt); + if(tt > ttLastDiscard + 60) { + ttLastDiscard = tt; + errmsg.LogError(NO_ERRCODE, + "UDP message from disallowed sender %s discarded", + (char*)fromHost); + } + } + } + } + } else if (l < 0 && errno != EINTR && errno != EAGAIN) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("INET socket error: %d = %s.\n", errno, errStr); + errmsg.LogError(NO_ERRCODE, "recvfrom inet"); + /* should be harmless */ + sleep(1); + } + --nfds; /* indicate we have processed one */ + } + } + } + } + + return iRet; +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + net.PrintAllowedSenders(1); /* UDP */ + + /* if we could not set up any listners, there is no point in running... */ + if(udpLstnSocks == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + + if((pRcvBuf = malloc(MAXLINE * sizeof(char))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"UDP"); + if(udpLstnSocks != NULL) { + net.closeUDPListenSockets(udpLstnSocks); + udpLstnSocks = NULL; + } + if(pRcvBuf != NULL) { + free(pRcvBuf); + pRcvBuf = NULL; + } +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + if(pszBindAddr != NULL) { + free(pszBindAddr); + pszBindAddr = NULL; + } + if(udpLstnSocks != NULL) { + net.closeUDPListenSockets(udpLstnSocks); + udpLstnSocks = 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(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"udpserverrun", 0, eCmdHdlrGetWord, + addListner, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"udpserveraddress", 0, eCmdHdlrGetWord, + NULL, &pszBindAddr, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/imuxsock/.cvsignore b/plugins/imuxsock/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/imuxsock/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/imuxsock/Makefile.am b/plugins/imuxsock/Makefile.am new file mode 100644 index 00000000..e165bb7d --- /dev/null +++ b/plugins/imuxsock/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imuxsock.la + +imuxsock_la_SOURCES = imuxsock.c +imuxsock_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imuxsock_la_LDFLAGS = -module -avoid-version +imuxsock_la_LIBADD = diff --git a/plugins/imuxsock/imuxsock.c b/plugins/imuxsock/imuxsock.c new file mode 100644 index 00000000..60ccaffb --- /dev/null +++ b/plugins/imuxsock/imuxsock.c @@ -0,0 +1,351 @@ +/* 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 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 <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/un.h> +#include "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" + +MODULE_TYPE_INPUT + +/* defines */ +#define MAXFUNIX 20 +#ifndef _PATH_LOG +#ifdef BSD +#define _PATH_LOG "/var/run/log" +#else +#define _PATH_LOG "/dev/log" +#endif +#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) + +static int startIndexUxLocalSockets; /* process funix from that index on (used to + * suppress local logging. rgerhards 2005-08-01 + * read-only after startup + */ +static int funixParseHost[MAXFUNIX] = { 0, }; /* should parser parse host name? read-only after startup */ +static int funixFlags[MAXFUNIX] = { ADDDATE, }; /* should parser parse host name? read-only after startup */ +static uchar *funixn[MAXFUNIX] = { (uchar*) _PATH_LOG }; /* read-only after startup */ +static int funix[MAXFUNIX] = { -1, }; /* read-only after startup */ +static int nfunix = 1; /* number of Unix sockets open / read-only after startup */ + +/* config settings */ +static int bOmitLocalLogging = 0; +static uchar *pLogSockName = NULL; +static int bIgnoreTimestamp = 1; /* ignore timestamps present in the incoming message? */ + + +/* set the timestamp ignore / not ignore option for the system + * log socket. This must be done separtely, as it is not added via a command + * but present by default. -- rgerhards, 2008-03-06 + */ +static rsRetVal setSystemLogTimestampIgnore(void __attribute__((unused)) *pVal, int iNewVal) +{ + DEFiRet; + funixFlags[0] = iNewVal ? ADDDATE : NOFLAG; + 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 + */ +static rsRetVal addLstnSocketName(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + char errStr[1024]; + + if(nfunix < MAXFUNIX) { + if(*pNewVal == ':') { + funixParseHost[nfunix] = 1; + } + else { + funixParseHost[nfunix] = 0; + } + funixFlags[nfunix] = bIgnoreTimestamp ? ADDDATE : NOFLAG; + funixn[nfunix++] = pNewVal; + } + else { + snprintf(errStr, sizeof(errStr), "rsyslogd: Out of unix socket name descriptors, ignoring %s\n", + pNewVal); + logmsgInternal(LOG_SYSLOG|LOG_ERR, errStr, ADDDATE); + } + + return RS_RET_OK; +} + +/* free the funixn[] socket names - needed as cleanup on several places + * note that nfunix is NOT reset! funixn[0] is never freed, as it comes from + * the constant memory pool - and if not, it is freeed via some other pointer. + */ +static rsRetVal discardFunixn(void) +{ + int i; + + for (i = 1; i < nfunix; i++) { + if(funixn[i] != NULL) { + free(funixn[i]); + funixn[i] = NULL; + } + } + + return RS_RET_OK; +} + + +static int create_unix_socket(const char *path) +{ + struct sockaddr_un sunx; + int fd; + char line[MAXLINE +1]; + + if (path[0] == '\0') + return -1; + + unlink(path); + + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_UNIX; + (void) strncpy(sunx.sun_path, path, sizeof(sunx.sun_path)); + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0 || bind(fd, (struct sockaddr *) &sunx, + SUN_LEN(&sunx)) < 0 || + chmod(path, 0666) < 0) { + snprintf(line, sizeof(line), "cannot create %s", path); + errmsg.LogError(NO_ERRCODE, "%s", line); + dbgprintf("cannot create %s (%d).\n", path, errno); + close(fd); + return -1; + } + return fd; +} + + +/* This function receives data from a socket indicated to be ready + * to receive and submits the message received for processing. + * rgerhards, 2007-12-20 + */ +static rsRetVal readSocket(int fd, int bParseHost, int flags) +{ + DEFiRet; + int iRcvd; + char line[MAXLINE +1]; + + iRcvd = recv(fd, line, MAXLINE - 1, 0); + dbgprintf("Message from UNIX socket: #%d\n", fd); + if (iRcvd > 0) { + parseAndSubmitMessage((char*)LocalHostName, line, iRcvd, bParseHost, flags, eFLOWCTL_NO_DELAY); + } else if (iRcvd < 0 && errno != EINTR) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("UNIX socket error: %d = %s.\n", errno, errStr); + errmsg.LogError(NO_ERRCODE, "recvfrom UNIX"); + } + + RETiRet; +} + + +/* This function is called to gather input. + */ +BEGINrunInput + int maxfds; + int nfds; + int i; + int fd; + fd_set readfds; +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) { + /* 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 (&readfds); + /* Copy master connections */ + for (i = startIndexUxLocalSockets; i < nfunix; i++) { + if (funix[i] != -1) { + FD_SET(funix[i], &readfds); + if (funix[i]>maxfds) maxfds=funix[i]; + } + } + + if(Debug) { + dbgprintf("--------imuxsock 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); + + for (i = 0; i < nfunix && nfds > 0; i++) { + if ((fd = funix[i]) != -1 && FD_ISSET(fd, &readfds)) { + readSocket(fd, funixParseHost[i], funixFlags[i]); + --nfds; /* indicate we have processed one */ + } + } + } + + RETiRet; +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + register int i; + + /* first apply some config settings */ + startIndexUxLocalSockets = bOmitLocalLogging ? 1 : 0; + if(pLogSockName != NULL) + funixn[0] = pLogSockName; + + /* initialize and return if will run or not */ + for (i = startIndexUxLocalSockets ; i < nfunix ; i++) { + if ((funix[i] = create_unix_socket((char*) funixn[i])) != -1) + dbgprintf("Opened UNIX socket '%s' (fd %d).\n", funixn[i], funix[i]); + } + + RETiRet; +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + int i; + /* do cleanup here */ + /* Close the UNIX sockets. */ + for (i = 0; i < nfunix; i++) + if (funix[i] != -1) + close(funix[i]); + + /* Clean-up files. */ + for (i = 0; i < nfunix; i++) + if (funixn[i] && funix[i] != -1) + unlink((char*) funixn[i]); + /* free no longer needed string */ + if(pLogSockName != NULL) + free(pLogSockName); + + discardFunixn(); + nfunix = 1; +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + bOmitLocalLogging = 0; + if(pLogSockName != NULL) { + free(pLogSockName); + pLogSockName = NULL; + } + + discardFunixn(); + nfunix = 1; + bIgnoreTimestamp = 1; + + 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)); + + /* initialize funixn[] array */ + for(i = 1 ; i < MAXFUNIX ; ++i) { + funixn[i] = NULL; + funix[i] = -1; + } + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omitlocallogging", 0, eCmdHdlrBinary, + NULL, &bOmitLocalLogging, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketignoremsgtimestamp", 0, eCmdHdlrBinary, + NULL, &bIgnoreTimestamp, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"systemlogsocketname", 0, eCmdHdlrGetWord, + NULL, &pLogSockName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"addunixlistensocket", 0, eCmdHdlrGetWord, + addLstnSocketName, NULL, 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, the timestamp can not be modified + * via $InputUnixListenSocketIgnoreMsgTimestamp". 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(omsdRegCFSLineHdlr((uchar *)"systemlogsocketignoremsgtimestamp", 0, eCmdHdlrBinary, + setSystemLogTimestampIgnore, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/omgssapi/Makefile.am b/plugins/omgssapi/Makefile.am index 9fa6a241..5280a1ce 100644 --- a/plugins/omgssapi/Makefile.am +++ b/plugins/omgssapi/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omgssapi.la -omgssapi_la_SOURCES = omgssapi.c ../../module-template.h -omgssapi_la_CPPFLAGS = $(pgsql_cflags) -I$(srcdir)/../.. +omgssapi_la_SOURCES = omgssapi.c +omgssapi_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) omgssapi_la_LDFLAGS = -module -avoid-version omgssapi_la_LIBADD = $(gss_libs) diff --git a/plugins/omgssapi/omgssapi.c b/plugins/omgssapi/omgssapi.c index 97b8bd55..34abfe0a 100644 --- a/plugins/omgssapi/omgssapi.c +++ b/plugins/omgssapi/omgssapi.c @@ -4,7 +4,7 @@ * NOTE: read comments in module-template.h to understand how this file * works! * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. * * This file is part of rsyslog. * @@ -54,28 +54,32 @@ #include "cfsysline.h" #include "module-template.h" #include "gss-misc.h" +#include "tcpclt.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +#define INET_SUSPEND_TIME 60 +/* equal to 1 minute - TODO: see if we can get rid of this now that we have + * the retry intervals in the engine -- rgerhards, 2008-03-12 + */ -#define INET_SUSPEND_TIME 60 /* equal to 1 minute */ - /* rgerhards, 2005-07-26: This was 3 minutes. As the - * same timer is used for tcp based syslog, we have - * reduced it. However, it might actually be worth - * thinking about a buffered tcp sender, which would be - * a much better alternative. When that happens, this - * time here can be re-adjusted to 3 minutes (or, - * even better, made configurable). - */ #define INET_RETRY_MAX 30 /* maximum of retries for gethostbyname() */ /* was 10, changed to 30 because we reduced INET_SUSPEND_TIME by one third. So * this "fixes" some of implications of it (see comment on INET_SUSPEND_TIME). * rgerhards, 2005-07-26 + * TODO: this needs to be reviewed in spite of the new engine, too -- rgerhards, 2008-03-12 */ /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(gssutil) +DEFobjCurrIf(tcpclt) typedef struct _instanceData { - char f_hname[MAXHOSTNAMELEN+1]; + char *f_hname; short sock; /* file descriptor */ enum { /* TODO: we shoud revisit these definitions */ eDestFORW, @@ -86,22 +90,14 @@ typedef struct _instanceData { struct addrinfo *f_addr; int compressionLevel; /* 0 - no compression, else level for zlib */ char *port; - char *savedMsg; - int savedMsgLen; /* length of savedMsg in octets */ - TCPFRAMINGMODE tcp_framing; - enum TCPSendStatus { - TCP_SEND_NOTCONNECTED = 0, - TCP_SEND_CONNECTING = 1, - TCP_SEND_READY = 2 - } status; time_t ttSuspend; /* time selector was suspended */ + tcpclt_t *pTCPClt; /* our tcpclt object */ gss_ctx_id_t gss_context; OM_uint32 gss_flags; -# ifdef USE_PTHREADS - pthread_mutex_t mtxTCPSend; -# endif } instanceData; +/* config data */ +static uchar *pszTplName = NULL; /* name of the default template to use */ static char *gss_base_service_name = NULL; static enum gss_mode_t { GSSMODE_MIC, @@ -114,7 +110,7 @@ static enum gss_mode_t { * We may change the implementation to try to lookup the port * if it is unspecified. So far, we use the IANA default auf 514. */ -char *getFwdSyslogPt(instanceData *pData) +static char *getFwdSyslogPt(instanceData *pData) { assert(pData != NULL); if(pData->port == NULL) @@ -123,48 +119,6 @@ char *getFwdSyslogPt(instanceData *pData) return(pData->port); } -/* get send status - * rgerhards, 2005-10-24 - */ -static void TCPSendSetStatus(instanceData *pData, enum TCPSendStatus iNewState) -{ - assert(pData != NULL); - assert( (iNewState == TCP_SEND_NOTCONNECTED) - || (iNewState == TCP_SEND_CONNECTING) - || (iNewState == TCP_SEND_READY)); - - /* there can potentially be a race condition, so guard by mutex */ -# ifdef USE_PTHREADS - pthread_mutex_lock(&pData->mtxTCPSend); -# endif - pData->status = iNewState; -# ifdef USE_PTHREADS - pthread_mutex_unlock(&pData->mtxTCPSend); -# endif -} - - -/* get send status - * rgerhards, 2005-10-24 - */ -static enum TCPSendStatus TCPSendGetStatus(instanceData *pData) -{ - enum TCPSendStatus eState; - assert(pData != NULL); - - /* there can potentially be a race condition, so guard by mutex */ -# ifdef USE_PTHREADS - pthread_mutex_lock(&pData->mtxTCPSend); -# endif - eState = pData->status; -# ifdef USE_PTHREADS - pthread_mutex_unlock(&pData->mtxTCPSend); -# endif - - return eState; -} - - BEGINcreateInstance CODESTARTcreateInstance ENDcreateInstance @@ -194,7 +148,7 @@ CODESTARTfreeInstance 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) - display_status("deleting context", maj_stat, min_stat); + 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... @@ -204,13 +158,13 @@ CODESTARTfreeInstance gss_base_service_name = NULL; } -# ifdef USE_PTHREADS - /* delete any mutex objects, if present */ - pthread_mutex_destroy(&pData->mtxTCPSend); -# endif /* final cleanup */ + tcpclt.Destruct(&pData->pTCPClt); if(pData->sock >= 0) close(pData->sock); + + if(pData->f_hname != NULL) + free(pData->f_hname); ENDfreeInstance @@ -271,7 +225,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) out_tok.length = 0; if (maj_stat != GSS_S_COMPLETE) { - display_status("parsing name", maj_stat, min_stat); + gssutil.display_status("parsing name", maj_stat, min_stat); goto fail; } @@ -284,7 +238,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) *sess_flags |= GSS_C_CONF_FLAG; } dbgprintf("GSS-API requested context flags:\n"); - display_ctx_flags(*sess_flags); + gssutil.display_ctx_flags(*sess_flags); do { maj_stat = gss_init_sec_context(&init_sec_min_stat, GSS_C_NO_CREDENTIAL, context, @@ -295,17 +249,17 @@ static rsRetVal TCPSendGSSInit(void *pvData) if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { - display_status("initializing context", maj_stat, init_sec_min_stat); + gssutil.display_status("initializing context", maj_stat, init_sec_min_stat); goto fail; } if (s == -1) - if ((s = pData->sock = TCPSendCreateSocket(pData->f_addr)) == -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 (send_token(s, &out_tok) < 0) { + if (gssutil.send_token(s, &out_tok) < 0) { goto fail; } } @@ -313,7 +267,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) if (maj_stat == GSS_S_CONTINUE_NEEDED) { dbgprintf("GSS-API Continue needed...\n"); - if (recv_token(s, &in_tok) <= 0) { + if (gssutil.recv_token(s, &in_tok) <= 0) { goto fail; } tok_ptr = &in_tok; @@ -322,16 +276,16 @@ static rsRetVal TCPSendGSSInit(void *pvData) dbgprintf("GSS-API Provided context flags:\n"); *sess_flags = ret_flags; - display_ctx_flags(*sess_flags); + gssutil.display_ctx_flags(*sess_flags); dbgprintf("GSS-API Context initialized\n"); gss_release_name(&min_stat, &target_name); finalize_it: - return iRet; + RETiRet; fail: - logerror("GSS-API Context initialization failed\n"); + errmsg.LogError(NO_ERRCODE, "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) { @@ -341,7 +295,7 @@ finalize_it: if (s != -1) close(s); pData->sock = -1; - return RS_RET_GSS_SENDINIT_ERROR; + ABORT_FINALIZE(RS_RET_GSS_SENDINIT_ERROR); } @@ -364,11 +318,11 @@ static rsRetVal TCPSendGSSSend(void *pvData, char *msg, size_t len) maj_stat = gss_wrap(&min_stat, *context, (gss_mode == GSSMODE_ENC) ? 1 : 0, GSS_C_QOP_DEFAULT, &in_buf, NULL, &out_buf); if (maj_stat != GSS_S_COMPLETE) { - display_status("wrapping message", maj_stat, min_stat); + gssutil.display_status("wrapping message", maj_stat, min_stat); goto fail; } - if (send_token(s, &out_buf) < 0) { + if (gssutil.send_token(s, &out_buf) < 0) { goto fail; } gss_release_buffer(&min_stat, &out_buf); @@ -430,7 +384,7 @@ static rsRetVal doTryResume(instanceData *pData) break; } - return iRet; + RETiRet; } @@ -499,7 +453,7 @@ CODESTARTdoAction } # endif - CHKiRet_Hdlr(TCPSend(pData, psz, l, pData->tcp_framing, TCPSendGSSInit, TCPSendGSSSend, TCPSendGSSPrepRetry)) { + CHKiRet_Hdlr(tcpclt.Send(pData->pTCPClt, pData, psz, l)) { /* error! */ dbgprintf("error forwarding via tcp, suspending\n"); pData->eDestState = eDestFORW_SUSP; @@ -516,6 +470,7 @@ BEGINparseSelectorAct 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 @@ -537,10 +492,6 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) if((iRet = createInstance(&pData)) != RS_RET_OK) goto finalize_it; -# ifdef USE_PTHREADS - pthread_mutex_init(&pData->mtxTCPSend, 0); -# endif - /* 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 @@ -569,20 +520,20 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) ++p; /* eat */ pData->compressionLevel = iLevel; } else { - logerrorInt("Invalid compression level '%c' specified in " + errmsg.LogError(NO_ERRCODE, "Invalid compression level '%c' specified in " "forwardig action - NOT turning on compression.", *p); } # else - logerror("Compression requested, but rsyslogd is not compiled " + errmsg.LogError(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 */ - pData->tcp_framing = TCP_FRAMING_OCTET_COUNTING; + tcp_framing = TCP_FRAMING_OCTET_COUNTING; } else { /* invalid option! Just skip it... */ - logerrorInt("Invalid option %c in forwarding action - ignoring.", *p); + errmsg.LogError(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 @@ -598,12 +549,12 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* we probably have end of string - leave it for the rest * of the code to handle it (but warn the user) */ - logerror("Option block not terminated in gssapi forward action."); + errmsg.LogError(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) + for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p) /* JUST SKIP */; pData->port = NULL; @@ -616,7 +567,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* SKIP AND COUNT */; pData->port = malloc(i + 1); if(pData->port == NULL) { - logerror("Could not get memory to store syslog forwarding port, " + errmsg.LogError(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(). @@ -627,32 +578,25 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) } } + /* 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; - logerror("invalid selector line (port), probably not doing " - "what was intended"); - } - } - ++p; - } + while(*p && *p != ';' && *p != '#' && !isspace((int) *p)) + ++p; /*JUST SKIP*/ /* TODO: make this if go away! */ - if(*p == ';') { + if(*p == ';' || *p == '#' || isspace(*p)) { + uchar cTmp = *p; *p = '\0'; /* trick to obtain hostname (later)! */ - strcpy(pData->f_hname, (char*) q); - *p = ';'; - } else - strcpy(pData->f_hname, (char*) q); + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = cTmp; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } /* process template */ - if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " StdFwdFmt")) - != RS_RET_OK) - goto finalize_it; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName)); /* first set the pData->eDestState */ memset(&hints, 0, sizeof(hints)); @@ -669,6 +613,14 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) 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 */ @@ -676,45 +628,16 @@ CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct -BEGINneedUDPSocket -CODESTARTneedUDPSocket - iRet = RS_RET_FALSE; -ENDneedUDPSocket - - -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite - dbgprintf("tcp send socket %d ready for writing.\n", pData->sock); - TCPSendSetStatus(pData, TCP_SEND_READY); - /* Send stored message (if any) */ - if(pData->savedMsg != NULL) { - if(TCPSend(pData, pData->savedMsg, pData->savedMsgLen, pData->tcp_framing, - TCPSendGSSInit, TCPSendGSSSend, TCPSendGSSPrepRetry) != RS_RET_OK) { - /* error! */ - pData->eDestState = eDestFORW_SUSP; - errno = 0; - logerror("error forwarding via tcp, suspending..."); - } - free(pData->savedMsg); - pData->savedMsg = NULL; - } -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect - if( (pData->eDestState == eDestFORW) - && TCPSendGetStatus(pData) == TCP_SEND_CONNECTING) { - *fd = pData->sock; - iRet = RS_RET_OK; - } -ENDgetWriteFDForSelect - - - - BEGINmodExit CODESTARTmodExit + objRelease(errmsg, CORE_COMPONENT); + objRelease(gssutil, LM_GSSUTIL_FILENAME); + objRelease(tcpclt, LM_TCPCLT_FILENAME); + + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } ENDmodExit @@ -727,21 +650,21 @@ ENDqueryEtryPt /* set a new GSSMODE based on config directive */ static rsRetVal setGSSMode(void __attribute__((unused)) *pVal, uchar *mode) { + DEFiRet; + if (!strcmp((char *) mode, "integrity")) { gss_mode = GSSMODE_MIC; - free(mode); dbgprintf("GSS-API gssmode set to GSSMODE_MIC\n"); } else if (!strcmp((char *) mode, "encryption")) { gss_mode = GSSMODE_ENC; - free(mode); dbgprintf("GSS-API gssmode set to GSSMODE_ENC\n"); } else { - logerrorSz("unknown gssmode parameter: %s", (char *) mode); - free(mode); - return RS_RET_ERR; + errmsg.LogError(NO_ERRCODE, "unknown gssmode parameter: %s", (char *) mode); + iRet = RS_RET_INVALID_PARAMS; } + free(mode); - return RS_RET_OK; + RETiRet; } @@ -752,20 +675,28 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a free(gss_base_service_name); gss_base_service_name = NULL; } + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } return RS_RET_OK; } BEGINmodInit() CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(gssutil, LM_GSSUTIL_FILENAME)); + CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"gssforwardservicename", 0, eCmdHdlrGetWord, NULL, &gss_base_service_name, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"gssmode", 0, eCmdHdlrGetWord, setGSSMode, &gss_mode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actiongssforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &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: +/* vi:set ai: */ diff --git a/plugins/omlibdbi/.cvsignore b/plugins/omlibdbi/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/omlibdbi/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/omlibdbi/Makefile.am b/plugins/omlibdbi/Makefile.am new file mode 100644 index 00000000..872fc67c --- /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) +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..a942a453 --- /dev/null +++ b/plugins/omlibdbi/omlibdbi.c @@ -0,0 +1,372 @@ +/* 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 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 <time.h> +#include <dbi/dbi.h> +#include "syslogd.h" +#include "syslogd-types.h" +#include "cfsysline.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "debug.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* 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 { + 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 */ +} instanceData; + + +/* config settings */ +static uchar *dbiDrvrDir = NULL;/* global: where do the dbi drivers reside? */ +static uchar *drvrName = NULL; /* driver to use */ +static uchar *host = NULL; /* host to connect to */ +static uchar *usrName = NULL; /* user name for connect */ +static uchar *pwd = NULL; /* password for connect */ +static uchar *dbName = NULL; /* database to use */ +#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); +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(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(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*) dbiDrvrDir, &dbiInst); +# else + iDrvrsLoaded = dbi_initialize((char*) dbiDrvrDir); +# endif + if(iDrvrsLoaded == 0) { + errmsg.LogError(NO_ERRCODE, "libdbi error: libdbi or libdbi drivers not present on this system - suspending."); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } else if(iDrvrsLoaded < 0) { + errmsg.LogError(NO_ERRCODE, "libdbi error: libdbi could not be initialized - 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(NO_ERRCODE, "can not initialize libdbi connection"); + iRet = 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 */ + iRet = RS_RET_SUSPENDED; + } + } + +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 + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + iRet = writeDB(ppString[0], pData); +ENDdoAction + + +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(drvrName == NULL) { + errmsg.LogError(NO_ERRCODE, "omlibdbi: no db driver name given - action can not be created"); + ABORT_FINALIZE(RS_RET_NO_DRIVERNAME); + } + + if((pData->drvrName = (uchar*) strdup((char*)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(host != NULL) + if((pData->host = (uchar*) strdup((char*)host)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + if(usrName != NULL) + if((pData->usrName = (uchar*) strdup((char*)usrName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + if(dbName != NULL) + if((pData->dbName = (uchar*) strdup((char*)dbName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + if(pwd != NULL) + if((pData->pwd = (uchar*) strdup((char*)"")) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_RQD_TPL_OPT_SQL, (uchar*) " StdDBFmt")); + +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 +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + + if(dbiDrvrDir != NULL) { + free(dbiDrvrDir); + dbiDrvrDir = NULL; + } + + if(drvrName != NULL) { + free(drvrName); + drvrName = NULL; + } + + if(host != NULL) { + free(host); + host = NULL; + } + + if(usrName != NULL) { + free(usrName); + usrName = NULL; + } + + if(pwd != NULL) { + free(pwd); + pwd = NULL; + } + + if(dbName != NULL) { + free(dbName); + dbName = NULL; + } + + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbidriverdirectory", 0, eCmdHdlrGetWord, NULL, &dbiDrvrDir, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbidriver", 0, eCmdHdlrGetWord, NULL, &drvrName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbihost", 0, eCmdHdlrGetWord, NULL, &host, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbiusername", 0, eCmdHdlrGetWord, NULL, &usrName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbipassword", 0, eCmdHdlrGetWord, NULL, &pwd, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbidbname", 0, eCmdHdlrGetWord, NULL, &dbName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/ommail/Makefile.am b/plugins/ommail/Makefile.am new file mode 100644 index 00000000..7e9f5f13 --- /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) +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..218c73c9 --- /dev/null +++ b/plugins/ommail/ommail.c @@ -0,0 +1,630 @@ +/* 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 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 <unistd.h> +#include <errno.h> +#include <netdb.h> +#include <time.h> +#include <sys/socket.h> +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +static uchar *pszSrv = NULL; +static uchar *pszSrvPort = NULL; +static uchar *pszFrom = NULL; +static uchar *pszTo = NULL; +static uchar *pszSubject = NULL; +static int bEnableBody = 1; /* should a mail body be generated? (set to 0 eg for SMS gateways) */ + +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; + uchar *pszTo; + 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; + + +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); + if(pData->md.smtp.pszTo != NULL) + free(pData->md.smtp.pszTo); + } +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"}; + + time(&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*)LocalHostName, strlen((char*)LocalHostName))); + 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(Send(pData->md.smtp.sock, "RCPT TO: <", sizeof("RCPT TO: <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pData->md.smtp.pszTo, strlen((char*)pData->md.smtp.pszTo))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 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(Send(pData->md.smtp.sock, "To: <", sizeof("To: <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pData->md.smtp.pszTo, strlen((char*)pData->md.smtp.pszTo))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 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(pszFrom == NULL) { + errmsg.LogError(NO_ERRCODE, "no sender address given - specify $ActionMailFrom"); + ABORT_FINALIZE(RS_RET_MAIL_NO_FROM); + } + if(pszTo == NULL) { + errmsg.LogError(NO_ERRCODE, "no recipient address given - specify $ActionMailTo"); + ABORT_FINALIZE(RS_RET_MAIL_NO_TO); + } + + pData->md.smtp.pszFrom = (uchar*) strdup((char*)pszFrom); + pData->md.smtp.pszTo = (uchar*) strdup((char*)pszTo); + + if(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*) pszSubject), OMSR_NO_RQD_TPL_OPTS)); + } + if(pszSrv != NULL) + pData->md.smtp.pszSrv = (uchar*) strdup((char*)pszSrv); + if(pszSrvPort != NULL) + pData->md.smtp.pszSrvPort = (uchar*) strdup((char*)pszSrvPort); + pData->bEnableBody = 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; + + if(pszSrv != NULL) { + free(pszSrv); + pszSrv = NULL; + } + if(pszSrvPort != NULL) { + free(pszSrvPort); + pszSrvPort = NULL; + } + if(pszFrom != NULL) { + free(pszFrom); + pszFrom = NULL; + } + if(pszTo != NULL) { + free(pszTo); + pszTo = NULL; + } + + RETiRet; +} + + +BEGINmodExit +CODESTARTmodExit + /* cleanup our allocations */ + freeConfigVariables(); + + /* release what we no longer need */ + 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; + bEnableBody = 1; + iRet = freeConfigVariables(); + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit + *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(omsdRegCFSLineHdlr( (uchar *)"actionmailsmtpserver", 0, eCmdHdlrGetWord, NULL, &pszSrv, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsmtpport", 0, eCmdHdlrGetWord, NULL, &pszSrvPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailfrom", 0, eCmdHdlrGetWord, NULL, &pszFrom, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailto", 0, eCmdHdlrGetWord, NULL, &pszTo, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsubject", 0, eCmdHdlrGetWord, NULL, &pszSubject, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailenablebody", 0, eCmdHdlrBinary, NULL, &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/ommysql/Makefile.am b/plugins/ommysql/Makefile.am index 6397de5b..3b4e6d75 100644 --- a/plugins/ommysql/Makefile.am +++ b/plugins/ommysql/Makefile.am @@ -1,7 +1,7 @@ pkglib_LTLIBRARIES = ommysql.la -ommysql_la_SOURCES = ommysql.c ommysql.h ../../module-template.h -ommysql_la_CPPFLAGS = $(mysql_cflags) -I$(srcdir)/../.. $(pthreads_cflags) +ommysql_la_SOURCES = ommysql.c ommysql.h +ommysql_la_CPPFLAGS = -I$(top_srcdir) $(mysql_cflags) $(pthreads_cflags) ommysql_la_LDFLAGS = -module -avoid-version ommysql_la_LIBADD = $(mysql_libs) diff --git a/plugins/ommysql/ommysql.c b/plugins/ommysql/ommysql.c index 02ab68a7..807351d2 100644 --- a/plugins/ommysql/ommysql.c +++ b/plugins/ommysql/ommysql.c @@ -8,19 +8,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -42,20 +43,29 @@ #include "template.h" #include "ommysql.h" #include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT /* 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 */ } instanceData; +/* config variables */ +static int iSrvPort = 0; /* database server port */ + BEGINcreateInstance CODESTARTcreateInstance @@ -75,7 +85,7 @@ ENDisCompatibleWithFeature */ static void closeMySQL(instanceData *pData) { - assert(pData != NULL); + ASSERT(pData != NULL); if(pData->f_hmysql != NULL) { /* just to be on the safe side... */ mysql_server_end(); @@ -90,27 +100,12 @@ CODESTARTfreeInstance ENDfreeInstance -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - BEGINdbgPrintInstInfo CODESTARTdbgPrintInstInfo /* nothing special here */ ENDdbgPrintInstInfo -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - /* 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 @@ -120,12 +115,12 @@ static void reportDBError(instanceData *pData, int bSilent) char errMsg[512]; unsigned uMySQLErrno; - assert(pData != NULL); + ASSERT(pData != NULL); /* output log message */ errno = 0; if(pData->f_hmysql == NULL) { - logerror("unknown DB error occured - could not obtain MySQL handle"); + errmsg.LogError(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, @@ -134,7 +129,7 @@ static void reportDBError(instanceData *pData, int bSilent) dbgprintf("mysql, DBError(silent): %s\n", errMsg); else { pData->uLastMySQLErrno = uMySQLErrno; - logerror(errMsg); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); } } @@ -150,24 +145,24 @@ static rsRetVal initMySQL(instanceData *pData, int bSilent) { DEFiRet; - assert(pData != NULL); - assert(pData->f_hmysql == NULL); + ASSERT(pData != NULL); + ASSERT(pData->f_hmysql == NULL); pData->f_hmysql = mysql_init(NULL); if(pData->f_hmysql == NULL) { - logerror("can not initialize MySQL handle"); + errmsg.LogError(NO_ERRCODE, "can not initialize MySQL handle"); iRet = RS_RET_SUSPENDED; } else { /* we could get the handle, now on with work... */ /* Connect to database */ if(mysql_real_connect(pData->f_hmysql, pData->f_dbsrv, pData->f_dbuid, - pData->f_dbpwd, pData->f_dbname, 0, NULL, 0) == NULL) { + pData->f_dbpwd, pData->f_dbname, pData->f_dbsrvPort, NULL, 0) == NULL) { reportDBError(pData, bSilent); closeMySQL(pData); /* ignore any error we may get */ iRet = RS_RET_SUSPENDED; } } - return iRet; + RETiRet; } @@ -179,8 +174,13 @@ rsRetVal writeMySQL(uchar *psz, instanceData *pData) { DEFiRet; - assert(psz != NULL); - assert(pData != NULL); + 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)) { @@ -200,7 +200,7 @@ finalize_it: pData->uLastMySQLErrno = 0; /* reset error for error supression */ } - return iRet; + RETiRet; } @@ -239,9 +239,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) } /* ok, if we reach this point, we have something for us */ - if((iRet = createInstance(&pData)) != RS_RET_OK) - goto finalize_it; - + CHKiRet(createInstance(&pData)); /* rger 2004-10-28: added support for MySQL * >server,dbname,userid,password @@ -277,10 +275,11 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) * Retries make no sense. */ if (iMySQLPropErr) { - logerror("Trouble with MySQL connection properties. -MySQL logging disabled"); + errmsg.LogError(NO_ERRCODE, "Trouble with MySQL connection properties. -MySQL logging disabled"); ABORT_FINALIZE(RS_RET_INVALID_PARAMS); } else { - CHKiRet(initMySQL(pData, 0)); + pData->f_dbsrvPort = (unsigned) iSrvPort; /* set configured port */ + pData->f_hmysql = NULL; /* initialize, but connect only on first message (important for queued mode!) */ } CODE_STD_FINALIZERparseSelectorAct @@ -298,11 +297,24 @@ 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; + iSrvPort = 0; /* zero is the default port */ + RETiRet; +} + BEGINmodInit() CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + /* register our config handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionommysqlserverport", 0, eCmdHdlrInt, NULL, &iSrvPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit -/* - * vi:set ai: + +/* vi:set ai: */ diff --git a/plugins/ommysql/ommysql.h b/plugins/ommysql/ommysql.h index d8d2d538..d8075785 100644 --- a/plugins/ommysql/ommysql.h +++ b/plugins/ommysql/ommysql.h @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ diff --git a/plugins/ompgsql/Makefile.am b/plugins/ompgsql/Makefile.am index 5206b36d..b2e3effa 100644 --- a/plugins/ompgsql/Makefile.am +++ b/plugins/ompgsql/Makefile.am @@ -1,7 +1,7 @@ pkglib_LTLIBRARIES = ompgsql.la -ompgsql_la_SOURCES = ompgsql.c ompgsql.h ../../module-template.h -ompgsql_la_CPPFLAGS = $(pgsql_cflags) -I$(srcdir)/../.. +ompgsql_la_SOURCES = ompgsql.c ompgsql.h +ompgsql_la_CPPFLAGS = -I$(top_srcdir) $(pgsql_cflags) ompgsql_la_LDFLAGS = -module -avoid-version ompgsql_la_LIBADD = $(pgsql_libs) diff --git a/plugins/ompgsql/ompgsql.c b/plugins/ompgsql/ompgsql.c index db48982b..03a2b79f 100644 --- a/plugins/ompgsql/ompgsql.c +++ b/plugins/ompgsql/ompgsql.c @@ -8,19 +8,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -41,10 +42,14 @@ #include "template.h" #include "ompgsql.h" #include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) typedef struct _instanceData { PGconn *f_hpgsql; /* handle to PgSQL */ @@ -87,27 +92,12 @@ CODESTARTfreeInstance ENDfreeInstance -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - BEGINdbgPrintInstInfo CODESTARTdbgPrintInstInfo /* nothing special here */ ENDdbgPrintInstInfo -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - /* 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 @@ -123,7 +113,7 @@ static void reportDBError(instanceData *pData, int bSilent) /* output log message */ errno = 0; if(pData->f_hpgsql == NULL) { - logerror("unknown DB error occured - could not obtain PgSQL handle"); + errmsg.LogError(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, @@ -132,7 +122,7 @@ static void reportDBError(instanceData *pData, int bSilent) dbgprintf("pgsql, DBError(silent): %s\n", errMsg); else { pData->eLastPgSQLStatus = ePgSQLStatus; - logerror(errMsg); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); } } @@ -160,7 +150,7 @@ static rsRetVal initPgSQL(instanceData *pData, int bSilent) iRet = RS_RET_SUSPENDED; } - return iRet; + RETiRet; } @@ -196,7 +186,7 @@ finalize_it: pData->eLastPgSQLStatus = CONNECTION_OK; /* reset error for error supression */ } - return iRet; + RETiRet; } @@ -274,7 +264,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) * Retries make no sense. */ if (iPgSQLPropErr) { - logerror("Trouble with PgSQL connection properties. -PgSQL logging disabled"); + errmsg.LogError(NO_ERRCODE, "Trouble with PgSQL connection properties. -PgSQL logging disabled"); ABORT_FINALIZE(RS_RET_INVALID_PARAMS); } else { CHKiRet(initPgSQL(pData, 0)); @@ -296,8 +286,9 @@ ENDqueryEtryPt BEGINmodInit() CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *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/plugins/ompgsql/ompgsql.h b/plugins/ompgsql/ompgsql.h index 8285ac33..495291f4 100644 --- a/plugins/ompgsql/ompgsql.h +++ b/plugins/ompgsql/ompgsql.h @@ -5,19 +5,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ diff --git a/plugins/omrelp/.cvsignore b/plugins/omrelp/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/omrelp/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/omrelp/Makefile.am b/plugins/omrelp/Makefile.am new file mode 100644 index 00000000..dfc2111f --- /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) +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..04571682 --- /dev/null +++ b/plugins/omrelp/omrelp.c @@ -0,0 +1,338 @@ +/* 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 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 <errno.h> +#include <ctype.h> +#include <librelp.h> +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +static relpEngine_t *pRelpEngine; /* our relp engine */ + +typedef struct _instanceData { + char *f_hname; + int compressionLevel; /* 0 - no compression, else level for zlib */ + char *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 */ + relpClt_t *pRelpClt; /* relp client for this instance */ +} instanceData; + +/* 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 *getRelpPt(instanceData *pData) +{ + assert(pData != NULL); + if(pData->port == NULL) + return("514"); + else + return(pData->port); +} + +BEGINcreateInstance +CODESTARTcreateInstance + pData->bInitialConnect = 1; +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->port != NULL) + free(pData->port); + + /* final cleanup */ + if(pData->pRelpClt != NULL) + relpEngineCltDestruct(pRelpEngine, &pData->pRelpClt); + + if(pData->f_hname != NULL) + free(pData->f_hname); + +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("RELP/%s", pData->f_hname); +ENDdbgPrintInstInfo + + +/* try to connect to server + * rgerhards, 2008-03-21 + */ +static rsRetVal doConnect(instanceData *pData) +{ + DEFiRet; + + if(pData->bInitialConnect) { + iRet = relpCltConnect(pData->pRelpClt, family, (uchar*) pData->port, (uchar*) pData->f_hname); + 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 + + +BEGINdoAction + uchar *pMsg; /* temporary buffering */ + size_t lenMsg; + relpRetVal ret; +CODESTARTdoAction + dbgprintf(" %s:%s/RELP\n", pData->f_hname, getRelpPt(pData)); + + if(!pData->bIsConnected) { + CHKiRet(doConnect(pData)); + } + + pMsg = ppString[0]; + lenMsg = strlen((char*) pMsg); /* TODO: don't we get this? */ + + /* TODO: think about handling oversize messages! */ + if(lenMsg > MAXLINE) + lenMsg = MAXLINE; + + /* forward */ + ret = relpCltSendSyslog(pData->pRelpClt, (uchar*) pMsg, lenMsg); +RUNLOG_VAR("%d", ret); + if(ret != RELP_RET_OK) { + /* error! */ + dbgprintf("error forwarding via relp, suspending\n"); + iRet = RS_RET_SUSPENDED; + } + +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; + + /* 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: + * :omrelp:(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 + * TODO: think of all this in spite of RELP -- rgerhards, 2008-03-13 + */ + 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(NO_ERRCODE, "Invalid compression level '%c' specified in " + "forwardig action - NOT turning on compression.", + *p); + } +# else + errmsg.LogError(NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " + "with compression support - request ignored."); +# endif /* #ifdef USE_NETZIP */ + } else { /* invalid option! Just skip it... */ + errmsg.LogError(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(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 + */ + for(q = 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(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(NO_ERRCODE, "invalid selector line (port), probably not doing " + "what was intended"); + } + } + ++p; + } + + /* TODO: make this if go away! */ + if(*p == ';') { + *p = '\0'; /* trick to obtain hostname (later)! */ + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = ';'; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_ForwardFormat")); + + /* create our relp client */ + CHKiRet(relpEngineCltConstruct(pRelpEngine, &pData->pRelpClt)); /* we use CHKiRet as librelp has a similar return value range */ + + /* TODO: do we need to call freeInstance if we failed - this is a general question for + * all output modules. I'll address it later as the interface evolves. rgerhards, 2007-07-25 + */ +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + relpEngineDestruct(&pRelpEngine); + + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *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)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/omsnmp/.cvsignore b/plugins/omsnmp/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/omsnmp/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/omsnmp/Makefile.am b/plugins/omsnmp/Makefile.am new file mode 100644 index 00000000..d74f7bb4 --- /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) +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..22d48340 --- /dev/null +++ b/plugins/omsnmp/omsnmp.c @@ -0,0 +1,533 @@ +/* omsnmp.c + * + * This module sends an snmp trap. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * 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 <netinet/in.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netdb.h> +#include <ctype.h> +#include <assert.h> +#include "syslogd.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 + +/* 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 }; + +static uchar* pszTransport = NULL; /* default transport */ +static uchar* pszTarget = NULL; +/* note using an unsigned for a port number is not a good idea from an IPv6 point of view */ +static int iPort = 0; +static int iSNMPVersion = 1; /* 0 Means SNMPv1, 1 Means SNMPv2c */ +static uchar* pszCommunity = NULL; +static uchar* pszEnterpriseOID = NULL; +static uchar* pszSnmpTrapOID = NULL; +static uchar* pszSyslogMessageOID = NULL; +static int iSpecificType = 0; +static int iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC;/*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) +*/ + +typedef struct _instanceData { + uchar szTransport[OMSNMP_MAXTRANSPORLENGTH+1]; /* Transport - Can be udp, tcp, udp6, tcp6 and other types supported by NET-SNMP */ + uchar *szTarget; /* IP/hostname of Snmp Target*/ + uchar *szTargetAndPort; /* IP/hostname + Port,needed format for SNMP LIB */ + uchar szCommunity[OMSNMP_MAXCOMMUNITYLENGHT+1]; /* Snmp Community */ + uchar szEnterpriseOID[OMSNMP_MAXOIDLENGHT+1]; /* Snmp Enterprise OID - default is (1.3.6.1.4.1.3.1.1 = enterprises.cmu.1.1) */ + uchar szSnmpTrapOID[OMSNMP_MAXOIDLENGHT+1]; /* Snmp Trap OID - default is (1.3.6.1.4.1.19406.1.2.1 = ADISCON-MONITORWARE-MIB::syslogtrap) */ + uchar szSyslogMessageOID[OMSNMP_MAXOIDLENGHT+1]; /* 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 */ +} instanceData; + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("SNMPTransport: %s\n", pData->szTransport); + dbgprintf("SNMPTarget: %s\n", pData->szTarget); + dbgprintf("SNMPPort: %d\n", pData->iPort); + dbgprintf("SNMPTarget+PortStr: %s\n", pData->szTargetAndPort); + 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) +{ + DEFiRet; + netsnmp_session session; + + /* should not happen, but if session is not cleared yet - we do it now! */ + if (pData->snmpsession != NULL) + omsnmp_exitSession(pData); + + 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*) pData->szTargetAndPort; + + /* Set SNMP Community */ + if (session.version == SNMP_VERSION_1 || session.version == SNMP_VERSION_2c) + { + session.community = (unsigned char *) pData->szCommunity; + session.community_len = strlen((char*) pData->szCommunity); + } + + pData->snmpsession = snmp_open(&session); + if (pData->snmpsession == NULL) { + errmsg.LogError(NO_ERRCODE, "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( (char*) pData->szEnterpriseOID, enterpriseoid, &enterpriseoidlen )) + { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(NO_ERRCODE, "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', (char*) pData->szSnmpTrapOID ) != 0) + { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(NO_ERRCODE, "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( (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(NO_ERRCODE, "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(NO_ERRCODE, "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(NO_ERRCODE, "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); + + if(pData->szTarget != NULL) + free(pData->szTarget); + if(pData->szTargetAndPort != NULL) + free(pData->szTargetAndPort); + +ENDfreeInstance + + +BEGINparseSelectorAct + uchar szTargetAndPort[MAXHOSTNAMELEN+128]; /* work buffer for specifying a full target and port string */ +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 Transport */ + if (pszTransport == NULL) { + /* + * Default transport is UDP. Other values supported by NETSNMP are possible as well + */ + strncpy( (char*) pData->szTransport, "udp", sizeof("udp") ); + } else { + /* Copy Transport */ + strncpy( (char*) pData->szTransport, (char*) pszTransport, strlen((char*) pszTransport) ); + } + + /* Check Target */ + if (pszTarget == NULL) { + ABORT_FINALIZE( RS_RET_PARAM_ERROR ); + } else { + /* Copy Target */ + CHKmalloc(pData->szTarget = (uchar*) strdup((char*)pszTarget)); + } + + /* Copy Community */ + if (pszCommunity == NULL) /* Failsave */ + strncpy( (char*) pData->szCommunity, "public", sizeof("public") ); + else /* Copy Target */ + strncpy( (char*) pData->szCommunity, (char*) pszCommunity, strlen((char*) pszCommunity) ); + + /* Copy Enterprise OID */ + if (pszEnterpriseOID == NULL) /* Failsave */ + strncpy( (char*) pData->szEnterpriseOID, "1.3.6.1.4.1.3.1.1", sizeof("1.3.6.1.4.1.3.1.1") ); + else /* Copy Target */ + strncpy( (char*) pData->szEnterpriseOID, (char*) pszEnterpriseOID, strlen((char*) pszEnterpriseOID) ); + + /* Copy SnmpTrap OID */ + if (pszSnmpTrapOID == NULL) /* Failsave */ + strncpy( (char*) pData->szSnmpTrapOID, "1.3.6.1.4.1.19406.1.2.1", sizeof("1.3.6.1.4.1.19406.1.2.1") ); + else /* Copy Target */ + strncpy( (char*) pData->szSnmpTrapOID, (char*) pszSnmpTrapOID, strlen((char*) pszSnmpTrapOID) ); + + + /* Copy SyslogMessage OID */ + if (pszSyslogMessageOID == NULL) /* Failsave */ + strncpy( (char*) pData->szSyslogMessageOID, "1.3.6.1.4.1.19406.1.1.2.1", sizeof("1.3.6.1.4.1.19406.1.1.2.1") ); + else /* Copy Target */ + strncpy( (char*) pData->szSyslogMessageOID, (char*) pszSyslogMessageOID, strlen((char*) pszSyslogMessageOID) ); + + /* Copy Port */ + if ( iPort == 0) /* If no Port is set we use the default Port 162 */ + pData->iPort = 162; + else + pData->iPort = iPort; + + /* Set SNMPVersion */ + if ( iSNMPVersion < 0 || iSNMPVersion > 1) /* Set default to 1 if out of range */ + pData->iSNMPVersion = 1; + else + pData->iSNMPVersion = iSNMPVersion; + + /* Copy SpecificType */ + if ( iSpecificType == 0) /* If no iSpecificType is set, we use the default 0 */ + pData->iSpecificType = 0; + else + pData->iSpecificType = iSpecificType; + + /* Copy TrapType */ + if ( iTrapType < 0 && iTrapType >= 6) /* Only allow values from 0 to 6 !*/ + pData->iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + else + pData->iTrapType = iTrapType; + + /* Create string for session peername! */ + snprintf((char*)szTargetAndPort, sizeof(szTargetAndPort), "%s:%s:%d", pData->szTransport, pData->szTarget, pData->iPort); + CHKmalloc(pData->szTargetAndPort = (uchar*)strdup((char*)szTargetAndPort)); + + /* Print Debug info */ + dbgprintf("SNMPTransport: %s\n", pData->szTransport); + dbgprintf("SNMPTarget: %s\n", pData->szTarget); + dbgprintf("SNMPPort: %d\n", pData->iPort); + dbgprintf("SNMPTarget+PortStr: %s\n", pData->szTargetAndPort); + 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; + + if (pszTarget != NULL) + free(pszTarget); + pszTarget = NULL; + + if (pszCommunity != NULL) + free(pszCommunity); + pszCommunity = NULL; + + if (pszEnterpriseOID != NULL) + free(pszEnterpriseOID); + pszEnterpriseOID = NULL; + + if (pszSnmpTrapOID != NULL) + free(pszSnmpTrapOID); + pszSnmpTrapOID = NULL; + + if (pszSyslogMessageOID != NULL) + free(pszSyslogMessageOID); + pszSyslogMessageOID = NULL; + + iPort = 0; + iSNMPVersion = 1; + iSpecificType = 0; + iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + + RETiRet; +} + + +BEGINmodExit +CODESTARTmodExit + if (pszTarget != NULL) + free(pszTarget); + if (pszCommunity != NULL) + free(pszCommunity); + if (pszEnterpriseOID != NULL) + free(pszEnterpriseOID); + if (pszSnmpTrapOID != NULL) + free(pszSnmpTrapOID); + if (pszSyslogMessageOID != NULL) + free(pszSyslogMessageOID); + + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptransport", 0, eCmdHdlrGetWord, NULL, &pszTransport, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptarget", 0, eCmdHdlrGetWord, NULL, &pszTarget, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptargetport", 0, eCmdHdlrInt, NULL, &iPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpversion", 0, eCmdHdlrInt, NULL, &iSNMPVersion, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpcommunity", 0, eCmdHdlrGetWord, NULL, &pszCommunity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpenterpriseoid", 0, eCmdHdlrGetWord, NULL, &pszEnterpriseOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptrapoid", 0, eCmdHdlrGetWord, NULL, &pszSnmpTrapOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpsyslogmessageoid", 0, eCmdHdlrGetWord, NULL, &pszSyslogMessageOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpspecifictype", 0, eCmdHdlrInt, NULL, &iSpecificType, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptraptype", 0, eCmdHdlrInt, NULL, &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..be3eb2a2 --- /dev/null +++ b/plugins/omsnmp/omsnmp.h @@ -0,0 +1,106 @@ +/* omsnmp.h + * 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 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/omtesting/.cvsignore b/plugins/omtesting/.cvsignore new file mode 100644 index 00000000..9730646f --- /dev/null +++ b/plugins/omtesting/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +*.la +*.lo diff --git a/plugins/omtesting/Makefile.am b/plugins/omtesting/Makefile.am new file mode 100644 index 00000000..7e376683 --- /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) +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..15d3cb80 --- /dev/null +++ b/plugins/omtesting/omtesting.c @@ -0,0 +1,183 @@ +/* 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 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 <ctype.h> +#include <assert.h> +#include "syslogd.h" +#include "syslogd-types.h" +#include "module-template.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +typedef struct _instanceData { + int iWaitSeconds; + int iWaitUSeconds; /* milli-seconds (one million of a second, just to make sure...) */ +} instanceData; + +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 + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + 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); + //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(0) + /* 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; + + 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); + } + /* once there are other modes, here is the spot to add it! */ + else { + dbgprintf("invalid mode '%s', doing 'sleep 1 0' - fix your config\n", szBuf); + } + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr +ENDmodInit +/* + * vi:set ai: + */ diff --git a/queue.c b/queue.c new file mode 100644 index 00000000..37782cf0 --- /dev/null +++ b/queue.c @@ -0,0 +1,2324 @@ +/* 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. + * + * 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 <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 "syslogd.h" +#include "queue.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "wtp.h" +#include "wti.h" + +/* static data */ +DEFobjStaticHelpers + +/* forward-definitions */ +rsRetVal queueChkPersist(queue_t *pThis); +static rsRetVal queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex); +static rsRetVal queueRateLimiter(queue_t *pThis); +static int queueChkStopWrkrDA(queue_t *pThis); +static int queueIsIdleDA(queue_t *pThis); +static rsRetVal queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave); +static rsRetVal queueConsumerCancelCleanup(void *arg1, void *arg2); +static rsRetVal queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex); + +/* some constants for queuePersist () */ +#define QUEUE_CHECKPOINT 1 +#define QUEUE_NO_CHECKPOINT 0 + +/* methods */ + + +/* get the overall queue size, which includes ungotten objects. Must only be called + * while mutex is locked! + * rgerhards, 2008-01-29 + */ +static inline int +queueGetOverallQueueSize(queue_t *pThis) +{ +#if 0 /* leave a bit in for debugging -- rgerhards, 2008-01-30 */ +BEGINfunc +dbgoprint((obj_t*) pThis, "queue size: %d (regular %d, ungotten %d)\n", + pThis->iQueueSize + pThis->iUngottenObjs, pThis->iQueueSize, pThis->iUngottenObjs); +ENDfunc +#endif + return pThis->iQueueSize + pThis->iUngottenObjs; +} + +/* --------------- 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 queueAdviseMaxWorkers(queue_t *pThis) +{ + DEFiRet; + int iMaxWorkers; + + ISOBJ_TYPE_assert(pThis, queue); + + if(!pThis->bEnqOnly) { + if(pThis->bRunsDA) { + /* if we have not yet reached the high water mark, there is no need to start a + * worker. -- rgerhards, 2008-01-26 + */ + if(queueGetOverallQueueSize(pThis) >= pThis->iHighWtrMrk || pThis->bQueueStarted == 0) { + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* disk queues have always one worker */ + } + } else { + if(pThis->qType == QUEUETYPE_DISK || pThis->iMinMsgsPerWrkr == 0) { + iMaxWorkers = 1; + } else { + iMaxWorkers = queueGetOverallQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1; + } + wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers); /* disk queues have always one worker */ + } + } + + RETiRet; +} + + +/* wait until we have a fully initialized DA queue. Sometimes, we need to + * sync with it, as we expect it for some function. + * rgerhards, 2008-02-27 + */ +static rsRetVal +queueWaitDAModeInitialized(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->bRunsDA); + + while(pThis->bRunsDA != 2) { + d_pthread_cond_wait(&pThis->condDAReady, pThis->mut); + } + + RETiRet; +} + + +/* Destruct DA queue. This is the last part of DA-to-normal-mode + * transistion. This is called asynchronously and some time quite a + * while after the actual transistion. The key point is that we need to + * do it at some later time, because we need to destruct the DA queue. That, + * however, can not be done in a thread that has been signalled + * This is to be called when we revert back to our own queue. + * This function must be called with the queue mutex locked (the wti + * class ensures this). + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueTurnOffDAMode(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->bRunsDA); + + /* at this point, we need a fully initialized DA queue. So if it isn't, we finally need + * to wait for its startup... -- rgerhards, 2008-01-25 + */ + queueWaitDAModeInitialized(pThis); + + /* if we need to pull any data that we still need from the (child) disk queue, + * now would be the time to do so. At present, we do not need this, but I'd like to + * keep that comment if future need arises. + */ + + /* we need to check if the DA queue is empty because the DA worker may simply have + * terminated do to no new messages arriving. That does not, however, mean that the + * DA queue is empty. If there is still data in that queue, we do nothing and leave + * that for a later incarnation of this function (it will be called multiple times + * during the lifetime of DA-mode, depending on how often the DA worker receives an + * inactivity timeout. -- rgerhards, 2008-01-25 + */ + if(pThis->pqDA->iQueueSize == 0) { + pThis->bRunsDA = 0; /* tell the world we are back in non-DA mode */ + /* we destruct the queue object, which will also shutdown the queue worker. As the queue is empty, + * this will be quick. + */ + queueDestruct(&pThis->pqDA); /* and now we are ready to destruct the DA queue */ + dbgoprint((obj_t*) pThis, "disk-assistance has been turned off, disk queue was empty (iRet %d)\n", + iRet); + /* now we need to check if the regular queue has some messages. This may be the case + * when it is waiting that the high water mark is reached again. If so, we need to start up + * a regular worker. -- rgerhards, 2008-01-26 + */ + if(queueGetOverallQueueSize(pThis) > 0) { + queueAdviseMaxWorkers(pThis); + } + } + + /* TODO: we have a *really biiiiig* memory leak here: if the queue could not be persisted, all of + * its data elements are still in memory. That doesn't really matter if we are terminated, but on + * HUP this memory leaks. We MUST add a loop of destructor calls here. However, this takes time + * (possibly a lot), so it is probably best to have a config variable for that. + * Something for 3.11.1! + * rgerhards, 2008-01-30 + */ + + 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 +queueChkIsDA(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + 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. All internal settings are changed. This is supposed + * to be called from the DA worker, which must have been started before. The most important + * chore of this function is to create the DA queue object. If that function fails, + * the DA worker should return with an appropriate state, which in turn should lead to + * a re-set to non-DA mode in the Enq process. The queue mutex must be locked when this + * function is called, else a number of races will happen. + * Please note that this function may be called *while* we in DA mode. This is due to the + * fact that the DA worker calls it and the DA worker may be suspended (and restarted) due + * to inactivity timeouts. + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueStartDA(queue_t *pThis) +{ + DEFiRet; + uchar pszDAQName[128]; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->bRunsDA == 2) /* check if already in (fully initialized) DA mode... */ + FINALIZE; /* ... then we are already done! */ + + /* create message queue */ + CHKiRet(queueConstruct(&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(queueSetpUsr(pThis->pqDA, pThis->pUsr)); + CHKiRet(queueSetsizeOnDiskMax(pThis->pqDA, pThis->sizeOnDiskMax)); + CHKiRet(queueSetiDeqSlowdown(pThis->pqDA, pThis->iDeqSlowdown)); + CHKiRet(queueSetMaxFileSize(pThis->pqDA, pThis->iMaxFileSize)); + CHKiRet(queueSetFilePrefix(pThis->pqDA, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(queueSetiPersistUpdCnt(pThis->pqDA, pThis->iPersistUpdCnt)); + CHKiRet(queueSettoActShutdown(pThis->pqDA, pThis->toActShutdown)); + CHKiRet(queueSettoEnq(pThis->pqDA, pThis->toEnq)); + CHKiRet(queueSetEnqOnly(pThis->pqDA, pThis->bDAEnqOnly, MUTEX_ALREADY_LOCKED)); + CHKiRet(queueSetiDeqtWinFromHr(pThis->pqDA, pThis->iDeqtWinFromHr)); + CHKiRet(queueSetiDeqtWinToHr(pThis->pqDA, pThis->iDeqtWinToHr)); + CHKiRet(queueSetiHighWtrMrk(pThis->pqDA, 0)); + CHKiRet(queueSetiDiscardMrk(pThis->pqDA, 0)); + if(pThis->toQShutdown == 0) { + CHKiRet(queueSettoQShutdown(pThis->pqDA, 0)); /* if the user really wants... */ + } else { + /* we use the shortest possible shutdown (0 is endless!) because when we run on disk AND + * have an obviously large backlog, we can't finish it in any case. So there is no point + * in holding shutdown longer than necessary. -- rgerhards, 2008-01-15 + */ + CHKiRet(queueSettoQShutdown(pThis->pqDA, 1)); + } + + iRet = queueStart(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) + FINALIZE; /* something is wrong */ + + /* as we are right now starting DA mode because we are so busy, it is + * extremely unlikely that any regular worker is sleeping on empty queue. HOWEVER, + * we want to be on the safe side, and so we awake anyone that is waiting + * on one. So even if the scheduler plays badly with us, things should be + * quite well. -- rgerhards, 2008-01-15 + */ + wtpWakeupWrkr(pThis->pWtpReg); /* awake all workers, but not ourselves ;) */ + + pThis->bRunsDA = 2; /* we are now in DA mode, but not fully initialized */ + pThis->bChildIsDone = 0;/* set to 1 when child's worker detect queue is finished */ + pthread_cond_broadcast(&pThis->condDAReady); /* signal we are now initialized and ready to go ;) */ + + dbgoprint((obj_t*) pThis, "is now running in disk assisted mode, disk queue 0x%lx\n", + queueGetID(pThis->pqDA)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pqDA != NULL) { + queueDestruct(&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 inline rsRetVal +queueInitDA(queue_t *pThis, int bEnqOnly, 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 + */ + if(pThis->pWtpDA == NULL) { + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:DA", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpDA)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpDA, pszBuf, lenBuf)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrDA)); + CHKiRet(wtpSetpfIsIdle (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueIsIdleDA)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerDA)); + CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void*pWti)) queueConsumerCancelCleanup)); + CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueStartDA)); + CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueTurnOffDAMode)); + 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 */ + + /* indicate we now run in DA mode - this is reset by the DA worker if it fails */ + pThis->bRunsDA = 1; + pThis->bDAEnqOnly = bEnqOnly; + + /* now we must now adivse the wtp that we need one worker. If none is yet active, + * that will also start one up. If we forgot that step, everything would be stalled + * until the next enqueue request. + */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* DA queues alsways have just one worker max */ + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + RETiRet; +} + + +/* check if we need to start disk assisted mode and send some signals to + * keep it running if we are already in it. It also checks if DA mode is + * partially initialized, in which case it waits for initialization to + * complete. + * rgerhards, 2008-01-14 + */ +static inline rsRetVal +queueChkStrtDA(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + /* if we do not hit the high water mark, we have nothing to do */ + if(queueGetOverallQueueSize(pThis) != pThis->iHighWtrMrk) + ABORT_FINALIZE(RS_RET_OK); + + if(pThis->bRunsDA) { + /* then we need to signal that we are at the high water mark again. If that happens + * on our way down the queue, that doesn't matter, because then nobody is waiting + * on the condition variable. + * (Remember that a DA queue stops draining the queue once it has reached the low + * water mark and restarts it when the high water mark is reached again - this is + * what this code here is responsible for. Please note that all workers may have been + * terminated due to the inactivity timeout, thus we need to advise the pool that + * we need at least one). + */ + dbgoprint((obj_t*) pThis, "%d entries - passed high water mark in DA mode, send notify\n", + queueGetOverallQueueSize(pThis)); + queueAdviseMaxWorkers(pThis); + } else { + /* this is the case when we are currently not running in DA mode. So it is time + * to turn it back on. + */ + dbgoprint((obj_t*) pThis, "%d entries - passed high water mark for disk-assisted mode, initiating...\n", + queueGetOverallQueueSize(pThis)); + queueInitDA(pThis, QUEUE_MODE_ENQDEQ, MUTEX_ALREADY_LOCKED); /* initiate DA mode */ + } + +finalize_it: + 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(queue_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.head = 0; + pThis->tVars.farray.tail = 0; + + queueChkIsDA(pThis); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructFixedArray(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->tVars.farray.pBuf != NULL) + free(pThis->tVars.farray.pBuf); + + RETiRet; +} + +static rsRetVal qAddFixedArray(queue_t *pThis, void* 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 qDelFixedArray(queue_t *pThis, void **out) +{ + DEFiRet; + + ASSERT(pThis != NULL); + *out = (void*) pThis->tVars.farray.pBuf[pThis->tVars.farray.head]; + + pThis->tVars.farray.head++; + if (pThis->tVars.farray.head == pThis->iMaxQueueSize) + pThis->tVars.farray.head = 0; + + RETiRet; +} + + +/* -------------------- linked list -------------------- */ + +/* first some generic functions which are also used for the unget linked list */ + +static inline rsRetVal queueAddLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, void* pUsr) +{ + DEFiRet; + qLinkedList_t *pEntry; + + ASSERT(ppRoot != NULL); + ASSERT(ppLast != NULL); + + if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pEntry->pNext = NULL; + pEntry->pUsr = pUsr; + + if(*ppRoot == NULL) { + *ppRoot = *ppLast = pEntry; + } else { + (*ppLast)->pNext = pEntry; + *ppLast = pEntry; + } + +finalize_it: + RETiRet; +} + +static inline rsRetVal queueDelLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, obj_t **ppUsr) +{ + DEFiRet; + qLinkedList_t *pEntry; + + ASSERT(ppRoot != NULL); + ASSERT(ppLast != NULL); + ASSERT(ppUsr != NULL); + ASSERT(*ppRoot != NULL); + + pEntry = *ppRoot; + *ppUsr = pEntry->pUsr; + + if(*ppRoot == *ppLast) { + *ppRoot = NULL; + *ppLast = NULL; + } else { + *ppRoot = pEntry->pNext; + } + free(pEntry); + + RETiRet; +} + +/* end generic functions which are also used for the unget linked list */ + + +static rsRetVal qConstructLinkedList(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + pThis->tVars.linklist.pRoot = 0; + pThis->tVars.linklist.pLast = 0; + + queueChkIsDA(pThis); + + RETiRet; +} + + +static rsRetVal qDestructLinkedList(queue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + /* with the linked list type, there is nothing to do here. The + * reason is that the Destructor is only called after all entries + * have bene taken off the queue. In this case, there is nothing + * dynamic left with the linked list. + */ + + RETiRet; +} + +static rsRetVal qAddLinkedList(queue_t *pThis, void* pUsr) +{ + DEFiRet; + + iRet = queueAddLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, pUsr); +#if 0 + qLinkedList_t *pEntry; + + ASSERT(pThis != NULL); + if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pEntry->pNext = NULL; + pEntry->pUsr = pUsr; + + if(pThis->tVars.linklist.pRoot == NULL) { + pThis->tVars.linklist.pRoot = pThis->tVars.linklist.pLast = pEntry; + } else { + pThis->tVars.linklist.pLast->pNext = pEntry; + pThis->tVars.linklist.pLast = pEntry; + } + +finalize_it: +#endif + RETiRet; +} + +static rsRetVal qDelLinkedList(queue_t *pThis, obj_t **ppUsr) +{ + DEFiRet; + iRet = queueDelLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, ppUsr); +#if 0 + qLinkedList_t *pEntry; + + ASSERT(pThis != NULL); + ASSERT(pThis->tVars.linklist.pRoot != NULL); + + pEntry = pThis->tVars.linklist.pRoot; + *ppUsr = pEntry->pUsr; + + if(pThis->tVars.linklist.pRoot == pThis->tVars.linklist.pLast) { + pThis->tVars.linklist.pRoot = NULL; + pThis->tVars.linklist.pLast = NULL; + } else { + pThis->tVars.linklist.pRoot = pEntry->pNext; + } + free(pEntry); + +#endif + RETiRet; +} + + +/* -------------------- disk -------------------- */ + + +static rsRetVal +queueLoadPersStrmInfoFixup(strm_t *pStrm, queue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_TYPE_assert(pThis, queue); + CHKiRet(strmSetDir(pStrm, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); +finalize_it: + RETiRet; +} + + +/* This method checks if we have a QIF file for the current queue (no matter of + * queue mode). Returns RS_RET_OK if we have a QIF file or an error status otherwise. + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueHaveQIF(queue_t *pThis) +{ + DEFiRet; + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + struct stat stat_buf; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pszFilePrefix == NULL) + ABORT_FINALIZE(RS_RET_NO_FILEPREFIX); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); + + /* check if the file exists */ + if(stat((char*) pszQIFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + dbgoprint((obj_t*) pThis, "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 */ + +finalize_it: + RETiRet; +} + + +/* The method loads the persistent queue information. + * rgerhards, 2008-01-11 + */ +static rsRetVal +queueTryLoadPersistedInfo(queue_t *pThis) +{ + DEFiRet; + strm_t *psQIF = NULL; + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + struct stat stat_buf; + int iUngottenObjs; + obj_t *pUsr; + + ISOBJ_TYPE_assert(pThis, queue); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); + + /* check if the file exists */ + if(stat((char*) 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(strmConstruct(&psQIF)); + CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_READ)); + CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); + CHKiRet(strmConstructFinalize(psQIF)); + + /* first, we try to read the property bag for ourselfs */ + CHKiRet(obj.DeserializePropBag((obj_t*) pThis, psQIF)); + + /* then the ungotten object queue */ + iUngottenObjs = pThis->iUngottenObjs; + pThis->iUngottenObjs = 0; /* will be incremented when we add objects! */ + + while(iUngottenObjs > 0) { + /* fill the queue from disk */ + CHKiRet(obj.Deserialize((void*) &pUsr, (uchar*)"msg", psQIF, NULL, NULL)); + queueUngetObj(pThis, pUsr, MUTEX_ALREADY_LOCKED); + --iUngottenObjs; /* one less */ + } + + /* and now the stream objects (some order as when persisted!) */ + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pWrite, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pRead, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); + + CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pWrite)); + CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pRead)); + + /* 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) + strmDestruct(&psQIF); + + if(iRet != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "error %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(queue_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 = queueTryLoadPersistedInfo(pThis); + if(iRet == RS_RET_OK) + bRestarted = 1; + else if(iRet != RS_RET_FILE_NOT_FOUND) + FINALIZE; + + if(bRestarted == 1) { + ; + } else { + CHKiRet(strmConstruct(&pThis->tVars.disk.pWrite)); + CHKiRet(strmSetDir(pThis->tVars.disk.pWrite, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); + CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pWrite, 10000000)); + CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pWrite, STREAMMODE_WRITE)); + CHKiRet(strmSetsType(pThis->tVars.disk.pWrite, STREAMTYPE_FILE_CIRCULAR)); + CHKiRet(strmConstructFinalize(pThis->tVars.disk.pWrite)); + + CHKiRet(strmConstruct(&pThis->tVars.disk.pRead)); + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); + CHKiRet(strmSetDir(pThis->tVars.disk.pRead, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); + CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pRead, 10000000)); + CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pRead, STREAMMODE_READ)); + CHKiRet(strmSetsType(pThis->tVars.disk.pRead, STREAMTYPE_FILE_CIRCULAR)); + CHKiRet(strmConstructFinalize(pThis->tVars.disk.pRead)); + + + CHKiRet(strmSetFName(pThis->tVars.disk.pWrite, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(strmSetFName(pThis->tVars.disk.pRead, 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(strmSetiMaxFileSize(pThis->tVars.disk.pWrite, pThis->iMaxFileSize)); +CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pRead, pThis->iMaxFileSize)); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructDisk(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + strmDestruct(&pThis->tVars.disk.pWrite); + strmDestruct(&pThis->tVars.disk.pRead); + + RETiRet; +} + +static rsRetVal qAddDisk(queue_t *pThis, void* pUsr) +{ + DEFiRet; + number_t nWriteCount; + + ASSERT(pThis != NULL); + + CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, &nWriteCount)); + CHKiRet((objSerialize(pUsr))(pUsr, pThis->tVars.disk.pWrite)); + CHKiRet(strmFlush(pThis->tVars.disk.pWrite)); + CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, NULL)); /* no more counting for now... */ + + pThis->tVars.disk.sizeOnDisk += nWriteCount; + + /* The following line is a backport from 3.19.10 - fixes mem leak */ + objDestruct(pUsr); + dbgoprint((obj_t*) pThis, "write wrote %lld octets to disk, queue disk size now %lld octets\n", + nWriteCount, pThis->tVars.disk.sizeOnDisk); + +finalize_it: + RETiRet; +} + +static rsRetVal qDelDisk(queue_t *pThis, void **ppUsr) +{ + DEFiRet; + + int64 offsIn; + int64 offsOut; + + CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsIn)); + CHKiRet(obj.Deserialize(ppUsr, (uchar*) "msg", pThis->tVars.disk.pRead, NULL, NULL)); + CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsOut)); + + /* 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(offsIn < offsOut) { + pThis->tVars.disk.bytesRead += offsOut - offsIn; + } else { + pThis->tVars.disk.sizeOnDisk -= pThis->tVars.disk.bytesRead; + pThis->tVars.disk.bytesRead = offsOut; + dbgoprint((obj_t*) pThis, "a file has been deleted, now %lld octets disk space used\n", pThis->tVars.disk.sizeOnDisk); + /* awake possibly waiting enq process */ + pthread_cond_signal(&pThis->notFull); /* we hold the mutex while we are in here! */ + } + +finalize_it: + RETiRet; +} + +/* -------------------- direct (no queueing) -------------------- */ +static rsRetVal qConstructDirect(queue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + + +static rsRetVal qDestructDirect(queue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + +static rsRetVal qAddDirect(queue_t *pThis, void* pUsr) +{ + 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 + */ + iRet = pThis->pConsumer(pThis->pUsr, pUsr); + + RETiRet; +} + +static rsRetVal qDelDirect(queue_t __attribute__((unused)) *pThis, __attribute__((unused)) void **out) +{ + return RS_RET_OK; +} + + +/* --------------- end type-specific handlers -------------------- */ + + +/* unget a user pointer that has been dequeued. This functionality is especially important + * for consumer cancel cleanup handlers. To support it, a short list of ungotten user pointers + * is maintened in memory. + * rgerhards, 2008-01-20 + */ +static rsRetVal +queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_assert(pUsr); /* TODO: we aborted right at this place at least 3 times -- race? 2008-02-28, -03-10, -03-15 + The second time I noticed it the queue was in destruction with NO worker threads + running. The pUsr ptr was totally off and provided no clue what it may be pointing + at (except that it looked like the static data pool). Both times, the abort happend + inside an action queue */ + + dbgoprint((obj_t*) pThis, "ungetting user object %s\n", obj.GetName(pUsr)); + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + iRet = queueAddLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, pUsr); + ++pThis->iUngottenObjs; /* indicate one more */ + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + + RETiRet; +} + + +/* dequeues a user pointer from the ungotten queue. Pointers from there should always be + * dequeued first. + * + * This function must only be called when the mutex is locked! + * + * rgerhards, 2008-01-29 + */ +static rsRetVal +queueGetUngottenObj(queue_t *pThis, obj_t **ppUsr) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(ppUsr != NULL); + + iRet = queueDelLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, ppUsr); + --pThis->iUngottenObjs; /* indicate one less */ + dbgoprint((obj_t*) pThis, "dequeued ungotten user object %s\n", obj.GetName(*ppUsr)); + + RETiRet; +} + + +/* 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 +queueAdd(queue_t *pThis, void *pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + CHKiRet(pThis->qAdd(pThis, pUsr)); + + if(pThis->qType != QUEUETYPE_DIRECT) { + ++pThis->iQueueSize; + dbgoprint((obj_t*) pThis, "entry added, size now %d entries\n", pThis->iQueueSize); + } + +finalize_it: + RETiRet; +} + + +/* generic code to remove a queue entry + * rgerhards, 2008-01-29: we must first see if there is any object in the + * ungotten list and, if so, dequeue it first. + */ +static rsRetVal +queueDel(queue_t *pThis, void *pUsr) +{ + 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 + */ + if(pThis->iUngottenObjs > 0) { + iRet = queueGetUngottenObj(pThis, (obj_t**) pUsr); + } else { + iRet = pThis->qDel(pThis, pUsr); + --pThis->iQueueSize; + } + + dbgoprint((obj_t*) pThis, "entry deleted, state %d, size now %d entries\n", + iRet, pThis->iQueueSize); + + RETiRet; +} + + +/* This function shuts down all worker threads and waits until they + * have terminated. If they timeout, they are cancelled. Parameters have been set + * before this function is called so that DA queues will be fully persisted to + * disk (if configured to do so). + * 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. + */ +static rsRetVal queueShutdownWorkers(queue_t *pThis) +{ + DEFiRet; + DEFVARS_mutexProtection; + struct timespec tTimeout; + rsRetVal iRetLocal; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ + + dbgoprint((obj_t*) pThis, "initiating worker thread shutdown sequence\n"); + + /* we reduce the low water mark in any case. This is not absolutely necessary, but + * it is useful because we enable DA mode at several spots below and so we do not need + * to think about the low water mark each time. + */ + pThis->iHighWtrMrk = 1; /* if we do not do this, the DA queue will not stop! */ + pThis->iLowWtrMrk = 0; + + /* first try to shutdown the queue within the regular shutdown period */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(queueGetOverallQueueSize(pThis) > 0) { + if(pThis->bRunsDA) { + /* We may have waited on the low water mark. As it may have changed, we + * see if we reactivate the worker. + */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); + } + } + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + + /* Now wait for the queue's workers to shut down. Note that we run into the code even if we just found + * out there are no active workers - that doesn't matter: the wtp knows about that and so will + * return immediately. + * We do not yet care about the DA worker - that will be handled down later in the process. + * Note that we must not request shutdown right now - that may introduce a race: if the regular queue + * still runs DA assisted and the DA worker gets scheduled first, it will terminate itself (if the DA + * queue happens to be empty at that instant). Then the regular worker enqueues messages, what will lead + * to a restart of the worker. Of course, everything will continue to run, but in a bit sub-optimal way + * (from a performance point of view). So we don't do anything right now. The DA queue will continue to + * process messages and shutdown itself in any case if there is nothing to do. So we don't loose anything + * by not requesting shutdown now. + * rgerhards, 2008-01-25 + */ + /* 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 { + /* OK, the regular queue is now shut down. So we can now wait for the DA queue (if running DA) */ + dbgoprint((obj_t*) pThis, "regular queue workers shut down.\n"); + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(pThis->bRunsDA) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "we have a DA queue (0x%lx), requesting its shutdown.\n", + queueGetID(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 DA workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "shutdown timed out on DA queue (this is OK)\n"); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + } + + /* when we reach this point, both queues are either empty or the regular queue shutdown timeout + * has expired. Now we need to check if we are configured to not loose messages. If so, we need + * to persist the queue to disk (this is only possible if the queue is DA-enabled). We must also + * set the primary queue to SHUTDOWN_IMMEDIATE, as it shall now terminate as soon as its consumer + * is done. This is especially important as we otherwise may interfere with queue order while the + * DA consumer is running. -- rgerhards, 2008-01-27 + * Note: there was a note that we should not wait eternally on the DA worker if we run in + * enqueue-only note. I have reviewed the code and think there is no need for this check. Howerver, + * I'd like to keep this note in here should we happen to run into some related trouble. + * rgerhards, 2008-01-28 + */ + wtpSetState(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE); /* set primary queue to shutdown only */ + + /* at this stage, we need to have the DA worker properly initialized and running (if there is one) */ + if(pThis->bRunsDA) + queueWaitDAModeInitialized(pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + /* optimize parameters for shutdown of DA-enabled queues */ + if(pThis->bIsDA && queueGetOverallQueueSize(pThis) > 0 && pThis->bSaveOnShutdown) { + /* switch to enqueue-only mode so that no more actions happen */ + if(pThis->bRunsDA == 0) { + queueInitDA(pThis, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to DA mode */ + } else { + /* TODO: RACE: we may reach this point when the DA worker has been initialized (state 1) + * but is not yet running (state 2). In this case, pThis->pqDA is NULL! rgerhards, 2008-02-27 + */ + queueSetEnqOnly(pThis->pqDA, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to enqueue-only mode */ + } + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + /* make sure we do not timeout before we are done */ + dbgoprint((obj_t*) pThis, "bSaveOnShutdown configured, eternal timeout set\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); + 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); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + + /* now the primary queue is either empty, persisted to disk - or set to loose messages. So we + * can now request immediate shutdown of any remaining workers. Note that if bSaveOnShutdown was set, + * the queue is now empty. If regular workers are still running, and try to pull the next message, + * they will automatically terminate as there no longer is any message left to process. + */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(queueGetOverallQueueSize(pThis) > 0) { + timeoutComp(&tTimeout, pThis->toActShutdown); + if(wtpGetCurNumWrkr(pThis->pWtpReg, LOCK_MUTEX) > 0) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "trying immediate shutdown of regular workers\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); + } + /* we need to re-aquire the mutex for the next check in this case! */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + } + if(pThis->bIsDA && wtpGetCurNumWrkr(pThis->pWtpDA, LOCK_MUTEX) > 0) { + /* and now the same for the DA queue */ + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "trying immediate shutdown of DA workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, 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); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + + /* 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. Note that the + * cancellation handler will probably re-queue a user pointer, so the queue's enqueue + * function is still needed (what is no problem as we do not yet destroy the queue - but I + * thought it's a good idea to mention that fact). -- rgerhards, 2008-01-25 + */ + 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); + } + + + /* TODO: think: do we really need to do this here? Can't it happen on DA queue destruction? If we + * disable it, we get an assertion... I think this is OK, as we need to have a certain order and + * canceling the DA workers here ensures that order. But in any instant, we may have a look at this + * code after we have reaced the milestone. -- rgerhards, 2008-01-27 + */ + /* ... 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 ... all worker threads have terminated :-) + * Well, more precisely, they *are in termination*. Some cancel cleanup handlers + * may still be running. + */ + dbgoprint((obj_t*) pThis, "worker threads terminated, remaining queue size %d.\n", queueGetOverallQueueSize(pThis)); + + 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 queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)) +{ + DEFiRet; + queue_t *pThis; + + ASSERT(ppThis != NULL); + ASSERT(pConsumer != NULL); + ASSERT(iWorkerThreads >= 0); + + if((pThis = (queue_t *)calloc(1, sizeof(queue_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we have an object, so let's fill the properties */ + objConstructSetObjInfo(pThis); + if((pThis->pszSpoolDir = (uchar*) strdup((char*)glblGetWorkDir())) == 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 = (iMaxQueueSize < 100) ? iMaxQueueSize : 100; /* 100 should be far sufficient */ + pThis->iLightDlyMrk = iMaxQueueSize - (iMaxQueueSize / 100) * 70; /* default 70% */ + + pThis->lenSpoolDir = strlen((char*)pThis->pszSpoolDir); + pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */ + pThis->iQueueSize = 0; + pThis->iMaxQueueSize = iMaxQueueSize; + pThis->pConsumer = pConsumer; + pThis->iNumWorkerThreads = iWorkerThreads; + pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ + + pThis->pszFilePrefix = NULL; + pThis->qType = qType; + + /* set type-specific handlers and other very type-specific things (we can not totally hide it...) */ + switch(qType) { + case QUEUETYPE_FIXED_ARRAY: + pThis->qConstruct = qConstructFixedArray; + pThis->qDestruct = qDestructFixedArray; + pThis->qAdd = qAddFixedArray; + pThis->qDel = qDelFixedArray; + break; + case QUEUETYPE_LINKEDLIST: + pThis->qConstruct = qConstructLinkedList; + pThis->qDestruct = qDestructLinkedList; + pThis->qAdd = qAddLinkedList; + pThis->qDel = (rsRetVal (*)(queue_t*,void**)) qDelLinkedList; + break; + case QUEUETYPE_DISK: + pThis->qConstruct = qConstructDisk; + pThis->qDestruct = qDestructDisk; + pThis->qAdd = qAddDisk; + pThis->qDel = qDelDisk; + /* special handling */ + pThis->iNumWorkerThreads = 1; /* we need exactly one worker */ + break; + case QUEUETYPE_DIRECT: + pThis->qConstruct = qConstructDirect; + pThis->qDestruct = qDestructDirect; + pThis->qAdd = qAddDirect; + pThis->qDel = qDelDirect; + break; + } + +finalize_it: + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP + RETiRet; +} + + +/* cancellation cleanup handler for queueWorker () + * Updates admin structure and frees ressources. + * Params: + * arg1 - user pointer (in this case a queue_t) + * arg2 - user data pointer (in this case a queue data element, any object [queue's pUsr ptr!]) + * Note that arg2 may be NULL, in which case no dequeued but unprocessed pUsr exists! + * rgerhards, 2008-01-16 + */ +static rsRetVal +queueConsumerCancelCleanup(void *arg1, void *arg2) +{ + DEFiRet; + + queue_t *pThis = (queue_t*) arg1; + obj_t *pUsr = (obj_t*) arg2; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pUsr != NULL) { + /* make sure the data element is not lost */ + dbgoprint((obj_t*) pThis, "cancelation cleanup handler consumer called, we need to unget one user data element\n"); + CHKiRet(queueUngetObj(pThis, pUsr, LOCK_MUTEX)); + } + +finalize_it: + RETiRet; +} + + + +/* 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 and bRunsDA are 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 queueChkDiscardMsg(queue_t *pThis, int iQueueSize, int bRunsDA, void *pUsr) +{ + DEFiRet; + rsRetVal iRetLocal; + int iSeverity; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_assert(pUsr); + + if(pThis->iDiscardMrk > 0 && iQueueSize >= pThis->iDiscardMrk && bRunsDA == 0) { + iRetLocal = objGetSeverity(pUsr, &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); + objDestruct(pUsr); + 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; +} + + +/* dequeue the queued object for the queue consumers. + * rgerhards, 2008-10-21 + */ +static rsRetVal +queueDequeueConsumable(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + void *pUsr; + int iQueueSize; + int bRunsDA; /* cache for early mutex release */ + + /* dequeue element (still protected from mutex) */ + iRet = queueDel(pThis, &pUsr); + queueChkPersist(pThis); + iQueueSize = queueGetOverallQueueSize(pThis); /* cache this for after mutex release */ + bRunsDA = pThis->bRunsDA; /* cache this for after mutex release */ + + /* We now need to save the user pointer for the cancel cleanup handler, BUT ONLY + * if we could successfully obtain a user pointer. Otherwise, we would bring the + * cancel cleanup handler into big troubles (and we did ;)). Note that we can + * NOT set the variable further below, as this may lead to an object leak. We + * may get cancelled before we reach that part of the code, so the only + * solution is to do it here. -- rgerhards, 2008-02-27 + */ + if(iRet == RS_RET_OK) { + pWti->pUsrp = pUsr; + } + + /* 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 + */ + if(iQueueSize < pThis->iFullDlyMrk) { + pthread_cond_broadcast(&pThis->belowFullDlyWtrMrk); + } + + if(iQueueSize < pThis->iLightDlyMrk) { + pthread_cond_broadcast(&pThis->belowLightDlyWtrMrk); + } + + d_pthread_mutex_unlock(pThis->mut); + pthread_cond_signal(&pThis->notFull); + pthread_setcancelstate(iCancelStateSave, NULL); + /* WE ARE NO LONGER PROTECTED BY THE MUTEX */ + + /* do actual processing (the lengthy part, runs in parallel) + * If we had a problem while dequeing, we do not call the consumer, + * but we otherwise ignore it. This is in the hopes that it will be + * self-healing. However, this is really not a good thing. + * rgerhards, 2008-01-03 + */ + if(iRet != RS_RET_OK) + FINALIZE; + + /* we are running in normal, non-disk-assisted mode do a quick check if we need to drain the queue. + * 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: It is OK to use the cached iQueueSize here, because it does not hurt if it is slightly wrong. + */ + CHKiRet(queueChkDiscardMsg(pThis, iQueueSize, bRunsDA, pUsr)); + +finalize_it: + 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 +queueRateLimiter(queue_t *pThis) +{ + DEFiRet; + int iDelay; + int iHrCurr; + time_t tCurr; + struct tm m; + + ISOBJ_TYPE_assert(pThis, queue); + + dbgoprint((obj_t*) pThis, "entering rate limiter\n"); + + iDelay = 0; + if(pThis->iDeqtWinToHr != 25) { /* 25 means disabled */ + /* time calls are expensive, so only do them when needed */ + time(&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 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 +queueConsumerReg(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); + CHKiRet(pThis->pConsumer(pThis->pUsr, pWti->pUsrp)); + + /* we now need to check if we should deliberately delay processing a bit + * and, if so, do that. -- rgerhards, 2008-01-30 + */ + 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); + } + +finalize_it: + RETiRet; +} + + +/* This is a special consumer to feed the disk-queue in disk-assited 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 +queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); + CHKiRet(queueEnqObj(pThis->pqDA, eFLOWCTL_NO_DELAY, pWti->pUsrp)); + +finalize_it: + 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! + * If we are a child, we have done our duty when the queue is empty. In that case, + * we can terminate. + * Version for the DA worker thread. NOTE: the pThis->bRunsDA is different from + * the DA queue + */ +static int +queueChkStopWrkrDA(queue_t *pThis) +{ + /* if our queue is in destruction, we drain to the DA queue and so we shall not terminate + * until we have done so. + */ + int bStopWrkr; + + BEGINfunc + + if(pThis->bEnqOnly) { + bStopWrkr = 1; + } else { + if(pThis->bRunsDA) { + ASSERT(pThis->pqDA != NULL); + if( pThis->pqDA->bEnqOnly + && pThis->pqDA->sizeOnDiskMax > 0 + && pThis->pqDA->tVars.disk.sizeOnDisk > pThis->pqDA->sizeOnDiskMax) { + /* this queue can never grow, so we can give up... */ + bStopWrkr = 1; + } else if(queueGetOverallQueueSize(pThis) < pThis->iHighWtrMrk && pThis->bQueueStarted == 1) { + bStopWrkr = 1; + } else { + bStopWrkr = 0; + } + } else { + bStopWrkr = 1; + } + } + + ENDfunc + return bStopWrkr; +} + + +/* 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. NOTE: the pThis->bRunsDA is different from + * the DA queue + */ +static int +queueChkStopWrkrReg(queue_t *pThis) +{ + return pThis->bEnqOnly || pThis->bRunsDA || (pThis->pqParent != NULL && queueGetOverallQueueSize(pThis) == 0); +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! DA queue version + */ +static int +queueIsIdleDA(queue_t *pThis) +{ + /* remember: iQueueSize is the DA queue size, not the main queue! */ + /* TODO: I think we need just a single function for DA and non-DA mode - but I leave it for now as is */ + return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); +} +/* must only be called when the queue mutex is locked, else results + * are not stable! Regular queue version + */ +static int +queueIsIdleReg(queue_t *pThis) +{ +#if 0 /* enable for performance testing */ + int ret; + ret = queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk); + if(ret) fprintf(stderr, "queue is idle\n"); + return ret; +#else + /* regular code! */ + return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); +#endif +} + + +/* This function is called when a worker thread for the regular queue is shut down. + * If we are the primary queue, this is not really interesting to us. If, however, + * we are the DA (child) queue, that means the DA queue is empty. In that case, we + * need to signal the parent queue's DA worker, so that it can terminate DA mode. + * rgerhards, 2008-01-26 + * rgerhards, 2008-02-27: HOWEVER, in a shutdown condition, it may be that the parent's worker thread pool + * has already been terminated and destructed. This *is* a legal condition and happens + * from time to time in practice. So we need to signal only if there still is a + * parent DA worker queue. Please keep in mind that the the parent's DA worker + * pool is DIFFERENT from our (DA queue) regular worker pool. So when the parent's + * pWtpDA is destructed, there can still be some of our (DAq/wtp) threads be running. + * I am telling this, because I, too, always get confused by those... + */ +static rsRetVal +queueRegOnWrkrShutdown(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pqParent != NULL) { + pThis->pqParent->bChildIsDone = 1; /* indicate we are done */ + if(pThis->pqParent->pWtpDA != NULL) { /* see comment in function header from 2008-02-27 */ + wtpAdviseMaxWorkers(pThis->pqParent->pWtpDA, 1); /* reactivate DA worker (always 1) */ + } + } + + RETiRet; +} + + +/* The following function is called when a regular queue worker starts up. We need this + * hook to indicate in the parent queue (if we are a child) that we are not done yet. + */ +static rsRetVal +queueRegOnWrkrStartup(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pqParent != NULL) { + pThis->pqParent->bChildIsDone = 0; + } + + RETiRet; +} + + +/* start up the queue - it must have been constructed and parameters defined + * before. + */ +rsRetVal queueStart(queue_t *pThis) /* this is the ConstructionFinalizer */ +{ + DEFiRet; + rsRetVal iRetLocal; + int bInitialized = 0; /* is queue already initialized? */ + uchar pszBuf[64]; + size_t lenBuf; + + ASSERT(pThis != NULL); + + /* 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->condDAReady, 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 */ + + dbgoprint((obj_t*) pThis, "type %d, enq-only %d, disk assisted %d, maxFileSz %lld, qsize %d, child %d starting\n", + pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->iMaxFileSize, + queueGetOverallQueueSize(pThis), pThis->pqParent == NULL ? 0 : 1); + + if(pThis->qType == QUEUETYPE_DIRECT) + FINALIZE; /* with direct queues, we are already finished... */ + + /* create worker thread pools for regular operation. The DA pool is created on an as-needed + * basis, which potentially means never under most circumstances. + */ + 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)) queueRateLimiter)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrReg)); + CHKiRet(wtpSetpfIsIdle (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueIsIdleReg)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerReg)); + CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void*pWti))queueConsumerCancelCleanup)); + CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrStartup)); + CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrShutdown)); + 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)); + + /* initialize worker thread instances */ + if(pThis->bIsDA) { + /* If we are disk-assisted, we need to check if there is a QIF file + * which we need to load. -- rgerhards, 2008-01-15 + */ + iRetLocal = queueHaveQIF(pThis); + if(iRetLocal == RS_RET_OK) { + dbgoprint((obj_t*) pThis, "on-disk queue present, needs to be reloaded\n"); + queueInitDA(pThis, QUEUE_MODE_ENQDEQ, LOCK_MUTEX); /* initiate DA mode */ + bInitialized = 1; /* we are done */ + } else { + /* TODO: use logerror? -- rgerhards, 2008-01-16 */ + dbgoprint((obj_t*) pThis, "error %d trying to access on-disk queue files, starting without them. " + "Some data may be lost\n", iRetLocal); + } + } + + if(!bInitialized) { + dbgoprint((obj_t*) pThis, "queue starts up without (loading) any DA disk state (this is normal for the DA " + "queue itself!)\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. + */ + queueAdviseMaxWorkers(pThis); + pThis->bQueueStarted = 1; + +finalize_it: + RETiRet; +} + + +/* persist the queue to disk. 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 queuePersist(queue_t *pThis, int bIsCheckpoint) +{ + DEFiRet; + strm_t *psQIF = NULL; /* Queue Info File */ + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + obj_t *pUsr; + + ASSERT(pThis != NULL); + + if(pThis->qType != QUEUETYPE_DISK) { + if(queueGetOverallQueueSize(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", queueGetOverallQueueSize(pThis)); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); + + if((bIsCheckpoint != QUEUE_CHECKPOINT) && (queueGetOverallQueueSize(pThis) == 0)) { + if(pThis->bNeedDelQIF) { + unlink((char*)pszQIFNam); + pThis->bNeedDelQIF = 0; + } + /* indicate spool file needs to be deleted */ + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); + FINALIZE; /* nothing left to do, so be happy */ + } + + CHKiRet(strmConstruct(&psQIF)); + CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_WRITE)); + CHKiRet(strmSetiAddtlOpenFlags(psQIF, O_TRUNC)); + CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); + CHKiRet(strmConstructFinalize(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, iUngottenObjs, INT); + objSerializeSCALAR(psQIF, tVars.disk.sizeOnDisk, INT64); + objSerializeSCALAR(psQIF, tVars.disk.bytesRead, INT64); + CHKiRet(obj.EndSerialize(psQIF)); + + /* now we must persist all objects on the ungotten queue - they can not go to + * to the regular files. -- rgerhards, 2008-01-29 + */ + while(pThis->iUngottenObjs > 0) { + CHKiRet(queueGetUngottenObj(pThis, &pUsr)); + CHKiRet((objSerialize(pUsr))(pUsr, psQIF)); + objDestruct(pUsr); + } + + /* now persist the stream info */ + CHKiRet(strmSerialize(pThis->tVars.disk.pWrite, psQIF)); + CHKiRet(strmSerialize(pThis->tVars.disk.pRead, 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) { + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 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) + strmDestruct(&psQIF); + + RETiRet; +} + + +/* check if we need to persist the current queue info. If an + * error occurs, thus should be ignored by caller (but we still + * abide to our regular call interface)... + * rgerhards, 2008-01-13 + */ +rsRetVal queueChkPersist(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->iPersistUpdCnt && ++pThis->iUpdsSincePersist >= pThis->iPersistUpdCnt) { + queuePersist(pThis, QUEUE_CHECKPOINT); + pThis->iUpdsSincePersist = 0; + } + + RETiRet; +} + + +/* destructor for the queue object */ +BEGINobjDestruct(queue) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(queue) + pThis->bQueueInDestruction = 1; /* indicate we are in destruction (modifies some behaviour) */ + + /* shut down all workers (handles *all* of the persistence logic) + * See function head comment of queueShutdownWorkers () on why we don't call it + * We also 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) + queueShutdownWorkers(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) { + queueDestruct(&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(queuePersist(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->condDAReady); + pthread_cond_destroy(&pThis->notFull); + pthread_cond_destroy(&pThis->notEmpty); + pthread_cond_destroy(&pThis->belowFullDlyWtrMrk); + pthread_cond_destroy(&pThis->belowLightDlyWtrMrk); + + /* type-specific destructor */ + iRet = pThis->qDestruct(pThis); + + if(pThis->pszFilePrefix != NULL) + free(pThis->pszFilePrefix); + + if(pThis->pszSpoolDir != NULL) + free(pThis->pszSpoolDir); +ENDobjDestruct(queue) + + +/* 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 +queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix) +{ + DEFiRet; + + if(pThis->pszFilePrefix != NULL) + free(pThis->pszFilePrefix); + + 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 +queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(iMaxFileSize < 1024) { + ABORT_FINALIZE(RS_RET_VALUE_TOO_LOW); + } + + pThis->iMaxFileSize = iMaxFileSize; + +finalize_it: + RETiRet; +} + + +/* enqueue a new user data element + * Enqueues the new element and awakes worker thread. + * TODO: this code still uses the "discard if queue full" approach from + * the main queue. This needs to be reconsidered or, better, done via a + * caller-selectable parameter mode. For the time being, I leave it in. + * rgerhards, 2008-01-03 + */ +rsRetVal +queueEnqObj(queue_t *pThis, flowControl_t flowCtlType, void *pUsr) +{ + DEFiRet; + int iCancelStateSave; + int i; + struct timespec t; + + ISOBJ_TYPE_assert(pThis, queue); + + /* 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, 2008-01-08 + */ + if(pThis->qType != QUEUETYPE_DIRECT) { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(pThis->mut); + } + + /* first check if we need to discard this message (which will cause CHKiRet() to exit) */ + CHKiRet(queueChkDiscardMsg(pThis, pThis->iQueueSize, pThis->bRunsDA, pUsr)); + + /* then check if we need to add an assistance disk queue */ + if(pThis->bIsDA) + CHKiRet(queueChkStrtDA(pThis)); + + + /* 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) { + dbgoprint((obj_t*) pThis, "enqueueMsg: FullDelay mark reached for full delayble message - blocking.\n"); + pthread_cond_wait(&pThis->belowFullDlyWtrMrk, pThis->mut); /* TODO error check? But what do then? */ + } + } else if(flowCtlType == eFLOWCTL_LIGHT_DELAY) { + if(pThis->iQueueSize >= pThis->iLightDlyMrk) { + dbgoprint((obj_t*) pThis, "enqueueMsg: LightDelay mark reached for light delayble message - blocking a bit.\n"); + timeoutComp(&t, 1000); /* 1000 millisconds = 1 second TODO: make configurable */ + pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t); /* TODO error check? But what do then? */ + } + } + + /* 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)) { + dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); + timeoutComp(&t, pThis->toEnq); + if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { + dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } + } + +#if 0 // previous code, remove when done with advanced flow control + /* wait for the queue to be ready... */ + while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) + || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 + && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { + dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); + timeoutComp(&t, pThis->toEnq); + if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { + dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } + } +#endif + + /* and finally enqueue the message */ + CHKiRet(queueAdd(pThis, pUsr)); + queueChkPersist(pThis); + +finalize_it: + if(pThis->qType != QUEUETYPE_DIRECT) { + d_pthread_mutex_unlock(pThis->mut); + i = pthread_cond_signal(&pThis->notEmpty); + dbgoprint((obj_t*) pThis, "EnqueueMsg signaled condition (%d)\n", i); + pthread_setcancelstate(iCancelStateSave, NULL); + } + + /* make sure at least one worker is running. */ + if(pThis->qType != QUEUETYPE_DIRECT) { + queueAdviseMaxWorkers(pThis); + } + + RETiRet; +} + + +/* set queue mode to enqueue only or not + * There is one subtle issue: this method may be called during queue + * construction or while it is running. In the former case, the queue + * mutex does not yet exist (it is NULL), while in the later case it + * must be locked. The function detects the state and operates as + * required. + * rgerhards, 2008-01-16 + */ +static rsRetVal +queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, queue); + + /* for simplicity, we do one big mutex lock. This method is extremely seldom + * called, so that doesn't matter... -- rgerhards, 2008-01-16 + */ + if(pThis->mut != NULL) { + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + } + + if(bEnqOnly == pThis->bEnqOnly) + FINALIZE; /* no change, nothing to do */ + + if(pThis->bQueueStarted) { + /* we need to adjust queue operation only if we are not during initial param setup */ + if(bEnqOnly == 1) { + /* switch to enqueue-only mode */ + /* this means we need to terminate all workers - that's it... */ + dbgoprint((obj_t*) pThis, "switching to enqueue-only mode, terminating all worker threads\n"); + if(pThis->pWtpReg != NULL) + wtpWakeupAllWrkr(pThis->pWtpReg); + if(pThis->pWtpDA != NULL) + wtpWakeupAllWrkr(pThis->pWtpDA); + } else { + /* switch back to regular mode */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); /* we don't need this so far... */ + } + } + + pThis->bEnqOnly = bEnqOnly; + +finalize_it: + if(pThis->mut != NULL) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + RETiRet; +} + + +/* some simple object access methods */ +DEFpropSetMeth(queue, iPersistUpdCnt, int); +DEFpropSetMeth(queue, iDeqtWinFromHr, int); +DEFpropSetMeth(queue, iDeqtWinToHr, int); +DEFpropSetMeth(queue, toQShutdown, long); +DEFpropSetMeth(queue, toActShutdown, long); +DEFpropSetMeth(queue, toWrkShutdown, long); +DEFpropSetMeth(queue, toEnq, long); +DEFpropSetMeth(queue, iHighWtrMrk, int); +DEFpropSetMeth(queue, iLowWtrMrk, int); +DEFpropSetMeth(queue, iDiscardMrk, int); +DEFpropSetMeth(queue, iFullDlyMrk, int); +DEFpropSetMeth(queue, iDiscardSeverity, int); +DEFpropSetMeth(queue, bIsDA, int); +DEFpropSetMeth(queue, iMinMsgsPerWrkr, int); +DEFpropSetMeth(queue, bSaveOnShutdown, int); +DEFpropSetMeth(queue, pUsr, void*); +DEFpropSetMeth(queue, iDeqSlowdown, int); +DEFpropSetMeth(queue, 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 queueSetProperty(queue_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pProp != NULL); + + if(isProp("iQueueSize")) { + pThis->iQueueSize = pProp->val.num; + } else if(isProp("iUngottenObjs")) { + pThis->iUngottenObjs = pProp->val.num; + } else if(isProp("tVars.disk.sizeOnDisk")) { + pThis->tVars.disk.sizeOnDisk = pProp->val.num; + } else if(isProp("tVars.disk.bytesRead")) { + pThis->tVars.disk.bytesRead = 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 queueQueryInterface(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(queue, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_SETPROPERTY, queueSetProperty); +ENDObjClassInit(queue) + +/* vi:set ai: + */ diff --git a/queue.h b/queue.h new file mode 100644 index 00000000..9e75b31b --- /dev/null +++ b/queue.h @@ -0,0 +1,205 @@ +/* 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 "stream.h" + +/* 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; + void *pUsr; +} qLinkedList_t; + + +typedef struct qWrkThrd_s { + pthread_t thrdID; /* thread ID */ + qWrkCmd_t tCurrCmd; /* current command to be carried out by worker */ + obj_t *pUsr; /* current user object being processed (or NULL if none) */ + struct queue_s *pQueue; /* my queue (important if only the work thread instance is passed! */ + int iThrd; /* my worker thread array index */ + pthread_cond_t condInitDone; /* signaled when the thread startup is done (once per thread existance) */ + pthread_mutex_t mut; +} qWrkThrd_t; /* type for queue worker threads */ + +/* the queue object */ +typedef struct queue_s { + BEGINobjInstance; + queueType_t qType; + int bEnqOnly; /* does queue run in enqueue-only mode (1) or not (0)? */ + int bSaveOnShutdown;/* persists everthing on shutdown (if DA!)? 1-yes, 0-no */ + int bQueueStarted; /* has queueStart() been called on this queue? 1-yes, 0-no */ + int bQueueInDestruction;/* 1 if queue is in destruction process, 0 otherwise */ + 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; + void *pUsr; /* a global, user-supplied pointer. Is passed back to consumer. */ + 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 */ + 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 */ + int 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) */ + int toEnq; /* enqueue timeout */ + /* 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 + */ + /* ane dequeue time window */ + rsRetVal (*pConsumer)(void *,void*); /* 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 that was dequeued (actual sample: for actions, arg1 is the pAction and arg2 is pointer + * to message) + * rgerhards, 2008-01-28 + */ + /* type-specific handlers (set during construction) */ + rsRetVal (*qConstruct)(struct queue_s *pThis); + rsRetVal (*qDestruct)(struct queue_s *pThis); + rsRetVal (*qAdd)(struct queue_s *pThis, void *pUsr); + rsRetVal (*qDel)(struct queue_s *pThis, void **ppUsr); + /* end type-specific handler */ + /* 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 */ + pthread_cond_t condDAReady;/* signalled when the DA queue is fully initialized and ready for processing */ + int bChildIsDone; /* set to 1 when the child DA queue has finished processing, 0 otherwise */ + 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; + 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 */ + int bIsDA; /* is this queue disk assisted? */ + int bRunsDA; /* is this queue actually *running* 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 */ + /* some data elements for the queueUngetObj() functionality. This list should always be short + * and is always kept in memory + */ + qLinkedList_t *pUngetRoot; + qLinkedList_t *pUngetLast; + int iUngottenObjs; /* number of objects currently in the "ungotten" list */ + /* now follow queueing mode specific data elements */ + union { /* different data elements based on queue type (qType) */ + struct { + long head, tail; + void** pBuf; /* the queued user data structure */ + } farray; + struct { + qLinkedList_t *pRoot; + qLinkedList_t *pLast; + } linklist; + struct { + int64 sizeOnDisk; /* current amount of disk space used */ + int64 bytesRead; /* number of bytes read from current (undeleted!) file */ + strm_t *pWrite; /* current file to be written */ + strm_t *pRead; /* current file to be read */ + } disk; + } tVars; +} queue_t; + +/* some symbolic constants for easier reference */ +#define QUEUE_MODE_ENQDEQ 0 +#define QUEUE_MODE_ENQONLY 1 + +#define QUEUE_IDX_DA_WORKER 0 /* index for the DA worker (fixed) */ +#define QUEUE_PTR_DA_WORKER(x) (&((pThis)->pWrkThrds[0])) + +/* 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 queueDestruct(queue_t **ppThis); +rsRetVal queueEnqObj(queue_t *pThis, flowControl_t flwCtlType, void *pUsr); +rsRetVal queueStart(queue_t *pThis); +rsRetVal queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize); +rsRetVal queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix); +rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)); +PROTOTYPEObjClassInit(queue); +PROTOTYPEpropSetMeth(queue, iPersistUpdCnt, int); +PROTOTYPEpropSetMeth(queue, iDeqtWinFromHr, int); +PROTOTYPEpropSetMeth(queue, iDeqtWinToHr, int); +PROTOTYPEpropSetMeth(queue, toQShutdown, long); +PROTOTYPEpropSetMeth(queue, toActShutdown, long); +PROTOTYPEpropSetMeth(queue, toWrkShutdown, long); +PROTOTYPEpropSetMeth(queue, toEnq, long); +PROTOTYPEpropSetMeth(queue, iHighWtrMrk, int); +PROTOTYPEpropSetMeth(queue, iLowWtrMrk, int); +PROTOTYPEpropSetMeth(queue, iDiscardMrk, int); +PROTOTYPEpropSetMeth(queue, iDiscardSeverity, int); +PROTOTYPEpropSetMeth(queue, iMinMsgsPerWrkr, int); +PROTOTYPEpropSetMeth(queue, bSaveOnShutdown, int); +PROTOTYPEpropSetMeth(queue, pUsr, void*); +PROTOTYPEpropSetMeth(queue, iDeqSlowdown, int); +PROTOTYPEpropSetMeth(queue, sizeOnDiskMax, int64); +#define queueGetID(pThis) ((unsigned long) pThis) + +#endif /* #ifndef QUEUE_H_INCLUDED */ diff --git a/regexp.c b/regexp.c new file mode 100644 index 00000000..86b3e6c4 --- /dev/null +++ b/regexp.c @@ -0,0 +1,102 @@ +/* The regexp object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some 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 <regex.h> +#include <string.h> +#include <assert.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "regexp.h" + +MODULE_TYPE_LIB + +/* 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/regexp.h b/regexp.h new file mode 100644 index 00000000..8f6ac891 --- /dev/null +++ b/regexp.h @@ -0,0 +1,46 @@ +/* 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 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_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 */ @@ -1,7 +1,7 @@ .\" Copyright 2005 Rainer Gerhards and Adiscon for the rsyslog modifications .\" Distributed under the GNU General Public License. .\" -.TH RSYSLOGD 8 "12 February 2008" "Version 2.0.2" "Linux System Administration" +.TH RFC3195D 8 "02 April 2008" "Version 3.14.0" "Linux System Administration" .SH NAME rfc3195d \- RFC 3195 listener .SH SYNOPSIS @@ -8,19 +8,20 @@ * * Copyright 2003-2005 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ diff --git a/rklogd.8 b/rklogd.8 deleted file mode 100644 index 8ef99c2c..00000000 --- a/rklogd.8 +++ /dev/null @@ -1,440 +0,0 @@ -.\" Copyright 1994 Dr. Greg Wettstein, Enjellic Systems Development. -.\" May be distributed under the GNU General Public License -.\" Sun Jul 30 01:35:55 MET: Martin Schulze: Updates -.\" Sun Nov 19 23:22:21 MET: Martin Schulze: Updates -.\" Mon Aug 19 09:42:08 CDT 1996: Dr. G.W. Wettstein: Updates -.\" -.TH RKLOGD 8 "12 February 2008" "Version 2.0.2" "Linux System Administration" -.SH NAME -rklogd \- Kernel Log Daemon -.LP -.SH SYNOPSIS -.B rklogd -.RB [ " \-c " -.I n -] -.RB [ " \-d " ] -.RB [ " \-f " -.I fname -] -.RB [ " \-iI " ] -.RB [ " \-n " ] -.RB [ " \-o " ] -.RB [ " \-p " ] -.RB [ " \-s " ] -.RB [ " \-k " -.I fname -] -.RB [ " \-v " ] -.RB [ " \-x " ] -.RB [ " \-2 " ] -.LP -.SH DESCRIPTION -.B rklogd -is a system daemon which intercepts and logs Linux kernel -messages. -.LP -.SH OPTIONS -.TP -.BI "\-c " n -Sets the default log level of console messages to \fIn\fR. -.TP -.B "\-d" -Enable debugging mode. This will generate \fBLOTS\fR of output to -stderr. -.TP -.BI "\-f " file -Log messages to the specified filename rather than to the syslog facility. -.TP -.BI "\-i \-I" -Signal the currently executing rklogd daemon. Both of these switches control -the loading/reloading of symbol information. The \-i switch signals the -daemon to reload the kernel module symbols. The \-I switch signals for a -reload of both the static kernel symbols and the kernel module symbols. -.TP -.B "\-n" -Avoid auto-backgrounding. This is needed especially if the -.B rklogd -is started and controlled by -.BR init (8). -.TP -.B "-o" -Execute in 'one\-shot' mode. This causes \fBrklogd\fP to read and log -all the messages that are found in the kernel message buffers. After -a single read and log cycle the daemon exits. -.TP -.B "-p" -Enable paranoia. This option controls when rklogd loads kernel module symbol -information. Setting this switch causes rklogd to load the kernel module -symbol information whenever an Oops string is detected in the kernel message -stream. -.TP -.B "-s" -Force \fBrklogd\fP to use the system call interface to the kernel message -buffers. -.TP -.BI "\-k " file -Use the specified file as the source of kernel symbol information. -.TP -.B "\-v" -Print version and exit. -.TP -.B "\-x" -Omits EIP translation and therefore doesn't read the System.map file. -.TP -.B "\-2" -When symbols are expanded, print the line twice. Once with addresses -converted to symbols, once with the raw text. This allows external -programs such as ksymoops do their own processing on the original -data. -.LP -.SH OVERVIEW -The functionality of rklogd has been typically incorporated into other -versions of syslogd but this seems to be a poor place for it. In the -modern Linux kernel a number of kernel messaging issues such as -sourcing, prioritization and resolution of kernel addresses must be -addressed. Incorporating kernel logging into a separate process -offers a cleaner separation of services. - -In Linux there are two potential sources of kernel log information: the -.I /proc -file system and the syscall (sys_syslog) interface, although -ultimately they are one and the same. Klogd is designed to choose -whichever source of information is the most appropriate. It does this -by first checking for the presence of a mounted -.I /proc -file system. If this is found the -.I /proc/kmsg -file is used as the source of kernel log -information. If the proc file system is not mounted -.B rklogd -uses a -system call to obtain kernel messages. The command line switch -.RB ( "\-s" ) -can be used to force rklogd to use the system call interface as its -messaging source. - -If kernel messages are directed through the -.BR syslogd " daemon the " rklogd -daemon, as of version 1.1, has the ability to properly prioritize -kernel messages. Prioritization of the kernel messages was added to it -at approximately version 0.99pl13 of the kernel. The raw kernel messages -are of the form: -.IP -\<[0\-7]\>Something said by the kernel. -.PP -The priority of the kernel message is encoded as a single numeric -digit enclosed inside the <> pair. The definitions of these values is -given in the kernel include file kernel.h. When a message is received -from the kernel the rklogd daemon reads this priority level and assigns -the appropriate priority level to the syslog message. If file output -(\fB-f\fR) is used the prioritization sequence is left pre\-pended to the -kernel message. - -The -.B rklogd -daemon also allows the ability to alter the presentation of -kernel messages to the system console. Consequent with the -prioritization of kernel messages was the inclusion of default -messaging levels for the kernel. In a stock kernel the the default -console log level is set to 7. Any messages with a priority level -numerically lower than 7 (higher priority) appear on the console. - -Messages of priority level 7 are considered to be 'debug' messages and -will thus not appear on the console. Many administrators, -particularly in a multi\-user environment, prefer that all kernel -messages be handled by rklogd and either directed to a file or to -the syslogd daemon. This prevents 'nuisance' messages such as line -printer out of paper or disk change detected from cluttering the -console. - -When -.B \-c -is given on the commandline the -.B rklogd -daemon will execute a system call to inhibit all kernel messages from -being displayed on the console. Former versions always issued this -system call and defaulted to all kernel messages except for panics. -This is handled differently nowardays so -.B rklogd -doesn't need to set this value anymore. The -argument given to the \fB\-c\fR switch specifies the priority level of -messages which will be directed to the console. Note that messages of -a priority value LOWER than the indicated number will be directed to -the console. -.IP -For example, to have the kernel display all messages with a -priority level of 3 -.BR "" ( KERN_ERR ) -or more severe the following -command would be executed: -.IP -.nf - rklogd \-c 4 -.fi -.PP -The definitions of the numeric values for kernel messages are given in -the file -.IR kernel.h " which can be found in the " /usr/include/linux -directory if the kernel sources are installed. These values parallel -the syslog priority values which are defined in the file -.IR syslog.h " found in the " /usr/include/sys " sub\-directory." - -The rklogd daemon can also be used in a 'one\-shot' mode for reading the -kernel message buffers. One shot mode is selected by specifying the -\fB\-o\fR switch on the command line. Output will be directed to either the -syslogd daemon or to an alternate file specified by the \fB-f\fR switch. -.IP -For example, to read all the kernel messages after a system -boot and record them in a file called krnl.msg the following -command would be given. -.IP -.nf - rklogd -o -f ./krnl.msg -.fi -.PP -.SH KERNEL ADDRESS RESOLUTION -If the kernel detects an internal error condition a general protection -fault will be triggered. As part of the GPF handling procedure the -kernel prints out a status report indicating the state of the -processor at the time of the fault. Included in this display are the -contents of the microprocessor's registers, the contents of the kernel -stack and a tracing of what functions were being executed at the time -of the fault. - -This information is -.B EXTREMELY IMPORTANT -in determining what caused the internal error condition. The -difficulty comes when a kernel developer attempts to analyze this -information. The raw numeric information present in the protection -fault printout is of very little use to the developers. This is due -to the fact that kernels are not identical and the addresses of -variable locations or functions will not be the same in all kernels. -In order to correctly diagnose the cause of failure a kernel developer -needs to know what specific kernel functions or variable locations -were involved in the error. - -As part of the kernel compilation process a listing is created which -specified the address locations of important variables and function in -the kernel being compiled. This listing is saved in a file called -System.map in the top of the kernel directory source tree. Using this -listing a kernel developer can determine exactly what the kernel was -doing when the error condition occurred. - -The process of resolving the numeric addresses from the protection -fault printout can be done manually or by using the -.B ksymoops -program which is included in the kernel sources. - -As a convenience -.B rklogd -will attempt to resolve kernel numeric addresses to their symbolic -forms if a kernel symbol table is available at execution time. If you -require the original address of the symbol, use the -.B -2 -switch to preserve the numeric address. A -symbol table may be specified by using the \fB\-k\fR switch on the -command line. If a symbol file is not explicitly specified the -following filenames will be tried: - -.nf -.I /boot/System.map -.I /System.map -.I /usr/src/linux/System.map -.fi - -Version information is supplied in the system maps as of kernel -1.3.43. This version information is used to direct an intelligent -search of the list of symbol tables. This feature is useful since it -provides support for both production and experimental kernels. - -For example a production kernel may have its map file stored in -/boot/System.map. If an experimental or test kernel is compiled with -the sources in the 'standard' location of /usr/src/linux the system -map will be found in /usr/src/linux/System.map. When rklogd starts -under the experimental kernel the map in /boot/System.map will be -bypassed in favor of the map in /usr/src/linux/System.map. - -Modern kernels as of 1.3.43 properly format important kernel addresses -so that they will be recognized and translated by rklogd. Earlier -kernels require a source code patch be applied to the kernel sources. -This patch is supplied with the sysrklogd sources. - -The process of analyzing kernel protections faults works very well -with a static kernel. Additional difficulties are encountered when -attempting to diagnose errors which occur in loadable kernel modules. -Loadable kernel modules are used to implement kernel functionality in -a form which can be loaded or unloaded at will. The use of loadable -modules is useful from a debugging standpoint and can also be useful -in decreasing the amount of memory required by a kernel. - -The difficulty with diagnosing errors in loadable modules is due to -the dynamic nature of the kernel modules. When a module is loaded the -kernel will allocate memory to hold the module, when the module is -unloaded this memory will be returned back to the kernel. This -dynamic memory allocation makes it impossible to produce a map file -which details the addresses of the variable and functions in a kernel -loadable module. Without this location map it is not possible for a -kernel developer to determine what went wrong if a protection fault -involves a kernel module. - -.B rklogd -has support for dealing with the problem of diagnosing protection -faults in kernel loadable modules. At program start time or in -response to a signal the daemon will interrogate the kernel for a -listing of all modules loaded and the addresses in memory they are -loaded at. Individual modules can also register the locations of -important functions when the module is loaded. The addresses of these -exported symbols are also determined during this interrogation -process. - -When a protection fault occurs an attempt will be made to resolve -kernel addresses from the static symbol table. If this fails the -symbols from the currently loaded modules are examined in an attempt -to resolve the addresses. At the very minimum this allows rklogd to -indicate which loadable module was responsible for generating the -protection fault. Additional information may be available if the -module developer chose to export symbol information from the module. - -Proper and accurate resolution of addresses in kernel modules requires -that -.B rklogd -be informed whenever the kernel module status changes. The -.B \-i -and -.B \-I -switches can be used to signal the currently executing daemon that -symbol information be reloaded. Of most importance to proper -resolution of module symbols is the -.B \-i -switch. Each time a kernel module is loaded or removed from the -kernel the following command should be executed: - -.nf -.I rklogd \-i -.fi - -The -.B \-p -switch can also be used to insure that module symbol information is up -to date. This switch instructs -.B rklogd -to reload the module symbol information whenever a protection fault -is detected. Caution should be used before invoking the program in -\'paranoid\' mode. The stability of the kernel and the operating -environment is always under question when a protection fault occurs. -Since the rklogd daemon must execute system calls in order to read the -module symbol information there is the possibility that the system may -be too unstable to capture useful information. A much better policy -is to insure that rklogd is updated whenever a module is loaded or -unloaded. Having uptodate symbol information loaded increases the -probability of properly resolving a protection fault if it should occur. - -Included in the sysrklogd source distribution is a patch to the -modules-2.0.0 package which allows the -.B insmod, -.B rmmod -and -.B modprobe -utilities to automatically signal -.B rklogd -whenever a module is inserted or removed from the kernel. Using this -patch will insure that the symbol information maintained in rklogd is -always consistent with the current kernel state. -.PP -.SH SIGNAL HANDLING -The -.B rklogd -will respond to eight signals: -.BR SIGHUP ", " SIGINT ", " SIGKILL ", " SIGTERM ", " SIGTSTP ", " -.BR SIGUSR1 ", "SIGUSR2 " and " SIGCONT ". The" -.BR SIGINT ", " SIGKILL ", " SIGTERM " and " SIGHUP -signals will cause the daemon to close its kernel log sources and -terminate gracefully. - -The -.BR SIGTSTP " and " SIGCONT -signals are used to start and stop kernel logging. Upon receipt of a -.B SIGTSTP -signal the daemon will close its -log sources and spin in an idle loop. Subsequent receipt of a -.B SIGCONT -signal will cause the daemon to go through its initialization sequence -and re-choose an input source. Using -.BR SIGSTOP " and " SIGCONT -in combination the kernel log input can be re-chosen without stopping and -restarting the daemon. For example if the \fI/proc\fR file system is to be -un-mounted the following command sequence should be used: -.PP -.PD 0 -.TP - # kill -TSTP pid -.TP - # umount /proc -.TP - # kill -CONT pid -.PD -.PP -Notations will be made in the system logs with -.B LOG_INFO -priority -documenting the start/stop of logging. - -The -.BR SIGUSR1 " and " SIGUSR2 -signals are used to initiate loading/reloading of kernel symbol information. -Receipt of the -.B SIGUSR1 -signal will cause the kernel module symbols to be reloaded. Signaling the -daemon with -.B SIGUSR2 -will cause both the static kernel symbols and the kernel module symbols to -be reloaded. - -Provided that the System.map file is placed in an appropriate location the -signal of generally greatest usefulness is the -.B SIGUSR1 -signal. This signal is designed to be used to signal the daemon when kernel -modules are loaded/unloaded. Sending this signal to the daemon after a -kernel module state change will insure that proper resolution of symbols will -occur if a protection fault occurs in the address space occupied by a kernel -module. -.LP -.SH FILES -.PD 0 -.TP -.I /proc/kmsg -One Source for kernel messages -.B rklogd -.TP -.I /var/run/rklogd.pid -The file containing the process id of -.B rklogd -.TP -.I /boot/System.map, /System.map, /usr/src/linux/System.map -Default locations for kernel system maps. -.PD -.SH BUGS -Probably numerous. Well formed context diffs appreciated. -.LP -.SH AUTHOR -The -.B rklogd -was originally written by Steve Lord (lord@cray.com), Greg Wettstein -made major improvements. - -.PD 0 -.TP -Dr. Greg Wettstein (greg@wind.enjellic.com) -.TP -Enjellic Systems Development -.PD -.PP -.PD 0 -.TP -Oncology Research Divsion Computing Facility -.TP -Roger Maris Cancer Center -.TP -Fargo, ND 58122 -.PD diff --git a/rsyslog.conf b/rsyslog.conf index 9d34c805..94487601 100644 --- a/rsyslog.conf +++ b/rsyslog.conf @@ -1,10 +1,21 @@ +# 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. + +$ModLoad immark.so # provides --MARK-- message capability +$ModLoad imuxsock.so # provides support for local system logging (e.g. via logger command) +$ModLoad imklog.so # kernel logging (formerly provided by rklogd) + # 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 +*.info;mail.none;authpriv.none;cron.none -/var/log/messages # The authpriv file has restricted access. authpriv.* /var/log/secure @@ -14,13 +25,39 @@ mail.* -/var/log/maillog # Log cron stuff -cron.* /var/log/cron +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 +uucp,news.crit -/var/log/spooler # Save boot messages also to boot.log local7.* /var/log/boot.log + +# 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 /rsyslog/spool # where to place spool files +#$ActionQueueFileName uniqName # 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 + + +# ######### 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 +# Note: as of now, you need to use the -t command line option to +# enable TCP reception (e.g. -t514 to run a server at port 514/tcp) +# This will change in later v3 releases. + +# UDP Syslog Server: +#$ModLoad imudp.so # provides UDP syslog reception +#$UDPServerRun 514 # start a UDP syslog server at standard port 514 + diff --git a/rsyslog.conf.5 b/rsyslog.conf.5 index a21aca0f..0a2422c6 100644 --- a/rsyslog.conf.5 +++ b/rsyslog.conf.5 @@ -1,5 +1,5 @@ .\" rsyslog.conf - rsyslogd(8) configuration file -.\" Copyright 2003-2007 Rainer Gerhards and Adiscon GmbH. +.\" Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. .\" .\" This file is part of the rsyslog package, an enhanced system log daemon. .\" @@ -17,7 +17,7 @@ .\" 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 "12 February 2008" "Version 2.0.2" "Linux System Administration" +.TH RSYSLOG.CONF 5 "11 July 2008" "Version 3.18.0" "Linux System Administration" .SH NAME rsyslog.conf \- rsyslogd(8) configuration file .SH DESCRIPTION @@ -28,8 +28,104 @@ file is the main configuration file for the which logs system messages on *nix systems. This file specifies rules for logging. For special features see the .BR rsyslogd (8) -manpage. Ryslog.conf is backward-compatible with sysklogd's syslog.conf file. So if you migrate -from syklogd you can rename it and it should work. +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 +$InputUDPServerRun 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 imudp + +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 @@ -63,6 +159,54 @@ Every rule line consists of two fields, a selector field and an action field. Th 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 rsyslog(3). The names mentioned +below correspond to the similar LOG_-values in /usr/include/rsyslog.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 equation 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 equation +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 @@ -74,7 +218,16 @@ beginning with a slash ('/'). .B Example: .RS -*.* /var/log/traditionalfile.log;TraditionalFormat # log to a file in the traditional format +*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFormat # 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 @@ -87,14 +240,14 @@ the mkfifo(1) command before rsyslogd(8) is started. If the file you specified is a tty, special tty-handling is done, same with /dev/console. .SS Remote machine -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 this version of rsyslogd by default does NOT forward messages it has received -from the network to another host. Specify the "-h" option to enable this. +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. -Using the $GssMode directive TCP messages can be wrapped with GSS-API. +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 @@ -102,7 +255,21 @@ Using the $GssMode directive TCP messages can be wrapped with GSS-API. .RE .sp In the example above, messages are forwarded via UDP to the machine 192.168.0.1, the destination -port defaults to 514. +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 @@ -115,29 +282,13 @@ Emergency messages often go to all users currently online to notify them that so is happening with the system. To specify this wall(1)-feature use an asterisk ('*'). .SS Database table -This allows logging of the message to a database table. Currently, only MySQL databases are -supported. By default, a MonitorWare-compatible schema is required for this to work. You can +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. -The database writer is called by specifying a greater-then sign ('>') in front of the database -connect information. Immediately after that 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. - -.B Example: -.RS ->dbhost,dbname,dbuser,dbpassword;dbtemplate -.RE - -.B Important: to use the database functionality, the MySQL output module must be loaded -in the config file BEFORE the first database table action is used. This is done by placing the -.B $ModLoad -MySQL directive some place above the first use of the database write (we recommend doing at the -the beginning of the config file). -.B You have to install the rsyslog-mysql package to get this module. +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 @@ -173,11 +324,13 @@ The program-to-execute can be any valid executable. It receives the template str (argv[1]). .SH FILTER CONDITIONS -Rsyslog offers two different types "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 Blocks @@ -231,6 +384,10 @@ Checks if the value is found exactly at the beginning of the property value 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 @@ -263,11 +420,11 @@ To escape: .sp 0 \\ = \\\\ --> '\\' is used to escape (as in C) .sp 0 -$template TraditionalFormat,%timegenerated% %HOSTNAME% %syslogtag%%msg%\n" +$template TraditionalFormat,"%timegenerated% %HOSTNAME% %syslogtag%%msg%\n" Properties can be accessed by the property replacer (see there for details). -.B Please note that as of 1.15.0, templates can also by used to generate selector lines with dynamic file names. +.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: @@ -325,10 +482,6 @@ 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. -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 -NO_BACKSLASH_ESCAPES in your MySQL config, you need to supply a template with -the stdsql option. Otherwise you will become vulnerable to SQL injection. .SS Template examples Please note that the samples are split across multiple lines. A template MUST @@ -388,7 +541,7 @@ NOTE 2: You have to have MySQL module installed to use this template. 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 +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: @@ -584,6 +737,17 @@ replace control characters by spaces 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 @@ -600,10 +764,13 @@ The complete documentation can be found in the doc folder of the rsyslog distrib .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 -The .B rsyslogd is taken from sysklogd sources, which have been heavily modified by Rainer Gerhards (rgerhards@adiscon.com) and others. @@ -2,6 +2,25 @@ * rsyslog project (including all subprojects like * rfc3195d). * Begun 2005-09-15 RGerhards + * + * Copyright (C) 2005 by 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 INCLUDED_RSYSLOG_H #define INCLUDED_RSYSLOG_H @@ -24,6 +43,27 @@ # define _FILE_OFFSET_BITS 64 #endif + +/* some universal 64 bit define... */ +typedef long long int64; +typedef long long unsigned uint64; +typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */ + +#ifdef __hpux +typedef unsigned int u_int32_t; /* TODO: is this correct? */ +typedef int socklen_t; +#endif + +/* 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; + + /* 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 ;) @@ -33,8 +73,9 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth 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 = -1, - RS_RET_FALSE = -2, + RS_RET_TRUE = -1, /**< 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_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 */ @@ -46,6 +87,14 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth 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 */ @@ -69,11 +118,69 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth 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_QUEUE_FULL = -2074, /**< queue is full, operation could not be completed */ + RS_RET_CODE_ERR = -2109, /**< program code (internal) error */ + + /* RainerScript error messages (range 1000.. 1999) */ + RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ + + /* some generic error/status codes */ 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_OK = 0 /**< operation successful */ }; typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ @@ -85,9 +192,13 @@ typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ #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 rsRetVal iRet = RS_RET_OK +#define DEFiRet BEGINfunc rsRetVal iRet = RS_RET_OK +#define RETiRet do{ ENDfuncIRet return iRet; }while(0) + #define ABORT_FINALIZE(errCode) \ do { \ iRet = errCode; \ @@ -108,13 +219,13 @@ enum rsObjectID * invalid object pointer! */ OIDrsInvalid = 0, /**< value created by calloc(), so do not use ;) */ - /* The 0xEFCD is a debug aid. It helps us find object IDs in memory - * dumps (on X86, this is CDEF in the dump ;) + /* 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 = 0xEFCD0001, - OIDrsPars = 0xEFCD0002 + OIDrsCStr = 0x34120001, + OIDrsPars = 0x34120002 }; typedef enum rsObjectID rsObjID; @@ -149,6 +260,11 @@ typedef unsigned char uchar; # define __attribute__(x) /*NOTHING*/ #endif +/* 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" + #endif /* multi-include protection */ /* * vi:set ai: @@ -1,7 +1,7 @@ -.\" Copyright 2004-2005 Rainer Gerhards and Adiscon for the rsyslog modifications +.\" Copyright 2004-2008 Rainer Gerhards and Adiscon for the rsyslog modifications .\" May be distributed under the GNU General Public License .\" -.TH RSYSLOGD 8 "28 March 2008" "Version 2.0.5" "Linux System Administration" +.TH RSYSLOGD 8 "11 July 2008" "Version 3.18.0" "Linux System Administration" .SH NAME rsyslogd \- reliable and extended syslogd .SH SYNOPSIS @@ -9,19 +9,10 @@ rsyslogd \- reliable and extended syslogd .RB [ " \-4 " ] .RB [ " \-6 " ] .RB [ " \-A " ] -.RB [ " \-a " -.I socket -] .RB [ " \-d " ] -.RB [ " \-e " ] -.br .RB [ " \-f " .I config file ] -.RB [ " \-g " -.I port,max-nbr-of-sessions -] -.RB [ " \-h " ] .br .RB [ " \-i " .I pid file @@ -29,27 +20,13 @@ rsyslogd \- reliable and extended syslogd .RB [ " \-l " .I hostlist ] -.RB [ " \-m " -.I interval -] .RB [ " \-n " ] -.RB [ " \-o " ] .br -.RB [ " \-p" -.IB socket -] .RB [ " \-q " ] .RB [ " \-Q " ] -.RB [ " \-r " -.I [port] -] .RB [ " \-s " .I domainlist ] -.br -.RB [ " \-t " -.I port,max-nbr-of-sessions -] .RB [ " \-v " ] .RB [ " \-w " ] .RB [ " \-x " ] @@ -59,7 +36,17 @@ rsyslogd \- reliable and extended syslogd 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 (via UDP and TCP). +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 @@ -71,7 +58,7 @@ 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 MySQL databases. If the database option is used, tools like phpLogCon can +to databases. If the database option is used, tools like phpLogCon can be used to view the log data. While the @@ -81,7 +68,7 @@ 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 orginal syslogd. However, an original syslogd +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 @@ -98,21 +85,20 @@ 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. -For details and configuration examples, see the -.B rsyslog.conf (5) -man page. - .LP .SH OPTIONS +.B Note that in version 3 of rsyslog a number of command line options +.B have been deprecated and replaced with config file directives. The +.B -c option controls the backward compatibility mode in use. .TP .BI "\-A" -When sending UDP messages, there are potentially multiple pathes to +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 duplicaton. This option -should enabled only if it is fully understood. +reliability, but may also cause message duplication. This option +should be enabled only if it is fully understood. .TP .BI "\-4" Causes @@ -130,17 +116,27 @@ If neither -4 nor -6 is given, .B rsyslogd listens to all configured addresses of the system. .TP -.BI "\-a " "socket" -Using this argument you can specify additional sockets from that -.B rsyslogd -has to listen to. This is needed if you're going to let some daemon -run within a chroot() environment. You can use up to 19 additional -sockets. If your environment needs even more, you have to increase -the symbol -.B MAXFUNIX -within the syslogd.c source file. An example for a chroot() daemon is -described by the people from OpenBSD at -http://www.psionic.com/papers/dns.html. +.BI "\-c " "version" +Selects 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<version> where +.IR version +is the rsyslog version that it shall be +compatible with. Using -c0 tells rsyslog to be command-line compatible +to sysklogd, which is the default if -c is not given. +.B Please note that rsyslogd issues warning messages if the -c3 +.B command line option is not given. +This is to alert you that your are running in compatibility +mode. Compatibility mode interferes with your 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 advice 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. .TP .B "\-d" Turns on debug mode. Using this the daemon will not proceed a @@ -149,27 +145,11 @@ 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 -.B "\-e" -Set the default of $RepeatedMsgReduction config option to "off". -Hine: "e" like "every message". For further information, see there. -.TP .BI "\-f " "config file" Specify an alternative configuration file instead of .IR /etc/rsyslog.conf "," which is the default. .TP -.BI "\-g " -Identical to -t except that every tcp connection is authenticated -using gss-api (kerberos 5). Service name may be set using -$GssListenServiceName or the default "host" will be used. Encryption -can be used if specified by the client and supported by both sides. -.TP -.BI "\-h " -By default rsyslogd will not forward messages it receives from remote hosts. -Specifying this switch on the command line will cause the log daemon to -forward any remote messages it receives to forwarding hosts which have been -defined. -.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 @@ -180,33 +160,14 @@ 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 -.BI "\-m " "interval" -The -.B rsyslogd -logs a mark timestamp regularly. The default -.I interval -between two \fI-- MARK --\fR lines is 20 minutes. This can be changed -with this option. Setting the -.I interval -to zero turns it off entirely. -.TP .B "\-n" Avoid auto-backgrounding. This is needed especially if the .B rsyslogd is started and controlled by .BR init (8). .TP -.B "\-o" -Omit reading the standard local log socket. This option is most -useful for running multiple instances of rsyslogd on a single -machine. When specified, no local log socket is opened at all. -.TP -.BI "\-p " "socket" -You can specify an alternative unix domain socket instead of -.IR /dev/log "." -.TP .BI "\-q " "add hostname if DNS fails during ACL processing" -During ACL processing, hostnames are resolved to IP addreses for +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. @@ -214,15 +175,6 @@ slower operation once DNS is up again. .BI "\-Q " "do not resolve hostnames during ACL processing" Do not resolve hostnames to IP addresses during ACL processing. .TP -.BI "\-r " ["port"] -Activates the syslog/udp listener service. The listener -will listen to the specified port. If no port is specified, -0 is used as port number, which in turn will lead to a -lookup of the system default syslog port. If there is -no system default, 514 is used. Please note that the port -must immediately follow the -r option. Thus "-r514" is valid -while "-r 514" is invalid (note the space). -.TP .BI "\-s " "domainlist" Specify a domainname that should be stripped off before logging. Multiple domains may be specified using the colon (``:'') @@ -234,22 +186,11 @@ 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 "\-t " "port,max-nbr-of-sessions" -Activates the syslog/tcp listener service. The listener will listen to -the specified port. If max-nbr-of-sessions is specified, that becomes -the maximum number of concurrent tcp sessions. If not specified, the -default is 200. Please note that syslog/tcp is not standardized, -but the implementation in rsyslogd follows common practice and is -compatible with e.g. Cisco PIX, syslog-ng and MonitorWare (Windows). -Please note that the port -must immediately follow the -t option. Thus "-t514" is valid -while "-t 514" is invalid (note the space). -.TP .B "\-v" Print version and exit. .TP .B "\-w" -Supress warnings issued when messages are received from non-authorized +Suppress warnings issued when messages are received from non-authorized machines (those, that are in no AllowedSender list). .TP .B "\-x" @@ -262,11 +203,18 @@ reacts to a set of signals. You may easily send a signal to using the following: .IP .nf -kill -SIGNAL `cat /var/run/rsyslogd.pid` +kill -SIGNAL $(cat /var/run/syslogd.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/syslogd.pid) .fi .PP .TP -.B SIGHUP +.B HUP This lets .B rsyslogd perform a re-initialization. All open files are closed, the @@ -276,187 +224,20 @@ will be reread and the .BR rsyslog (3) facility is started again. .TP -.B SIGTERM "," SIGINT "," SIGQUIT +.B TERM ", " INT ", " QUIT .B Rsyslogd will die. .TP -.B SIGUSR1 +.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 SIGCHLD +.B CHLD Wait for childs if some were born, because of wall'ing messages. .LP -.SH SUPPORT FOR REMOTE LOGGING -.B Rsyslogd -provides network support to the syslogd facility. -Network support means that messages can be forwarded from one node -running rsyslogd to another node running rsyslogd (or a -compatible syslog implementation) where they will be -actually logged to a disk file. - -To enable this you have to specify one of -.B "\-g" -, -.B "\-r" -or -.B "\-t" -options on the command line. The default behavior is that -.B rsyslogd -won't listen to the network. You can also combine these -options if you want rsyslogd to listen to both TCP and UDP -messages. Only one of the TCP listener options can be used. -The last one specified will take effect. - -The strategy is to have rsyslogd listen on a unix domain socket for -locally generated log messages. This behavior will allow rsyslogd to -inter-operate with the syslog found in the standard C library. At the -same time rsyslogd listens on the standard syslog port for messages -forwarded from other hosts. To have this work correctly the -.BR services (5) -files (typically found in -.IR /etc ) -must have the following -entry: -.IP -.nf - syslog 514/udp -.fi -.PP -If this entry is missing -.B rsyslogd -will use the well known port of 514 (so in most cases, it's not -really needed). - -To cause messages to be forwarded to another host replace -the normal file line in the -.I rsyslog.conf -file with the name of the host to which the messages is to be sent -prepended with an @ (for UDP delivery) or the sequence @@ (for -TCP delivery). The host name can also be followed by a colon and -a port number, in which case the message is sent to the specified -port on the remote host. -.IP -For example, to forward -.B ALL -messages to a remote host use the -following -.I rsyslog.conf -entry: -.IP -.nf - # Sample rsyslogd configuration file to - # messages to a remote host forward all. - *.* @hostname -.fi -More samples can be found in sample.conf. - -If the remote hostname cannot be resolved at startup, because the -name-server might not be accessible (it may be started after rsyslogd) -you don't have to worry. -.B Rsyslogd -will retry to resolve the name ten times and then complain. Another -possibility to avoid this is to place the hostname in -.IR /etc/hosts . - -With normal -.BR syslogd s -you would get syslog-loops if you send out messages that were received -from a remote host to the same host (or more complicated to a third -host that sends it back to the first one, and so on). - -To avoid this no messages that were received from a -remote host are sent out to another (or the same) remote host. You can -disable this feature by the -.B \-h -option. - -If the remote host is located in the same domain as the host, -.B rsyslogd -is running on, only the simple hostname will be logged instead of -the whole fqdn. - -In a local network you may provide a central log server to have all -the important information kept on one machine. If the network consists -of different domains you don't have to complain about logging fully -qualified names instead of simple hostnames. You may want to use the -strip-domain feature -.B \-s -of this server. You can tell -.B rsyslogd -to strip off several domains other than the one the server is located -in and only log simple hostnames. - -Using the -.B \-l -option there's also a possibility to define single hosts as local -machines. This, too, results in logging only their simple hostnames -and not the fqdns. - -.SH OUTPUT TO DATABASES -.B Rsyslogd -has support for writing data to MySQL database tables. The exact specifics -are described in the -.B rsyslog.conf (5) -man page. Be sure to read it if you plan to use database logging. - -While it is often handy to have the data in a database, you must be aware -of the implications. Most importantly, database logging takes far -longer than logging to a text file. A system that can handle a large -log volume when writing to text files can most likely not handle -a similar large volume when writing to a database table. - -.SH OUTPUT TO NAMED PIPES (FIFOs) -.B Rsyslogd -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 pipy symbol (``|'') to the name of the -file. This is handy for debugging. Note that the fifo must be created -with the mkfifo command before -.B rsyslogd -is started. -.IP -The following configuration file routes debug messages from the -kernel to a fifo: -.IP -.nf - # Sample configuration to route kernel debugging - # messages ONLY to /usr/adm/debug which is a - # named pipe. - kern.=debug |/usr/adm/debug -.fi -.LP -.SH INSTALLATION CONCERNS -There is probably one important consideration when installing -rsyslogd. It is dependent on proper -formatting of messages by the syslog function. The functioning of the -syslog function in the shared libraries changed somewhere in the -region of libc.so.4.[2-4].n. The specific change was to -null-terminate the message before transmitting it to the -.I /dev/log -socket. Proper functioning of this version of rsyslogd is dependent on -null-termination of the message. - -This problem will typically manifest itself if old statically linked -binaries are being used on the system. Binaries using old versions of -the syslog function will cause empty lines to be logged followed by -the message with the first character in the message removed. -Relinking these binaries to newer versions of the shared libraries -will correct this problem. - -The -.BR rsyslogd (8) -can be run from -.BR init (8) -or started as part of the rc.* -sequence. If it is started from init the option \fI\-n\fR must be set, -otherwise you'll get tons of syslog daemons started. This is because -.BR init (8) -depends on the process ID. -.LP .SH SECURITY THREATS There is the potential for the rsyslogd daemon to be used as a conduit for a denial of service attack. @@ -477,7 +258,7 @@ if filled, will not impair the machine. 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 since +\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. @@ -485,7 +266,7 @@ Disabling inet domain sockets will limit risk to the local machine. 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 reply recorded messages or spoof a sender's IP address, +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. @@ -495,45 +276,7 @@ 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. Whenever -the configuration file is reread and re-parsed you'll see a tabular, -corresponding to the internal data structure. This tabular consists of -four fields: -.TP -.I number -This field contains a serial number starting by zero. This number -represents the position in the internal data structure (i.e. the -array). If one number is left out then there might be an error in the -corresponding line in -.IR /etc/rsyslog.conf . -.TP -.I pattern -This field is tricky and represents the internal structure -exactly. Every column stands for a facility (refer to -.BR syslog (3)). -As you can see, there are still some facilities left free for former -use, only the left most are used. Every field in a column represents -the priorities (refer to -.BR syslog (3)). -.TP -.I action -This field describes the particular action that takes place whenever a -message is received that matches the pattern. Refer to the -.BR syslog.conf (5) -manpage for all possible actions. -.TP -.I arguments -This field shows additional arguments to the actions in the last -field. For file-logging this is the filename for the logfile; for -user-logging this is a list of users; for remote logging this is the -hostname of the machine to log to; for console-logging this is the -used console; for tty-logging this is the specified tty; wall has no -additional arguments. -.TP -.SS templates -There will also be a second internal structure which lists all -defined templates and there contents. This also enables you to see -the internally-defined, hardcoded templates. +will be very verbose by writing much of what it does on stdout. .SH FILES .PD 0 .TP @@ -550,10 +293,58 @@ The Unix domain socket to from where local syslog messages are read. .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 annouyances. +bugs and annoyances. .SH Further Information Please visit .BR http://www.rsyslog.com/doc @@ -581,11 +372,4 @@ Adiscon GmbH Grossrinderfeld, Germany .TP rgerhards@adiscon.com - -.TP -Michael Meckelein -.TP -Adiscon GmbH -.TP -mmeckelein@adiscon.com .PD diff --git a/srUtils.c b/srUtils.c index 9dcea299..fa451b7e 100755..100644 --- a/srUtils.c +++ b/srUtils.c @@ -7,27 +7,28 @@ * \date 2003-09-09 * Coding begun. * - * Copyright 2003-2007 Rainer Gerhards and Adiscon GmbH. + * Copyright 2003-2008 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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" /* THIS IS A MODIFICATION FOR RSYSLOG! 2004-11-18 rgerards */ +#include "rsyslog.h" #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -38,12 +39,64 @@ #include <assert.h> #include <sys/wait.h> #include <ctype.h> -#include "liblogging-stub.h" /* THIS IS A MODIFICATION FOR RSYSLOG! 2004-11-18 rgerards */ +#include "liblogging-stub.h" #define TRUE 1 #define FALSE 0 #include "srUtils.h" #include "syslogd.h" +#include "obj.h" + +/* 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 + */ +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 */ + {"syslog", LOG_SYSLOG}, + {"user", LOG_USER}, + {"uucp", LOG_UUCP}, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif + {"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 * @@ -57,11 +110,11 @@ * public members * * ################################################################# */ -rsRetVal srUtilItoA(char *pBuf, int iLenBuf, int iToConv) +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv) { int i; int bIsNegative; - char szBuf[32]; /* sufficiently large for my lifespan and those of my children... ;) */ + 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... */ @@ -238,6 +291,216 @@ void skipWhiteSpace(uchar **pp) } -/* - * vi:set ai: +/* 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() + * rgerhards, 2008-01-14 + */ +rsRetVal +timeoutComp(struct timespec *pt, long iTimeout) +{ + assert(pt != NULL); + /* compute timeout */ + clock_gettime(CLOCK_REALTIME, pt); + pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */ + if(pt->tv_nsec > 999999999) { /* overrun? */ + pt->tv_nsec -= 1000000000; + } + pt->tv_sec += iTimeout / 1000; + 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; + + assert(pt != NULL); + /* compute timeout */ + clock_gettime(CLOCK_REALTIME, &t); + iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000; + iTimeout += (pt->tv_sec - t.tv_sec) * 1000; + + if(iTimeout < 0) + iTimeout = 0; + + 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) { +#ifdef __hpux + 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); + } + return (-1); +} + + +/* vim:set ai: */ diff --git a/srUtils.h b/srUtils.h index a4e70214..ebd6518f 100755..100644 --- a/srUtils.h +++ b/srUtils.h @@ -7,25 +7,44 @@ * * Copyright 2003-2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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 __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 @@ -43,7 +62,7 @@ * * \param iToConv The integer to be converted. */ -rsRetVal srUtilItoA(char *pBuf, int iLenBuf, int iToConv); +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv); /** * A method to duplicate a string for which the length is known. @@ -60,7 +79,47 @@ unsigned char *srUtilStrDup(unsigned char *pOld, size_t len); * 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 wait, uchar *arg); +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); + +/* mutex operations */ +/* some macros to cancel-safe lock a mutex (it will automatically be released + * when the thread is cancelled. This needs to be done as macros because + * pthread_cleanup_push sometimes is a macro that can not be used inside a function. + * It's a bit ugly, but works well... rgerhards, 2008-01-20 + */ +#define DEFVARS_mutex_cancelsafeLock int iCancelStateSave +#define mutex_cancelsafe_lock(mut) \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ + d_pthread_mutex_lock(mut); \ + pthread_cleanup_push(mutexCancelCleanup, mut); \ + pthread_setcancelstate(iCancelStateSave, NULL); +#define mutex_cancelsafe_unlock(mut) pthread_cleanup_pop(1) + +/* some useful constants */ +#define MUTEX_ALREADY_LOCKED 0 +#define LOCK_MUTEX 1 +#define DEFVARS_mutexProtection\ + int iCancelStateSave; \ + int bLockedOpIsLocked=0 +#define BEGIN_MTX_PROTECTED_OPERATIONS(mut, bMustLock) \ + if(bMustLock == LOCK_MUTEX) { \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ + d_pthread_mutex_lock(mut); \ + bLockedOpIsLocked = 1; \ + } +#define END_MTX_PROTECTED_OPERATIONS(mut) \ + if(bLockedOpIsLocked) { \ + d_pthread_mutex_unlock(mut); \ + pthread_setcancelstate(iCancelStateSave, NULL); \ + } #endif diff --git a/stream.c b/stream.c new file mode 100644 index 00000000..1be4571a --- /dev/null +++ b/stream.c @@ -0,0 +1,934 @@ +//TODO: O_TRUC mode! +/* 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 + * + * 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 <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 <errno.h> + +#include "rsyslog.h" +#include "syslogd.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "stream.h" + +/* static data */ +DEFobjStaticHelpers + +/* methods */ + +/* first, 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. + */ + +/* 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; + int iFlags; + + ASSERT(pThis != NULL); + ASSERT(pThis->tOperationsMode == STREAMMODE_READ || pThis->tOperationsMode == STREAMMODE_WRITE); + + if(pThis->fd != -1) + ABORT_FINALIZE(RS_RET_OK); + + if(pThis->pszFName == NULL) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + 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 = (uchar*) strdup((char*) 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)); + } + } + + /* compute which flags we need to provide to open */ + if(pThis->tOperationsMode == STREAMMODE_READ) + iFlags = O_RDONLY; + else + iFlags = O_WRONLY | O_CREAT; + + iFlags |= pThis->iAddtlOpenFlags; + + pThis->fd = open((char*)pThis->pszCurrFName, iFlags, pThis->tOpenMode); + if(pThis->fd == -1) { + int ierrnoSave = errno; + dbgoprint((obj_t*) pThis, "open error %d, file '%s'\n", errno, pThis->pszCurrFName); + if(ierrnoSave == ENOENT) + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + else + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + pThis->iCurrOffs = 0; + + dbgoprint((obj_t*) pThis, "opened file '%s' for %s (0x%x) as %d\n", pThis->pszCurrFName, + (pThis->tOperationsMode == STREAMMODE_READ) ? "READ" : "WRITE", iFlags, pThis->fd); + +finalize_it: + RETiRet; +} + + +/* 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. + */ +static rsRetVal strmCloseFile(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pThis->fd != -1); + dbgoprint((obj_t*) pThis, "file %d closing\n", pThis->fd); + + if(pThis->tOperationsMode == STREAMMODE_WRITE) + strmFlush(pThis); + + close(pThis->fd); // TODO: error check + pThis->fd = -1; + + if(pThis->bDeleteOnClose) { + unlink((char*) pThis->pszCurrFName); // TODO: check returncode + } + + pThis->iCurrOffs = 0; /* we are back at begin of file */ + if(pThis->pszCurrFName != NULL) { + free(pThis->pszCurrFName); /* no longer needed in any case (just for open) */ + pThis->pszCurrFName = NULL; + } + + 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 + */ +static rsRetVal +strmHandleEOFMonitor(strm_t *pThis) +{ + DEFiRet; + struct stat statOpen; + struct stat statName; + + ISOBJ_TYPE_assert(pThis, strm); + /* find inodes of both current descriptor as well as file now in file + * system. If they are different, the file has been rotated (or + * otherwise rewritten). We also check the size, because the inode + * does not change if the file is truncated (this, BTW, is also a case + * where we actually loose log lines, because we can not do anything + * against truncation...). We do NOT rely on the time of last + * modificaton because that may not be available under all + * circumstances. -- rgerhards, 2008-02-13 + */ + if(fstat(pThis->fd, &statOpen) == -1) + ABORT_FINALIZE(RS_RET_IO_ERROR); + if(stat((char*) pThis->pszCurrFName, &statName) == -1) + ABORT_FINALIZE(RS_RET_IO_ERROR); + if(statOpen.st_ino == statName.st_ino && pThis->iCurrOffs == statName.st_size) { + ABORT_FINALIZE(RS_RET_EOF); + } else { + /* we had a file change! */ + 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: + 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) */ +#if 0 + if(pThis->iMaxFiles == 0) /* TODO: why do we need this? ;) */ + ABORT_FINALIZE(RS_RET_EOF); +#endif + 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) +{ + DEFiRet; + int bRun; + long iLenRead; + + 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)); + iLenRead = read(pThis->fd, pThis->pIOBuf, pThis->sIOBufSize); + dbgoprint((obj_t*) pThis, "file %d read %ld bytes\n", pThis->fd, iLenRead); + if(iLenRead == 0) { + CHKiRet(strmHandleEOF(pThis)); + } else if(iLenRead < 0) + ABORT_FINALIZE(RS_RET_IO_ERROR); + else { /* good read */ + 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). + */ +rsRetVal strmReadChar(strm_t *pThis, uchar *pC) +{ + 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)); + } + + /* 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 + */ +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 line from a strm file. A line is terminated by LF. The LF is read, but it + * is not returned in the buffer (it is discared). The caller is responsible for + * destruction of the returned CStr object! -- rgerhards, 2008-01-07 + * rgerhards, 2008-03-27: I now use the ppCStr directly, without any interim + * string pointer. The reason is that this function my be called by inputs, which + * are pthread_killed() upon termination. So if we use their native pointer, they + * can cleanup (but only then). + */ +rsRetVal +strmReadLine(strm_t *pThis, cstr_t **ppCStr) +{ + DEFiRet; + uchar c; + + ASSERT(pThis != NULL); + ASSERT(ppCStr != NULL); + + CHKiRet(rsCStrConstruct(ppCStr)); + + /* now read the line */ + CHKiRet(strmReadChar(pThis, &c)); + while(c != '\n') { + CHKiRet(rsCStrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } + CHKiRet(rsCStrFinish(*ppCStr)); + +finalize_it: + if(iRet != RS_RET_OK && *ppCStr != NULL) + rsCStrDestruct(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->iUngetC = -1; + pThis->sType = STREAMTYPE_FILE_SINGLE; + pThis->sIOBufSize = glblGetIOBufSize(); + pThis->tOpenMode = 0600; /* TODO: make configurable */ +ENDobjConstruct(strm) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal strmConstructFinalize(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->pIOBuf == NULL) { /* allocate our io buffer in case we have not yet */ + if((pThis->pIOBuf = (uchar*) malloc(sizeof(uchar) * pThis->sIOBufSize)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + pThis->iBufPtrMax = 0; /* results in immediate read request */ + } + +finalize_it: + RETiRet; +} + + +/* destructor for the strm object */ +BEGINobjDestruct(strm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(strm) + if(pThis->tOperationsMode == STREAMMODE_WRITE) + strmFlush(pThis); + + /* ... then free resources */ + if(pThis->fd != -1) + strmCloseFile(pThis); + + if(pThis->pszDir != NULL) + free(pThis->pszDir); + if(pThis->pIOBuf != NULL) + free(pThis->pIOBuf); + if(pThis->pszCurrFName != NULL) + free(pThis->pszCurrFName); + if(pThis->pszFName != NULL) + free(pThis->pszFName); +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; + + 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; +} + +/* write memory buffer to a stream object. + * To support direct writes of large objects, this method may be called + * with a buffer pointing to some region other than the stream buffer itself. + * However, in that case the stream buffer must be empty (strmFlush() has to + * be called before), because we would otherwise mess up with the sequence + * inside the stream. -- rgerhards, 2008-01-10 + */ +static rsRetVal strmWriteInternal(strm_t *pThis, uchar *pBuf, size_t lenBuf) +{ + DEFiRet; + int iWritten; + + ASSERT(pThis != NULL); + ASSERT(pBuf == pThis->pIOBuf || pThis->iBufPtr == 0); + + if(pThis->fd == -1) + CHKiRet(strmOpenFile(pThis)); + + iWritten = write(pThis->fd, pBuf, lenBuf); + dbgoprint((obj_t*) pThis, "file %d write wrote %d bytes\n", pThis->fd, iWritten); + /* TODO: handle error case -- rgerhards, 2008-01-07 */ + + /* Now indicate buffer empty again. We do this in any case, because there + * is no way we could react more intelligently to an error during write. + * This MUST be done BEFORE strCheckNextOutputFile(), otherwise we have an + * endless loop. We reset the buffer pointer also in finalize_it - this is + * necessary if we run into problems. Not resetting it would again cause an + * endless loop. So it is better to loose some data (which also justifies + * duplicating that code, too...) -- rgerhards, 2008-01-10 + */ + pThis->iBufPtr = 0; + pThis->iCurrOffs += iWritten; + /* update user counter, if provided */ + if(pThis->pUsrWCntr != NULL) + *pThis->pUsrWCntr += iWritten; + + if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) + CHKiRet(strmCheckNextOutputFile(pThis)); + +finalize_it: + pThis->iBufPtr = 0; /* see comment above */ + + 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 + */ +rsRetVal strmFlush(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + dbgoprint((obj_t*) pThis, "file %d flush, buflen %ld\n", pThis->fd, (long) pThis->iBufPtr); + + if(pThis->tOperationsMode == STREAMMODE_WRITE && pThis->iBufPtr > 0) { + iRet = strmWriteInternal(pThis, pThis->pIOBuf, pThis->iBufPtr); + } + + 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, off_t offs) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(pThis->fd == -1) + strmOpenFile(pThis); + else + strmFlush(pThis); + int i; + dbgoprint((obj_t*) pThis, "file %d seek, pos %ld\n", pThis->fd, (long) offs); + i = lseek(pThis->fd, offs, SEEK_SET); // TODO: check error! + pThis->iCurrOffs = offs; /* we are now at *this* offset */ + pThis->iBufPtr = 0; /* buffer invalidated */ + + RETiRet; +} + + +/* seek to current offset. This is primarily a helper to readjust the OS file + * pointer after a strm object has been deserialized. + */ +rsRetVal strmSeekCurrOffs(strm_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + iRet = strmSeek(pThis, pThis->iCurrOffs); + RETiRet; +} + + +/* write a *single* character to a stream object -- rgerhards, 2008-01-10 + */ +rsRetVal strmWriteChar(strm_t *pThis, uchar c) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* if the buffer is full, we need to flush before we can write */ + if(pThis->iBufPtr == pThis->sIOBufSize) { + CHKiRet(strmFlush(pThis)); + } + /* we now always have space for one character, so we simply copy it */ + *(pThis->pIOBuf + pThis->iBufPtr) = c; + pThis->iBufPtr++; + +finalize_it: + RETiRet; +} + + +/* write an integer value (actually a long) to a stream object */ +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 + */ +rsRetVal strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf) +{ + DEFiRet; + size_t iPartial; + + ASSERT(pThis != NULL); + ASSERT(pBuf != NULL); + + /* check if the to-be-written data is larger than our buffer size */ + if(lenBuf >= pThis->sIOBufSize) { + /* it is - so we do a direct write, that is most efficient. + * TODO: is it really? think about disk block sizes! + */ + CHKiRet(strmFlush(pThis)); /* we need to flush first!!! */ + CHKiRet(strmWriteInternal(pThis, pBuf, lenBuf)); + } else { + /* data fits into a buffer - we just need to see if it + * fits into the current buffer... + */ + if(pThis->iBufPtr + lenBuf > pThis->sIOBufSize) { + /* nope, so we must split it */ + iPartial = pThis->sIOBufSize - pThis->iBufPtr; /* this fits in current buf */ + if(iPartial > 0) { /* the buffer was exactly full, can not write anything! */ + memcpy(pThis->pIOBuf + pThis->iBufPtr, pBuf, iPartial); + pThis->iBufPtr += iPartial; + } + CHKiRet(strmFlush(pThis)); /* get a new buffer for rest of data */ + memcpy(pThis->pIOBuf, pBuf + iPartial, lenBuf - iPartial); + pThis->iBufPtr = lenBuf - iPartial; + } else { + /* we have space, so we simply copy over the string */ + memcpy(pThis->pIOBuf + pThis->iBufPtr, pBuf, lenBuf); + pThis->iBufPtr += lenBuf; + } + } + +finalize_it: + RETiRet; +} + + +/* property set methods */ +/* simple ones first */ +DEFpropSetMeth(strm, bDeleteOnClose, int) +DEFpropSetMeth(strm, iMaxFileSize, int) +DEFpropSetMeth(strm, iFileNumDigits, int) +DEFpropSetMeth(strm, tOperationsMode, int) +DEFpropSetMeth(strm, tOpenMode, mode_t) +DEFpropSetMeth(strm, sType, strmType_t); + +rsRetVal strmSetiMaxFiles(strm_t *pThis, int iNewVal) +{ + pThis->iMaxFiles = iNewVal; + pThis->iFileNumDigits = getNumberDigits(iNewVal); + return RS_RET_OK; +} + +rsRetVal strmSetiAddtlOpenFlags(strm_t *pThis, int iNewVal) +{ + DEFiRet; + + if(iNewVal & O_APPEND) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + pThis->iAddtlOpenFlags = iNewVal; + +finalize_it: + RETiRet; +} + + +/* 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 + */ +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 + */ +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); + + if((pThis->pszDir = malloc(sizeof(uchar) * iLenDir + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + 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 + */ +rsRetVal strmRecordBegin(strm_t *pThis) +{ + ASSERT(pThis != NULL); + ASSERT(pThis->bInRecord == 0); + pThis->bInRecord = 1; + return RS_RET_OK; +} + +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 + */ +rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm) +{ + DEFiRet; + int i; + long l; + + ISOBJ_TYPE_assert(pThis, strm); + ISOBJ_TYPE_assert(pStrm, strm); + + strmFlush(pThis); + 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 = (long) pThis->iCurrOffs; + objSerializeSCALAR_VAR(pStrm, iCurrOffs, LONG, l); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + 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 + */ +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*) name, sizeof(name) - 1) +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("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)); + } + +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 + */ +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). + */ + //xxxpIf->oID = OBJvm; + +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/stream.h b/stream.h new file mode 100644 index 00000000..371358ab --- /dev/null +++ b/stream.h @@ -0,0 +1,131 @@ +/* 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. + * + * 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 STREAM_H_INCLUDED +#define STREAM_H_INCLUDED + +#include <pthread.h> +#include "obj-types.h" +#include "glbl.h" +#include "stream.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 */ +} strmType_t; + +typedef enum { + STREAMMMODE_INVALID = 0, + STREAMMODE_READ = 1, + STREAMMODE_WRITE = 2 +} strmMode_t; + +/* 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; + int iAddtlOpenFlags; /* can be used to specifiy additional (compatible!) open flags */ + 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) */ + int 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 */ + size_t sIOBufSize;/* size of IO buffer */ + uchar *pszDir; /* Directory */ + int lenDir; + int fd; /* the file descriptor, -1 if closed */ + uchar *pszCurrFName; /* name of current file (if open) */ + uchar *pIOBuf; /* io Buffer */ + 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 */ + int bInRecord; /* if 1, indicates that we are currently writing a not-yet complete record */ +} strm_t; + +/* interfaces */ +BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */ +ENDinterface(strm) +#define strmCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +rsRetVal strmConstruct(strm_t **ppThis); +rsRetVal strmConstructFinalize(strm_t __attribute__((unused)) *pThis); +rsRetVal strmDestruct(strm_t **ppThis); +rsRetVal strmSetMaxFileSize(strm_t *pThis, int64 iMaxFileSize); +rsRetVal strmSetFileName(strm_t *pThis, uchar *pszName, size_t iLenName); +rsRetVal strmReadChar(strm_t *pThis, uchar *pC); +rsRetVal strmUnreadChar(strm_t *pThis, uchar c); +rsRetVal strmReadLine(strm_t *pThis, cstr_t **ppCStr); +rsRetVal strmSeekCurrOffs(strm_t *pThis); +rsRetVal strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf); +rsRetVal strmWriteChar(strm_t *pThis, uchar c); +rsRetVal strmWriteLong(strm_t *pThis, long i); +rsRetVal strmSetFName(strm_t *pThis, uchar *pszPrefix, size_t iLenPrefix); +rsRetVal strmSetDir(strm_t *pThis, uchar *pszDir, size_t iLenDir); +rsRetVal strmFlush(strm_t *pThis); +rsRetVal strmRecordBegin(strm_t *pThis); +rsRetVal strmRecordEnd(strm_t *pThis); +rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm); +rsRetVal strmSetiAddtlOpenFlags(strm_t *pThis, int iNewVal); +rsRetVal strmGetCurrOffset(strm_t *pThis, int64 *pOffs); +rsRetVal strmSetWCntr(strm_t *pThis, number_t *pWCnt); +PROTOTYPEObjClassInit(strm); +PROTOTYPEpropSetMeth(strm, bDeleteOnClose, int); +PROTOTYPEpropSetMeth(strm, iMaxFileSize, int); +PROTOTYPEpropSetMeth(strm, iMaxFiles, int); +PROTOTYPEpropSetMeth(strm, iFileNumDigits, int); +PROTOTYPEpropSetMeth(strm, tOperationsMode, int); +PROTOTYPEpropSetMeth(strm, tOpenMode, mode_t); +PROTOTYPEpropSetMeth(strm, sType, strmType_t); + +#endif /* #ifndef STREAM_H_INCLUDED */ diff --git a/stringbuf.c b/stringbuf.c index 01467f4d..4254d5bd 100755..100644 --- a/stringbuf.c +++ b/stringbuf.c @@ -5,8 +5,26 @@ * 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). - * This code is placed under the GPL. * begun 2005-09-07 rgerhards + * + * Copyright (C) 2007 by 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" @@ -15,29 +33,35 @@ #include <string.h> #include <ctype.h> #include <sys/types.h> -#include <regex.h> #include "rsyslog.h" #include "stringbuf.h" #include "srUtils.h" +#include "regexp.h" +#include "obj.h" /* ################################################################# * * private members * * ################################################################# */ - +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(regexp) /* ################################################################# * * public members * * ################################################################# */ -rsCStrObj *rsCStrConstruct(void) +rsRetVal rsCStrConstruct(cstr_t **ppThis) { - rsCStrObj *pThis; + DEFiRet; + cstr_t *pThis; + + ASSERT(ppThis != NULL); - if((pThis = (rsCStrObj*) calloc(1, sizeof(rsCStrObj))) == NULL) - return NULL; + if((pThis = (cstr_t*) calloc(1, sizeof(cstr_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); rsSETOBJTYPE(pThis, OIDrsCStr); pThis->pBuf = NULL; @@ -45,71 +69,82 @@ rsCStrObj *rsCStrConstruct(void) pThis->iBufSize = 0; pThis->iStrLen = 0; pThis->iAllocIncrement = RS_STRINGBUF_ALLOC_INCREMENT; + *ppThis = pThis; - return pThis; +finalize_it: + RETiRet; } + /* construct from sz string * rgerhards 2005-09-15 */ -rsRetVal rsCStrConstructFromszStr(rsCStrObj **ppThis, uchar *sz) +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz) { - rsCStrObj *pThis; + DEFiRet; + cstr_t *pThis; assert(ppThis != NULL); - if((pThis = rsCStrConstruct()) == NULL) - return RS_RET_OUT_OF_MEMORY; + CHKiRet(rsCStrConstruct(&pThis)); pThis->iBufSize = pThis->iStrLen = strlen((char*)(char *) sz); if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { RSFREEOBJ(pThis); - return RS_RET_OUT_OF_MEMORY; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } /* we do NOT need to copy the \0! */ memcpy(pThis->pBuf, sz, pThis->iStrLen); *ppThis = pThis; - return RS_RET_OK; + +finalize_it: + RETiRet; } /* construct from CStr object. only the counted string is * copied, not the szString. * rgerhards 2005-10-18 */ -rsRetVal rsCStrConstructFromCStr(rsCStrObj **ppThis, rsCStrObj *pFrom) +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom) { - rsCStrObj *pThis; + DEFiRet; + cstr_t *pThis; assert(ppThis != NULL); rsCHECKVALIDOBJECT(pFrom, OIDrsCStr); - if((pThis = rsCStrConstruct()) == NULL) - return RS_RET_OUT_OF_MEMORY; + CHKiRet(rsCStrConstruct(&pThis)); pThis->iBufSize = pThis->iStrLen = pFrom->iStrLen; if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { RSFREEOBJ(pThis); - return RS_RET_OUT_OF_MEMORY; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } /* copy properties */ memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen); *ppThis = pThis; - return RS_RET_OK; +finalize_it: + RETiRet; } -void rsCStrDestruct(rsCStrObj *pThis) +void rsCStrDestruct(cstr_t **ppThis) { + cstr_t *pThis = *ppThis; + /* rgerhards 2005-10-19: The free of pBuf was contained in conditional compilation. * The code was only compiled if STRINGBUF_TRIM_ALLOCSIZE was set to 1. I honestly * do not know why it was so, I think it was an artifact. Anyhow, I have changed this * now. Should there any issue occur, this comment hopefully will shed some light * on what happened. I re-verified, and this function has never before been called * by anyone. So changing it can have no impact for obvious reasons... + * + * rgerhards, 2008-02-20: I changed the interface to the new calling conventions, where + * the destructor receives a pointer to the object, so that it can set it to NULL. */ if(pThis->pBuf != NULL) { free(pThis->pBuf); @@ -120,39 +155,76 @@ void rsCStrDestruct(rsCStrObj *pThis) } 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 + */ +static rsRetVal rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded) +{ + DEFiRet; + uchar *pNewBuf; + size_t iNewSize; + + /* first compute the new size needed */ + if(iMinNeeded > pThis->iAllocIncrement) { + /* we allocate "n" iAllocIncrements. 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 / pThis->iAllocIncrement) + 1) * pThis->iAllocIncrement; + } else { + iNewSize = pThis->iBufSize + pThis->iAllocIncrement; + } + iNewSize += pThis->iBufSize; /* add current size */ + + /* and then allocate and copy over */ + /* DEV debugging only: dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize); */ + if((pNewBuf = (uchar*) malloc(iNewSize * sizeof(uchar))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pNewBuf, pThis->pBuf, pThis->iBufSize); + pThis->iBufSize = iNewSize; + if(pThis->pBuf != NULL) { + free(pThis->pBuf); + } + pThis->pBuf = pNewBuf; + +finalize_it: + RETiRet; } -rsRetVal rsCStrAppendStrWithLen(rsCStrObj *pThis, uchar* psz, size_t iStrLen) +/* 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) { - rsRetVal iRet; - int iOldAllocInc; + DEFiRet; rsCHECKVALIDOBJECT(pThis, OIDrsCStr); assert(psz != NULL); - /* we first check if the to-be-added string is larger than the - * alloc increment. If so, we temporarily increase the alloc - * increment to the length of the string. This will ensure that - * one string copy will be needed at most. As this is a very - * costly operation, it outweights the cost of the strlen((char*)) and - * related stuff - at least I think so. - * rgerhards 2005-09-22 - */ - /* We save the current alloc increment in any case, so we can just - * overwrite it below, this is faster than any if-construct. - */ - iOldAllocInc = pThis->iAllocIncrement; - if(iStrLen > pThis->iAllocIncrement) { - pThis->iAllocIncrement = iStrLen; + /* does the string fit? */ + if(pThis->iStrLen + iStrLen > pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */ } - while(*psz) - if((iRet = rsCStrAppendChar(pThis, *psz++)) != RS_RET_OK) - return iRet; + /* ok, now we always have sufficient continues memory to do a memcpy() */ + memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen); + pThis->iStrLen += iStrLen; - pThis->iAllocIncrement = iOldAllocInc; /* restore */ - return RS_RET_OK; +finalize_it: + RETiRet; } @@ -161,42 +233,44 @@ rsRetVal rsCStrAppendStrWithLen(rsCStrObj *pThis, uchar* psz, size_t iStrLen) * need to change existing code. * rgerhards, 2007-07-03 */ -rsRetVal rsCStrAppendStr(rsCStrObj *pThis, uchar* psz) +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz) { - return rsCStrAppendStrWithLen(pThis, psz, strlen((char*)(char*) psz)); + return rsCStrAppendStrWithLen(pThis, psz, strlen((char*) psz)); } -rsRetVal rsCStrAppendInt(rsCStrObj *pThis, int i) +/* append the contents of one cstr_t object to another + * rgerhards, 2008-02-25 + */ +rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend) { - rsRetVal iRet; + return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen); +} + + +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i) +{ + DEFiRet; uchar szBuf[32]; rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - if((iRet = srUtilItoA((char*) szBuf, sizeof(szBuf), i)) != RS_RET_OK) - return iRet; + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), i)); - return rsCStrAppendStr(pThis, szBuf); + iRet = rsCStrAppendStr(pThis, szBuf); +finalize_it: + RETiRet; } -rsRetVal rsCStrAppendChar(rsCStrObj *pThis, uchar c) +rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c) { - uchar* pNewBuf; + DEFiRet; rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - if(pThis->iStrLen >= pThis->iBufSize) - { /* need more memory! */ - if((pNewBuf = (uchar*) malloc((pThis->iBufSize + pThis->iAllocIncrement) * sizeof(uchar))) == NULL) - return RS_RET_OUT_OF_MEMORY; - memcpy(pNewBuf, pThis->pBuf, pThis->iBufSize); - pThis->iBufSize += pThis->iAllocIncrement; - if(pThis->pBuf != NULL) { - free(pThis->pBuf); - } - pThis->pBuf = pNewBuf; + if(pThis->iStrLen >= pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */ } /* ok, when we reach this, we have sufficient memory */ @@ -208,7 +282,8 @@ rsRetVal rsCStrAppendChar(rsCStrObj *pThis, uchar c) pThis->pszBuf = NULL; } - return RS_RET_OK; +finalize_it: + RETiRet; } @@ -219,7 +294,7 @@ rsRetVal rsCStrAppendChar(rsCStrObj *pThis, uchar c) * not modified by this function. * rgerhards, 2005-10-18 */ -rsRetVal rsCStrSetSzStr(rsCStrObj *pThis, uchar *pszNew) +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew) { rsCHECKVALIDOBJECT(pThis, OIDrsCStr); @@ -260,7 +335,7 @@ rsRetVal rsCStrSetSzStr(rsCStrObj *pThis, uchar *pszNew) * WARNING: The returned pointer MUST NOT be freed, as it may be * obtained from that constant memory pool (in case of NULL!) */ -uchar* rsCStrGetSzStrNoNULL(rsCStrObj *pThis) +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis) { rsCHECKVALIDOBJECT(pThis, OIDrsCStr); if(pThis->pBuf == NULL) @@ -278,7 +353,7 @@ uchar* rsCStrGetSzStrNoNULL(rsCStrObj *pThis) * rsCStrGetSzStrNoNULL() instead. * rgerhards, 2005-09-15 */ -uchar* rsCStrGetSzStr(rsCStrObj *pThis) +uchar* rsCStrGetSzStr(cstr_t *pThis) { size_t i; @@ -342,7 +417,7 @@ uchar* rsCStrGetSzStr(rsCStrObj *pThis) * PLEASE NOTE: the caller must free the memory returned in ppSz in any case * (except, of course, if it is NULL). */ -rsRetVal rsCStrConvSzStrAndDestruct(rsCStrObj *pThis, uchar **ppSz, int bRetNULL) +rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL) { DEFiRet; uchar* pRetBuf; @@ -373,17 +448,52 @@ finalize_it: free(pThis->pBuf); RSFREEOBJ(pThis); - return(iRet); + RETiRet; } -rsRetVal rsCStrFinish(rsCStrObj __attribute__((unused)) *pThis) +#if STRINGBUF_TRIM_ALLOCSIZE == 1 + /* Only in this mode, we need to trim the string. To do + * so, we must allocate a new buffer of the exact + * string size, and then copy the old one over. + */ + /* WARNING + * STRINGBUF_TRIM_ALLOCSIZE can, in theory, be used to trim + * memory buffers. This part of the code was inherited from + * liblogging (where it is used in a different context) but + * never put to use in rsyslog. The reason is that it is hardly + * imaginable where the extra performance cost is worth the save + * in memory alloc. Then Anders Blomdel rightfully pointed out that + * the code does not work at all - and nobody even know that it + * probably shouldn't. Rather than removing, I deciced to somewhat + * fix the code, so that this feature may be enabled if somebody + * really has a need for it. Be warned, however, that I NEVER + * tested the fix. So if you intend to use this feature, you must + * do full testing before you rely on it. -- rgerhards, 2008-02-12 + */ +rsRetVal rsCStrFinish(cstr_t __attribute__((unused)) *pThis) { + DEFiRet; + uchar* pBuf; rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - return RS_RET_OK; + + if((pBuf = malloc((pThis->iStrLen) * sizeof(uchar))) == NULL) + { /* OK, in this case we use the previous buffer. At least + * we have it ;) + */ + } + else + { /* got the new buffer, so let's use it */ + memcpy(pBuf, pThis->pBuf, pThis->iStrLen); + pThis->pBuf = pBuf; + } + + RETiRet; } +#endif /* #if STRINGBUF_TRIM_ALLOCSIZE == 1 */ + -void rsCStrSetAllocIncrement(rsCStrObj *pThis, int iNewIncrement) +void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement) { rsCHECKVALIDOBJECT(pThis, OIDrsCStr); assert(iNewIncrement > 0); @@ -399,7 +509,7 @@ void rsCStrSetAllocIncrement(rsCStrObj *pThis, int iNewIncrement) * This is due to performance reasons. */ #ifndef NDEBUG -int rsCStrLen(rsCStrObj *pThis) +int rsCStrLen(cstr_t *pThis) { rsCHECKVALIDOBJECT(pThis, OIDrsCStr); return(pThis->iStrLen); @@ -409,7 +519,7 @@ int rsCStrLen(rsCStrObj *pThis) /* Truncate characters from the end of the string. * rgerhards 2005-09-15 */ -rsRetVal rsCStrTruncate(rsCStrObj *pThis, size_t nTrunc) +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc) { rsCHECKVALIDOBJECT(pThis, OIDrsCStr); @@ -432,7 +542,7 @@ rsRetVal rsCStrTruncate(rsCStrObj *pThis, size_t nTrunc) /* Trim trailing whitespace from a given string */ -rsRetVal rsCStrTrimTrailingWhiteSpace(rsCStrObj *pThis) +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis) { register int i; register uchar *pC; @@ -459,7 +569,7 @@ rsRetVal rsCStrTrimTrailingWhiteSpace(rsCStrObj *pThis) * in equal-size strings. * rgerhards, 2005-09-26 */ -int rsCStrCStrCmp(rsCStrObj *pCS1, rsCStrObj *pCS2) +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2) { rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); rsCHECKVALIDOBJECT(pCS2, OIDrsCStr); @@ -483,12 +593,15 @@ int rsCStrCStrCmp(rsCStrObj *pCS1, rsCStrObj *pCS2) } -/* check if a sz-type string start with a CStr object. This function +/* 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(rsCStrObj *pCS1, uchar *psz, size_t iLenSz) +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) { register int i; int iMax; @@ -518,9 +631,12 @@ int rsCStrSzStrStartsWithCStr(rsCStrObj *pCS1, uchar *psz, size_t iLenSz) /* 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(rsCStrObj *pCS1, uchar *psz, size_t iLenSz) +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) { register size_t i; @@ -546,22 +662,66 @@ int rsCStrStartsWithSzStr(rsCStrObj *pCS1, uchar *psz, size_t iLenSz) 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. + * TODO: change calling interface! -- rgerhards, 2008-03-07 */ -int rsCStrSzStrMatchRegex(rsCStrObj *pCS1, uchar *psz) +int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz) { - regex_t preq; - regcomp(&preq, (char*) rsCStrGetSzStr(pCS1), 0); - int iRet = regexec(&preq, (char*) psz, 0, NULL, 0); - regfree(&preq); - return iRet; + regex_t preq; + int ret; + + BEGINfunc + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + regexp.regcomp(&preq, (char*) rsCStrGetSzStr(pCS1), 0); + ret = regexp.regexec(&preq, (char*) psz, 0, NULL, 0); + regexp.regfree(&preq); + } else { + ret = 1; /* simulate "not found" */ + } + + ENDfunc + return ret; } + /* 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. @@ -583,8 +743,9 @@ int rsCStrSzStrMatchRegex(rsCStrObj *pCS1, uchar *psz) * program bug and will lead to unpredictable results and program aborts). * rgerhards 2005-09-26 */ -int rsCStrOffsetSzStrCmp(rsCStrObj *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) { + BEGINfunc rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); assert(iOffset < pCS1->iStrLen); assert(psz != NULL); @@ -593,9 +754,10 @@ int rsCStrOffsetSzStrCmp(rsCStrObj *pCS1, size_t iOffset, uchar *psz, size_t iLe /* we are using iLenSz below, because the lengths * are equal and iLenSz is faster to access */ - if(iLenSz == 0) + if(iLenSz == 0) { return 0; /* zero-sized strings are equal ;) */ - else { /* we now have two non-empty strings of equal + ENDfunc + } else { /* we now have two non-empty strings of equal * length, so we need to actually check if they * are equal. */ @@ -606,10 +768,110 @@ int rsCStrOffsetSzStrCmp(rsCStrObj *pCS1, size_t iOffset, uchar *psz, size_t iLe } /* if we arrive here, the strings are equal */ return 0; + ENDfunc } } - else + 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] * 10; + ++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; } @@ -626,7 +888,7 @@ int rsCStrOffsetSzStrCmp(rsCStrObj *pCS1, size_t iOffset, uchar *psz, size_t iLe * The to sz string pointer must not be NULL! * rgerhards 2005-09-26 */ -int rsCStrSzStrCmp(rsCStrObj *pCS1, uchar *psz, size_t iLenSz) +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz) { rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); assert(psz != NULL); @@ -659,7 +921,7 @@ int rsCStrSzStrCmp(rsCStrObj *pCS1, uchar *psz, size_t iLenSz) * returned. Both parameters MUST be given (NULL is not allowed). * rgerhards 2005-09-19 */ -int rsCStrLocateInSzStr(rsCStrObj *pThis, uchar *sz) +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz) { int i; int iMax; @@ -694,6 +956,46 @@ int rsCStrLocateInSzStr(rsCStrObj *pThis, uchar *sz) } +/* 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); +} + + #if 0 /* read comment below why this is commented out. In short: for future use! */ /* locate the first occurence of a standard sz string inside a rsCStr object. * Returns the offset (0-bound) of this first occurrence. If not found, -1 is @@ -704,7 +1006,7 @@ int rsCStrLocateInSzStr(rsCStrObj *pThis, uchar *sz) * some time later. However, it is not fully tested, so start with testing * it before you put it to first use). */ -int rsCStrLocateSzStr(rsCStrObj *pThis, uchar *sz) +int rsCStrLocateSzStr(cstr_t *pThis, uchar *sz) { int iLenSz; int i; @@ -744,6 +1046,29 @@ int rsCStrLocateSzStr(rsCStrObj *pThis, uchar *sz) #endif /* end comment out */ +/* 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; +} + + /* * Local variables: * c-indent-level: 8 diff --git a/stringbuf.h b/stringbuf.h index 2c3f4c3a..e44e86e1 100755..100644 --- a/stringbuf.h +++ b/stringbuf.h @@ -1,135 +1,168 @@ -/*! \file stringbuf.h
- * \brief 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.
- *
- * All functions in this "class" start with rsCStr (rsyslog Counted String).
- * Copyright 2005
- * Rainer Gerhards and Adiscon GmbH. All Rights Reserved.
- * This code is placed under the GPL.
- */
-#ifndef _STRINGBUF_H_INCLUDED__
-#define _STRINGBUF_H_INCLUDED__ 1
-
-/**
- * The dynamic string buffer object.
- */
-struct rsCStrObject
-{
-#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. */
- size_t iAllocIncrement; /**< the amount of bytes the string should be expanded if it needs to */
-};
-typedef struct rsCStrObject rsCStrObj;
-
-
-/**
- * Construct a rsCStr object.
- */
-rsCStrObj *rsCStrConstruct(void);
-rsRetVal rsCStrConstructFromszStr(rsCStrObj **ppThis, uchar *sz);
-rsRetVal rsCStrConstructFromCStr(rsCStrObj **ppThis, rsCStrObj *pFrom);
-
-/**
- * Destruct the string buffer object.
- */
-void rsCStrDestruct(rsCStrObj *pThis);
-
-/**
- * Append a character to an existing string. If necessary, the
- * method expands the string buffer.
- *
- * \param c Character to append to string.
- */
-rsRetVal rsCStrAppendChar(rsCStrObj *pThis, uchar c);
-
-/**
- * Finish the string buffer dynamic allocation.
- */
-rsRetVal rsCStrFinish(rsCStrObj *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(rsCStrObj *pThis, size_t nTrunc);
-
-rsRetVal rsCStrTrimTrailingWhiteSpace(rsCStrObj *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(rsCStrObj *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(rsCStrObj *pThis, uchar* psz, size_t iStrLen);
-
-/**
- * Set a new allocation incremet. This will influence
- * the allocation the next time the string will be expanded.
- * It can be set and changed at any time. If done immediately
- * after custructing the StrB object, this will also be
- * the inital allocation.
- *
- * \param iNewIncrement The new increment size
- *
- * \note It is possible to use a very low increment, e.g. 1 byte.
- * This can generate a considerable overhead. We highly
- * advise not to use an increment below 32 bytes, except
- * if you are very well aware why you are doing it ;)
- */
-void rsCStrSetAllocIncrement(rsCStrObj *pThis, int iNewIncrement);
-
-/**
- * Append an integer to the string. No special formatting is
- * done.
- */
-rsRetVal rsCStrAppendInt(rsCStrObj *pThis, int i);
-
-
-uchar* rsCStrGetSzStr(rsCStrObj *pThis);
-uchar* rsCStrGetSzStrNoNULL(rsCStrObj *pThis);
-rsRetVal rsCStrSetSzStr(rsCStrObj *pThis, uchar *pszNew);
-rsRetVal rsCStrConvSzStrAndDestruct(rsCStrObj *pThis, uchar **ppSz, int bRetNULL);
-int rsCStrCStrCmp(rsCStrObj *pCS1, rsCStrObj *pCS2);
-int rsCStrSzStrCmp(rsCStrObj *pCS1, uchar *psz, size_t iLenSz);
-int rsCStrOffsetSzStrCmp(rsCStrObj *pCS1, size_t iOffset, uchar *psz, size_t iLenSz);
-int rsCStrLocateSzStr(rsCStrObj *pCStr, uchar *sz);
-int rsCStrLocateInSzStr(rsCStrObj *pThis, uchar *sz);
-int rsCStrStartsWithSzStr(rsCStrObj *pCS1, uchar *psz, size_t iLenSz);
-int rsCStrSzStrStartsWithCStr(rsCStrObj *pCS1, uchar *psz, size_t iLenSz);
-int rsCStrSzStrMatchRegex(rsCStrObj *pCS1, uchar *psz);
-
-/* now come inline-like functions */
-#ifdef NDEBUG
-# define rsCStrLen(x) ((int)((x)->iStrLen))
-#else
- int rsCStrLen(rsCStrObj *pThis);
-#endif
-
-#define rsCStrGetBufBeg(x) ((x)->pBuf)
-
-#endif /* single include */
+/*! \file stringbuf.h + * \brief 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. + * + * All functions in this "class" start with rsCStr (rsyslog Counted String). + * Copyright 2005 + * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. + * + * 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 _STRINGBUF_H_INCLUDED__ +#define _STRINGBUF_H_INCLUDED__ 1 + +/** + * 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. */ + size_t iAllocIncrement; /**< the amount of bytes the string should be expanded if it needs to */ +} cstr_t; + + +/** + * Construct a rsCStr object. + */ +rsRetVal rsCStrConstruct(cstr_t **ppThis); +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz); +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom); + +/** + * Destruct the string buffer object. + */ +void rsCStrDestruct(cstr_t **ppThis); + +/** + * Append a character to an existing string. If necessary, the + * method expands the string buffer. + * + * \param c Character to append to string. + */ +rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c); + +/** + * 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); + +/** + * 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); + +/** + * Set a new allocation incremet. This will influence + * the allocation the next time the string will be expanded. + * It can be set and changed at any time. If done immediately + * after custructing the StrB object, this will also be + * the inital allocation. + * + * \param iNewIncrement The new increment size + * + * \note It is possible to use a very low increment, e.g. 1 byte. + * This can generate a considerable overhead. We highly + * advise not to use an increment below 32 bytes, except + * if you are very well aware why you are doing it ;) + */ +void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement); +#define rsCStrGetAllocIncrement(pThis) ((pThis)->iAllocIncrement) + +/** + * 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* rsCStrGetSzStr(cstr_t *pThis); +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis); +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew); +rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL); +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); +int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz); +rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber); +rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool); +rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend); + +/* now come inline-like functions */ +#ifdef NDEBUG +# define rsCStrLen(x) ((int)((x)->iStrLen)) +#else + int rsCStrLen(cstr_t *pThis); +#endif + +#if STRINGBUF_TRIM_ALLOCSIZE != 1 +/* This is the normal case (see comment in rsCStrFinish!). In those cases, the function + * simply needs to do nothing, so that we can save us the function call. + * rgerhards, 2008-02-12 + */ +# define rsCStrFinish(pThis) RS_RET_OK +#else + /** + * Finish the string buffer dynamic allocation. + */ + rsRetVal rsCStrFinish(cstr_t *pThis); +#endif + +#define rsCStrGetBufBeg(x) ((x)->pBuf) + +rsRetVal strInit(); +rsRetVal strExit(); + +#endif /* single include */ @@ -3,29 +3,27 @@ * * Copyright 2007 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 file is part of the rsyslog runtime library. * - * This program is distributed in the hope that it will be useful, + * 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 General Public License for more details. + * GNU Lesser 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. + * 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 USE_PTHREADS -/* all of this code is compiled only if PTHREADS is supported - otherwise - * we do not need syncrhonization objects (and do not have them!). - */ #include <stdlib.h> #include "rsyslog.h" @@ -56,23 +54,3 @@ SyncObjExit(pthread_mutex_t **mut) *mut = NULL; } } - -#ifndef NDEBUG -/* lock an object. The synchronization tool (mutex) must be passed in. - */ -void -lockObj(pthread_mutex_t *mut) -{ - pthread_mutex_lock(mut); -} - -/* unlock an object. The synchronization tool (mutex) must be passed in. - */ -void -unlockObj(pthread_mutex_t *mut) -{ - pthread_mutex_unlock(mut); -} -#endif /* #ifndef NDEBUG */ - -#endif /* #ifdef USE_PTHREADS */ @@ -3,34 +3,35 @@ * * Copyright 2007 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 file is part of the rsyslog runtime library. * - * This program is distributed in the hope that it will be useful, + * 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 General Public License for more details. + * GNU Lesser 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. + * 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_SYNC_H #define INCLUDED_SYNC_H -#ifdef USE_PTHREADS /* Code to compile for threading support */ #include <pthread.h> /* SYNC_OBJ_TOOL definition must be placed in object to be synced! * SYNC_OBJ_TOOL_INIT must be called upon of object construction and * SUNC_OBJ_TOOL_EXIT must be called upon object destruction */ -#define SYNC_OBJ_TOOL pthread_mutex_t *Sync_mut; +#define SYNC_OBJ_TOOL pthread_mutex_t *Sync_mut #define SYNC_OBJ_TOOL_INIT(x) SyncObjInit(&((x)->Sync_mut)) #define SYNC_OBJ_TOOL_EXIT(x) SyncObjExit(&((x)->Sync_mut)) @@ -38,25 +39,12 @@ * operations. If we run in debug mode, we use functions, because they * are better to trace in the stackframe. */ -#ifdef NDEBUG -#define LockObj(x) pthread_mutex_lock((x)->Sync_mut) -#define UnlockObj(x) pthread_mutex_unlock((x)->Sync_mut) -#else -#define LockObj(x) lockObj((x)->Sync_mut) -#define UnlockObj(x) unlockObj((x)->Sync_mut) -#endif +#define LockObj(x) d_pthread_mutex_lock((x)->Sync_mut) +#define UnlockObj(x) d_pthread_mutex_unlock((x)->Sync_mut) void SyncObjInit(pthread_mutex_t **mut); void SyncObjExit(pthread_mutex_t **mut); extern void lockObj(pthread_mutex_t *mut); extern void unlockObj(pthread_mutex_t *mut); -#else /* Code not to compile for threading support */ -#define SYNC_OBJ_TOOL -#define SYNC_OBJ_TOOL_INIT(x) -#define SYNC_OBJ_TOOL_EXIT(X) -#define LockObj(x) -#define UnlockObj(x) -#endif - #endif /* #ifndef INCLUDED_SYNC_H */ diff --git a/syslog.c b/syslog.c deleted file mode 100644 index 225b6f7a..00000000 --- a/syslog.c +++ /dev/null @@ -1,140 +0,0 @@ -/* logger.c - * Helper routines for klogd. It replaces the regular glibc syslog() - * call. The reason is that the glibc version does not support - * logging with the kernel facility. This file is a re-implementation - * of the functions with only the functionality required for klogd. - * So it can NOT be used as a general-purpose replacment. - * Thanks to being special, this version is deemed to be considerable - * faster than its glibc counterpart. - * To avoid confusion, the syslog() replacement is named writeSyslog(). - * all other functions are just helpers to it. - * RGerhards, 2007-06-14 - * - * Copyright (C) 2007 Rainer Gerhards - * - * 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 <stdio.h> -#include <assert.h> -#include <sys/socket.h> -#include <string.h> -#include <time.h> -#include <stdarg.h> -#include <unistd.h> - -#define PATH_LOGNAME "/dev/log" /* be sure you know what you do if you change this! */ - -static struct sockaddr LoggerAddr; /* AF_UNIX address of local logger */ -static int connected; /* have done connect */ -static int fdLog = -1; /* fd for log - -1 if closed */ - -/* klogOpenLog - * opens the system log for writing. The log is opened immediately. - * If it is already open, no action is taken) - */ -static void klogOpenlog(void) -{ - if (fdLog == -1) { - LoggerAddr.sa_family = AF_UNIX; - strncpy(LoggerAddr.sa_data, PATH_LOGNAME, sizeof(LoggerAddr.sa_data)); - fdLog = socket(AF_UNIX, SOCK_DGRAM, 0); - } - if (fdLog != -1 && !connected) - if(connect(fdLog, &LoggerAddr, sizeof(LoggerAddr.sa_family) + - sizeof(PATH_LOGNAME) - 1 /* for \0 byte!*/ ) != -1) - connected = 1; -} - -/* Close the log file if it is currently open. - */ -static void klogCloselog(void) -{ - if(fdLog != -1) - close(fdLog); - fdLog = -1; - connected = 0; -} - -/* Write a message to the syslogd. - * returns -1 if it fails, something else otherwise - */ -int writeSyslogV(int iPRI, const char *szFmt, va_list va) -{ - int iChars; - int iLen; - time_t tNow; - int iWritten; /* number of bytes written */ - char msgBuf[2048]; /* we use the same size as sysklogd to remain compatible */ - - assert(szFmt != NULL); - - if (fdLog < 0 || !connected) - klogOpenlog(); - - /* build the message */ - time(&tNow); - /* we can use sprintf safely below, because we know the size of the constants. - * By doing so, we save some cpu cycles and code complexity (for unnecessary - * error checking). - */ - iLen = sprintf(msgBuf, "<%d>%.15s kernel: ", iPRI, ctime(&tNow) + 4); - - iChars = vsnprintf(msgBuf + iLen, sizeof(msgBuf) / sizeof(char) - iLen, szFmt, va); - if(iChars > (int)(sizeof(msgBuf) / sizeof(char) - iLen)) - iLen = sizeof(msgBuf) / sizeof(char) - 1; /* full buffer siz minus \0 byte */ - else - iLen += iChars; - - /* output the message to the local logger */ - iWritten = write(fdLog, msgBuf, iLen); - /* Debug aid below - uncomment to use - printf("wrote to log(%d): '%s'\n", iWritten, msgBuf); */ - - if(iWritten == -1) { - /* retry */ - klogCloselog(); - klogOpenlog(); - iWritten = write(fdLog, msgBuf, iLen); - } - - return(iWritten); -} - -/* And now the same with variable arguments */ -int writeSyslog(int iPRI, const char *szFmt, ...) -{ - int iRet; - va_list va; - - assert(szFmt != NULL); - va_start(va, szFmt); - iRet = writeSyslogV(iPRI, szFmt, va); - va_end(va); - - return(iRet); -} - -/* - * Local variables: - * c-indent-level: 4 - * c-basic-offset: 4 - * tab-width: 4 - * End: - * vi:set ai: - */ diff --git a/syslogd-types.h b/syslogd-types.h index 14bc5c53..9aea3778 100644 --- a/syslogd-types.h +++ b/syslogd-types.h @@ -6,19 +6,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -26,9 +27,11 @@ #define SYSLOGD_TYPES_INCLUDED 1 #include "stringbuf.h" -#include "net.h" +//#include "net.h" #include <sys/param.h> -#include <sys/syslog.h> +#if HAVE_SYSLOG_H +#include <syslog.h> +#endif #define FALSE 0 #define TRUE 1 @@ -63,11 +66,6 @@ typedef struct _syslogCode { int c_val; } syslogCODE; -typedef enum _TCPFRAMINGMODE { - TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ - TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ - } TCPFRAMINGMODE; - /* values for host comparisons specified with host selector blocks * (+host, -host). rgerhards 2005-10-18. */ @@ -98,14 +96,7 @@ struct syslogTime { * OffsetMode to know the direction. */ }; - -#ifdef SYSLOG_INET -struct AllowedSenders { - struct NetAddr allowedSender; /* ip address allowed */ - uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */ - struct AllowedSenders *pNext; -}; -#endif +typedef struct syslogTime syslogTime_t; #endif /* #ifndef SYSLOGD_TYPES_INCLUDED */ /* @@ -7,41 +7,13 @@ * * to learn more about it and discuss any questions you may have. * - * Please note that as of now, a lot of the code in this file stems - * from the sysklogd project. To learn more over this project, please - * visit - * - * http://www.infodrom.org/projects/sysklogd/ - * + * 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 I made quite some changes to the orignal package. - * I expect to do even more changes - up - * to a full rewrite - to meet my design goals, which among others - * contain a (at least) dual-thread design with a memory buffer for - * storing received bursts of data. This is also the reason why I - * kind of "forked" a completely new branch of the package. My intension - * is to do many changes and only this initial release will look - * similar to sysklogd (well, one never knows...). - * - * As I have made a lot of modifications, please assume that all bugs - * in this package are mine and not those of the sysklogd team. - * - * As of this writing, there already exist heavy - * modifications to the orginal sysklogd package. I suggest to no - * longer rely too much on code knowledge you eventually have with - * sysklogd - rgerhards 2005-07-05 - * The code is now almost completely different. Be careful! - * rgerhards, 2006-11-30 - * - * I have decided to put my code under the GPL. The sysklog package - * is distributed under the BSD license. As such, this package here - * currently comes with two licenses. Both are given below. As it is - * probably hard for you to see what was part of the sysklogd package - * and what is part of my code, I suggest that you visit the - * sysklogd site on the URL above if you would like to base your - * development on a version that is not under the GPL. + * 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>. See @@ -64,31 +36,28 @@ * to the database). * * rsyslog - An Enhanced syslogd Replacement. - * Copyright 2003-2007 Rainer Gerhards and Adiscon GmbH. + * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. * - * 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. + * 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. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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" -#ifdef __FreeBSD__ -#define BSD -#endif - /* change the following 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 increase it to 32k, @@ -116,31 +85,10 @@ * I have increased the default message size to 2048 to be in sync * with recent IETF syslog standardization efforts. * rgerhards, 2006-11-30 - * - * I have removed syslogdPanic(). That function was supposed to be used - * for logging in low-memory conditons. Ever since it was introduced, it - * was a wrapper for dbgprintf(). A more intelligent choice was hard to - * find. After all, if we are short on memory, doing anything fance will - * again cause memory problems. I have now modified the code so that - * those elements for which we do not get memory are simply discarded. - * That might be a single property like the TAG, but it might also be - * a complete message. The overall goal of this code change is to keep - * rsyslogd up and running, while we sacrifice some messages to reach - * that goal. It also keeps the code cleaner. A real out of memory - * condition is highly unlikely. If it happens, there will probably be - * much more trouble on the system in question. Anyhow - rsyslogd will - * most probably be able to survive it and carry on with processing - * once the situation has been resolved. */ #define DEFUPRI (LOG_USER|LOG_NOTICE) -#define DEFSPRI (LOG_KERN|LOG_CRIT) #define TIMERINTVL 30 /* interval for checking flush, mark */ -#define CONT_LINE 1 /* Allow continuation lines */ - -#ifdef MTRACE -#include <mcheck.h> -#endif #include <unistd.h> #include <stdlib.h> #include <stdio.h> @@ -151,48 +99,28 @@ #include <string.h> #include <stdarg.h> #include <time.h> -#include <dlfcn.h> +#include <assert.h> +#include <libgen.h> -#include <sys/syslog.h> -#include <sys/param.h> #ifdef __sun -#include <errno.h> +# include <errno.h> #else -#include <sys/errno.h> +# include <sys/errno.h> #endif #include <sys/ioctl.h> #include <sys/wait.h> -#include <sys/socket.h> #include <sys/file.h> -#include <sys/un.h> -#include <sys/time.h> #if HAVE_SYS_TIMESPEC_H # include <sys/timespec.h> #endif -#include <sys/resource.h> -#include <signal.h> - -#include <netinet/in.h> -#include <netdb.h> -#include <fnmatch.h> -#include <dirent.h> -#include <glob.h> -#include <sys/types.h> -#include <sys/stat.h> - -#include <arpa/nameser.h> -#include <arpa/inet.h> -#include <resolv.h> -#include "pidfile.h" - -#include <assert.h> - -#ifdef USE_PTHREADS -#include <pthread.h> +#if HAVE_SYS_STAT_H +# include <sys/stat.h> #endif +#include <signal.h> + #if HAVE_PATHS_H #include <paths.h> #endif @@ -201,22 +129,14 @@ #include <zlib.h> #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 - +#include "pidfile.h" #include "srUtils.h" #include "stringbuf.h" #include "syslogd-types.h" #include "template.h" #include "outchannel.h" #include "syslogd.h" -#include "net.h" /* struct NetAddr */ -#include "sync.h" /* struct NetAddr */ -#include "parse.h" #include "msg.h" #include "modules.h" #include "action.h" @@ -228,6 +148,36 @@ #include "omfwd.h" #include "omfile.h" #include "omdiscard.h" +#include "threads.h" +#include "queue.h" +#include "stream.h" +#include "wti.h" +#include "wtp.h" +#include "expr.h" +#include "ctok.h" +#include "conf.h" +#include "vmop.h" +#include "vmstk.h" +#include "vm.h" +#include "vmprg.h" +#include "errmsg.h" +#include "datetime.h" +#include "sysvar.h" + +/* definitions for objects we access */ +DEFobjCurrIf(obj) +DEFobjCurrIf(datetime) +DEFobjCurrIf(conf) +DEFobjCurrIf(expr) +DEFobjCurrIf(vm) +DEFobjCurrIf(var) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) /* TODO: make go away! */ + + +/* forward definitions */ +static rsRetVal GlobalClassExit(void); /* We define our own set of syslog defintions so that we * do not need to rely on (possibly different) implementations. @@ -239,12 +189,8 @@ #ifdef __sun # define LOG_AUTHPRIV LOG_AUTH #endif -#define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) -#define LOG_PRI(p) ((p) & LOG_PRIMASK) -#define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) #define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ #define LOG_FTP (11<<3) /* ftp daemon */ -#define INTERNAL_MARK LOG_MAKEPRI((LOG_NFACILITIES<<3), 0) #ifndef UTMP_FILE @@ -268,30 +214,33 @@ #endif #if defined(SYSLOGD_PIDNAME) -#undef _PATH_LOGPID -#if defined(FSSTND) -#ifdef BSD -#define _PATH_VARRUN "/var/run/" -#endif -#ifdef __sun -#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(FSSTND) -#define _PATH_LOGPID _PATH_VARRUN "rsyslogd.pid" +# 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 -#define _PATH_LOGPID "/etc/rsyslogd.pid" -#endif -#endif +# 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_DEV -#define _PATH_DEV "/dev/" +# define _PATH_DEV "/dev/" #endif #ifndef _PATH_CONSOLE @@ -302,81 +251,16 @@ #define _PATH_TTY "/dev/tty" #endif -#ifndef _PATH_LOG -#ifdef BSD -#define _PATH_LOG "/var/run/log" -#else -#define _PATH_LOG "/dev/log" -#endif -#endif - - static uchar *ConfFile = (uchar*) _PATH_LOGCONF; /* read-only after startup */ static char *PidFile = _PATH_LOGPID; /* read-only after startup */ -static uchar *pModDir = NULL; /* read-only after startup */ char ctty[] = _PATH_CONSOLE; /* this is read-only; used by omfile -- TODO: remove that dependency */ static pid_t myPid; /* our pid for use in self-generated messages, e.g. on startup */ /* mypid is read-only after the initial fork() */ -static int debugging_on = 0; /* read-only, except on sig USR1 */ static int restart = 0; /* do restart (config read) - multithread safe */ -static int bRequestDoMark = 0; /* do mark processing? (multithread safe) */ -#define MAXFUNIX 20 - int glblHadMemShortage = 0; /* indicates if we had memory shortage some time during the run */ -int startIndexUxLocalSockets = 0; /* process funix from that index on (used to - * suppress local logging. rgerhards 2005-08-01 - * read-only after startup - */ -int funixParseHost[MAXFUNIX] = { 0, }; /* should parser parse host name? read-only after startup */ -char *funixn[MAXFUNIX] = { _PATH_LOG }; /* read-only after startup */ -int funix[MAXFUNIX] = { -1, }; /* read-only after startup */ - -#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" */ - -/* 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}; - -/* The following 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. These - * variables are no longer used once the configuration has been loaded (except, - * of course, during a reload). rgerhards 2005-10-18 - */ -static EHostnameCmpMode eDfltHostnameCmpMode; -static rsCStrObj *pDfltHostnameCmp; -static rsCStrObj *pDfltProgNameCmp; - -/* supporting structures for multithreading */ -#ifdef USE_PTHREADS -/* this is the first approach to a queue, this time with static - * memory. - */ -typedef struct { - void** pbuf; - long head, tail; - int full, empty; - pthread_mutex_t *mut; - pthread_cond_t *notFull, *notEmpty; -} msgQueue; - -int iMainMsgQueueSize; -int bRunningMultithreaded = 0; /* Is this program running in multithreaded mode? */ -msgQueue *pMsgQueue = NULL; -static pthread_t thrdWorker; -static int bGlblDone = 0; -#endif -/* END supporting structures for multithreading */ + static int bParseHOSTNAMEandTAG = 1; /* global config var: should the hostname and tag be * parsed inside message - rgerhards, 2006-03-13 */ @@ -385,180 +269,81 @@ static int bFinished = 0; /* used by termination signal handler, read-only excep * termination. */ -/* - * Intervals at which we flush out "message repeated" messages, +/* Intervals at which we flush out "message repeated" messages, * in seconds after previous message is logged. After each flush, * we move to the next interval until we reach the largest. + * TODO: this shall go into action object! -- rgerhards, 2008-01-29 */ -int repeatinterval[] = { 30, 60 }; /* # of secs before flush */ -#define MAXREPEAT ((int)((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)) -#define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) -#define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ - (f)->f_repeatcount = MAXREPEAT; \ - } -#ifdef SYSLOG_INET -union sockunion { - struct sockinet { - u_char si_len; - u_char si_family; - } su_si; - struct sockaddr_in su_sin; - struct sockaddr_in6 su_sin6; -}; -#endif +int repeatinterval[2] = { 30, 60 }; /* # of secs before flush */ #define LIST_DELIMITER ':' /* delimiter between two hosts */ struct filed *Files = NULL; /* read-only after init() (but beware of sigusr1!) */ -struct code { - char *c_name; - int c_val; -}; - -static struct code PriNames[] = { - {"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} -}; - -static struct code FacNames[] = { - {"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 */ - {"syslog", LOG_SYSLOG}, - {"user", LOG_USER}, - {"uucp", LOG_UUCP}, -#if defined(LOG_FTP) - {"ftp", LOG_FTP}, -#endif - {"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}, -}; - static pid_t ppid; /* This is a quick and dirty hack used for spliting main/startup thread */ +typedef struct legacyOptsLL_s { + uchar *line; + struct legacyOptsLL_s *next; +} legacyOptsLL_t; +legacyOptsLL_t *pLegacyOptsLL = NULL; + /* global variables for config file state */ static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */ -int Debug; /* debug flag - read-only after startup */ +int iCompatibilityMode = 0; /* version we should be compatible with; 0 means sysklogd. It is + the default, so if no -c<n> option is given, we make ourselvs + as compatible to sysklogd as possible. */ static int bDebugPrintTemplateList = 1;/* output template list in debug mode? */ static int bDebugPrintCfSysLineHandlerList = 1;/* output cfsyslinehandler list in debug mode? */ static int bDebugPrintModuleList = 1;/* output module list in debug mode? */ int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */ 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 bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */ -static int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */ -static int logEveryMsg = 0;/* no repeat message processing - read-only after startup - * 0 - suppress duplicate messages - * 1 - do NOT suppress duplicate messages - */ +int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */ +int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */ +int iActExecOnceInterval = 0; /* execute action once every nn seconds */ +uchar *pszWorkDir = NULL;/* name of rsyslog's spool directory (without trailing slash) */ +uchar *glblModPath = NULL; /* module load path - only used during initial init, only settable via -M command line option */ /* end global config file state variables */ -static unsigned int Forwarding = 0; -static int nfunix = 1; /* number of Unix sockets open / read-only after startup */ -char LocalHostName[MAXHOSTNAMELEN+1];/* our hostname - read-only after startup */ +uchar *LocalHostName;/* our hostname - read-only after startup */ char *LocalDomain; /* our local domain name - read-only after startup */ -int *finet = NULL; /* Internet datagram sockets, first element is nbr of elements - * read-only after init(), but beware of restart! */ -static char *LogPort = "514"; /* port number for INET connections */ -static int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ +int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both), set via cmdline */ int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */ -static int MarkSeq = 0; /* mark sequence number - modified in domark() only */ static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */ -static int AcceptRemote = 0;/* receive messages that come via UDP - read-only after startup */ -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) */ int DisableDNS = 0; /* don't look up IP addresses of remote messages */ char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */ char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */ -int NoHops = 1; /* Can we bounce syslog messages through an - intermediate host. Read-only after startup */ -static int Initialized = 0; /* set when we have initialized ourselves - * rgerhards 2004-11-09: and by initialized, we mean that - * the configuration file could be properly read AND the - * syslog/udp port could be obtained (the later is debatable). - * It is mainly a setting used for emergency logging: if - * something really goes wild, we can not do as indicated in - * the log file, but we still log messages to the system - * console. This is probably the best that can be done in - * such a case. - * read-only after startup, but modified during restart - */ +static 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; - -/* This structure represents the files that will have log - * copies printed. - * RGerhards 2004-11-08: Each instance of the filed structure - * describes what I call an "output channel". This is important - * to mention as we now allow database connections to be - * present in the filed structure. If helps immensely, if we - * think of it as the abstraction of an output channel. - * rgerhards, 2005-10-26: The structure below provides ample - * opportunity for non-thread-safety. Each of the variable - * accesses must be carefully evaluated, many of them probably - * be guarded by mutexes. But beware of deadlocks... - * rgerhards, 2007-08-01: as you can see, the structure has shrunk pretty much. I will - * remove some of the comments some time. It's still the structure that controls much - * of the processing that goes on in syslogd, but it now has lots of helpers. - */ -struct filed { - struct filed *f_next; /* next in linked list */ - /* filter properties */ - enum { - FILTER_PRI = 0, /* traditional PRI based filer */ - FILTER_PROP = 1 /* extended filter, property based */ - } f_filter_type; - EHostnameCmpMode eHostnameCmpMode; - rsCStrObj *pCSHostnameComp; /* hostname to check */ - rsCStrObj *pCSProgNameComp; /* tag to check or NULL, if not to be checked */ - union { - u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */ - struct { - rsCStrObj *pCSPropName; - 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 regular expression? */ - } operation; - rsCStrObj *pCSCompValue; /* value to "compare" against */ - char isNegated; /* actually a boolean ;) */ - } prop; - } f_filterData; - - linkedList_t llActList; /* list of configured actions */ -}; -typedef struct filed selector_t; /* new type name */ +/* main message queue and its configuration parameters */ +static queue_t *pMsgQueue = NULL; /* the main message queue */ +static int iMainMsgQueueSize = 10000; /* size of the main message queue above */ +static int iMainMsgQHighWtrMark = 8000; /* high water mark for disk-assisted queues */ +static int iMainMsgQLowWtrMark = 2000; /* low water mark for disk-assisted queues */ +static int iMainMsgQDiscardMark = 9800; /* begin to discard messages */ +static int iMainMsgQDiscardSeverity = 8; /* by default, discard nothing to prevent unintentional loss */ +static int iMainMsgQueueNumWorkers = 1; /* number of worker threads for the mm queue above */ +static queueType_t MainMsgQueType = QUEUETYPE_FIXED_ARRAY; /* type of the main message queue above */ +static uchar *pszMainMsgQFName = NULL; /* prefix for the main message queue file */ +static int64 iMainMsgQueMaxFileSize = 1024*1024; +static int iMainMsgQPersistUpdCnt = 0; /* persist queue info every n updates */ +static int iMainMsgQtoQShutdown = 0; /* queue shutdown */ +static int iMainMsgQtoActShutdown = 1000; /* action shutdown (in phase 2) */ +static int iMainMsgQtoEnq = 2000; /* timeout for queue enque */ +static int iMainMsgQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */ +static int iMainMsgQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */ +static int iMainMsgQDeqSlowdown = 0; /* dequeue slowdown (simple rate limiting) */ +static int64 iMainMsgQueMaxDiskSpace = 0; /* max disk space allocated 0 ==> unlimited */ +static int bMainMsgQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ +static int iMainMsgQueueDeqtWinFromHr = 0; /* hour begin of time frame when queue is to be dequeued */ +static int iMainMsgQueueDeqtWinToHr = 25; /* hour begin of time frame when queue is to be dequeued */ /* support for simple textual representation of FIOP names @@ -595,54 +380,55 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a { cCCEscapeChar = '#'; bActExecWhenPrevSusp = 0; + iActExecOnceInterval = 0; bDebugPrintTemplateList = 1; bDebugPrintCfSysLineHandlerList = 1; bDebugPrintModuleList = 1; bEscapeCCOnRcv = 1; /* default is to escape control characters */ - bReduceRepeatMsgs = (logEveryMsg == 1) ? 0 : 1; + bReduceRepeatMsgs = 0; bDropMalPTRMsgs = 0; - if(pModDir != NULL) { - free(pModDir); - pModDir = NULL; + if(pszWorkDir != NULL) { + free(pszWorkDir); + pszWorkDir = NULL; } -#ifdef USE_PTHREADS - iMainMsgQueueSize = 10000; -#endif -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) - if (gss_listen_service_name != NULL) { - free(gss_listen_service_name); - gss_listen_service_name = NULL; + if(pszMainMsgQFName != NULL) { + free(pszMainMsgQFName); + pszMainMsgQFName = NULL; } -#endif + iMainMsgQueueSize = 10000; + iMainMsgQHighWtrMark = 8000; + iMainMsgQLowWtrMark = 2000; + iMainMsgQDiscardMark = 9800; + iMainMsgQDiscardSeverity = 8; + iMainMsgQueMaxFileSize = 1024 * 1024; + iMainMsgQueueNumWorkers = 1; + iMainMsgQPersistUpdCnt = 0; + iMainMsgQtoQShutdown = 0; + iMainMsgQtoActShutdown = 1000; + iMainMsgQtoEnq = 2000; + iMainMsgQtoWrkShutdown = 60000; + iMainMsgQWrkMinMsgs = 100; + iMainMsgQDeqSlowdown = 0; + bMainMsgQSaveOnShutdown = 1; + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + iMainMsgQueMaxDiskSpace = 0; + glbliActionResumeRetryCount = 0; return RS_RET_OK; } -/* 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 - */ -#ifdef SYSLOG_INET -/* All of the five below are read-only after startup */ -static 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 -#endif /* #ifdef SYSLOG_INET */ int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */ /* hardcoded standard templates (used for defaults) */ -static uchar template_TraditionalFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n\""; +static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\""; +static uchar template_TraditionalFileFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n\""; +static uchar template_FileFormat[] = "\"%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n\""; static uchar template_WallFmt[] = "\"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r\""; -static uchar template_StdFwdFmt[] = "\"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%\""; +static uchar template_ForwardFormat[] = "\"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%\""; +static uchar template_TraditionalForwardFormat[] = "\"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%\""; 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"; @@ -650,1202 +436,49 @@ static uchar template_StdPgSQLFmt[] = "\"insert into SystemEvents (Message, Faci /* up to the next comment, prototypes that should be removed by reordering */ -#ifdef USE_PTHREADS -static msgQueue *queueInit (void); -static void *singleWorker(); /* REMOVEME later 2005-10-24 */ -#endif /* Function prototypes. */ static char **crunch_list(char *list); -static void printline(char *hname, char *msg, int iSource); -static void logmsg(int pri, msg_t*, int flags); -static rsRetVal fprintlog(action_t *pAction); static void reapchild(); static void debug_switch(); -static rsRetVal cfline(uchar *line, selector_t **pfCurr); -static int decode(uchar *name, struct code *codetab); static void sighup_handler(); -static void die(int sig); static void freeSelectors(void); -static rsRetVal processConfFile(uchar *pConfFile); -static rsRetVal selectorAddList(selector_t *f); static void processImInternal(void); -/* Code for handling allowed/disallowed senders - */ -#ifdef SYSLOG_INET -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 *)(sa)) -#define SIN6(sa) ((struct sockaddr_in6 *)(sa)) - -/* 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) { - glblHadMemShortage = 1; - 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. - * TODO: reconsider recursive implementation - */ -static void clearAllowedSenders (struct AllowedSenders *pAllow) { - if (pAllow != NULL) { - if (pAllow->pNext != NULL) - clearAllowedSenders (pAllow->pNext); - else { - if (F_ISSET(pAllow->allowedSender.flags, ADDR_NAME)) - free (pAllow->allowedSender.addr.HostWildcard); - else - free (pAllow->allowedSender.addr.NetAddr); - - free (pAllow); - } - } -} - -/* 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. - */ - logerror("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)) { - logerrorInt("Invalid bit number 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)) { - logerrorInt("Invalid bit number 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. - */ - logerrorInt("Internal error caused AllowedSender to be ignored, AF = %d", - iAllow->addr.NetAddr->sa_family); - return 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 (DisableDNS) { - logerror ("Ignoring hostname based ACLs because DNS is disabled."); - return 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) { - logerrorSz("DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); - - if (ACLAddHostnameOnFail) { - logerrorSz("Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); - return AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); - } else { - logerrorSz("Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard); - return 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) { - glblHadMemShortage = 1; - return 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) - return(iRet); - 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) { - glblHadMemShortage = 1; - return 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 (struct sockaddr_in)); - - if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, - iSignificantBits)) - != RS_RET_OK) - return(iRet); - } else { - /* finally add IPv6 */ - - iSignificantBits = 128; - allowIP.flags = 0; - if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { - glblHadMemShortage = 1; - return 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) - return(iRet); - } - 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); - } - } - - return iRet; -} -#endif /* #ifdef SYSLOG_INET */ - - -#ifdef SYSLOG_INET -/* Print an allowed sender list. The caller must tell us which one. - * iListToPrint = 1 means UDP, 2 means TCP - * rgerhards, 2005-09-27 - */ -static void PrintAllowedSenders(int iListToPrint) -{ - struct AllowedSenders *pSender; - uchar szIP[64]; - - assert((iListToPrint == 1) || (iListToPrint == 2) -#ifdef USE_GSSAPI - || (iListToPrint == 3) -#endif - ); - - printf("\nAllowed %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) { - printf("\tNo restrictions set.\n"); - } else { - while(pSender != NULL) { - if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME)) - printf ("\t%s\n", pSender->allowedSender.addr.HostWildcard); - else { - if(getnameinfo (pSender->allowedSender.addr.NetAddr, - SALEN(pSender->allowedSender.addr.NetAddr), - (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) { - printf ("\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; - } - } -} - - -/* 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, something else otherwise. - * contributed 1007-07-16 by mildew@gmail.com - */ -static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost) -{ - assert(pAllow != NULL); - assert(pFrom != NULL); - - if(F_ISSET(pAllow->flags, ADDR_NAME)) { - dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard); - - return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0); - } 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 otherwise. - * rgerhards, 2005-09-26 - */ -int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost) -{ - struct AllowedSenders *pAllow; - - assert(pFrom != NULL); - - 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) { - if (MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost)) - return 1; - } - return 0; -} -#endif /* #ifdef SYSLOG_INET */ - - -/* code to free all sockets within a socket table. - * A socket table is a descriptor table where the zero - * element has the count of elements. This is used for - * listening sockets. The socket table itself is also - * freed. - * A POINTER to this structure must be provided, thus - * double indirection! - * rgerhards, 2007-06-28 - */ -void freeAllSockets(int **socks) -{ - assert(socks != NULL); - assert(*socks != NULL); - while(**socks) { - dbgprintf("Closing socket %d.\n", (*socks)[**socks]); - close((*socks)[**socks]); - (**socks)--; - } - free(*socks); - socks = NULL; -} - - - - -/******************************************************************* - * 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. - * \retval The number parsed. - */ - -static int srSLMGParseInt32(char** ppsz) -{ - int i; - - i = 0; - while(isdigit((int) **ppsz)) - { - i = i * 10 + **ppsz - '0'; - ++(*ppsz); - } - - return i; -} - - -/** - * Parse a TIMESTAMP-3339. - * updates the parse pointer position. - */ -static int srSLMGParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS) -{ - char *pszTS = *ppszTS; - - assert(pTime != NULL); - assert(ppszTS != NULL); - assert(pszTS != NULL); - - pTime->year = srSLMGParseInt32(&pszTS); - - /* 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(*pszTS++ != '-') - return FALSE; - pTime->month = srSLMGParseInt32(&pszTS); - if(pTime->month < 1 || pTime->month > 12) - return FALSE; - - if(*pszTS++ != '-') - return FALSE; - pTime->day = srSLMGParseInt32(&pszTS); - if(pTime->day < 1 || pTime->day > 31) - return FALSE; - - if(*pszTS++ != 'T') - return FALSE; - - pTime->hour = srSLMGParseInt32(&pszTS); - if(pTime->hour < 0 || pTime->hour > 23) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->minute = srSLMGParseInt32(&pszTS); - if(pTime->minute < 0 || pTime->minute > 59) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->second = srSLMGParseInt32(&pszTS); - if(pTime->second < 0 || pTime->second > 60) - return FALSE; - - /* Now let's see if we have secfrac */ - if(*pszTS == '.') - { - char *pszStart = ++pszTS; - pTime->secfrac = srSLMGParseInt32(&pszTS); - pTime->secfracPrecision = (int) (pszTS - pszStart); - } - else - { - pTime->secfracPrecision = 0; - pTime->secfrac = 0; - } - - /* check the timezone */ - if(*pszTS == 'Z') - { - pszTS++; /* eat Z */ - pTime->OffsetMode = 'Z'; - pTime->OffsetHour = 0; - pTime->OffsetMinute = 0; - } - else if((*pszTS == '+') || (*pszTS == '-')) - { - pTime->OffsetMode = *pszTS; - pszTS++; - - pTime->OffsetHour = srSLMGParseInt32(&pszTS); - if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->OffsetMinute = srSLMGParseInt32(&pszTS); - if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59) - return FALSE; - } - else - /* there MUST be TZ information */ - return FALSE; - - /* OK, we actually have a 3339 timestamp, so let's indicated this */ - if(*pszTS == ' ') - ++pszTS; - else - return FALSE; - - /* update parse pointer */ - *ppszTS = pszTS; - - return TRUE; -} - - -/** - * Parse a TIMESTAMP-3164. - * Returns TRUE on parse OK, FALSE on parse error. - */ -static int srSLMGParseTIMESTAMP3164(struct syslogTime *pTime, char* pszTS) -{ - assert(pTime != NULL); - assert(pszTS != NULL); - - getCurrTime(pTime); /* obtain the current year and UTC offsets! */ - - /* 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. - * - * 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 - */ - switch(*pszTS++) - { - case 'J': - if(*pszTS == 'a') { - ++pszTS; - if(*pszTS == 'n') { - ++pszTS; - pTime->month = 1; - } else - return FALSE; - } else if(*pszTS == 'u') { - ++pszTS; - if(*pszTS == 'n') { - ++pszTS; - pTime->month = 6; - } else if(*pszTS == 'l') { - ++pszTS; - pTime->month = 7; - } else - return FALSE; - } else - return FALSE; - break; - case 'F': - if(*pszTS == 'e') { - ++pszTS; - if(*pszTS == 'b') { - ++pszTS; - pTime->month = 2; - } else - return FALSE; - } else - return FALSE; - break; - case 'M': - if(*pszTS == 'a') { - ++pszTS; - if(*pszTS == 'r') { - ++pszTS; - pTime->month = 3; - } else if(*pszTS == 'y') { - ++pszTS; - pTime->month = 5; - } else - return FALSE; - } else - return FALSE; - break; - case 'A': - if(*pszTS == 'p') { - ++pszTS; - if(*pszTS == 'r') { - ++pszTS; - pTime->month = 4; - } else - return FALSE; - } else if(*pszTS == 'u') { - ++pszTS; - if(*pszTS == 'g') { - ++pszTS; - pTime->month = 8; - } else - return FALSE; - } else - return FALSE; - break; - case 'S': - if(*pszTS == 'e') { - ++pszTS; - if(*pszTS == 'p') { - ++pszTS; - pTime->month = 9; - } else - return FALSE; - } else - return FALSE; - break; - case 'O': - if(*pszTS == 'c') { - ++pszTS; - if(*pszTS == 't') { - ++pszTS; - pTime->month = 10; - } else - return FALSE; - } else - return FALSE; - break; - case 'N': - if(*pszTS == 'o') { - ++pszTS; - if(*pszTS == 'v') { - ++pszTS; - pTime->month = 11; - } else - return FALSE; - } else - return FALSE; - break; - case 'D': - if(*pszTS == 'e') { - ++pszTS; - if(*pszTS == 'c') { - ++pszTS; - pTime->month = 12; - } else - return FALSE; - } else - return FALSE; - break; - default: - return FALSE; - } - - /* done month */ - - if(*pszTS++ != ' ') - return FALSE; - - /* we accept a slightly malformed timestamp when receiving. This is - * we accept one-digit days - */ - if(*pszTS == ' ') - ++pszTS; - - pTime->day = srSLMGParseInt32(&pszTS); - if(pTime->day < 1 || pTime->day > 31) - return FALSE; - - if(*pszTS++ != ' ') - return FALSE; - pTime->hour = srSLMGParseInt32(&pszTS); - if(pTime->hour < 0 || pTime->hour > 23) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->minute = srSLMGParseInt32(&pszTS); - if(pTime->minute < 0 || pTime->minute > 59) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->second = srSLMGParseInt32(&pszTS); - if(pTime->second < 0 || pTime->second > 60) - return FALSE; - - /* OK, we actually have a 3164 timestamp, so let's indicate this - * and fill the rest of the properties. */ - pTime->timeType = 1; - pTime->secfracPrecision = 0; - pTime->secfrac = 0; - return TRUE; -} - -/******************************************************************* - * 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* pDst, size_t iLenDst) -{ - /* 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(pDst != NULL); - - if (iLenDst < 15) /* we need at least 14 bytes - 14 digits for timestamp + '\n' */ - return(0); - - return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", - ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); - -} - -int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst) -{ - /* see note in formatTimestampToMySQL, applies here as well */ - assert(ts != NULL); - assert(pDst != NULL); - - if (iLenDst < 21) /* we need 20 bytes + '\n' */ - return(0); - - return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", - ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); -} - -/** - * 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, size_t iLenBuf) -{ - int iRet; - char szTZ[7]; /* buffer for TZ information */ - - assert(ts != NULL); - assert(pBuf != NULL); - - if(iLenBuf < 20) - return(0); /* we NEED at least 20 bytes */ - - /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */ - if(ts->OffsetMode == 'Z') { - szTZ[0] = 'Z'; - szTZ[1] = '\0'; - } else { - snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d", - ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute); - } - - if(ts->secfracPrecision > 0) - { /* we now need to include fractional seconds. While doing so, we must look at - * the precision specified. For example, if we have millisec precision (3 digits), a - * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this - * is a huge difference ;). To avoid this, we first create a format string with - * the specific precision and *then* use that format string to do the actual - * formating (mmmmhhh... kind of self-modifying code... ;)). - */ - char szFmtStr[64]; - /* be careful: there is ONE actual %d in the format string below ;) */ - snprintf(szFmtStr, sizeof(szFmtStr), - "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s", - ts->secfracPrecision); - iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day, - ts->hour, ts->minute, ts->second, ts->secfrac, szTZ); - } - else - iRet = snprintf(pBuf, iLenBuf, - "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s", - ts->year, ts->month, ts->day, - ts->hour, ts->minute, ts->second, szTZ); - return(iRet); -} - -/** - * 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. - */ -int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf) -{ - static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar", - "Apr", "May", "Jun", "Jul", - "Aug", "Sep", "Oct", "Nov", "Dec"}; - assert(ts != NULL); - assert(pBuf != NULL); - - if(iLenBuf < 16) - return(0); /* we NEED 16 bytes */ - return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d", - monthNames[ts->month], ts->day, ts->hour, - ts->minute, ts->second - )); -} - -/** - * Format a syslogTimestamp to a text format. - * 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. - */ -#if 0 /* This method is currently not called, be we like to preserve it */ -static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf) -{ - assert(ts != NULL); - assert(pBuf != NULL); - - if(ts->timeType == 1) { - return(formatTimestamp3164(ts, pBuf, iLenBuf)); - } - - if(ts->timeType == 2) { - return(formatTimestamp3339(ts, pBuf, iLenBuf)); - } - - return(0); -} -#endif - - -/** - * 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, all caller-provided pointers must not be NULL... - */ -void getCurrTime(struct syslogTime *t) -{ - struct timeval tp; - struct tm *tm; - struct tm tmBuf; - long lBias; - - assert(t != NULL); - gettimeofday(&tp, NULL); - tm = localtime_r((time_t*) &(tp.tv_sec), &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 = -(daylight ? altzone : timezone); -# else - lBias = tm->tm_gmtoff; -# endif - if(lBias < 0) - { - t->OffsetMode = '-'; - lBias *= -1; - } - else - t->OffsetMode = '+'; - t->OffsetHour = lBias / 3600; - t->OffsetMinute = lBias % 3600; -} -/* rgerhards 2004-11-09: end of helper routines. On to the - * "real" code ;) - */ - static int usage(void) { - fprintf(stderr, "usage: rsyslogd [-46AdhqQvw] [-l hostlist] [-m markinterval] [-n] [-p path]\n" \ - " [-s domainlist] [-r[port]] [-tport[,max-sessions]] [-gport[,max-sessions]] [-f conffile] [-i pidfile] [-x]\n"); + fprintf(stderr, "usage: rsyslogd [-c<version>] [-46AdnqQvwx] [-l<hostlist>] [-s<domainlist>]\n" + " [-f<conffile>] [-i<pidfile>] [-M<module load path>]\n" + " [-u<number>]\n" + "To run rsyslogd in native mode, use \"rsyslogd -c3 <other options>\"\n\n" + "For further information see http://www.rsyslog.com/doc\n"); exit(1); /* "good" exit - done to terminate usage() */ } -#ifdef SYSLOG_UNIXAF -static int create_unix_socket(const char *path) -{ - struct sockaddr_un sunx; - int fd; - char line[MAXLINE +1]; - - if (path[0] == '\0') - return -1; - - (void) unlink(path); - - memset(&sunx, 0, sizeof(sunx)); - sunx.sun_family = AF_UNIX; - (void) strncpy(sunx.sun_path, path, sizeof(sunx.sun_path)); - fd = socket(AF_UNIX, SOCK_DGRAM, 0); - if (fd < 0 || bind(fd, (struct sockaddr *) &sunx, - SUN_LEN(&sunx)) < 0 || - chmod(path, 0666) < 0) { - snprintf(line, sizeof(line), "cannot create %s", path); - logerror(line); - dbgprintf("cannot create %s (%d).\n", path, errno); - close(fd); - return -1; - } - return fd; -} -#endif - -#ifdef SYSLOG_INET -/* closes the UDP listen sockets (if they exist) and frees - * all dynamically assigned memory. - */ -static void closeUDPListenSockets() -{ - register int i; - - if(finet != NULL) { - for (i = 0; i < *finet; i++) - close(finet[i+1]); - free(finet); - finet = NULL; - } -} - - -/* creates the UDP listen sockets - */ -static int *create_udp_socket() -{ - struct addrinfo hints, *res, *r; - int error, maxs, *s, *socks, on = 1; - int sockflags; - - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; - hints.ai_family = family; - hints.ai_socktype = SOCK_DGRAM; - error = getaddrinfo(NULL, LogPort, &hints, &res); - if(error) { - logerror((char*) gai_strerror(error)); - logerror("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) { - logerror("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)) - logerror("create_udp_socket(), socket"); - /* 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(*s, IPPROTO_IPV6, IPV6_V6ONLY, - (char *)&ion, sizeof (ion)) < 0) { - logerror("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 ) { - logerror("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. - */ -#ifndef BSD - if (should_use_so_bsdcompat()) { - if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, - (char *) &on, sizeof(on)) < 0) { - logerror("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) { - logerror("fcntl(O_NONBLOCK)"); - close(*s); - *s = -1; - continue; - } - - /* 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 - ) { - logerror("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) { - logerror("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); -} -#endif - /* function to destruct a selector_t object * rgerhards, 2007-08-01 */ -static rsRetVal selectorDestruct(void *pVal) +rsRetVal +selectorDestruct(void *pVal) { selector_t *pThis = (selector_t *) pVal; assert(pThis != NULL); if(pThis->pCSHostnameComp != NULL) - rsCStrDestruct(pThis->pCSHostnameComp); + rsCStrDestruct(&pThis->pCSHostnameComp); if(pThis->pCSProgNameComp != NULL) - rsCStrDestruct(pThis->pCSProgNameComp); + rsCStrDestruct(&pThis->pCSProgNameComp); if(pThis->f_filter_type == FILTER_PROP) { if(pThis->f_filterData.prop.pCSPropName != NULL) - rsCStrDestruct(pThis->f_filterData.prop.pCSPropName); + rsCStrDestruct(&pThis->f_filterData.prop.pCSPropName); if(pThis->f_filterData.prop.pCSCompValue != NULL) - rsCStrDestruct(pThis->f_filterData.prop.pCSCompValue); + rsCStrDestruct(&pThis->f_filterData.prop.pCSCompValue); + } else if(pThis->f_filter_type == FILTER_EXPR) { + if(pThis->f_filterData.f_expr != NULL) + expr.Destruct(&pThis->f_filterData.f_expr); } llDestroy(&pThis->llActList); @@ -1858,7 +491,8 @@ static rsRetVal selectorDestruct(void *pVal) /* function to construct a selector_t object * rgerhards, 2007-08-01 */ -static rsRetVal selectorConstruct(selector_t **ppThis) +rsRetVal +selectorConstruct(selector_t **ppThis) { DEFiRet; selector_t *pThis; @@ -1878,7 +512,7 @@ finalize_it: } } *ppThis = pThis; - return iRet; + RETiRet; } @@ -1965,7 +599,14 @@ void untty(void) if ( !Debug ) { i = open(_PATH_TTY, O_RDWR); if (i >= 0) { - (void) ioctl(i, (int) TIOCNOTTY, (char *)0); +# if !defined(__hpux) + (void) ioctl(i, (int) TIOCNOTTY, (char *)0); +# 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 (void) close(i); } } @@ -1973,7 +614,84 @@ void untty(void) #endif -/* rgerhards, 2006-11-30: I have greatly changed this function. Formerly, +/* Take a raw input line, decode the message, and print the message + * on the appropriate log files. + * rgerhards 2004-11-08: Please note + * that this function does only a partial decoding. At best, it splits + * the PRI part. No further decode happens. The rest is done in + * logmsg(). + * Added the iSource parameter so that we know if we have to parse + * HOSTNAME or not. rgerhards 2004-11-16. + * changed parameter iSource to bParseHost. For details, see comment in + * printchopped(). rgerhards 2005-10-06 + * rgerhards: 2008-03-06: added "flags" to allow an input module to specify + * flags, most importantly to request ignoring the messages' timestamp. + * + * rgerhards, 2008-03-19: + * I added an additional calling parameter to permit specifying the flow + * control capability of the source. + */ +rsRetVal printline(char *hname, char *msg, int bParseHost, int flags, flowControl_t flowCtlType) +{ + DEFiRet; + register char *p; + int pri; + msg_t *pMsg; + + /* Now it is time to create the message object (rgerhards) + */ + CHKiRet(msgConstruct(&pMsg)); + MsgSetFlowControlType(pMsg, flowCtlType); + MsgSetRawMsg(pMsg, msg); + + pMsg->bParseHOSTNAME = bParseHost; + /* test for special codes */ + pri = DEFUPRI; + p = msg; + if (*p == '<') { + pri = 0; + while (isdigit((int) *++p)) + { + pri = 10 * pri + (*p - '0'); + } + if (*p == '>') + ++p; + } + if (pri &~ (LOG_FACMASK|LOG_PRIMASK)) + pri = DEFUPRI; + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + + /* Now we look at the HOSTNAME. That is a bit complicated... + * If we have a locally received message, it does NOT + * contain any hostname information in the message itself. + * As such, the HOSTNAME is the same as the system that + * the message was received from (that, for obvious reasons, + * being the local host). rgerhards 2004-11-16 + */ + if(bParseHost == 0) + MsgSetHOSTNAME(pMsg, hname); + MsgSetRcvFrom(pMsg, hname); + + /* rgerhards 2004-11-19: well, well... we've now seen that we + * have the "hostname problem" also with the traditional Unix + * message. As we like to emulate it, we need to add the hostname + * to it. + */ + if(MsgSetUxTradMsg(pMsg, p) != 0) + ABORT_FINALIZE(RS_RET_ERR); + + logmsg(pMsg, flags | SYNC_FILE); + +finalize_it: + RETiRet; +} + + +/* This takes a received message that must be decoded and submits it to + * the main message queue. The function calls the necessary parser. + * + * rgerhards, 2006-11-30: I have greatly changed this function. Formerly, * it tried to reassemble multi-part messages, which is a legacy stock * sysklogd concept. In essence, that was that messages not ending with * \0 were glued together. As far as I can see, this is a sysklogd @@ -1992,9 +710,22 @@ void untty(void) * For rfc3195 support, we needed to modify the algo for host parsing, so we can * no longer rely just on the source (rfc3195d forwarded messages arrive via * unix domain sockets but contain the hostname). rgerhards, 2005-10-06 + * + * rgerhards, 2008-02-18: + * This function was previously called "printchopped"() and has been renamed + * as part of the effort to create a clean internal message submission interface. + * It also has been adopted to our usual calling interface, but currently does + * not provide any useful return states. But we now have the hook and things can + * improve in the future. <-- TODO! + * + * rgerhards, 2008-03-19: + * I added an additional calling parameter to permit specifying the flow + * control capability of the source. */ -void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) +rsRetVal +parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType) { + DEFiRet; register int iMsg; char *pMsg; char *pData; @@ -2009,8 +740,6 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) assert(msg != NULL); assert(len >= 0); - dbgprintf("Message length: %d, File descriptor: %d.\n", len, fd); - /* we first 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, @@ -2056,8 +785,8 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) int ret; iLenDefBuf = MAXLINE; ret = uncompress((uchar *) deflateBuf, &iLenDefBuf, (uchar *) msg+1, len-1); - dbgprintf("Compressed message uncompressed with status %d, length: new %d, old %d.\n", - ret, iLenDefBuf, len-1); + dbgprintf("Compressed message uncompressed with status %d, length: new %ld, old %d.\n", + ret, (long) iLenDefBuf, len-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 @@ -2067,10 +796,10 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) * rgerhards, 2006-12-07 */ if(ret != Z_OK) { - logerrorInt("Uncompression of a message failed with return code %d " + errmsg.LogError(NO_ERRCODE, "Uncompression of a message failed with return code %d " "- enable debug logging if you need further information. " "Message ignored.", ret); - return; /* unconditional exit, nothing left to do... */ + FINALIZE; /* unconditional exit, nothing left to do... */ } pData = deflateBuf; pEnd = deflateBuf + iLenDefBuf; @@ -2080,9 +809,9 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) * tell the user we can not accept it. */ if(len > 0 && *msg == 'z') { - logerror("Received a compressed message, but rsyslogd does not have compression " + errmsg.LogError(NO_ERRCODE, "Received a compressed message, but rsyslogd does not have compression " "support enabled. The message will be ignored."); - return; + FINALIZE; } # endif /* ifdef USE_NETZIP */ @@ -2093,7 +822,7 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) */ if(iMsg == MAXLINE) { *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */ - printline(hname, tmpline, bParseHost); + printline(hname, tmpline, bParseHost, flags, flowCtlType); } else { /* This case in theory never can happen. If it happens, we have * a logic error. I am checking for it, because if I would not, @@ -2104,7 +833,7 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) */ dbgprintf("internal error: iMsg > MAXLINE in printchopped()\n"); } - return; /* in this case, we are done... nothing left we can do */ + FINALIZE; /* in this case, we are done... nothing left we can do */ } if(*pData == '\0') { /* guard against \0 characters... */ /* changed to the sequence (somewhat) proposed in @@ -2145,79 +874,10 @@ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */ /* typically, we should end up here! */ - printline(hname, tmpline, bParseHost); - - return; -} + printline(hname, tmpline, bParseHost, flags, flowCtlType); -/* Take a raw input line, decode the message, and print the message - * on the appropriate log files. - * rgerhards 2004-11-08: Please note - * that this function does only a partial decoding. At best, it splits - * the PRI part. No further decode happens. The rest is done in - * logmsg(). - * Added the iSource parameter so that we know if we have to parse - * HOSTNAME or not. rgerhards 2004-11-16. - * changed parameter iSource to bParseHost. For details, see comment in - * printchopped(). rgerhards 2005-10-06 - */ -void printline(char *hname, char *msg, int bParseHost) -{ - register char *p; - int pri; - msg_t *pMsg; - - /* Now it is time to create the message object (rgerhards) - */ - if((pMsg = MsgConstruct()) == NULL){ - /* rgerhards, 2007-06-21: if we can not get memory, we discard this - * message but continue to run (in the hope that things improve) - */ - glblHadMemShortage = 1; - dbgprintf("Memory shortage in printline(): Could not construct Msg object.\n"); - return; - } - MsgSetRawMsg(pMsg, msg); - - pMsg->bParseHOSTNAME = bParseHost; - /* test for special codes */ - pri = DEFUPRI; - p = msg; - if (*p == '<') { - pri = 0; - while (isdigit((int) *++p)) - { - pri = 10 * pri + (*p - '0'); - } - if (*p == '>') - ++p; - } - if (pri &~ (LOG_FACMASK|LOG_PRIMASK)) - pri = DEFUPRI; - pMsg->iFacility = LOG_FAC(pri); - pMsg->iSeverity = LOG_PRI(pri); - - /* Now we look at the HOSTNAME. That is a bit complicated... - * If we have a locally received message, it does NOT - * contain any hostname information in the message itself. - * As such, the HOSTNAME is the same as the system that - * the message was received from (that, for obvious reasons, - * being the local host). rgerhards 2004-11-16 - */ - if(bParseHost == 0) - MsgSetHOSTNAME(pMsg, hname); - MsgSetRcvFrom(pMsg, hname); - - /* rgerhards 2004-11-19: well, well... we've now seen that we - * have the "hostname problem" also with the traditional Unix - * message. As we like to emulate it, we need to add the hostname - * to it. - */ - if(MsgSetUxTradMsg(pMsg, p) != 0) return; - - logmsg(pri, pMsg, SYNC_FILE); - - return; +finalize_it: + RETiRet; } /* rgerhards 2004-11-09: the following is a function that can be used @@ -2229,52 +889,34 @@ void printline(char *hname, char *msg, int bParseHost) * function here probably is only an interim solution and that we need to * think on the best way to do this. */ -static void +rsRetVal logmsgInternal(int pri, char *msg, int flags) { + DEFiRet; msg_t *pMsg; - if((pMsg = MsgConstruct()) == NULL){ - /* rgerhards 2004-11-09: calling panic might not be the - * brightest idea - however, it is the best I currently have - * (think a bit more about this). - * rgehards, 2007-06-21: I have now thought a bit more about - * it. If we are so low on memory, there is few we can do. calling - * panic so far only write a debug line - this is seomthing we keep. - * Other than that, however, we ignore the error and hope that - * memory shortage will be resolved while we continue to run. In any - * case, there is no valid point in aborting the syslogd for this - * reason - that would be counter-productive. So we ignore the - * to be logged message. - */ - glblHadMemShortage = 1; - dbgprintf("Memory shortage in logmsgInternal: could not construct Msg object.\n"); - return; - } - + CHKiRet(msgConstruct(&pMsg)); MsgSetUxTradMsg(pMsg, msg); MsgSetRawMsg(pMsg, msg); - MsgSetHOSTNAME(pMsg, LocalHostName); - MsgSetRcvFrom(pMsg, LocalHostName); + MsgSetHOSTNAME(pMsg, (char*)LocalHostName); + MsgSetRcvFrom(pMsg, (char*)LocalHostName); MsgSetTAG(pMsg, "rsyslogd:"); pMsg->iFacility = LOG_FAC(pri); pMsg->iSeverity = LOG_PRI(pri); pMsg->bParseHOSTNAME = 0; - getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ flags |= INTERNAL_MSG; -#ifdef USE_PTHREADS - if(bRunningMultithreaded == 0) { /* not yet in queued mode */ + if(bHaveMainQueue == 0) { /* not yet in queued mode */ iminternalAddMsg(pri, pMsg, flags); } else { /* we have the queue, so we can simply provide the * message to the queue engine. */ - logmsg(pri, pMsg, flags); + logmsg(pMsg, flags); } -#else - iminternalAddMsg(pri, pMsg, flags); -#endif +finalize_it: + RETiRet; } /* This functions looks at the given message and checks if it matches the @@ -2284,12 +926,17 @@ logmsgInternal(int pri, char *msg, int flags) * decision code to grow more complex over time AND logmsg() is already * a very lengthy function, I thought a separate function is more appropriate. * 2005-09-19 rgerhards + * 2008-02-25 rgerhards: changed interface, now utilizes iRet, bProcessMsg + * returns is message should be procesed. */ -int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) +static rsRetVal shouldProcessThisMessage(selector_t *f, msg_t *pMsg, int *bProcessMsg) { + DEFiRet; unsigned short pbMustBeFreed; char *pszPropVal; - int iRet = 0; + int bRet = 0; + vm_t *pVM = NULL; + var_t *pResult = NULL; assert(f != NULL); assert(pMsg != NULL); @@ -2307,14 +954,14 @@ int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) /* not equal, so we are already done... */ dbgprintf("hostname filter '+%s' does not match '%s'\n", rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg)); - return 0; + FINALIZE; } } else { /* must be -hostname */ if(!rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { /* not equal, so we are already done... */ dbgprintf("hostname filter '-%s' does not match '%s'\n", rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg)); - return 0; + FINALIZE; } } @@ -2335,7 +982,7 @@ int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) /* not equal or inverted selection, so we are already done... */ dbgprintf("programname filter '%s' does not match '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp), getProgramName(pMsg)); - return 0; + FINALIZE; } } @@ -2345,9 +992,18 @@ int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) /* skip messages that are incorrect priority */ if ( (f->f_filterData.f_pmask[pMsg->iFacility] == TABLE_NOPRI) || \ ((f->f_filterData.f_pmask[pMsg->iFacility] & (1<<pMsg->iSeverity)) == 0) ) - iRet = 0; + bRet = 0; else - iRet = 1; + bRet = 1; + } else if(f->f_filter_type == FILTER_EXPR) { + CHKiRet(vm.Construct(&pVM)); + CHKiRet(vm.ConstructFinalize(pVM)); + CHKiRet(vm.SetMsg(pVM, pMsg)); + CHKiRet(vm.ExecProg(pVM, f->f_filterData.f_expr->pVmprg)); + CHKiRet(vm.PopBoolFromStack(pVM, &pResult)); + dbgprintf("result of expression evaluation: %lld\n", pResult->val.num); + /* VM is destructed on function exit */ + bRet = (pResult->val.num) ? 1 : 0; } else { assert(f->f_filter_type == FILTER_PROP); /* assert() just in case... */ pszPropVal = MsgGetProp(pMsg, NULL, f->f_filterData.prop.pCSPropName, &pbMustBeFreed); @@ -2356,44 +1012,44 @@ int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) switch(f->f_filterData.prop.operation ) { case FIOP_CONTAINS: if(rsCStrLocateInSzStr(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal) != -1) - iRet = 1; + bRet = 1; break; case FIOP_ISEQUAL: if(rsCStrSzStrCmp(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal, strlen(pszPropVal)) == 0) - iRet = 1; /* process message! */ + bRet = 1; /* process message! */ break; case FIOP_STARTSWITH: if(rsCStrSzStrStartsWithCStr(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal, strlen(pszPropVal)) == 0) - iRet = 1; /* process message! */ + bRet = 1; /* process message! */ break; case FIOP_REGEX: if(rsCStrSzStrMatchRegex(f->f_filterData.prop.pCSCompValue, (unsigned char*) pszPropVal) == 0) - iRet = 1; + bRet = 1; break; default: /* here, it handles NOP (for performance reasons) */ assert(f->f_filterData.prop.operation == FIOP_NOP); - iRet = 1; /* as good as any other default ;) */ + bRet = 1; /* as good as any other default ;) */ break; } /* now check if the value must be negated */ if(f->f_filterData.prop.isNegated) - iRet = (iRet == 1) ? 0 : 1; + bRet = (bRet == 1) ? 0 : 1; if(Debug) { - printf("Filter: check for property '%s' (value '%s') ", + dbgprintf("Filter: check for property '%s' (value '%s') ", rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName), pszPropVal); if(f->f_filterData.prop.isNegated) - printf("NOT "); - printf("%s '%s': %s\n", + dbgprintf("NOT "); + dbgprintf("%s '%s': %s\n", getFIOPName(f->f_filterData.prop.operation), rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue), - iRet ? "TRUE" : "FALSE"); + bRet ? "TRUE" : "FALSE"); } /* cleanup */ @@ -2401,112 +1057,16 @@ int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) free(pszPropVal); } - return(iRet); -} - - -/* doEmergencyLoggin() - * ... does exactly do that. It logs messages when the subsystem has not yet - * been initialized. This almost always happens during initial startup or - * during HUPing. -- rgerhards, 2007-07-25 - * rgerhards, 2007-08-03: as of now, this can normally no longer happen. All - * startup messages are now buffered until the system is ready to run. I leave - * this minimal implementation here in in the very remote case that it might - * be needed in the future or due to a program bug. Do *not* excpect this - * code to be called. - */ -static void doEmergencyLogging(msg_t *pMsg) -{ - assert(pMsg != NULL); - fprintf(stderr, "rsyslog: %s\n", pMsg->pszMSG); -} - - -/* call the configured action. Does all necessary housekeeping. - * rgerhards, 2007-08-01 - */ -static rsRetVal callAction(msg_t *pMsg, action_t *pAction) -{ - DEFiRet; - - assert(pMsg != NULL); - assert(pAction != NULL); - - /* Make sure nodbody else modifies/uses this action object. Right now, this - * is important because of "message repeated n times" processing, later it will - * become important when we (possibly) have multiple worker threads. - * rgerhards, 2007-12-11 - */ - LockObj(pAction); - - /* first, we need to check if this is a disabled - * entry. If so, we must not further process it. - * rgerhards 2005-09-26 - * In the future, disabled modules may be re-probed from time - * to time. They are in a perfectly legal state, except that the - * doAction method indicated that it wanted to be disabled - but - * we do not consider this is a solution for eternity... So we - * should check from time to time if affairs have improved. - * rgerhards, 2007-07-24 - */ - if(pAction->bEnabled == 0) { - ABORT_FINALIZE(RS_RET_OK); - } - - if(actionIsSuspended(pAction)) { - CHKiRet(actionTryResume(pAction)); - } +finalize_it: + /* destruct in any case, not just on error, but it makes error handling much easier */ + if(pVM != NULL) + vm.Destruct(&pVM); - /* don't output marks to recently written files */ - if ((pMsg->msgFlags & MARK) && (time(NULL) - pAction->f_time) < MarkInterval / 2) { - ABORT_FINALIZE(RS_RET_OK); - } + if(pResult != NULL) + var.Destruct(&pResult); - /* suppress duplicate messages - */ - if ((pAction->f_ReduceRepeated == 1) && pAction->f_pMsg != NULL && - (pMsg->msgFlags & MARK) == 0 && getMSGLen(pMsg) == getMSGLen(pAction->f_pMsg) && - !strcmp(getMSG(pMsg), getMSG(pAction->f_pMsg)) && - !strcmp(getHOSTNAME(pMsg), getHOSTNAME(pAction->f_pMsg)) && - !strcmp(getPROCID(pMsg), getPROCID(pAction->f_pMsg)) && - !strcmp(getAPPNAME(pMsg), getAPPNAME(pAction->f_pMsg))) { - pAction->f_prevcount++; - dbgprintf("msg repeated %d times, %ld sec of %d.\n", - pAction->f_prevcount, time(NULL) - pAction->f_time, - repeatinterval[pAction->f_repeatcount]); - /* use current message, so we have the new timestamp (means we need to discard previous one) */ - MsgDestruct(pAction->f_pMsg); - pAction->f_pMsg = MsgAddRef(pMsg); - /* If domark would have logged this by now, flush it now (so we don't hold - * isolated messages), but back off so we'll flush less often in the future. - */ - if(time(NULL) > REPEATTIME(pAction)) { - iRet = fprintlog(pAction); - BACKOFF(pAction); - } - } else { - /* new message, save it */ - /* first check if we have a previous message stored - * if so, emit and then discard it first - */ - if(pAction->f_pMsg != NULL) { - if(pAction->f_prevcount > 0) - CHKiRet(fprintlog(pAction)); - /* if we run into trouble (most importantly a suspended - * action), we keep the old message (by virtue of not - * destructing it) and discard the new one (done - * automatically when we return. - */ - MsgDestruct(pAction->f_pMsg); - } - pAction->f_pMsg = MsgAddRef(pMsg); - /* call the output driver */ - iRet = fprintlog(pAction); - } - -finalize_it: - UnlockObj(pAction); - return iRet; + *bProcessMsg = bRet; + RETiRet; } @@ -2532,7 +1092,7 @@ DEFFUNC_llExecFunc(processMsgDoActions) ABORT_FINALIZE(RS_RET_OK); } - iRetMod = callAction(pDoActData->pMsg, pAction); + iRetMod = actionCallAction(pAction, pDoActData->pMsg); if(iRetMod == RS_RET_DISCARDMSG) { ABORT_FINALIZE(RS_RET_DISCARDMSG); } else if(iRetMod == RS_RET_SUSPENDED) { @@ -2543,42 +1103,32 @@ DEFFUNC_llExecFunc(processMsgDoActions) } finalize_it: - return iRet; + RETiRet; } /* Process (consume) a received message. Calls the actions configured. - * Can some time later run in its own thread. To aid this, the calling - * parameters should be reduced to just pMsg. - * See comment dated 2005-10-13 in logmsg() on multithreading. * rgerhards, 2005-10-13 */ -static void processMsg(msg_t *pMsg) +static void +processMsg(msg_t *pMsg) { selector_t *f; int bContinue; + int bProcessMsg; processMsgDoActions_t DoActData; + rsRetVal iRet; + BEGINfunc assert(pMsg != NULL); /* log the message to the particular outputs */ - if (!Initialized) { - doEmergencyLogging(pMsg); - return; - } bContinue = 1; for (f = Files; f != NULL && bContinue ; f = f->f_next) { - /* This is actually the "filter logic". Looks like we need - * to improve it a little for complex selector line conditions. We - * won't do that for now, but at least we now know where - * to look at. - * 2005-09-09 rgerhards - * ok, we are now ready to move to something more advanced. Because - * of this, I am moving the actual decision code to outside this function. - * 2005-09-19 rgerhards - */ - if(!shouldProcessThisMessage(f, pMsg)) { + /* first check the filters... */ + iRet = shouldProcessThisMessage(f, pMsg, &bProcessMsg); + if(!bProcessMsg) { continue; } @@ -2588,243 +1138,31 @@ static void processMsg(msg_t *pMsg) if(llExecFunc(&f->llActList, processMsgDoActions, (void*)&DoActData) == RS_RET_DISCARDMSG) bContinue = 0; } + ENDfunc } -#ifdef USE_PTHREADS -/* This block contains code that is only present when USE_PTHREADS is - * enabled. I plan to move it to some other file, but for the time - * being, I include it here because that saves me from the need to - * do so many external definitons. - * rgerhards, 2005-10-24 - */ - -/* shuts down the worker process. The worker will first finish - * with the message queue. Control returns, when done. - * This function is intended to be called during syslogd shutdown - * AND restart (init()!). - * rgerhards, 2005-10-25 - */ -static void stopWorker(void) -{ - if(bRunningMultithreaded) { - /* we could run single-threaded if there was an error - * during startup. Then, we obviously do not need to - * do anything to stop the worker ;) - */ - dbgprintf("Initiating worker thread shutdown sequence...\n"); - /* We are now done with all messages, so we need to wake up the - * worker thread and then wait for it to finish. - */ - bGlblDone = 1; - /* It's actually not "not empty" below but awaking the worker. The worker - * then finds out that it shall terminate and does so. - */ - pthread_cond_signal(pMsgQueue->notEmpty); - pthread_join(thrdWorker, NULL); - bRunningMultithreaded = 0; - dbgprintf("Worker thread terminated.\n"); - } -} - - -/* starts the worker thread. It must be made sure that the queue is - * already existing and the worker is NOT already running. - * rgerhards 2005-10-25 +/* The consumer of dequeued messages. This function is called by the + * queue engine on dequeueing of a message. It runs on a SEPARATE + * THREAD. + * NOTE: Having more than one worker requires guarding of some + * message object structures and potentially others - need to be checked + * before we support multiple worker threads on the message queue. + * Please note: the message object is destructed by the queue itself! */ -static void startWorker(void) +static rsRetVal +msgConsumer(void __attribute__((unused)) *notNeeded, void *pUsr) { - int i; - if(pMsgQueue != NULL) { - bGlblDone = 0; /* we are NOT done (else worker would immediately terminate) */ - i = pthread_create(&thrdWorker, NULL, singleWorker, NULL); - dbgprintf("Worker thread started with state %d.\n", i); - bRunningMultithreaded = 1; - } else { - dbgprintf("message queue not existing, remaining single-threaded.\n"); - } -} - - -static msgQueue *queueInit (void) -{ - msgQueue *q; - - q = (msgQueue *)malloc(sizeof(msgQueue)); - if (q == NULL) return (NULL); - if((q->pbuf = malloc(sizeof(void *) * iMainMsgQueueSize)) == NULL) { - free(q); - return NULL; - } - - q->empty = 1; - q->full = 0; - q->head = 0; - q->tail = 0; - q->mut = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); - pthread_mutex_init (q->mut, NULL); - q->notFull = (pthread_cond_t *) malloc (sizeof (pthread_cond_t)); - pthread_cond_init (q->notFull, NULL); - q->notEmpty = (pthread_cond_t *) malloc (sizeof (pthread_cond_t)); - pthread_cond_init (q->notEmpty, NULL); - - return (q); -} - -static void queueDelete (msgQueue *q) -{ - pthread_mutex_destroy (q->mut); - free (q->mut); - pthread_cond_destroy (q->notFull); - free (q->notFull); - pthread_cond_destroy (q->notEmpty); - free (q->notEmpty); - free(q->pbuf); - free (q); -} - - -/* In queueAdd() and queueDel() we have a potential race condition. If a message - * is dequeued and at the same time a message is enqueued and the queue is either - * full or empty, the full (or empty) indicator may be invalidly updated. HOWEVER, - * this does not cause any real problems. No queue pointers can be wrong. And even - * if one of the flags is set invalidly, that does not pose a real problem. If - * "full" is invalidly set, at mose one message might be lost, if we are already in - * a timeout situation (this is quite acceptable). And if "empty" is accidently set, - * the receiver will not continue the inner loop, but break out of the outer. So no - * harm is done at all. For this reason, I do not yet use a mutex to guard the two - * flags - there would be a notable performance hit with, IMHO, no gain in stability - * or functionality. But anyhow, now it's documented... - * rgerhards, 2007-09-20 - * NOTE: this comment does not really apply - the callers handle the mutex, so it - * *is* guarded. - */ -static void queueAdd (msgQueue *q, void* in) -{ - q->pbuf[q->tail] = in; - q->tail++; - if (q->tail == iMainMsgQueueSize) - q->tail = 0; - if (q->tail == q->head) - q->full = 1; - q->empty = 0; - - return; -} - -static void queueDel (msgQueue *q, msg_t **out) -{ - *out = (msg_t*) q->pbuf[q->head]; - - q->head++; - if (q->head == iMainMsgQueueSize) - q->head = 0; - if (q->head == q->tail) - q->empty = 1; - q->full = 0; - - return; -} - - -/* The worker thread (so far, we have dual-threading, so only one - * worker thread. Having more than one worker requires considerable - * additional code review in regard to thread-safety. - */ -static void *singleWorker() -{ - msgQueue *fifo = pMsgQueue; - msg_t *pMsg; - sigset_t sigSet; - - assert(fifo != NULL); - - sigfillset(&sigSet); - pthread_sigmask(SIG_BLOCK, &sigSet, NULL); - - while(!bGlblDone || !fifo->empty) { - pthread_mutex_lock(fifo->mut); - while (fifo->empty && !bGlblDone) { - dbgprintf("singleWorker: queue EMPTY, waiting for next message.\n"); - pthread_cond_wait (fifo->notEmpty, fifo->mut); - } - if(!fifo->empty) { - /* dequeue element (still protected from mutex) */ - queueDel(fifo, &pMsg); - assert(pMsg != NULL); - pthread_mutex_unlock(fifo->mut); - pthread_cond_signal (fifo->notFull); - /* do actual processing (the lengthy part, runs in parallel) */ - dbgprintf("Lone worker is running...\n"); - processMsg(pMsg); - MsgDestruct(pMsg); - /* If you need a delay for testing, here do a */ - /* sleep(1); */ - } else { /* the mutex must be unlocked in any case (important for termination) */ - pthread_mutex_unlock(fifo->mut); - } - - if(debugging_on && bGlblDone && !fifo->empty) - dbgprintf("Worker does not yet terminate because it still has messages to process.\n"); - } - - dbgprintf("Worker thread terminates\n"); - pthread_exit(0); -} - -/* END threads-related code */ -#endif /* #ifdef USE_PTHREADS */ - - -/* This method enqueues a message into the the message buffer. It also - * the worker thread, so that the message will be processed. If we are - * compiled without PTHREADS support, we simply use this method as - * an alias for processMsg(). - * See comment dated 2005-10-13 in logmsg() on multithreading. - * rgerhards, 2005-10-24 - */ -#ifndef USE_PTHREADS -#else -static void enqueueMsg(msg_t *pMsg) -{ - int iRet; - msgQueue *fifo = pMsgQueue; - struct timespec t; + DEFiRet; + msg_t *pMsg = (msg_t*) pUsr; assert(pMsg != NULL); - if(bRunningMultithreaded == 0) { - /* multi-threading is not yet initialized, happens e.g. - * during startup and restart. rgerhards, 2005-10-25 - */ - dbgprintf("enqueueMsg: not yet running on multiple threads\n"); - processMsg(pMsg); - } else { - /* "normal" mode, threading initialized */ - pthread_mutex_lock(fifo->mut); - - while (fifo->full) { - dbgprintf("enqueueMsg: queue FULL.\n"); - - clock_gettime (CLOCK_REALTIME, &t); - t.tv_sec += 2; - - if(pthread_cond_timedwait (fifo->notFull, - fifo->mut, &t) != 0) { - dbgprintf("enqueueMsg: cond timeout, dropping message!\n"); - MsgDestruct(pMsg); - goto unlock; - } - } - queueAdd(fifo, pMsg); - unlock: - /* now activate the worker thread */ - pthread_mutex_unlock(fifo->mut); - iRet = pthread_cond_signal(fifo->notEmpty); - dbgprintf("EnqueueMsg signaled condition (%d)\n", iRet); - } + processMsg(pMsg); + msgDestruct(&pMsg); + + RETiRet; } -#endif /* #ifndef USE_PTHREADS */ /* Helper to parseRFCSyslogMsg. This function parses a field up to @@ -2971,14 +1309,14 @@ static int parseRFCSyslogMsg(msg_t *pMsg, int flags) */ /* TIMESTAMP */ - if(srSLMGParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == FALSE) { + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == FALSE) { dbgprintf("no TIMESTAMP detected!\n"); bContParse = 0; flags |= ADDDATE; } if (flags & ADDDATE) { - getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ } /* HOSTNAME */ @@ -3022,6 +1360,8 @@ static int parseRFCSyslogMsg(msg_t *pMsg, int flags) free(pBuf); return 0; /* all ok */ } + + /* parse a legay-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 @@ -3040,7 +1380,7 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) char *p2parse; char *pBuf; char *pWork; - rsCStrObj *pStrB; + cstr_t *pStrB; int iCnt; int bTAGCharDetected; @@ -3048,11 +1388,26 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) assert(pMsg->pszUxTradMsg != NULL); p2parse = (char*) pMsg->pszUxTradMsg; - /* Check to see if msg contains a timestamp + /* Check to see if msg contains a timestamp. We stary trying with a + * high-precision one... */ - if(srSLMGParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse) == TRUE) - p2parse += 16; - else { + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == TRUE) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3339 */; + } else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse) == TRUE) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3164 */; + } else if(*p2parse == ' ') { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */ + ++p2parse; /* move over space */ + if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse) == TRUE) { + /* 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; + flags |= ADDDATE; + } + } else { flags |= ADDDATE; } @@ -3062,7 +1417,7 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) * rgerhards 2004-12-03 */ if(flags & ADDDATE) { - getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ } /* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we @@ -3072,7 +1427,6 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) * 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 && !(flags & 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 @@ -3130,16 +1484,18 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); } - /* now parse TAG - that should be present in message from - * all sources. + /* 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). Finally, we allow only up to 32 characters for - * TAG, as it is specified in RFC 3164. + * (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. */ /* The following code in general is quick & dirty - I need to get * it going for a test, rgerhards 2004-11-16 */ @@ -3148,20 +1504,15 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) * the records: the code is currently clean, but we could optimize it! */ if(!bTAGCharDetected) { uchar *pszTAG; - if((pStrB = rsCStrConstruct()) == NULL) + if(rsCStrConstruct(&pStrB) != RS_RET_OK) return 1; rsCStrSetAllocIncrement(pStrB, 33); pWork = pBuf; iCnt = 0; - while(*p2parse && *p2parse != ':' && *p2parse != ' ' && iCnt < 32) { + while(*p2parse && *p2parse != ':' && *p2parse != ' ') { rsCStrAppendChar(pStrB, *p2parse++); ++iCnt; } - if (iCnt == 32) { - while(*p2parse && *p2parse != ':' && *p2parse != ' ') { - ++p2parse; - } - } if(*p2parse == ':') { ++p2parse; rsCStrAppendChar(pStrB, ':'); @@ -3206,8 +1557,28 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) } -/* - * Log a message to the appropriate log files, users, etc. based on +/* submit a fully created message to the main message queue. The message is + * fully processed and parsed, so no parsing at all happens. This is primarily + * a hook to prevent the need for callers to know about the main message queue + * (which may change in the future as we will probably have multiple rule + * sets and thus queues...). + * rgerhards, 2008-02-13 + */ +rsRetVal +submitMsg(msg_t *pMsg) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pMsg, msg); + + MsgPrepareEnqueue(pMsg); + queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg); + + RETiRet; +} + + +/* Log a message to the appropriate log files, users, etc. based on * the priority. * rgerhards 2004-11-08: actually, this also decodes all but the PRI part. * rgerhards 2004-11-09: ... but only, if syslogd could properly be initialized @@ -3226,17 +1597,15 @@ static int parseLegacySyslogMsg(msg_t *pMsg, int flags) * circumstances given. */ void -logmsg(int pri, msg_t *pMsg, int flags) +logmsg(msg_t *pMsg, int flags) { char *msg; - char PRItext[20]; + BEGINfunc assert(pMsg != NULL); assert(pMsg->pszUxTradMsg != NULL); msg = (char*) pMsg->pszUxTradMsg; - dbgprintf("logmsg: %s, flags %x, from '%s', msg %s\n", - textpri(PRItext, sizeof(PRItext) / sizeof(char), pri), - flags, getRcvFrom(pMsg), msg); + dbgprintf("logmsg: flags %x, from '%s', msg %s\n", flags, getRcvFrom(pMsg), msg); /* rger 2005-11-24 (happy thanksgiving!): we now need to check if we have * a traditional syslog message or one formatted according to syslog-protocol. @@ -3247,176 +1616,25 @@ logmsg(int pri, msg_t *pMsg, int flags) dbgprintf("Message has syslog-protocol format.\n"); setProtocolVersion(pMsg, 1); if(parseRFCSyslogMsg(pMsg, flags) == 1) { - MsgDestruct(pMsg); + msgDestruct(&pMsg); return; } } else { /* we have legacy syslog */ dbgprintf("Message has legacy syslog format.\n"); setProtocolVersion(pMsg, 0); if(parseLegacySyslogMsg(pMsg, flags) == 1) { - MsgDestruct(pMsg); + msgDestruct(&pMsg); return; } } /* ---------------------- END PARSING ---------------- */ - - /* rgerhards, 2005-10-13: if we consider going multi-threaded, this - * is probably the best point to split between a producer and a consumer - * thread. In general, with the first multi-threaded approach, we should - * NOT try to do more than have a single producer and consumer, at least - * if both are from the current code base. The issue is that this code - * was definitely not written with reentrancy in mind and uses a lot of - * global variables. So it is very dangerous to simply go ahead and multi - * thread it. However, I think there is a clear distinction between - * producer (where data is received) and consumer (where the actions are). - * It should be fairly safe to create a single thread for each and run them - * concurrently, thightly coupled via an in-memory queue. Even with this - * limited multithraeding, benefits are immediate: the lengthy actions - * (database writes!) are de-coupled from the receivers, what should result - * in less likely message loss (loss due to receiver overrun). It also allows - * us to utilize 2-cpu systems, which will soon be common given the current - * advances in multicore CPU hardware. So this is well worth trying. - * Another plus of this two-thread-approach would be that it can easily be configured, - * so if there are compatibility issues with the threading libs, we could simply - * disable it (as a makefile feature). - * There is one important thing to keep in mind when doing this basic - * multithreading. The syslog/tcp message forwarder manipulates a structutre - * that is used by the main thread, which actually sends the data. This - * structure must be guarded by a mutex, else we will have race conditions and - * some very bad things could happen. - * - * Additional consumer threads might be added relatively easy for new receivers, - * e.g. if we decide to move RFC 3195 via liblogging natively into rsyslogd. - * - * To aid this functionality, I am moving the rest of the code (the actual - * consumer) to its own method, now called "processMsg()". - * - * rgerhards, 2005-10-25: as of now, the dual-threading code is now in place. - * It is an optional feature and even when enabled, rsyslogd will run single-threaded - * if it gets any errors during thread creation. - */ + /* now submit the message to the main queue - then we are done */ pMsg->msgFlags = flags; -#ifndef USE_PTHREADS - processMsg(pMsg); - MsgDestruct(pMsg); -#else /* ifdef USE_PTHREADS */ - enqueueMsg(pMsg); -#endif -} - - -/* rgerhards 2004-11-09: fprintlog() is the actual driver for - * the output channel. It receives the channel description (f) as - * well as the message and outputs them according to the channel - * semantics. The message is typically already contained in the - * channel save buffer (f->f_prevline). This is not only the case - * when a message was already repeated but also when a new message - * arrived. - * rgerhards 2007-08-01: interface changed to use action_t - * rgerhards, 2007-12-11: please note: THIS METHOD MUST ONLY BE - * CALLED AFTER THE CALLER HAS LOCKED THE pAction OBJECT! We do - * not do this here. Failing to do so results in all kinds of - * "interesting" problems! - */ -rsRetVal -fprintlog(action_t *pAction) -{ - msg_t *pMsgSave; /* to save current message pointer, necessary to restore - it in case it needs to be updated (e.g. repeated msgs) */ - DEFiRet; - int i; - - pMsgSave = NULL; /* indicate message poiner not saved */ - /* first check if this is a regular message or the repeation of - * a previous message. If so, we need to change the message text - * to "last message repeated n times" and then go ahead and write - * it. Please note that we can not modify the message object, because - * that would update it in other selectors as well. As such, we first - * need to create a local copy of the message, which we than can update. - * rgerhards, 2007-07-10 - */ - if(pAction->f_prevcount > 1) { - msg_t *pMsg; - uchar szRepMsg[64]; - snprintf((char*)szRepMsg, sizeof(szRepMsg), "last message repeated %d times", - pAction->f_prevcount); - - if((pMsg = MsgDup(pAction->f_pMsg)) == NULL) { - /* it failed - nothing we can do against it... */ - dbgprintf("Message duplication failed, dropping repeat message.\n"); - return RS_RET_ERR; - /* This return is OK. The finalizer frees strings, which are not - * yet allocated. So we can not use the finalizer. - */ - } - - /* We now need to update the other message properties. - * ... RAWMSG is a problem ... Please note that digital - * signatures inside the message are also invalidated. - */ - getCurrTime(&(pMsg->tRcvdAt)); - getCurrTime(&(pMsg->tTIMESTAMP)); - MsgSetMSG(pMsg, (char*)szRepMsg); - MsgSetRawMsg(pMsg, (char*)szRepMsg); - - pMsgSave = pAction->f_pMsg; /* save message pointer for later restoration */ - pAction->f_pMsg = pMsg; /* use the new msg (pointer will be restored below) */ - } - - dbgprintf("Called fprintlog, logging to %s", modGetStateName(pAction->pMod)); - - time(&pAction->f_time); /* we need this for message repeation processing */ - - /* When we reach this point, we have a valid, non-disabled action. - * So let's execute it. -- rgerhards, 2007-07-24 - */ - /* here we must loop to process all requested strings */ - - for(i = 0 ; i < pAction->iNumTpls ; ++i) { - CHKiRet(tplToString(pAction->ppTpl[i], pAction->f_pMsg, &pAction->ppMsgs[i])); - } - /* call configured action */ - iRet = pAction->pMod->mod.om.doAction(pAction->ppMsgs, pAction->f_pMsg->msgFlags, pAction->pModData); - - if(iRet == RS_RET_DISABLE_ACTION) { - dbgprintf("Action requested to be disabled, done that.\n"); - pAction->bEnabled = 0; /* that's it... */ - } - - if(iRet == RS_RET_SUSPENDED) { - dbgprintf("Action requested to be suspended, done that.\n"); - actionSuspend(pAction); - } - - if(iRet == RS_RET_OK) - pAction->f_prevcount = 0; /* message processed, so we start a new cycle */ - -finalize_it: - /* cleanup */ - for(i = 0 ; i < pAction->iNumTpls ; ++i) { - if(pAction->ppMsgs[i] != NULL) { - free(pAction->ppMsgs[i]); - pAction->ppMsgs[i] = NULL; - } - } - - if(pMsgSave != NULL) { - /* we had saved the original message pointer. That was - * done because we needed to create a temporary one - * (most often for "message repeated n time" handling). If so, - * we need to restore the original one now, so that procesing - * can continue as normal. We also need to discard the temporary - * one, as we do not like memory leaks ;) Please note that the original - * message object will be discarded by our callers, so this is nothing - * of our business. rgerhards, 2007-07-10 - */ - MsgDestruct(pAction->f_pMsg); - pAction->f_pMsg = pMsgSave; /* restore it */ - } - - return iRet; + MsgPrepareEnqueue(pMsg); + queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg); + ENDfunc } @@ -3436,161 +1654,193 @@ reapchild() } -/* helper to domark to flush the individual action links via llExecFunc +/* helper to doFlushRptdMsgs() to flush the individual action links via llExecFunc * rgerhards, 2007-08-02 */ -DEFFUNC_llExecFunc(domarkActions) +DEFFUNC_llExecFunc(flushRptdMsgsActions) { action_t *pAction = (action_t*) pData; assert(pAction != NULL); + BEGINfunc LockObj(pAction); if (pAction->f_prevcount && time(NULL) >= REPEATTIME(pAction)) { dbgprintf("flush %s: repeated %d times, %d sec.\n", - modGetStateName(pAction->pMod), pAction->f_prevcount, + module.GetStateName(pAction->pMod), pAction->f_prevcount, repeatinterval[pAction->f_repeatcount]); - if(actionIsSuspended(pAction) && - (actionTryResume(pAction) != RS_RET_OK)) { - goto finalize_it; - } - fprintlog(pAction); + actionWriteToAction(pAction); BACKOFF(pAction); } - -finalize_it: UnlockObj(pAction); + ENDfunc return RS_RET_OK; /* we ignore errors, we can not do anything either way */ } -/* This method writes mark messages and - some time later - flushes reapeat - * messages. - * This method was initially called by an alarm handler. As such, it could potentially - * have race-conditons. For details, see - * http://lkml.org/lkml/2005/3/26/37 - * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=301511 - * I have now changed it so that the alarm handler only sets a global variable, telling - * the main thread that it must do mark processing. So domark() is now called from the - * main thread itself, which is the only thing to make sure rsyslogd will not do - * strange things. The way it originally was seemed to work because mark occurs very - * seldom. However, the code called was anything else but reentrant, so it was like - * russian roulette. - rgerhards, 2005-10-20 - * rgerhards, 2007-12-11: ... and it still is, if running multithreaded. Because in this - * case we run concurrently to the actions... I have now fixed that by using synchronization - * macros. +/* This method flushes reapeat messages. */ static void -domark(void) +doFlushRptdMsgs(void) { register selector_t *f; - if (MarkInterval > 0) { - MarkSeq += TIMERINTVL; - if (MarkSeq >= MarkInterval) { - logmsgInternal(LOG_INFO, "-- MARK --", ADDDATE|MARK); - MarkSeq = 0; - } - - /* see if we need to flush any "message repeated n times"... - * Note that this interferes with objects running on another thread. - * We are using appropriate locking inside the function to handle that. - */ - for (f = Files; f != NULL ; f = f->f_next) { - llExecFunc(&f->llActList, domarkActions, NULL); - } + /* see if we need to flush any "message repeated n times"... + * Note that this interferes with objects running on other threads. + * We are using appropriate locking inside the function to handle that. + */ + for (f = Files; f != NULL ; f = f->f_next) { + llExecFunc(&f->llActList, flushRptdMsgsActions, NULL); } } -/* This is the alarm handler setting the global variable for - * domark request. See domark() comments for further details. - * rgerhards, 2005-10-20 - */ -static void -domarkAlarmHdlr() +static void debug_switch() { struct sigaction sigAct; - bRequestDoMark = 1; /* request alarm */ - + if(debugging_on == 0) { + debugging_on = 1; + dbgprintf("Switching debugging_on to true\n"); + } else { + dbgprintf("Switching debugging_on to false\n"); + debugging_on = 0; + } + memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = domarkAlarmHdlr; - sigaction(SIGALRM, &sigAct, NULL); - - (void) alarm(TIMERINTVL); + sigAct.sa_handler = debug_switch; + sigaction(SIGUSR1, &sigAct, NULL); } -static void debug_switch() +void legacyOptsEnq(uchar *line) { - struct sigaction sigAct; + legacyOptsLL_t *pNew; - dbgprintf("Switching debugging_on to %s\n", (debugging_on == 0) ? "true" : "false"); - debugging_on = (debugging_on == 0) ? 1 : 0; - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = debug_switch; - sigaction(SIGUSR1, &sigAct, NULL); + pNew = malloc(sizeof(legacyOptsLL_t)); + if(line == NULL) + pNew->line = NULL; + else + pNew->line = (uchar *) strdup((char *) line); + pNew->next = NULL; + + if(pLegacyOptsLL == NULL) + pLegacyOptsLL = pNew; + else { + legacyOptsLL_t *pThis = pLegacyOptsLL; + + while(pThis->next != NULL) + pThis = pThis->next; + pThis->next = pNew; + } } -/* - * Add a string to error message and send it to logerror() - * The error message is passed to snprintf() and must be - * correctly formatted for it (containing a single %s param). - * rgerhards 2005-09-19 - */ -void logerrorSz(char *type, char *errMsg) +void legacyOptsFree(void) { - char buf[1024]; + legacyOptsLL_t *pThis = pLegacyOptsLL, *pNext; - snprintf(buf, sizeof(buf), type, errMsg); - buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ - logerror(buf); - return; + while(pThis != NULL) { + if(pThis->line != NULL) + free(pThis->line); + pNext = pThis->next; + free(pThis); + pThis = pNext; + } } -/* - * Add an integer to error message and send it to logerror() - * The error message is passed to snprintf() and must be - * correctly formatted for it (containing a single %d param). - * rgerhards 2005-09-19 - */ -void logerrorInt(char *type, int errCode) + +void legacyOptsHook(void) { - char buf[1024]; + legacyOptsLL_t *pThis = pLegacyOptsLL; - snprintf(buf, sizeof(buf), type, errCode); - buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ - logerror(buf); - return; + while(pThis != NULL) { + if(pThis->line != NULL) { + errno = 0; + errmsg.LogError(NO_ERRCODE, "Warning: backward compatibility layer added to following " + "directive to rsyslog.conf: %s", pThis->line); + conf.cfsysline(pThis->line); + } + pThis = pThis->next; + } } -/* Print syslogd errors some place. - */ -void logerror(char *type) + +void legacyOptsParseTCP(char ch, char *arg) { - char buf[1024]; - char errStr[1024]; + register int i; + register char *pArg = arg; + static char conflict = '\0'; - dbgprintf("Called logerr, msg: %s\n", type); + if((conflict == 'g' && ch == 't') || (conflict == 't' && ch == 'g')) { + fprintf(stderr, "rsyslog: If you want to use both -g and -t, use directives instead, -%c ignored.\n", ch); + return; + } else + conflict = ch; - if (errno == 0) - snprintf(buf, sizeof(buf), "%s", type); - else { - rs_strerror_r(errno, errStr, sizeof(errStr)); - snprintf(buf, sizeof(buf), "%s: %s", type, errStr); - } - buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ - errno = 0; - logmsgInternal(LOG_SYSLOG|LOG_ERR, buf, ADDDATE); - return; + /* extract port */ + i = 0; + while(isdigit((int) *pArg)) + i = i * 10 + *pArg++ - '0'; + + /* number of sessions */ + if(*pArg == '\0' || *pArg == ',') { + if(ch == 't') + legacyOptsEnq((uchar *) "ModLoad imtcp"); + else if(ch == 'g') + legacyOptsEnq((uchar *) "ModLoad imgssapi"); + + if(i >= 0 && i <= 65535) { + uchar line[30]; + + if(ch == 't') { + snprintf((char *) line, sizeof(line), "InputTCPServerRun %d", i); + } else if(ch == 'g') { + snprintf((char *) line, sizeof(line), "InputGSSServerRun %d", i); + } + legacyOptsEnq(line); + } else { + if(ch == 't') { + fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i); + legacyOptsEnq((uchar *) "InputTCPServerRun 514"); + } else if(ch == 'g') { + fprintf(stderr, "rsyslogd: Invalid GSS listen port %d - changed to 514.\n", i); + legacyOptsEnq((uchar *) "InputGSSServerRun 514"); + } + } + + if(*pArg == ',') { + ++pArg; + while(isspace((int) *pArg)) + ++pArg; + i = 0; + while(isdigit((int) *pArg)) { + i = i * 10 + *pArg++ - '0'; + } + if(i > 0) { + uchar line[30]; + + snprintf((char *) line, sizeof(line), "InputTCPMaxSessions %d", i); + legacyOptsEnq(line); + } else { + if(ch == 't') { + fprintf(stderr, "rsyslogd: TCP session max configured " + "to %d [-t %s] - changing to 1.\n", i, arg); + legacyOptsEnq((uchar *) "InputTCPMaxSessions 1"); + } else if (ch == 'g') { + fprintf(stderr, "rsyslogd: GSS session max configured " + "to %d [-g %s] - changing to 1.\n", i, arg); + legacyOptsEnq((uchar *) "InputTCPMaxSessions 1"); + } + } + } + } else + fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", arg); } + /* 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 @@ -3601,27 +1851,68 @@ void logerror(char *type) */ static void doDie(int sig) { - dbgprintf("DoDie called.\n"); + static int iRetries = 0; /* debug aid */ + printf("DoDie called.\n"); + if(iRetries++ == 4) { + printf("DoDie called 5 times - unconditional exit\n"); + abort(); + } bFinished = sig; } +/* This function frees all dynamically allocated memory for program termination. + * It must be called only immediately before exit(). It is primarily an aid + * for memory debuggers, which prevents cluttered outupt. + * rgerhards, 2008-03-20 + */ +static void +freeAllDynMemForTermination(void) +{ + if(pszWorkDir != NULL) + free(pszWorkDir); + if(pszMainMsgQFName != NULL) + free(pszMainMsgQFName); + if(pModDir != NULL) + free(pModDir); + if(LocalHostName != NULL) + free(LocalHostName); +} + + /* die() is called when the program shall end. This typically only occurs - * during sigterm or during the initialization. If you search for places where - * it is called, search for "die", not "die(", because the later will not find - * setting of signal handlers! As die() is intended to shutdown rsyslogd, it is + * 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) +static void +die(int sig) { char buf[256]; - int i; + 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"); + thrdTerminateAll(); /* TODO: inputs only, please */ + + /* and THEN send the termination log message (see long comment above) */ if (sig) { - dbgprintf(" exiting on signal %d\n", sig); (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.", @@ -3630,38 +1921,18 @@ static void die(int sig) logmsgInternal(LOG_SYSLOG|LOG_INFO, buf, ADDDATE); } - /* Free ressources and close connections */ - freeSelectors(); - -#ifdef USE_PTHREADS - /* Worker threads are stopped by freeSelectors() */ - queueDelete(pMsgQueue); /* delete fifo here! */ + /* drain queue (if configured so) and stop main queue worker thread pool */ + dbgprintf("Terminating main queue...\n"); + queueDestruct(&pMsgQueue); pMsgQueue = NULL; -#endif - - /* now clean up the listener part */ - /* Close the UNIX sockets. */ - for (i = 0; i < nfunix; i++) - if (funix[i] != -1) - close(funix[i]); -#ifdef SYSLOG_INET - /* Close the UDP inet socket. */ - closeUDPListenSockets(); - /* Close the TCP inet socket. */ - if(sockTCPLstn != NULL && *sockTCPLstn) { - deinit_tcp_listener(); - } -#ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) - TCPSessGSSDeinit(); -#endif -#endif - /* Clean-up files. */ - for (i = 0; i < nfunix; i++) - if (funixn[i] && funix[i] != -1) - (void)unlink(funixn[i]); + /* Free ressources and close connections. This includes flushing any remaining + * repeated msgs. + */ + dbgprintf("Terminating outputs...\n"); + freeSelectors(); + dbgprintf("all primary multi-thread sources have been terminated - now doing aux cleanup...\n"); /* 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 @@ -3677,6 +1948,18 @@ static void die(int sig) /* 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(); + + legacyOptsFree(); + + /* terminate the remaining classes */ + GlobalClassExit(); + /* TODO: this would also be the right place to de-init the builtin output modules. We * do not currently do that, because the module interface does not allow for * it. This will come some time later (it's essential with loadable modules). @@ -3687,19 +1970,18 @@ static void die(int sig) * init, so that modules are unloaded and reloaded on HUP to. Eventually it should go * into freeSelectors() - but that needs to be seen. -- rgerhards, 2007-08-09 */ - modUnloadAndDestructAll(); + module.UnloadAndDestructAll(eMOD_LINK_ALL); - /* the following line cleans up CfSysLineHandlers that were not based on loadable - * modules. As such, they are not yet cleared. - */ - unregCfSysLineHdlrs(); - - - /* clean up auxiliary data */ - if(pModDir != NULL) - free(pModDir); + dbgprintf("Clean shutdown completed, bye\n"); + /* dbgClassExit MUST be the last one, because it de-inits the debug system */ + dbgClassExit(); - dbgprintf("Clean shutdown completed, bye.\n"); + /* free all remaining memory blocks - this is not absolutely necessary, but helps + * us keep memory debugger logs clean and this is in aid in developing. It doesn't + * cost much time, so we do it always. -- rgerhards, 2008-03-20 + */ + freeAllDynMemForTermination(); + /* NO CODE HERE - feeelAllDynMemForTermination() must be the last thing before exit()! */ exit(0); /* "good" exit, this is the terminator function for rsyslog [die()] */ } @@ -3714,344 +1996,6 @@ static void doexit() } -/* parse an allowed sender config line and add the allowed senders - * (if the line is correct). - * rgerhards, 2005-09-27 - */ -static rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine) -{ -#ifdef SYSLOG_INET - struct AllowedSenders **ppRoot; - struct AllowedSenders **ppLast; - rsParsObj *pPars; - rsRetVal iRet; - struct NetAddr *uIP = NULL; - int iBits; -#endif - - assert(pName != NULL); - assert(ppRestOfConfLine != NULL); - assert(*ppRestOfConfLine != NULL); - -#ifndef SYSLOG_INET - errno = 0; - logerror("config file contains allowed sender list, but rsyslogd " - "compiled without Internet support - line ignored"); - return RS_RET_ERR; -#else - 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 { - logerrorSz("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)) { - logerrorInt("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) { - logerrorInt("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) { - logerrorInt("Error %d adding allowed sender entry " - "- ignoring.", iRet); - } else { - logerrorInt("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); -#endif /*#ifndef SYSLOG_INET */ -} - - -/* process a directory and include all of its files into - * the current config file. There is no specific order of inclusion, - * files are included in the order they are read from the directory. - * The caller must have make sure that the provided parameter is - * indeed a directory. - * rgerhards, 2007-08-01 - */ -static rsRetVal doIncludeDirectory(uchar *pDirName) -{ - DEFiRet; - int iEntriesDone = 0; - DIR *pDir; - union { - struct dirent d; - char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; - } u; - struct dirent *res; - size_t iDirNameLen; - size_t iFileNameLen; - uchar szFullFileName[MAXFNAME]; - - assert(pDirName != NULL); - - if((pDir = opendir((char*) pDirName)) == NULL) { - logerror("error opening include directory"); - ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); - } - - /* prepare file name buffer */ - iDirNameLen = strlen((char*) pDirName); - memcpy(szFullFileName, pDirName, iDirNameLen); - - /* now read the directory */ - iEntriesDone = 0; - while(readdir_r(pDir, &u.d, &res) == 0) { - if(res == NULL) - break; /* this also indicates end of directory */ - if(res->d_type != DT_REG) - continue; /* we are not interested in special files */ - if(res->d_name[0] == '.') - continue; /* these files we are also not interested in */ - ++iEntriesDone; - /* construct filename */ - iFileNameLen = strlen(res->d_name); - if (iFileNameLen > NAME_MAX) - iFileNameLen = NAME_MAX; - memcpy(szFullFileName + iDirNameLen, res->d_name, iFileNameLen); - *(szFullFileName + iDirNameLen + iFileNameLen) = '\0'; - dbgprintf("including file '%s'\n", szFullFileName); - processConfFile(szFullFileName); - /* we deliberately ignore the iRet of processConfFile() - this is because - * failure to process one file does not mean all files will fail. By ignoring, - * we retry with the next file, which is the best thing we can do. -- rgerhards, 2007-08-01 - */ - } - - if(iEntriesDone == 0) { - /* I just make it a debug output, because I can think of a lot of cases where it - * makes sense not to have any files. E.g. a system maintainer may place a $Include - * into the config file just in case, when additional modules be installed. When none - * are installed, the directory will be empty, which is fine. -- rgerhards 2007-08-01 - */ - dbgprintf("warning: the include directory contained no files - this may be ok.\n"); - } - -finalize_it: - if(pDir != NULL) - closedir(pDir); - - return iRet; -} - - -/* process a $include config line. That type of line requires - * inclusion of another file. - * rgerhards, 2007-08-01 - */ -static rsRetVal doIncludeLine(uchar **pp, __attribute__((unused)) void* pVal) -{ - DEFiRet; - char pattern[MAXFNAME]; - uchar *cfgFile; - glob_t cfgFiles; - size_t i = 0; - struct stat fileInfo; - - assert(pp != NULL); - assert(*pp != NULL); - - if(getSubString(pp, (char*) pattern, sizeof(pattern) / sizeof(char), ' ') != 0) { - logerror("could not extract group name"); - ABORT_FINALIZE(RS_RET_NOT_FOUND); - } - - /* Use GLOB_MARK to append a trailing slash for directories. - * Required by doIncludeDirectory(). - */ - glob(pattern, GLOB_MARK, NULL, &cfgFiles); - - for(i = 0; i < cfgFiles.gl_pathc; i++) { - cfgFile = (uchar*) cfgFiles.gl_pathv[i]; - - if(stat((char*) cfgFile, &fileInfo) != 0) - continue; /* continue with the next file if we can't stat() the file */ - - if(S_ISREG(fileInfo.st_mode)) { /* config file */ - dbgprintf("requested to include config file '%s'\n", cfgFile); - iRet = processConfFile(cfgFile); - } else if(S_ISDIR(fileInfo.st_mode)) { /* config directory */ - dbgprintf("requested to include directory '%s'\n", cfgFile); - iRet = doIncludeDirectory(cfgFile); - } else { /* TODO: shall we handle symlinks or not? */ - dbgprintf("warning: unable to process IncludeConfig directive '%s'\n", cfgFile); - } - } - - globfree(&cfgFiles); - -finalize_it: - return iRet; -} - - -/* process a $ModLoad config line. - * As of now, it is a dummy, that will later evolve into the - * loader for plug-ins. - * rgerhards, 2007-07-21 - * 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. - */ -static rsRetVal doModLoad(uchar **pp, __attribute__((unused)) void* pVal) -{ - DEFiRet; - uchar szName[512]; - uchar szPath[512]; - uchar errMsg[1024]; - uchar *pModName; - void *pModHdlr, *pModInit; - - assert(pp != NULL); - assert(*pp != NULL); - - if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { - logerror("could not extract module name"); - ABORT_FINALIZE(RS_RET_NOT_FOUND); - } - - /* this below is a quick and dirty hack to provide compatibility with the - * $ModLoad MySQL forward compatibility statement. TODO: clean this up - * For the time being, it is clean enough, it just needs to be done - * differently when we have a full design for loadable plug-ins. For the - * time being, we just mangle the names a bit. - * rgerhards, 2007-08-14 - */ - if(!strcmp((char*) szName, "MySQL")) - pModName = (uchar*) "ommysql.so"; - else - pModName = szName; - - dbgprintf("Requested to load module '%s'\n", szName); - - if(*pModName == '/') { - *szPath = '\0'; /* we do not need to append the path - its already in the module name */ - } else { - strncpy((char *) szPath, (pModDir == NULL) ? _PATH_MODDIR : (char*) pModDir, sizeof(szPath)); - } - strncat((char *) szPath, (char *) pModName, sizeof(szPath) - strlen((char*) szPath) - 1); - if(!(pModHdlr = dlopen((char *) szPath, RTLD_NOW))) { - snprintf((char *) errMsg, sizeof(errMsg), "could not load module '%s', dlopen: %s\n", szPath, dlerror()); - errMsg[sizeof(errMsg)/sizeof(uchar) - 1] = '\0'; - logerror((char *) errMsg); - ABORT_FINALIZE(RS_RET_ERR); - } - if(!(pModInit = dlsym(pModHdlr, "modInit"))) { - snprintf((char *) errMsg, sizeof(errMsg), "could not load module '%s', dlsym: %s\n", szPath, dlerror()); - errMsg[sizeof(errMsg)/sizeof(uchar) - 1] = '\0'; - logerror((char *) errMsg); - dlclose(pModHdlr); - ABORT_FINALIZE(RS_RET_ERR); - } - if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr)) != RS_RET_OK) - return iRet; - - skipWhiteSpace(pp); /* skip over any whitespace */ - -finalize_it: - return iRet; -} - -/* 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. - */ -static 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) { - logerror("Invalid config line: could not extract name - line ignored"); - ABORT_FINALIZE(RS_RET_NOT_FOUND); - } - 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(szName, &p); - break; - case DIR_OUTCHANNEL: - ochAddLine(szName, &p); - break; - case DIR_ALLOWEDSENDER: - 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: - return iRet; -} - - /* set the action resume interval */ static rsRetVal setActionResumeInterval(void __attribute__((unused)) *pVal, int iNewVal) @@ -4071,49 +2015,6 @@ static rsRetVal setUmask(void __attribute__((unused)) *pVal, int iUmask) } -/* 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]; - uchar errMsg[128]; /* for dynamic error messages */ - - assert(p != NULL); - errno = 0; - if(getSubString(&p, (char*) szCmd, sizeof(szCmd) / sizeof(uchar), ' ') != 0) { - logerror("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 */ - snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), - "error: extra characters in config line ignored: '%s'", p); - errno = 0; - logerror((char*) errMsg); - } - -finalize_it: - return iRet; -} - - /* helper to freeSelectors(), used with llExecFunc() to flush * pending output. -- rgerhards, 2007-08-02 * We do not need to lock the action object here as the processing @@ -4128,14 +2029,9 @@ DEFFUNC_llExecFunc(freeSelectorsActions) /* flush any pending output */ if(pAction->f_prevcount) { - if(actionIsSuspended(pAction) && - (actionTryResume(pAction) != RS_RET_OK)) { - goto finalize_it; - } - fprintlog(pAction); + actionWriteToAction(pAction); } -finalize_it: return RS_RET_OK; /* never fails ;) */ } @@ -4150,23 +2046,6 @@ static void freeSelectors(void) if(Files != NULL) { dbgprintf("Freeing log structures.\n"); - /* just in case, we flush the emergency log. If error messages occur after - * this stage, we loose them, but that's ok. With multi-threading, this can - * never happen. -- rgerhards, 2007-08-03 - */ - processImInternal(); - - /* we first wait until all messages are processed (stopWorker() does - * that. Then, we go one last time over all actions and flush any - * pending "message repeated n times" messages. We must use this sequence - * because otherwise we would flush at whatever message is currently being - * processed without draining the queue. That would lead to invalid - * results. -- rgerhards, 2007-12-12 - */ -# ifdef USE_PTHREADS - stopWorker(); -# endif - for(f = Files ; f != NULL ; f = f->f_next) { llExecFunc(&f->llActList, freeSelectorsActions, NULL); } @@ -4181,7 +2060,7 @@ static void freeSelectors(void) /* Reflect the deletion of the selectors linked list. */ Files = NULL; - Initialized = 0; + bHaveMainQueue = 0; } } @@ -4194,9 +2073,9 @@ DEFFUNC_llExecFunc(dbgPrintInitInfoAction) { DEFiRet; iRet = actionDbgPrint((action_t*) pData); - printf("\n"); + dbgprintf("\n"); - return iRet; + RETiRet; } /* print debug information as part of init(). This pretty much @@ -4210,257 +2089,141 @@ static void dbgPrintInitInfo(void) int iSelNbr = 1; int i; - printf("\nActive selectors:\n"); + dbgprintf("\nActive selectors:\n"); for (f = Files; f != NULL ; f = f->f_next) { - printf("Selector %d:\n", iSelNbr++); + dbgprintf("Selector %d:\n", iSelNbr++); if(f->pCSProgNameComp != NULL) - printf("tag: '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp)); + dbgprintf("tag: '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp)); if(f->eHostnameCmpMode != HN_NO_COMP) - printf("hostname: %s '%s'\n", + dbgprintf("hostname: %s '%s'\n", f->eHostnameCmpMode == HN_COMP_MATCH ? "only" : "allbut", rsCStrGetSzStrNoNULL(f->pCSHostnameComp)); if(f->f_filter_type == FILTER_PRI) { for (i = 0; i <= LOG_NFACILITIES; i++) if (f->f_filterData.f_pmask[i] == TABLE_NOPRI) - printf(" X "); + dbgprintf(" X "); else - printf("%2X ", f->f_filterData.f_pmask[i]); + dbgprintf("%2X ", f->f_filterData.f_pmask[i]); + } else if(f->f_filter_type == FILTER_EXPR) { + dbgprintf("EXPRESSION-BASED Filter: can currently not be displayed"); } else { - printf("PROPERTY-BASED Filter:\n"); - printf("\tProperty.: '%s'\n", + dbgprintf("PROPERTY-BASED Filter:\n"); + dbgprintf("\tProperty.: '%s'\n", rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName)); - printf("\tOperation: "); + dbgprintf("\tOperation: "); if(f->f_filterData.prop.isNegated) - printf("NOT "); - printf("'%s'\n", getFIOPName(f->f_filterData.prop.operation)); - printf("\tValue....: '%s'\n", + dbgprintf("NOT "); + dbgprintf("'%s'\n", getFIOPName(f->f_filterData.prop.operation)); + dbgprintf("\tValue....: '%s'\n", rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue)); - printf("\tAction...: "); + dbgprintf("\tAction...: "); } - printf("\nActions:\n"); + dbgprintf("\nActions:\n"); llExecFunc(&f->llActList, dbgPrintInitInfoAction, NULL); /* actions */ - printf("\n"); + dbgprintf("\n"); } - printf("\n"); + dbgprintf("\n"); if(bDebugPrintTemplateList) tplPrintList(); if(bDebugPrintModuleList) - modPrintList(); + module.PrintList(); ochPrintList(); if(bDebugPrintCfSysLineHandlerList) dbgPrintCfSysLineHandlers(); -#ifdef SYSLOG_INET - /* now the allowedSender lists: */ - PrintAllowedSenders(1); /* UDP */ - PrintAllowedSenders(2); /* TCP */ -#ifdef USE_GSSAPI - PrintAllowedSenders(3); /* GSS */ -#endif - printf("\n"); -#endif /* #ifdef SYSLOG_INET */ - - printf("Messages with malicious PTR DNS Records are %sdropped.\n", + dbgprintf("Messages with malicious PTR DNS Records are %sdropped.\n", bDropMalPTRMsgs ? "" : "not "); - printf("Control characters are %sreplaced upon reception.\n", + dbgprintf("Control characters are %sreplaced upon reception.\n", bEscapeCCOnRcv? "" : "not "); if(bEscapeCCOnRcv) - printf("Control character escape sequence prefix is '%c'.\n", + dbgprintf("Control character escape sequence prefix is '%c'.\n", cCCEscapeChar); -#ifdef USE_PTHREADS - printf("Main queue size %d messages.\n", iMainMsgQueueSize); -#endif + dbgprintf("Main queue size %d messages.\n", iMainMsgQueueSize); + dbgprintf("Main queue worker threads: %d, Perists every %d updates.\n", + iMainMsgQueueNumWorkers, iMainMsgQPersistUpdCnt); + dbgprintf("Main queue timeouts: shutdown: %d, action completion shutdown: %d, enq: %d\n", + iMainMsgQtoQShutdown, iMainMsgQtoActShutdown, iMainMsgQtoEnq); + dbgprintf("Main queue watermarks: high: %d, low: %d, discard: %d, discard-severity: %d\n", + iMainMsgQHighWtrMark, iMainMsgQLowWtrMark, iMainMsgQDiscardMark, iMainMsgQDiscardSeverity); + dbgprintf("Main queue save on shutdown %d, max disk space allowed %lld\n", + bMainMsgQSaveOnShutdown, iMainMsgQueMaxDiskSpace); + /* TODO: add + iActionRetryCount = 0; + iActionRetryInterval = 30000; + static int iMainMsgQtoWrkShutdown = 60000; + static int iMainMsgQtoWrkMinMsgs = 100; + static int iMainMsgQbSaveOnShutdown = 1; + iMainMsgQueMaxDiskSpace = 0; + setQPROP(queueSettoWrkShutdown, "$MainMsgQueueTimeoutWorkerThreadShutdown", 5000); + setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", 100); + setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", 1); + */ + dbgprintf("Work Directory: '%s'.\n", pszWorkDir); } -/* process a configuration file - * started with code from init() by rgerhards on 2007-07-31 +/* Start the input modules. This function will probably undergo big changes + * while we implement the input module interface. For now, it does the most + * important thing to get at least my poor initial input modules up and + * running. Almost no config option is taken. + * rgerhards, 2007-12-14 */ -static rsRetVal processConfFile(uchar *pConfFile) +static rsRetVal +startInputModules(void) { DEFiRet; - int iLnNbr = 0; - FILE *cf; - selector_t *fCurr = NULL; - uchar *p; -#ifdef CONT_LINE - uchar cbuf[BUFSIZ]; - uchar *cline; -#else - uchar cline[BUFSIZ]; -#endif - assert(pConfFile != NULL); - - if((cf = fopen((char*)pConfFile, "r")) == NULL) { - ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); - } - - /* Now process the file. - */ -#if CONT_LINE - cline = cbuf; - while (fgets((char*)cline, sizeof(cbuf) - (cline - cbuf), cf) != NULL) { -#else - while (fgets(cline, sizeof(cline), cf) != NULL) { -#endif - ++iLnNbr; - /* drop LF - TODO: make it better, replace fgets(), but its clean as it is */ - if(cline[strlen((char*)cline)-1] == '\n') { - cline[strlen((char*)cline) -1] = '\0'; - } - /* check for end-of-section, comments, strip off trailing - * spaces and newline character. - */ - p = cline; - skipWhiteSpace(&p); - if (*p == '\0' || *p == '#') - continue; - -#if CONT_LINE - strcpy((char*)cline, (char*)p); -#endif - for (p = (uchar*) strchr((char*)cline, '\0'); isspace((int) *--p);); -#if CONT_LINE - if (*p == '\\') { - if ((p - cbuf) > BUFSIZ - 30) { - /* Oops the buffer is full - what now? */ - cline = cbuf; - } else { - *p = 0; - cline = p; - continue; - } - } else - cline = cbuf; -#endif - *++p = '\0'; // TODO: check this + modInfo_t *pMod; - /* we now have the complete line, and are positioned at the first non-whitespace - * character. So let's process it - */ -#if CONT_LINE - if(cfline(cbuf, &fCurr) != RS_RET_OK) { -#else - if(cfline((uchar*)cline, &fCurr) != RS_RET_OK) { -#endif - /* we log a message, but otherwise ignore the error. After all, the next - * line can be correct. -- rgerhards, 2007-08-02 - */ - uchar szErrLoc[MAXFNAME + 64]; - dbgprintf("config line NOT successfully processed\n"); - snprintf((char*)szErrLoc, sizeof(szErrLoc) / sizeof(uchar), - "%s, line %d", pConfFile, iLnNbr); - logerrorSz("the last error occured in %s", (char*)szErrLoc); + /* loop through all modules and activate them (brr...) */ + pMod = module.GetNxtType(NULL, eMOD_IN); + while(pMod != NULL) { + if((iRet = pMod->mod.im.willRun()) == RS_RET_OK) { + /* activate here */ + thrdCreate(pMod->mod.im.runInput, pMod->mod.im.afterRun); + } else { + dbgprintf("module %lx will not run, iRet %d\n", (unsigned long) pMod, iRet); } + pMod = module.GetNxtType(pMod, eMOD_IN); } - /* we probably have one selector left to be added - so let's do that now */ - CHKiRet(selectorAddList(fCurr)); - - /* close the configuration file */ - (void) fclose(cf); - -finalize_it: - if(iRet != RS_RET_OK) { - char errStr[1024]; - if(fCurr != NULL) - selectorDestruct(fCurr); - - rs_strerror_r(errno, errStr, sizeof(errStr)); - dbgprintf("error %d processing config file '%s'; os error (if any): %s\n", - iRet, pConfFile, errStr); - } - return iRet; + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ } /* INIT -- Initialize syslogd from configuration table * init() is called at initial startup AND each time syslogd is HUPed */ -static void init(void) +static void +init(void) { DEFiRet; - register int i; -#ifdef CONT_LINE char cbuf[BUFSIZ]; -#else - char cline[BUFSIZ]; -#endif char bufStartUpMsg[512]; - struct servent *sp; struct sigaction sigAct; + thrdTerminateAll(); /* stop all running input threads - TODO: reconsider location! */ + /* initialize some static variables */ pDfltHostnameCmp = NULL; pDfltProgNameCmp = NULL; eDfltHostnameCmpMode = HN_NO_COMP; - Forwarding = 0; -#ifdef SYSLOG_INET - if (restart) { - if (pAllowedSenders_UDP != NULL) { - clearAllowedSenders (pAllowedSenders_UDP); - pAllowedSenders_UDP = NULL; - } - - if (pAllowedSenders_TCP != NULL) { - clearAllowedSenders (pAllowedSenders_TCP); - pAllowedSenders_TCP = NULL; - } -#ifdef USE_GSSAPI - if (pAllowedSenders_GSS != NULL) { - clearAllowedSenders (pAllowedSenders_GSS); - pAllowedSenders_GSS = NULL; - } -#endif - } + dbgprintf("rsyslog %s - called init()\n", VERSION); - assert(pAllowedSenders_UDP == NULL && pAllowedSenders_TCP == NULL -#ifdef USE_GSSAPI - && pAllowedSenders_GSS == NULL -#endif - ); -#endif - /* I was told by an IPv6 expert that calling getservbyname() seems to be - * still valid, at least for the use case we have. So I re-enabled that - * code. rgerhards, 2007-07-02 - */ - if(!strcmp(LogPort, "0")) { - /* we shall use the default syslog/udp port, so let's - * look it up. - * NOTE: getservbyname() is not thread-safe, but this is OK as - * it is called only during init, in single-threading mode. - */ - sp = getservbyname("syslog", "udp"); - if (sp == NULL) { - errno = 0; - logerror("Could not find syslog/udp port in /etc/services. " - "Now using IANA-assigned default of 514."); - LogPort = "514"; - } else { - /* we can dynamically allocate memory here and do NOT need - * to care about freeing it because even though init() is - * called on each restart, the LogPort can never again be - * "0". So we will only once run into this part of the code - * here. rgerhards, 2007-07-02 - * We save ourselfs the hassle of dynamic memory management - * for the very same reason. - */ - static char defPort[8]; - snprintf(defPort, sizeof(defPort), "%d", ntohs(sp->s_port)); - LogPort = defPort; - } - } - - dbgprintf("rsyslog %s.\n", VERSION); - dbgprintf("Called init.\n"); + /* delete the message queue, which also flushes all messages left over */ + if(pMsgQueue != NULL) { + dbgprintf("deleting main message queue\n"); + queueDestruct(&pMsgQueue); /* delete pThis here! */ + pMsgQueue = NULL; + } /* Close all open log files and free log descriptor array. This also frees * all output-modules instance data. @@ -4469,31 +2232,23 @@ static void init(void) /* Unload all non-static modules */ dbgprintf("Unloading non-static modules.\n"); - modUnloadAndDestructDynamic(); + module.UnloadAndDestructAll(eMOD_LINK_DYNAMIC_LOADED); dbgprintf("Clearing templates.\n"); tplDeleteNew(); -#ifdef USE_PTHREADS - if(pMsgQueue != NULL) { - dbgprintf("deleting message queue\n"); - queueDelete(pMsgQueue); /* delete fifo here! */ - pMsgQueue = NULL; - } -#endif - /* re-setting values to defaults (where applicable) */ - /* TODO: once we have loadable modules, we must re-visit this code. The reason is + /* once we have loadable modules, we must re-visit this code. The reason is * that config variables are not re-set, because the module is not yet loaded. On * the other hand, that doesn't matter, because the module got unloaded and is then - * re-loaded, so the variables should be re-set via that way. In any case, we should - * think about the whole situation when we implement loadable plugins. - * rgerhards, 2007-07-31 + * re-loaded, so the variables should be re-set via that way. And this is exactly how + * it works. Loadable module's variables are initialized on load, the rest here. + * rgerhards, 2008-04-28 */ - cfsysline((uchar*)"ResetConfigVariables"); + conf.cfsysline((uchar*)"ResetConfigVariables"); /* open the configuration file */ - if((iRet = processConfFile(ConfFile)) != RS_RET_OK) { + if((iRet = conf.processConfFile(ConfFile)) != RS_RET_OK) { /* rgerhards: this code is executed to set defaults when the * config file could not be opened. We might think about * abandoning the run in this case - but this, too, is not @@ -4503,93 +2258,109 @@ static void init(void) selector_t *f = NULL; char szTTYNameBuf[_POSIX_TTY_NAME_MAX+1]; /* +1 for NULL character */ dbgprintf("primary config file could not be opened - using emergency definitions.\n"); - cfline((uchar*)"*.ERR\t" _PATH_CONSOLE, &f); - cfline((uchar*)"*.PANIC\t*", &f); + conf.cfline((uchar*)"*.ERR\t" _PATH_CONSOLE, &f); + conf.cfline((uchar*)"*.PANIC\t*", &f); if(ttyname_r(0, szTTYNameBuf, sizeof(szTTYNameBuf)) == 0) { snprintf(cbuf,sizeof(cbuf), "*.*\t%s", szTTYNameBuf); - cfline((uchar*)cbuf, &f); + conf.cfline((uchar*)cbuf, &f); } selectorAddList(f); } + legacyOptsHook(); + /* we are now done with reading the configuration. This is the right time to * free some objects that were just needed for loading it. rgerhards 2005-10-19 */ if(pDfltHostnameCmp != NULL) { - rsCStrDestruct(pDfltHostnameCmp); - pDfltHostnameCmp = NULL; + rsCStrDestruct(&pDfltHostnameCmp); } if(pDfltProgNameCmp != NULL) { - rsCStrDestruct(pDfltProgNameCmp); - pDfltProgNameCmp = NULL; + rsCStrDestruct(&pDfltProgNameCmp); } -#ifdef SYSLOG_UNIXAF - for (i = startIndexUxLocalSockets ; i < nfunix ; i++) { - if (funix[i] != -1) - /* Don't close the socket, preserve it instead - close(funix[i]); - */ - continue; - if ((funix[i] = create_unix_socket(funixn[i])) != -1) - dbgprintf("Opened UNIX socket `%s' (fd %d).\n", funixn[i], funix[i]); + /* some checks */ + if(iMainMsgQueueNumWorkers < 1) { + errmsg.LogError(NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n"); + iMainMsgQueueNumWorkers = 1; } -#endif -#ifdef SYSLOG_INET - /* I have moved initializing UDP sockets before the TCP sockets. This ensures - * they are as soon ready for reception as possible. Of course, it is only a - * very small window of exposure, but it doesn't hurt to limit the message - * loss risk to as low as possible - especially if it costs nothing... - * rgerhards, 2007-06-28 - */ - if(Forwarding || AcceptRemote) { - if (finet == NULL) { - if((finet = create_udp_socket()) != NULL) - dbgprintf("Opened %d syslog UDP port(s).\n", *finet); + if(MainMsgQueType == QUEUETYPE_DISK) { + errno = 0; /* for logerror! */ + if(pszWorkDir == NULL) { + errmsg.LogError(NO_ERRCODE, "No $WorkDirectory specified - can not run main message queue in 'disk' mode. " + "Using 'FixedArray' instead.\n"); + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; } - } else { - /* this case can happen during HUP processing. */ - closeUDPListenSockets(); - } - - if (bEnableTCP) { - if(sockTCPLstn == NULL) { - /* even when doing a re-init, we do not shut down and - * re-open the TCP socket. That would break existing TCP - * session, which we do not desire. Should at some time arise - * need to do that, I recommend controlling that via a - * user-selectable option. rgerhards, 2007-06-21 - */ -# ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) { - if(TCPSessGSSInit()) { - logerror("GSS-API initialization failed\n"); - bEnableTCP &= ~(ALLOWEDMETHOD_GSS); - } - } - if(bEnableTCP) -# endif - if((sockTCPLstn = create_tcp_socket()) != NULL) { - dbgprintf("Opened %d syslog TCP port(s).\n", *sockTCPLstn); - } + if(pszMainMsgQFName == NULL) { + errmsg.LogError(NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main message queue in " + "'disk' mode. Using 'FixedArray' instead.\n"); + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; } } -#endif -# ifdef USE_PTHREADS - /* create message queue */ - pMsgQueue = queueInit(); - if(pMsgQueue == NULL) { - errno = 0; - logerror("error: could not create message queue - running single-threaded!\n"); + /* switch the message object to threaded operation, if necessary */ + if(MainMsgQueType == QUEUETYPE_DIRECT || iMainMsgQueueNumWorkers > 1) { + MsgEnableThreadSafety(); } - startWorker(); -# endif - - Initialized = 1; + /* create message queue */ + CHKiRet_Hdlr(queueConstruct(&pMsgQueue, MainMsgQueType, iMainMsgQueueNumWorkers, iMainMsgQueueSize, msgConsumer)) { + /* 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); + exit(1); + } + /* name our main queue object (it's not fatal if it fails...) */ + obj.SetName((obj_t*) pMsgQueue, (uchar*) "main queue"); + + /* ... set some properties ... */ +# define setQPROP(func, directive, data) \ + CHKiRet_Hdlr(func(pMsgQueue, data)) { \ + errmsg.LogError(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } +# define setQPROPstr(func, directive, data) \ + CHKiRet_Hdlr(func(pMsgQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \ + errmsg.LogError(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } + + setQPROP(queueSetMaxFileSize, "$MainMsgQueueFileSize", iMainMsgQueMaxFileSize); + setQPROP(queueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace", iMainMsgQueMaxDiskSpace); + setQPROPstr(queueSetFilePrefix, "$MainMsgQueueFileName", pszMainMsgQFName); + setQPROP(queueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval", iMainMsgQPersistUpdCnt); + setQPROP(queueSettoQShutdown, "$MainMsgQueueTimeoutShutdown", iMainMsgQtoQShutdown ); + setQPROP(queueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion", iMainMsgQtoActShutdown); + setQPROP(queueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown", iMainMsgQtoWrkShutdown); + setQPROP(queueSettoEnq, "$MainMsgQueueTimeoutEnqueue", iMainMsgQtoEnq); + setQPROP(queueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark", iMainMsgQHighWtrMark); + setQPROP(queueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark", iMainMsgQLowWtrMark); + setQPROP(queueSetiDiscardMrk, "$MainMsgQueueDiscardMark", iMainMsgQDiscardMark); + setQPROP(queueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity", iMainMsgQDiscardSeverity); + setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", iMainMsgQWrkMinMsgs); + setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", bMainMsgQSaveOnShutdown); + setQPROP(queueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown", iMainMsgQDeqSlowdown); + setQPROP(queueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin", iMainMsgQueueDeqtWinFromHr); + setQPROP(queueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd", iMainMsgQueueDeqtWinToHr); + +# undef setQPROP +# undef setQPROPstr + + /* ... and finally start the queue! */ + CHKiRet_Hdlr(queueStart(pMsgQueue)) { + /* no queue is fatal, we need to give up in that case... */ + fprintf(stderr, "fatal error %d: could not start message queue - rsyslogd can not run!\n", iRet); + exit(1); + } + + bHaveMainQueue = (MainMsgQueType == QUEUETYPE_DIRECT) ? 0 : 1; + dbgprintf("Main processing queue is initialized and running\n"); + + /* the output part and the queue is now ready to run. So it is a good time + * to start 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 + */ + startInputModules(); if(Debug) { dbgPrintInitInfo(); @@ -4600,17 +2371,8 @@ static void init(void) */ snprintf(bufStartUpMsg, sizeof(bufStartUpMsg)/sizeof(char), " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ - "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"][x-configInfo udpReception=\"%s\" " \ - "udpPort=\"%s\" tcpReception=\"%s\" tcpPort=\"%s\"]" \ - " restart", - (int) myPid, -#ifdef SYSLOG_INET - AcceptRemote ? "Yes" : "No", LogPort, - bEnableTCP ? "Yes" : "No", TCPLstnPort -#else - "No", "0", "No", "0" -#endif /* #ifdef SYSLOG_INET */ - ); + "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"] restart", + (int) myPid); logmsgInternal(LOG_SYSLOG|LOG_INFO, bufStartUpMsg, ADDDATE); memset(&sigAct, 0, sizeof (sigAct)); @@ -4618,684 +2380,8 @@ static void init(void) sigAct.sa_handler = sighup_handler; sigaction(SIGHUP, &sigAct, NULL); - dbgprintf(" restarted.\n"); -} - - -/* 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; - DEFiRet; - rsCStrObj *pStrB; - - 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 != '#') { - logerror("invalid character in selector line - ';template' expected"); - iRet = RS_RET_ERR; - goto finalize_it; - } - - skipWhiteSpace(&p); /* go to begin of template name */ - - if(*p == '\0') { - /* no template specified, use the default */ - /* TODO: check NULL ptr */ - tplName = (uchar*) strdup((char*)dfltTplName); - } else { - /* template specified, pick it up */ - if((pStrB = rsCStrConstruct()) == NULL) { - glblHadMemShortage = 1; - iRet = RS_RET_OUT_OF_MEMORY; - goto finalize_it; - } - - /* now copy the string */ - while(*p && *p != '#' && !isspace((int) *p)) { - CHKiRet(rsCStrAppendChar(pStrB, *p)); - ++p; - } - CHKiRet(rsCStrFinish(pStrB)); - CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &tplName, 0)); - } - - iRet = OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts); - if(iRet != RS_RET_OK) goto finalize_it; - -finalize_it: - *pp = p; - - return iRet; -} - -/* 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 - */ -rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts) -{ - 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 != ';' && i < MAXFNAME) { - *pName++ = *p++; - ++i; - } - *pName = '\0'; - - iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, (uchar*) " TradFmt"); - - return iRet; -} - - -/* - * Helper to cfline(). This function takes the filter part of a traditional, PRI - * based line and decodes the PRIs given in the selector line. It processed 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 - */ -static rsRetVal cflineProcessTradPRIFilter(uchar **pline, register selector_t *f) -{ - uchar *p; - register uchar *q; - register int i, i2; - uchar *bp; - int pri; - int singlpri = 0; - int ignorepri = 0; - uchar buf[MAXLINE]; - uchar xbuf[200]; - - assert(pline != NULL); - assert(*pline != NULL); - assert(f != NULL); - - dbgprintf(" - traditional PRI filter\n"); - errno = 0; /* keep strerror_r() stuff out of logerror messages */ - - f->f_filter_type = FILTER_PRI; - /* Note: file structure is pre-initialized to zero because it was - * created with calloc()! - */ - for (i = 0; i <= LOG_NFACILITIES; i++) { - f->f_filterData.f_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++ = *q++; - *bp = '\0'; - - /* skip cruft */ - while (strchr(",;", *q)) - q++; - - /* decode priority name */ - if ( *buf == '!' ) { - ignorepri = 1; - for (bp=buf; *(bp+1); bp++) - *bp=*(bp+1); - *bp='\0'; - } - else { - ignorepri = 0; - } - if ( *buf == '=' ) - { - singlpri = 1; - pri = decode(&buf[1], PriNames); - } - else { - singlpri = 0; - pri = decode(buf, PriNames); - } - - if (pri < 0) { - snprintf((char*) xbuf, sizeof(xbuf), "unknown priority name \"%s\"", buf); - logerror((char*) xbuf); - return RS_RET_ERR; - } - - /* scan facilities */ - while (*p && !strchr("\t .;", *p)) { - for (bp = buf; *p && !strchr("\t ,;.", *p); ) - *bp++ = *p++; - *bp = '\0'; - if (*buf == '*') { - for (i = 0; i <= LOG_NFACILITIES; i++) { - if ( pri == INTERNAL_NOPRI ) { - if ( ignorepri ) - f->f_filterData.f_pmask[i] = TABLE_ALLPRI; - else - f->f_filterData.f_pmask[i] = TABLE_NOPRI; - } - else if ( singlpri ) { - if ( ignorepri ) - f->f_filterData.f_pmask[i] &= ~(1<<pri); - else - f->f_filterData.f_pmask[i] |= (1<<pri); - } - else - { - if ( pri == TABLE_ALLPRI ) { - if ( ignorepri ) - f->f_filterData.f_pmask[i] = TABLE_NOPRI; - else - f->f_filterData.f_pmask[i] = TABLE_ALLPRI; - } - else - { - if ( ignorepri ) - for (i2= 0; i2 <= pri; ++i2) - f->f_filterData.f_pmask[i] &= ~(1<<i2); - else - for (i2= 0; i2 <= pri; ++i2) - f->f_filterData.f_pmask[i] |= (1<<i2); - } - } - } - } else { - i = decode(buf, FacNames); - if (i < 0) { - - snprintf((char*) xbuf, sizeof(xbuf), "unknown facility name \"%s\"", buf); - logerror((char*) xbuf); - return RS_RET_ERR; - } - - if ( pri == INTERNAL_NOPRI ) { - if ( ignorepri ) - f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; - else - f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; - } else if ( singlpri ) { - if ( ignorepri ) - f->f_filterData.f_pmask[i >> 3] &= ~(1<<pri); - else - f->f_filterData.f_pmask[i >> 3] |= (1<<pri); - } else { - if ( pri == TABLE_ALLPRI ) { - if ( ignorepri ) - f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; - else - f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; - } else { - if ( ignorepri ) - for (i2= 0; i2 <= pri; ++i2) - f->f_filterData.f_pmask[i >> 3] &= ~(1<<i2); - else - for (i2= 0; i2 <= pri; ++i2) - f->f_filterData.f_pmask[i >> 3] |= (1<<i2); - } - } - } - while (*p == ',' || *p == ' ') - p++; - } - - p = q; - } - - /* skip to action part */ - while (*p == '\t' || *p == ' ') - p++; - - *pline = p; - return RS_RET_OK; -} - - -/* - * 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 - */ -static rsRetVal cflineProcessPropFilter(uchar **pline, register selector_t *f) -{ - rsParsObj *pPars; - rsCStrObj *pCSCompOp; - rsRetVal iRet; - int iOffset; /* for compare operations */ - - assert(pline != NULL); - assert(*pline != NULL); - assert(f != NULL); - - dbgprintf(" - property-based filter\n"); - errno = 0; /* keep strerror_r() stuff out of logerror messages */ - - f->f_filter_type = FILTER_PROP; - - /* create parser object starting with line string without leading colon */ - if((iRet = rsParsConstructFromSz(&pPars, (*pline)+1)) != RS_RET_OK) { - logerrorInt("Error %d constructing parser object - ignoring selector", iRet); - return(iRet); - } - - /* read property */ - iRet = parsDelimCStr(pPars, &f->f_filterData.prop.pCSPropName, ',', 1, 1); - if(iRet != RS_RET_OK) { - logerrorInt("error %d parsing filter property - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); - } - - /* read operation */ - iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1); - if(iRet != RS_RET_OK) { - logerrorInt("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) == '!') { - f->f_filterData.prop.isNegated = 1; - iOffset = 1; /* ignore '!' */ - } else { - f->f_filterData.prop.isNegated = 0; - iOffset = 0; - } - } else { - f->f_filterData.prop.isNegated = 0; - iOffset = 0; - } - - if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "contains", 8)) { - f->f_filterData.prop.operation = FIOP_CONTAINS; - } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isequal", 7)) { - f->f_filterData.prop.operation = FIOP_ISEQUAL; - } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "startswith", 10)) { - f->f_filterData.prop.operation = FIOP_STARTSWITH; - } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "regex", 5)) { - f->f_filterData.prop.operation = FIOP_REGEX; - } else { - logerrorSz("error: invalid compare operation '%s' - ignoring selector", - (char*) rsCStrGetSzStrNoNULL(pCSCompOp)); - } - rsCStrDestruct (pCSCompOp); /* no longer needed */ - - /* read compare value */ - iRet = parsQuotedCStr(pPars, &f->f_filterData.prop.pCSCompValue); - if(iRet != RS_RET_OK) { - logerrorInt("error %d compare value property - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); - } - - /* skip to action part */ - if((iRet = parsSkipWhitespace(pPars)) != RS_RET_OK) { - logerrorInt("error %d skipping to action part - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); - } - - /* cleanup */ - *pline = *pline + rsParsGetParsePointer(pPars) + 1; - /* we are adding one for the skipped initial ":" */ - - return rsParsDestruct(pPars); -} - - -/* - * Helper to cfline(). This function interprets a BSD host selector line - * from the config file ("+/-hostname"). It stores it for further reference. - * rgerhards 2005-10-19 - */ -static rsRetVal cflineProcessHostSelector(uchar **pline) -{ - rsRetVal iRet; - - assert(pline != NULL); - assert(*pline != NULL); - assert(**pline == '-' || **pline == '+'); - - dbgprintf(" - host selector line\n"); - - /* check include/exclude setting */ - if(**pline == '+') { - eDfltHostnameCmpMode = HN_COMP_MATCH; - } else { /* we do not check for '-', it must be, else we wouldn't be here */ - eDfltHostnameCmpMode = HN_COMP_NOMATCH; - } - (*pline)++; /* eat + or - */ - - /* the below is somewhat of a quick hack, but it is efficient (this is - * why it is in here. "+*" resets the tag selector with BSD syslog. We mimic - * this, too. As it is easy to check that condition, we do not fire up a - * parser process, just make sure we do not address beyond our space. - * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 - */ - if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { - dbgprintf("resetting BSD-like hostname filter\n"); - eDfltHostnameCmpMode = HN_NO_COMP; - if(pDfltHostnameCmp != NULL) { - if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, NULL)) != RS_RET_OK) - return(iRet); - } - } else { - dbgprintf("setting BSD-like hostname filter to '%s'\n", *pline); - if(pDfltHostnameCmp == NULL) { - /* create string for parser */ - if((iRet = rsCStrConstructFromszStr(&pDfltHostnameCmp, *pline)) != RS_RET_OK) - return(iRet); - } else { /* string objects exists, just update... */ - if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, *pline)) != RS_RET_OK) - return(iRet); - } - } - return RS_RET_OK; -} - - -/* - * Helper to cfline(). This function interprets a BSD tag selector line - * from the config file ("!tagname"). It stores it for further reference. - * rgerhards 2005-10-18 - */ -static rsRetVal cflineProcessTagSelector(uchar **pline) -{ - rsRetVal iRet; - - assert(pline != NULL); - assert(*pline != NULL); - assert(**pline == '!'); - - dbgprintf(" - programname selector line\n"); - - (*pline)++; /* eat '!' */ - - /* the below is somewhat of a quick hack, but it is efficient (this is - * why it is in here. "!*" resets the tag selector with BSD syslog. We mimic - * this, too. As it is easy to check that condition, we do not fire up a - * parser process, just make sure we do not address beyond our space. - * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 - */ - if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { - dbgprintf("resetting programname filter\n"); - if(pDfltProgNameCmp != NULL) { - if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, NULL)) != RS_RET_OK) - return(iRet); - } - } else { - dbgprintf("setting programname filter to '%s'\n", *pline); - if(pDfltProgNameCmp == NULL) { - /* create string for parser */ - if((iRet = rsCStrConstructFromszStr(&pDfltProgNameCmp, *pline)) != RS_RET_OK) - return(iRet); - } else { /* string objects exists, just update... */ - if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, *pline)) != RS_RET_OK) - return(iRet); - } - } - 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, 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 processed this config line.\n", modGetName(pMod)); - - CHKiRet(actionConstruct(&pAction)); /* create action object first */ - pAction->pMod = pMod; - pAction->pModData = pModData; - pAction->bExecWhenPrevSusp = bActExecWhenPrevSusp; - - /* check if we can obtain the template pointers - TODO: move to separat 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 */ - if((pAction->ppTpl = calloc(pAction->iNumTpls, sizeof(struct template *))) == NULL) { - glblHadMemShortage = 1; - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - /* and now the array for doAction() message pointers */ - if((pAction->ppMsgs = calloc(pAction->iNumTpls, sizeof(uchar *))) == NULL) { - glblHadMemShortage = 1; - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - } - - 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((pAction->ppTpl[i] = tplFind((char*)pTplName, strlen((char*)pTplName))) == NULL) { - snprintf(errMsg, sizeof(errMsg) / sizeof(char), - " Could not find template '%s' - action disabled\n", - pTplName); - errno = 0; - logerror(errMsg); - ABORT_FINALIZE(RS_RET_NOT_FOUND); - } - /* check required template options */ - if( (iTplOpts & OMSR_RQD_TPL_OPT_SQL) - && (pAction->ppTpl[i]->optFormatForSQL == 0)) { - errno = 0; - logerror("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); - } - - dbgprintf("template: '%s' assigned\n", pTplName); - } - - pAction->pMod = pMod; - pAction->pModData = pModData; - /* now check if the module is compatible with select features */ - if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) - pAction->f_ReduceRepeated = bReduceRepeatMsgs; - else { - dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); - pAction->f_ReduceRepeated = 0; - } - pAction->bEnabled = 1; /* action is enabled */ - - if(bSuspended) - actionSuspend(pAction); - - *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); - } - - return iRet; -} - - -/* read the filter part of a configuration line and store the filter - * in the supplied selector_t - * rgerhards, 2007-08-01 - */ -static rsRetVal cflineDoFilter(uchar **pp, selector_t *f) -{ - DEFiRet; - - assert(pp != NULL); - assert(f != NULL); - - /* check which filter we need to pull... */ - switch(**pp) { - case ':': - iRet = cflineProcessPropFilter(pp, f); - break; - default: - iRet = cflineProcessTradPRIFilter(pp, f); - break; - } - - /* we now check if there are some global (BSD-style) filter conditions - * and, if so, we copy them over. rgerhards, 2005-10-18 - */ - if(pDfltProgNameCmp != NULL) - if((iRet = rsCStrConstructFromCStr(&(f->pCSProgNameComp), pDfltProgNameCmp)) != RS_RET_OK) - return(iRet); - - if(eDfltHostnameCmpMode != HN_NO_COMP) { - f->eHostnameCmpMode = eDfltHostnameCmpMode; - if((iRet = rsCStrConstructFromCStr(&(f->pCSHostnameComp), pDfltHostnameCmp)) != RS_RET_OK) - return(iRet); - } - - return iRet; -} - - -/* process the action part of a selector line - * rgerhards, 2007-08-01 - */ -static rsRetVal cflineDoAction(uchar **p, action_t **ppAction) -{ - DEFiRet; - modInfo_t *pMod; - omodStringRequest_t *pOMSR; - action_t *pAction; - void *pModData; - - assert(p != NULL); - assert(ppAction != NULL); - - /* loop through all modules and see if one picks up the line */ - pMod = omodGetNxt(NULL); - while(pMod != NULL) { - pOMSR = NULL; - iRet = pMod->mod.om.parseSelectorAct(p, &pModData, &pOMSR); - dbgprintf("tried selector action for %s: %d\n", modGetName(pMod), iRet); - if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) { - if((iRet = addAction(&pAction, pMod, pModData, pOMSR, (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) { - /* now check if the module is compatible with select features */ - if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) - pAction->f_ReduceRepeated = bReduceRepeatMsgs; - else { - dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); - pAction->f_ReduceRepeated = 0; - } - pAction->bEnabled = 1; /* action is enabled */ - } - 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; - } - pMod = omodGetNxt(pMod); - } - - *ppAction = pAction; - return iRet; -} - - -/* helper to selectorAddListCheckActions() - * This is the fucntion to be executed by llExecFunc - */ -DEFFUNC_llExecFunc(selectorAddListCheckActionsChecker) -{ - DEFiRet; - action_t *pAction = (action_t *) pData; - - assert(pAction != NULL); - - if(pAction->pMod->needUDPSocket(pAction->pModData) == RS_RET_TRUE) { - Forwarding++; - } - - return iRet; -} - -/* loop through a list of actions and perform necessary checks and - * housekeeping. This function must only be called when the owning - * selector_t looks valid and is not likely to be discarded. However, - * if we do not return RS_RET_OK, the caller MUST discard the - * owning selector_t. -- rgerhards, 2007-08-02 -*/ -static rsRetVal selectorAddListCheckActions(selector_t *f) -{ - DEFiRet; - - assert(f != NULL); - - CHKiRet(llExecFunc(&f->llActList, selectorAddListCheckActionsChecker, NULL)); - -finalize_it: - return iRet; + dbgprintf(" (re)started.\n"); + ENDfunc } @@ -5308,7 +2394,8 @@ finalize_it: * selector is NULL, which means we do not need to care about it at * all. -- rgerhards, 2007-08-01 */ -static rsRetVal selectorAddList(selector_t *f) +rsRetVal +selectorAddList(selector_t *f) { DEFiRet; int iActionCnt; @@ -5318,14 +2405,9 @@ static rsRetVal selectorAddList(selector_t *f) if(f != NULL) { CHKiRet(llGetNumElts(&f->llActList, &iActionCnt)); if(iActionCnt == 0) { - logerror("warning: selector line without actions will be discarded"); + errmsg.LogError(NO_ERRCODE, "warning: selector line without actions will be discarded"); selectorDestruct(f); } else { - if((iRet = selectorAddListCheckActions(f)) != RS_RET_OK) { - logerror("selector line will be discarded due to error in action(s)"); - selectorDestruct(f); - goto finalize_it; - } /* successfully created an entry */ dbgprintf("selector line successfully processed\n"); /* TODO: we should use the linked list class for the selector list, else we need to add globals @@ -5346,162 +2428,36 @@ static rsRetVal selectorAddList(selector_t *f) } finalize_it: - return iRet; + RETiRet; } -/* Process a configuration file line in traditional "filter selector" format +/* set the main message queue mode + * rgerhards, 2008-01-03 */ -static rsRetVal cflineClassic(uchar *p, selector_t **pfCurr) +static rsRetVal setMainMsgQueType(void __attribute__((unused)) *pVal, uchar *pszType) { DEFiRet; - action_t *pAction; - selector_t *fCurr; - assert(pfCurr != NULL); - - fCurr = *pfCurr; - - /* lines starting with '&' have no new filters and just add - * new actions to the currently processed selector. - */ - if(*p == '&') { - ++p; /* eat '&' */ - skipWhiteSpace(&p); /* on to command */ + if (!strcasecmp((char *) pszType, "fixedarray")) { + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + dbgprintf("main message queue type set to FIXED_ARRAY\n"); + } else if (!strcasecmp((char *) pszType, "linkedlist")) { + MainMsgQueType = QUEUETYPE_LINKEDLIST; + dbgprintf("main message queue type set to LINKEDLIST\n"); + } else if (!strcasecmp((char *) pszType, "disk")) { + MainMsgQueType = QUEUETYPE_DISK; + dbgprintf("main message queue type set to DISK\n"); + } else if (!strcasecmp((char *) pszType, "direct")) { + MainMsgQueType = QUEUETYPE_DIRECT; + dbgprintf("main message queue type set to DIRECT (no queueing at all)\n"); } else { - /* we are finished with the current selector. So we now need to check - * if it has any actions associated and, if so, link it to the linked - * list. If it has nothing associated with it, we can simply discard - * it. In any case, we create a fresh selector for our new filter. - * We have one special case during initialization: then, the current - * selector is NULL, which means we do not need to care about it at - * all. -- rgerhards, 2007-08-01 - */ - CHKiRet(selectorAddList(fCurr)); - CHKiRet(selectorConstruct(&fCurr)); /* create "fresh" selector */ - CHKiRet(cflineDoFilter(&p, fCurr)); /* pull filters */ - } - - CHKiRet(cflineDoAction(&p, &pAction)); - CHKiRet(llAppend(&fCurr->llActList, NULL, (void*) pAction)); - -finalize_it: - *pfCurr = fCurr; - return iRet; -} - - -/* process a configuration line - * I re-did this functon because it was desperately time to do so - * rgerhards, 2007-08-01 - */ -static rsRetVal cfline(uchar *line, selector_t **pfCurr) -{ - DEFiRet; - - assert(line != NULL); - - dbgprintf("cfline: '%s'\n", line); - - /* check type of line and call respective processing */ - switch(*line) { - case '!': - iRet = cflineProcessTagSelector(&line); - break; - case '+': - case '-': - iRet = cflineProcessHostSelector(&line); - break; - case '$': - ++line; /* eat '$' */ - iRet = cfsysline(line); - break; - default: - iRet = cflineClassic(line, pfCurr); - break; + errmsg.LogError(NO_ERRCODE, "unknown mainmessagequeuetype parameter: %s", (char *) pszType); + iRet = RS_RET_INVALID_PARAMS; } + free(pszType); /* no longer needed */ - return iRet; -} - - -/* Decode a symbolic name to a numeric value - */ -int decode(uchar *name, struct code *codetab) -{ - register struct code *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); - } - return (-1); -} - -extern void dbgprintf(char *fmt, ...) __attribute__((format(printf,1, 2))); -void dbgprintf(char *fmt, ...) -{ -# ifdef USE_PTHREADS - static int bWasNL = FALSE; -# endif - va_list ap; - - if ( !(Debug && debugging_on) ) - return; - -# ifdef USE_PTHREADS - /* 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(bWasNL) { - fprintf(stdout, "%8.8d: ", (unsigned int) pthread_self()); - } - bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? TRUE : FALSE; -# endif - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); - - fflush(stdout); - return; -} - - -char *rs_strerror_r(int errnum, char *buf, size_t buflen) { -#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 - return buf; + RETiRet; } @@ -5541,6 +2497,10 @@ void sighup_handler() * \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) { @@ -5567,48 +2527,6 @@ int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) } -/* print out which socket we are listening on. This is only - * a debug aid. rgerhards, 2007-07-02 - */ -static 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*) &sa; - port = ntohs(ipv4->sin_port); - break; - case PF_INET6: - szFamily = "IPv6"; - ipv6 = (struct sockaddr_in6*) &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); -} - - /* 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 @@ -5622,459 +2540,68 @@ static void processImInternal(void) msg_t *pMsg; while(iminternalRemoveMsg(&iPri, &pMsg, &iFlags) == RS_RET_OK) { - logmsg(iPri, pMsg, iFlags); + logmsg(pMsg, iFlags); } } -/* helper function for mainloop(). This is used to add all module - * writeFDsfor Select via llExecFunc(). - * rgerhards, 2007-08-02 - */ -typedef struct selectHelperWriteFDSInfo_s { /* struct for pParam */ - fd_set *pWritefds; - int *pMaxfds; -} selectHelperWriteFDSInfo_t; -DEFFUNC_llExecFunc(mainloopAddModWriteFDSforSelect) -{ - DEFiRet; - action_t *pAction = (action_t*) pData; - selectHelperWriteFDSInfo_t *pState = (selectHelperWriteFDSInfo_t*) pParam; - short fdMod; - - assert(pAction != NULL); - assert(pState != NULL); - - if(pAction->pMod->getWriteFDForSelect(pAction->pModData, &fdMod) == RS_RET_OK) { - FD_SET(fdMod, pState->pWritefds); - if(fdMod > *pState->pMaxfds) - *pState->pMaxfds = fdMod; - } - - return iRet; -} - - -/* helper function for mainloop(). This is used to call module action - * handlers after select if a fd is writable. - * HINT: when we change to the new threading model, this function - * is probably no longer needed. - * rgerhards, 2007-08-02 - */ -DEFFUNC_llExecFunc(mainloopCallWithWritableFDsActions) -{ - DEFiRet; - action_t *pAction = (action_t*) pData; - selectHelperWriteFDSInfo_t *pState = (selectHelperWriteFDSInfo_t*) pParam; - short fdMod; - - assert(pAction != NULL); - assert(pState != NULL); - - if(pAction->pMod->getWriteFDForSelect(pAction->pModData, &fdMod) == RS_RET_OK) { - if(FD_ISSET(fdMod, pState->pWritefds)) { - if((iRet = pAction->pMod->onSelectReadyWrite(pAction->pModData)) - != RS_RET_OK) { - dbgprintf("error %d from onSelectReadyWrite() - continuing\n", iRet); - } - if(--(pState->pMaxfds) == 0) { - ABORT_FINALIZE(RS_RET_FINISHED); /* all processed, nothing left to do */ - } - } - } - -finalize_it: - return iRet; -} - - -/* process the select() selector array after the successful select. - * processing is completed as soon as all selectors needing attention - * are processed. - * rgerhards, 2007-08-08 - */ -static rsRetVal processSelectAfter(int maxfds, int nfds, fd_set *pReadfds, fd_set *pWritefds) -{ - DEFiRet; - rsRetVal iRetLL; - int i; - int fd; - char line[MAXLINE +1]; - selectHelperWriteFDSInfo_t writeFDSInfo; -#ifdef SYSLOG_INET - selector_t *f; - struct sockaddr_storage frominet; - socklen_t socklen; - uchar fromHost[NI_MAXHOST]; - uchar fromHostFQDN[NI_MAXHOST]; - int iTCPSess; - ssize_t l; -#endif /* #ifdef SYSLOG_INET */ - - /* the following macro is used to decrement the number of to-be-probed - * fds and abort this function when we are done with all. - */ -# define FDPROCESSED() if(--nfds == 0) { ABORT_FINALIZE(RS_RET_OK); } - - if (nfds < 0) { - if (errno != EINTR) - logerror("select"); - dbgprintf("Select interrupted.\n"); - ABORT_FINALIZE(RS_RET_OK); /* we are done in any case */ - } - - if(debugging_on) { - dbgprintf("\nSuccessful select, descriptor count = %d, Activity on: ", nfds); - for (i = 0; i <= maxfds; ++i) - if ( FD_ISSET(i, pReadfds) ) - dbgprintf("%d ", i); - dbgprintf(("\n")); - } - -#ifdef SYSLOG_INET - /* Now check the TCP send sockets. So far, we only see if they become - * writable and then change their internal status. No real async - * writing is currently done. This code will be replaced once liblogging - * is used, thus we try not to focus too much on it. - * - * IMPORTANT: With the current code, the writefds must be checked first, - * because the readfds might have messages to be forwarded, which - * rely on the status setting that is done here! - * rgerhards 2005-07-20 - * - * liblogging implementation will not happen as anticipated above. So - * this code here will stay for quite a while. - * rgerhards, 2006-12-07 - */ - writeFDSInfo.pWritefds = pWritefds; - writeFDSInfo.pMaxfds = &nfds; - for(f = Files; f != NULL ; f = f->f_next) { - iRetLL = llExecFunc(&f->llActList, mainloopCallWithWritableFDsActions, &writeFDSInfo); - if(iRetLL == RS_RET_FINISHED) { - ABORT_FINALIZE(RS_RET_OK); /* we are done in this case */ - } - } -#endif /* #ifdef SYSLOG_INET */ -#ifdef SYSLOG_UNIXAF - for (i = 0; i < nfunix; i++) { - if ((fd = funix[i]) != -1 && FD_ISSET(fd, pReadfds)) { - int iRcvd; - iRcvd = recv(fd, line, MAXLINE - 1, 0); - dbgprintf("Message from UNIX socket: #%d\n", fd); - if (iRcvd > 0) { - printchopped(LocalHostName, line, iRcvd, fd, funixParseHost[i]); - } else if (iRcvd < 0 && errno != EINTR) { - char errStr[1024]; - rs_strerror_r(errno, errStr, sizeof(errStr)); - dbgprintf("UNIX socket error: %d = %s.\n", \ - errno, errStr); - logerror("recvfrom UNIX"); - } - FDPROCESSED(); - } - } -#endif - -#ifdef SYSLOG_INET - if (finet != NULL && AcceptRemote) { - for (i = 0; i < *finet; i++) { - if (FD_ISSET(finet[i+1], pReadfds)) { - socklen = sizeof(frominet); - memset(line, 0xff, sizeof(line)); // TODO: I think we need this for debug only - remove after bug hunt - l = recvfrom(finet[i+1], line, MAXLINE - 1, 0, - (struct sockaddr *)&frominet, &socklen); - if (l > 0) { - if(cvthname(&frominet, fromHost, fromHostFQDN) == RS_RET_OK) { - dbgprintf("Message from inetd socket: #%d, host: %s\n", - finet[i+1], fromHost); - /* 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). - * rgerhards, 2005-09-26 - */ - if(isAllowedSender(pAllowedSenders_UDP, - (struct sockaddr *)&frominet, (char*)fromHostFQDN)) { - printchopped((char*)fromHost, line, l, finet[i+1], 1); - } else { - dbgprintf("%s is not an allowed sender\n", (char*)fromHostFQDN); - if(option_DisallowWarning) { - logerrorSz("UDP message from disallowed sender %s discarded", - (char*)fromHost); - } - } - } - } else if (l < 0 && errno != EINTR && errno != EAGAIN) { - char errStr[1024]; - rs_strerror_r(errno, errStr, sizeof(errStr)); - dbgprintf("INET socket error: %d = %s.\n", errno, errStr); - logerror("recvfrom inet"); - /* should be harmless */ - sleep(1); - } - FDPROCESSED(); - } - } - } - - if(sockTCPLstn != NULL && *sockTCPLstn) { - for (i = 0; i < *sockTCPLstn; i++) { - if (FD_ISSET(sockTCPLstn[i+1], pReadfds)) { - dbgprintf("New connect on TCP inetd socket: #%d\n", sockTCPLstn[i+1]); -# ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) - TCPSessGSSAccept(sockTCPLstn[i+1]); - else -# endif - TCPSessAccept(sockTCPLstn[i+1]); - FDPROCESSED(); - } - } - - /* now check the sessions */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fdSess; - int state; - fdSess = pTCPSessions[iTCPSess].sock; - if(FD_ISSET(fdSess, pReadfds)) { - char buf[MAXLINE]; - dbgprintf("tcp session socket with new data: #%d\n", fdSess); - - /* Receive message */ -# ifdef USE_GSSAPI - int allowedMethods = pTCPSessions[iTCPSess].allowedMethods; - if(allowedMethods & ALLOWEDMETHOD_GSS) - state = TCPSessGSSRecv(iTCPSess, buf, sizeof(buf)); - else -# endif - state = recv(fdSess, buf, sizeof(buf), 0); - if(state == 0) { -# ifdef USE_GSSAPI - if(allowedMethods & ALLOWEDMETHOD_GSS) - TCPSessGSSClose(iTCPSess); - else { -# endif - /* process any incomplete frames left over */ - TCPSessPrepareClose(iTCPSess); - /* Session closed */ - TCPSessClose(iTCPSess); -# ifdef USE_GSSAPI - } -# endif - } else if(state == -1) { - logerrorInt("TCP session %d will be closed, error ignored\n", - fdSess); -# ifdef USE_GSSAPI - if(allowedMethods & ALLOWEDMETHOD_GSS) - TCPSessGSSClose(iTCPSess); - else -# endif - TCPSessClose(iTCPSess); - } else { - /* valid data received, process it! */ - if(TCPSessDataRcvd(iTCPSess, buf, state) == 0) { - /* in this case, something went awfully wrong. - * We are instructed to terminate the session. - */ - logerrorInt("Tearing down TCP Session %d - see " - "previous messages for reason(s)\n", - iTCPSess); -# ifdef USE_GSSAPI - if(allowedMethods & ALLOWEDMETHOD_GSS) - TCPSessGSSClose(iTCPSess); - else -# endif - TCPSessClose(iTCPSess); - } - } - FDPROCESSED(); - } - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - } - -#endif -finalize_it: - return iRet; -} - - /* 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) +static void +mainloop(void) { - fd_set readfds; - int i; - int maxfds; - int nfds; - int errnoSave; -#ifdef SYSLOG_INET - selectHelperWriteFDSInfo_t writeFDSInfo; - fd_set writefds; - selector_t *f; - int iTCPSess; -#endif /* #ifdef SYSLOG_INET */ -#ifdef BSD -#ifdef USE_PTHREADS struct timeval tvSelectTimeout; -#endif -#endif + BEGINfunc while(!bFinished){ - errno = 0; - maxfds = 0; - FD_ZERO (&readfds); - /* first check if we have any internal messages queued and spit them out */ + /* TODO: do we need this any longer? I doubt it, but let's care about it + * later -- rgerhards, 2007-12-21 + */ processImInternal(); -#ifdef SYSLOG_UNIXAF - /* 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.. - */ - /* Copy master connections */ - for (i = startIndexUxLocalSockets; i < nfunix; i++) { - if (funix[i] != -1) { - FD_SET(funix[i], &readfds); - if (funix[i]>maxfds) maxfds=funix[i]; - } - } -#endif -#ifdef SYSLOG_INET - /* Add the UDP listen sockets to the list of read descriptors. - */ - if(finet != NULL && AcceptRemote) { - for (i = 0; i < *finet; i++) { - if (finet[i+1] != -1) { - if(Debug) - debugListenInfo(finet[i+1], "UDP"); - FD_SET(finet[i+1], &readfds); - if(finet[i+1]>maxfds) maxfds=finet[i+1]; - } - } - } - - /* Add the TCP listen sockets to the list of read descriptors. - */ - if(sockTCPLstn != NULL && *sockTCPLstn) { - for (i = 0; i < *sockTCPLstn; i++) { - /* The if() below is theoretically not needed, but I leave it in - * so that a socket may become unsuable during execution. That - * feature is not yet supported by the current code base. - */ - if (sockTCPLstn[i+1] != -1) { - if(Debug) - debugListenInfo(sockTCPLstn[i+1], "TCP"); - FD_SET(sockTCPLstn[i+1], &readfds); - if(sockTCPLstn[i+1]>maxfds) maxfds=sockTCPLstn[i+1]; - } - } - /* do the sessions */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fdSess; - fdSess = pTCPSessions[iTCPSess].sock; - dbgprintf("Adding TCP Session %d\n", fdSess); - FD_SET(fdSess, &readfds); - if (fdSess>maxfds) maxfds=fdSess; - /* now get next... */ - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - } - - /* TODO: activate the code below only if we actually need to check - * for outstanding writefds. - */ - if(1) { - /* Now add the TCP output sockets to the writefds set. This implementation - * is not optimal (performance-wise) and it should be replaced with something - * better in the longer term. I've not yet done this, as this code is - * scheduled to be replaced after the liblogging integration. - * rgerhards 2005-07-20 - */ - FD_ZERO(&writefds); - writeFDSInfo.pWritefds = &writefds; - writeFDSInfo.pMaxfds = &maxfds; - for (f = Files; f != NULL ; f = f->f_next) { - llExecFunc(&f->llActList, mainloopAddModWriteFDSforSelect, &writeFDSInfo); - } - } -#endif - - if ( debugging_on ) { - dbgprintf("----------------------------------------\n"); - dbgprintf("Calling select, active file descriptors (max %d): ", maxfds); - for (nfds= 0; nfds <= maxfds; ++nfds) - if ( FD_ISSET(nfds, &readfds) ) - dbgprintf("%d ", nfds); - dbgprintf("\n"); - } - -#define MAIN_SELECT_TIMEVAL NULL -#ifdef BSD -#ifdef USE_PTHREADS - /* There seems to be a problem with BSD and threads. When running on - * multiple threads, a signal will not cause the select call to be - * interrrupted. I am not sure if this is by design or an bug (some - * information on the web let's me think it is a bug), but that really - * does not matter. The issue with our code is that we will not gain - * control when rsyslogd is terminated or huped. What I am doing now is - * make the select call timeout after 10 seconds, so that we can check - * the condition then. Obviously, this causes some sluggish behaviour and - * also the loss of some (very few) cpu cycles. Both, I think, are - * absolutely acceptable. - * rgerhards, 2005-10-26 - * TODO: I got some information: this seems to be expected signal() behaviour - * we should investigate the use of sigaction() (see klogd.c for an sample). - * rgerhards, 2007-06-22 - * rgerhards, 2007-09-11: code has been converted to sigaction() now. We need - * to re-check on BSD, I think the issue is now solved. - */ - tvSelectTimeout.tv_sec = 10; + /* this is now just a wait */ + tvSelectTimeout.tv_sec = TIMERINTVL; tvSelectTimeout.tv_usec = 0; -# undef MAIN_SELECT_TIMEVAL -# define MAIN_SELECT_TIMEVAL &tvSelectTimeout -#endif -#endif -#ifdef SYSLOG_INET -#define MAIN_SELECT_WRITEFDS (fd_set *) &writefds -#else -#define MAIN_SELECT_WRITEFDS NULL -#endif - nfds = select(maxfds+1, (fd_set *) &readfds, MAIN_SELECT_WRITEFDS, - (fd_set *) NULL, MAIN_SELECT_TIMEVAL); - errnoSave = errno; /* save errno for later reference */ - - if(bRequestDoMark) { - domark(); - bRequestDoMark = 0; - /* We do not use continue, because domark() is carried out - * only when something else happened. - */ - } + select(1, NULL, NULL, NULL, &tvSelectTimeout); + if(bFinished) + break; /* exit as quickly as possible - see long comment below */ + + /* If we received a HUP signal, we call doFlushRptdMsgs() a bit early. This + * doesn't matter, because doFlushRptdMsgs() checks timestamps. What may happen, + * however, is that the too-early call may lead to a bit too-late output + * of "last message repeated n times" messages. But that is quite acceptable. + * rgerhards, 2007-12-21 + * ... and just to explain, we flush here because that is exactly what the mainloop + * shall do - provide a periodic interval in which not-yet-flushed messages will + * be flushed. Be careful, there is a potential race condition: doFlushRptdMsgs() + * needs to aquire a lock on the action objects. If, however, long-running consumers + * cause the main queue worker threads to lock them for a long time, we may receive + * a starvation condition, resulting in the mainloop being held on lock for an extended + * period of time. That, in turn, could lead to unresponsiveness to termination + * requests. It is especially important that the bFinished flag is checked before + * doFlushRptdMsgs() is called (I know because I ran into that situation). I am + * not yet sure if the remaining probability window of a termination-related + * problem is large enough to justify changing the code - I would consider it + * extremely unlikely that the problem ever occurs in practice. Fixing it would + * require not only a lot of effort but would cost considerable performance. So + * for the time being, I think the remaining risk can be accepted. + * rgerhards, 2008-01-10 + */ + doFlushRptdMsgs(); + if(restart) { dbgprintf("\nReceived SIGHUP, reloading rsyslogd.\n"); - /* worker thread is stopped as part of init() */ + /* main queue is stopped as part of init() */ init(); restart = 0; continue; } - if (nfds == 0) { - dbgprintf("No select activity.\n"); - continue; - } - - errno = errnoSave; /* restore errno to state right after select (which is what we need) -- rgerhards, 2008-02-11 */ - processSelectAfter(maxfds, nfds, &readfds, MAIN_SELECT_WRITEFDS); - -#undef MAIN_SELECT_TIMEVAL -#undef MAIN_SELECT_WRITEFDS } + ENDfunc } /* If user is not root, prints warnings or even exits @@ -6084,6 +2611,11 @@ static void mainloop(void) */ static void checkPermissions() { +#if 0 + /* TODO: this function must either be redone or removed - now with the input modules, + * there is no such simple check we can do. What we can check, however, is if there is + * any input module active and terminate, if not. -- rgerhards, 2007-12-26 + */ /* we are not root */ if (geteuid() != 0) { @@ -6108,6 +2640,7 @@ static void checkPermissions() } #endif } +#endif } @@ -6118,16 +2651,20 @@ static rsRetVal loadBuildInModules(void) { DEFiRet; - if((iRet = doModInit(modInitFile, (uchar*) "builtin-file", NULL)) != RS_RET_OK) - return iRet; + if((iRet = module.doModInit(modInitFile, (uchar*) "builtin-file", NULL)) != RS_RET_OK) { + RETiRet; + } #ifdef SYSLOG_INET - if((iRet = doModInit(modInitFwd, (uchar*) "builtin-fwd", NULL)) != RS_RET_OK) - return iRet; + if((iRet = module.doModInit(modInitFwd, (uchar*) "builtin-fwd", NULL)) != RS_RET_OK) { + RETiRet; + } #endif - if((iRet = doModInit(modInitShell, (uchar*) "builtin-shell", NULL)) != RS_RET_OK) - return iRet; - if((iRet = doModInit(modInitDiscard, (uchar*) "builtin-discard", NULL)) != RS_RET_OK) - return iRet; + if((iRet = module.doModInit(modInitShell, (uchar*) "builtin-shell", NULL)) != RS_RET_OK) { + RETiRet; + } + if((iRet = module.doModInit(modInitDiscard, (uchar*) "builtin-discard", NULL)) != RS_RET_OK) { + RETiRet; + } /* 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 time of action selector. @@ -6138,8 +2675,8 @@ static rsRetVal loadBuildInModules(void) * User names now must begin with: * [a-zA-Z0-9_.] */ - if((iRet = doModInit(modInitUsrMsg, (uchar*) "builtin-usrmsg", NULL)) != RS_RET_OK) - return iRet; + if((iRet = module.doModInit(modInitUsrMsg, (uchar*) "builtin-usrmsg", NULL)) != RS_RET_OK) + RETiRet; /* ok, initialization of the command handler probably does not 100% belong right in * this space here. However, with the current design, this is actually quite a good @@ -6148,21 +2685,41 @@ static rsRetVal loadBuildInModules(void) * is that rsyslog will terminate if we can not register our built-in config commands. * This, I think, is the right thing to do. -- rgerhards, 2007-07-31 */ -#ifdef USE_PTHREADS + CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, NULL, &pszWorkDir, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeretrycount", 0, eCmdHdlrInt, NULL, &glbliActionResumeRetryCount, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuefilename", 0, eCmdHdlrGetWord, NULL, &pszMainMsgQFName, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesize", 0, eCmdHdlrInt, NULL, &iMainMsgQueueSize, NULL)); -#endif + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuehighwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQHighWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuelowwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQLowWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardmark", 0, eCmdHdlrInt, NULL, &iMainMsgQDiscardMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardseverity", 0, eCmdHdlrSeverity, NULL, &iMainMsgQDiscardSeverity, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuecheckpointinterval", 0, eCmdHdlrInt, NULL, &iMainMsgQPersistUpdCnt, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetype", 0, eCmdHdlrGetWord, setMainMsgQueType, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreads", 0, eCmdHdlrInt, NULL, &iMainMsgQueueNumWorkers, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoQShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutactioncompletion", 0, eCmdHdlrInt, NULL, &iMainMsgQtoActShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutenqueue", 0, eCmdHdlrInt, NULL, &iMainMsgQtoEnq, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkertimeoutthreadshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoWrkShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeueslowdown", 0, eCmdHdlrInt, NULL, &iMainMsgQDeqSlowdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreadminimummessages", 0, eCmdHdlrInt, NULL, &iMainMsgQWrkMinMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxfilesize", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxFileSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxdiskspace", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxDiskSpace, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesaveonshutdown", 0, eCmdHdlrBinary, NULL, &bMainMsgQSaveOnShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimebegin", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinFromHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimeend", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinToHr, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary, NULL, &bReduceRepeatMsgs, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlywhenpreviousissuspended", 0, eCmdHdlrBinary, NULL, &bActExecWhenPrevSusp, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyonceeveryinterval", 0, eCmdHdlrInt, NULL, &iActExecOnceInterval, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeinterval", 0, eCmdHdlrInt, setActionResumeInterval, NULL, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar, NULL, &cCCEscapeChar, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscapeCCOnRcv, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"template", 0, eCmdHdlrCustomHandler, doNameLine, (void*)DIR_TEMPLATE, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"outchannel", 0, eCmdHdlrCustomHandler, doNameLine, (void*)DIR_OUTCHANNEL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"allowedsender", 0, eCmdHdlrCustomHandler, doNameLine, (void*)DIR_ALLOWEDSENDER, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, doModLoad, NULL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"includeconfig", 0, eCmdHdlrCustomHandler, doIncludeLine, NULL, NULL)); + 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)); + CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, conf.doModLoad, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"includeconfig", 0, eCmdHdlrCustomHandler, conf.doIncludeLine, NULL, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"umask", 0, eCmdHdlrFileCreateMode, setUmask, NULL, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"debugprinttemplatelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintTemplateList, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"debugprintmodulelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintModuleList, NULL)); @@ -6170,12 +2727,14 @@ static rsRetVal loadBuildInModules(void) NULL, &bDebugPrintCfSysLineHandlerList, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"moddir", 0, eCmdHdlrGetWord, NULL, &pModDir, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) - CHKiRet(regCfSysLineHdlr((uchar *)"gsslistenservicename", 0, eCmdHdlrGetWord, NULL, &gss_listen_service_name, NULL)); -#endif + + /* now add other modules handlers (we should work on that to be able to do it in ClassInit(), but so far + * that is not possible). -- rgerhards, 2008-01-28 + */ + CHKiRet(actionAddCfSysLineHdrl()); finalize_it: - return iRet; + RETiRet; } @@ -6185,11 +2744,6 @@ static void printVersion(void) { printf("rsyslogd %s, ", VERSION); printf("compiled with:\n"); -#ifdef USE_PTHREADS - printf("\tFEATURE_PTHREADS (dual-threading):\tYes\n"); -#else - printf("\tFEATURE_PTHREADS (dual-threading):\tNo\n"); -#endif #ifdef FEATURE_REGEXP printf("\tFEATURE_REGEXP:\t\t\t\tYes\n"); #else @@ -6205,21 +2759,21 @@ static void printVersion(void) #else printf("\tFEATURE_NETZIP (message compression):\tNo\n"); #endif -#ifdef SYSLOG_INET - printf("\tSYSLOG_INET (Internet/remote support):\tYes\n"); -#else - printf("\tSYSLOG_INET (Internet/remote support):\tNo\n"); -#endif #if defined(SYSLOG_INET) && defined(USE_GSSAPI) - printf("\tFEATURE_GSSAPI (GSSAPI Kerberos 5 support):\tYes\n"); + printf("\tGSSAPI Kerberos 5 support:\t\tYes\n"); #else - printf("\tFEATURE_GSSAPI (GSSAPI Kerberos 5 support):\tNo\n"); + 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 RTINST + printf("\tRuntime Instrumentation (slow code):\tYes\n"); +#else + printf("\tRuntime Instrumentation (slow code):\tNo\n"); +#endif printf("\nSee http://www.rsyslog.com for more information.\n"); } @@ -6230,36 +2784,26 @@ static void printVersion(void) */ static void mainThread() { - DEFiRet; + BEGINfunc uchar *pTmp; - /* doing some core initializations */ - 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 */ - } - - if((iRet = loadBuildInModules()) != RS_RET_OK) { - fprintf(stderr, "fatal error: could not activate built-in modules. Error code %d.\n", - iRet); - exit(1); /* "good" exit, leaving at init for fatal error */ - } - /* Note: signals MUST be processed by the thread this code is running in. The reason * is that we need to interrupt the select() system call. -- rgerhards, 2007-10-17 */ - /* initialize the default templates - * we use template names with a SP in front - these - * can NOT be generated via the configuration file - */ - pTmp = template_TraditionalFormat; - tplAddLine(" TradFmt", &pTmp); + /* initialize the build-in templates */ + pTmp = template_SyslogProtocol23Format; + tplAddLine("RSYSLOG_SyslogProtocol23Format", &pTmp); + pTmp = template_FileFormat; /* new format for files with high-precision stamp */ + tplAddLine("RSYSLOG_FileFormat", &pTmp); + pTmp = template_TraditionalFileFormat; + tplAddLine("RSYSLOG_TraditionalFileFormat", &pTmp); pTmp = template_WallFmt; tplAddLine(" WallFmt", &pTmp); - pTmp = template_StdFwdFmt; - tplAddLine(" StdFwdFmt", &pTmp); + pTmp = template_ForwardFormat; + tplAddLine("RSYSLOG_ForwardFormat", &pTmp); + pTmp = template_TraditionalForwardFormat; + tplAddLine("RSYSLOG_TraditionalForwardFormat", &pTmp); pTmp = template_StdUsrMsgFmt; tplAddLine(" StdUsrMsgFmt", &pTmp); pTmp = template_StdDBFmt; @@ -6267,7 +2811,6 @@ static void mainThread() pTmp = template_StdPgSQLFmt; tplLastStaticInit(tplAddLine(" StdPgSQLFmt", &pTmp)); - dbgprintf("Starting.\n"); init(); if(Debug) { dbgprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); @@ -6284,19 +2827,232 @@ static void mainThread() * do the init() and then restart things. * rgerhards, 2005-10-24 */ + dbgprintf("initialization completed, transitioning to regular run mode\n"); mainloop(); + ENDfunc +} - /* do any de-init's that need to be done AFTER this comment */ - die(bFinished); + +/* Method to initialize all global classes. + * rgerhards, 2008-01-04 + */ +static rsRetVal +InitGlobalClasses(void) +{ + DEFiRet; + char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */ + + pErrObj = "obj"; + CHKiRet(objClassInit(NULL)); /* *THIS* *MUST* always be the first class initilizer being called! */ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + /* the following classes were intialized by objClassInit() */ + pErrObj = "errmsg"; + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + pErrObj = "module"; + CHKiRet(objUse(module, CORE_COMPONENT)); + pErrObj = "var"; + CHKiRet(objUse(var, CORE_COMPONENT)); + + /* initialize and use 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 + */ + pErrObj = "datetime"; + CHKiRet(datetimeClassInit(NULL)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + pErrObj = "msg"; + CHKiRet(msgClassInit(NULL)); + pErrObj = "str,"; + CHKiRet(strmClassInit(NULL)); + pErrObj = "wti"; + CHKiRet(wtiClassInit(NULL)); + pErrObj = "wtp"; + CHKiRet(wtpClassInit(NULL)); + pErrObj = "queue"; + CHKiRet(queueClassInit(NULL)); + pErrObj = "vmstk"; + CHKiRet(vmstkClassInit(NULL)); + pErrObj = "sysvar"; + CHKiRet(sysvarClassInit(NULL)); + pErrObj = "vm"; + CHKiRet(vmClassInit(NULL)); + CHKiRet(objUse(vm, CORE_COMPONENT)); + pErrObj = "vmop"; + CHKiRet(vmopClassInit(NULL)); + pErrObj = "vmprg"; + CHKiRet(vmprgClassInit(NULL)); + pErrObj = "ctok_token"; + CHKiRet(ctok_tokenClassInit(NULL)); + pErrObj = "ctok"; + CHKiRet(ctokClassInit(NULL)); + pErrObj = "expr"; + CHKiRet(exprClassInit(NULL)); + CHKiRet(objUse(expr, CORE_COMPONENT)); + pErrObj = "conf"; + CHKiRet(confClassInit(NULL)); + CHKiRet(objUse(conf, CORE_COMPONENT)); + + /* dummy "classes" */ + pErrObj = "action"; + CHKiRet(actionClassInit()); + pErrObj = "template"; + CHKiRet(templateInit()); + pErrObj = "str"; + CHKiRet(strInit()); + + /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ + pErrObj = "net"; + CHKiRet(objUse(net, LM_NET_FILENAME)); + +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(conf, CORE_COMPONENT); + objRelease(expr, CORE_COMPONENT); + objRelease(vm, CORE_COMPONENT); + objRelease(var, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + + /* TODO: implement the rest of the deinit */ + confClassExit(); +#if 0 + CHKiRet(datetimeClassInit(NULL)); + CHKiRet(msgClassInit(NULL)); + CHKiRet(strmClassInit(NULL)); + CHKiRet(wtiClassInit(NULL)); + CHKiRet(wtpClassInit(NULL)); + CHKiRet(queueClassInit(NULL)); + CHKiRet(vmstkClassInit(NULL)); + CHKiRet(sysvarClassInit(NULL)); + CHKiRet(vmClassInit(NULL)); + CHKiRet(vmopClassInit(NULL)); + CHKiRet(vmprgClassInit(NULL)); + CHKiRet(ctok_tokenClassInit(NULL)); + CHKiRet(ctokClassInit(NULL)); + CHKiRet(exprClassInit(NULL)); + + /* dummy "classes" */ + CHKiRet(actionClassInit()); + CHKiRet(templateInit()); +#endif + /* dummy "classes */ + strExit(); + +#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 + objClassExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + + 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; } /* This is the main entry point into rsyslogd. Over time, we should try to * modularize it a bit more... */ -int main(int argc, char **argv) +int realMain(int argc, char **argv) { + DEFiRet; + register int i; register char *p; int num_fds; @@ -6305,28 +3061,177 @@ int main(int argc, char **argv) extern int optind; extern char *optarg; struct sigaction sigAct; -#if 0 /* see comment for #if 0 below (towards end of function) */ - pthread_t thrdMain; - sigset_t sigSet; + int bIsFirstOption = 1; + int bEOptionWasGiven = 0; + int bImUxSockLoaded = 0; /* already generated a $ModLoad imuxsock? */ + char *arg; /* for command line option processing */ + uchar legacyConfLine[80]; + + /* 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. With later + * versions (if a dependency on -c option is introduced), we must move that code + * to other places, but I think it is quite appropriate and saves code to do this + * only when actually neeeded. + * rgerhards, 2008-04-04 + */ + while ((ch = getopt(argc, argv, "46a:Ac:def:g:hi:l:m:M:nop:qQr::s: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 'o': + case 'p': + case 'q': /* add hostname if DNS resolving has failed */ + case 'Q': /* dont resolve hostnames in ACL to IPs */ + case 's': + case 'u': /* misc user settings */ + case 'w': /* disable disallowed host warnings */ + case 'x': /* disable dns for remote messages */ + CHKiRet(bufOptAdd(ch, optarg)); + break; + case 'c': /* compatibility mode */ + if(!bIsFirstOption) { + fprintf(stderr, "-c option MUST be specified as the first option - aborting...\n"); + usage(); + exit(1); + } + iCompatibilityMode = atoi(optarg); + break; + case 'd': /* debug - must be handled now, so that debug is active during init! */ + Debug = 1; + break; + case 'e': /* log every message (no repeat message supression) */ + fprintf(stderr, "note: -e option is no longer supported, every message is now logged by default\n"); + bEOptionWasGiven = 1; + break; + case 'g': /* enable tcp gssapi logging */ +#if defined(SYSLOG_INET) && defined(USE_GSSAPI) + CHKiRet(bufOptAdd('g', optarg)); +#else + fprintf(stderr, "rsyslogd: -g not valid - not compiled with gssapi support"); #endif - -#ifdef MTRACE - mtrace(); /* this is a debug aid for leak detection - either remove - * or put in conditional compilation. 2005-01-18 RGerhards */ + break; + case 'M': /* default module load path -- this MUST be carried out immediately! */ + glblModPath = (uchar*) optarg; + break; + case 'r': /* accept remote messages */ +#ifdef SYSLOG_INET + CHKiRet(bufOptAdd(ch, optarg)); +#else + fprintf(stderr, "rsyslogd: -r not valid - not compiled with network support\n"); #endif + break; + case 't': /* enable tcp logging */ +#ifdef SYSLOG_INET + CHKiRet(bufOptAdd(ch, optarg)); +#else + fprintf(stderr, "rsyslogd: -t not valid - not compiled with network support\n"); +#endif + break; + case 'v': /* MUST be carried out immediately! */ + printVersion(); + exit(0); /* exit for -v option - so this is a "good one" */ + case '?': + default: + usage(); + } + bIsFirstOption = 0; /* we already saw an option character */ + } + + if ((argc -= optind)) + usage(); + + dbgprintf("rsyslogd %s startup, compatibility mode %d, module path '%s'\n", + VERSION, iCompatibilityMode, glblModPath == NULL ? "" : (char*)glblModPath); + + /* we are done with the initial option parsing and processing. Now we init the system. */ ppid = getpid(); if(chdir ("/") != 0) fprintf(stderr, "Can not do 'cd /' - still trying to run\n"); - for (i = 1; i < MAXFUNIX; i++) { - funixn[i] = ""; - funix[i] = -1; + + 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 */ + + /* 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 + */ + net.getLocalHostname(&LocalHostName); + if((p = strchr((char*)LocalHostName, '.'))) { + *p++ = '\0'; + LocalDomain = p; + } else { + LocalDomain = ""; + + /* 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) { + free(LocalHostName); + CHKmalloc(LocalHostName = (uchar*)strdup(hent->h_name)); + + if((p = strchr((char*)LocalHostName, '.'))) + { + *p++ = '\0'; + LocalDomain = p; + } + } } - /* END core initializations */ + /* Convert to lower case to recognize the correct domain laterly */ + for (p = (char *)LocalDomain ; *p ; p++) + *p = (char)tolower((int)*p); - while ((ch = getopt(argc, argv, "46Aa:c:dehi:f:g:l:m:nop:qQr::s:t:u:vwx")) != EOF) { + /* 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 */ + } + + if((iRet = loadBuildInModules()) != RS_RET_OK) { + fprintf(stderr, "fatal error: could not activate built-in modules. 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); switch((char)ch) { case '4': family = PF_INET; @@ -6337,108 +3242,108 @@ int main(int argc, char **argv) case 'A': send_to_all++; break; - case 'a': - if (nfunix < MAXFUNIX) - if(*optarg == ':') { - funixParseHost[nfunix] = 1; - funixn[nfunix++] = optarg+1; - } - else { - funixParseHost[nfunix] = 0; - funixn[nfunix++] = optarg; + case 'a': + if(iCompatibilityMode < 3) { + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + bImUxSockLoaded = 1; } - else - fprintf(stderr, "rsyslogd: Out of descriptors, ignoring %s\n", optarg); - break; - case 'c': /* forward-compatibility: sets mode in v3+ */ - fprintf(stderr, "-c option not yet supported, reserved for future use\n"); - break; - case 'd': /* debug */ - Debug = 1; - break; - case 'e': /* log every message (no repeat message supression) */ - logEveryMsg = 1; - break; + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "addunixlistensocket %s", arg); + legacyOptsEnq(legacyConfLine); + } else { + fprintf(stderr, "error -a is no longer supported, use module imuxsock instead"); + } + break; case 'f': /* configuration file */ - ConfFile = (uchar*) optarg; + ConfFile = (uchar*) arg; break; case 'g': /* enable tcp gssapi logging */ -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) - if (!bEnableTCP) - configureTCPListen(optarg); - bEnableTCP |= ALLOWEDMETHOD_GSS; -#else - fprintf(stderr, "rsyslogd: -g not valid - not compiled with gssapi support"); -#endif + if(iCompatibilityMode < 3) { + legacyOptsParseTCP(ch, arg); + } else + fprintf(stderr, "-g option only supported in compatibility modes 0 to 2 - ignored\n"); break; case 'h': - NoHops = 0; + if(iCompatibilityMode < 3) { + errmsg.LogError(NO_ERRCODE, "WARNING: -h option is no longer supported - ignored"); + } else { + usage(); /* for v3 and above, it simply is an error */ + } break; case 'i': /* pid file name */ - PidFile = optarg; + PidFile = arg; break; case 'l': if (LocalHosts) { - fprintf (stderr, "rsyslogd: Only one -l argument allowed," \ - "the first one is taken.\n"); + fprintf (stderr, "rsyslogd: Only one -l argument allowed, the first one is taken.\n"); } else { - LocalHosts = crunch_list(optarg); + LocalHosts = crunch_list(arg); } break; case 'm': /* mark interval */ - MarkInterval = atoi(optarg) * 60; + if(iCompatibilityMode < 3) { + MarkInterval = atoi(arg) * 60; + } else + fprintf(stderr, + "-m option only supported in compatibility modes 0 to 2 - ignored\n"); break; case 'n': /* don't fork */ NoFork = 1; break; - case 'o': /* omit local logging (/dev/log) */ - startIndexUxLocalSockets = 1; - break; - case 'p': /* path to regular log socket */ - funixn[0] = optarg; - break; + case 'o': + if(iCompatibilityMode < 3) { + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + bImUxSockLoaded = 1; + } + legacyOptsEnq((uchar *) "OmitLocalLogging"); + } else { + fprintf(stderr, "error -o is no longer supported, use module imuxsock instead"); + } + break; + case 'p': + if(iCompatibilityMode < 3) { + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + bImUxSockLoaded = 1; + } + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "SystemLogSocketName %s", arg); + legacyOptsEnq(legacyConfLine); + } else { + fprintf(stderr, "error -p is no longer supported, use module imuxsock instead"); + } case 'q': /* add hostname if DNS resolving has failed */ - ACLAddHostnameOnFail = 1; + *net.pACLAddHostnameOnFail = 1; break; case 'Q': /* dont resolve hostnames in ACL to IPs */ - ACLDontResolve = 1; + *net.pACLDontResolve = 1; break; case 'r': /* accept remote messages */ -#ifdef SYSLOG_INET - AcceptRemote = 1; - if(optarg == NULL) - LogPort = "0"; - else - LogPort = optarg; -#else - fprintf(stderr, "rsyslogd: -r not valid - not compiled with network support"); -#endif + if(iCompatibilityMode < 3) { + legacyOptsEnq((uchar *) "ModLoad imudp"); + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "UDPServerRun %s", arg); + legacyOptsEnq(legacyConfLine); + } else + fprintf(stderr, "-r option only supported in compatibility modes 0 to 2 - ignored\n"); break; case 's': if (StripDomains) { - fprintf (stderr, "rsyslogd: Only one -s argument allowed," \ - "the first one is taken.\n"); + fprintf (stderr, "rsyslogd: Only one -s argument allowed, the first one is taken.\n"); } else { - StripDomains = crunch_list(optarg); + StripDomains = crunch_list(arg); } break; case 't': /* enable tcp logging */ -#ifdef SYSLOG_INET - if (!bEnableTCP) - configureTCPListen(optarg); - bEnableTCP |= ALLOWEDMETHOD_TCP; -#else - fprintf(stderr, "rsyslogd: -t not valid - not compiled with network support"); -#endif + if(iCompatibilityMode < 3) { + legacyOptsParseTCP(ch, arg); + } else + fprintf(stderr, "-t option only supported in compatibility modes 0 to 2 - ignored\n"); break; case 'u': /* misc user settings */ - if(atoi(optarg) == 1) + if(atoi(arg) == 1) bParseHOSTNAMEandTAG = 0; break; - case 'v': - printVersion(); - exit(0); /* exit for -v option - so this is a "good one" */ - case 'w': /* disable disallowed host warnigs */ + case 'w': /* disable disallowed host warnings */ option_DisallowWarning = 0; break; case 'x': /* disable dns for remote messages */ @@ -6450,12 +3355,36 @@ int main(int argc, char **argv) } } - if ((argc -= optind)) - usage(); + if(iRet != RS_RET_END_OF_LINKEDLIST) + FINALIZE; + + /* process compatibility mode settings */ + if(iCompatibilityMode < 3) { + errmsg.LogError(NO_ERRCODE, "WARNING: rsyslogd is running in compatibility mode. Automatically " + "generated config directives may interfer with your rsyslog.conf settings. " + "We suggest upgrading your config and adding -c3 as the first " + "rsyslogd option."); + if(MarkInterval > 0) { + legacyOptsEnq((uchar *) "ModLoad immark"); + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "MarkMessagePeriod %d", MarkInterval); + legacyOptsEnq(legacyConfLine); + } + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + } + } + + if(bEOptionWasGiven && iCompatibilityMode < 3) { + errmsg.LogError(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."); + } checkPermissions(); + thrdInit(); - if ( !(Debug || NoFork) ) + if( !(Debug || NoFork) ) { dbgprintf("Checking pidfile.\n"); if (!check_pid(PidFile)) @@ -6495,69 +3424,29 @@ int main(int argc, char **argv) debugging_on = 1; /* tuck my process id away */ - if ( !Debug ) + dbgprintf("Writing pidfile %s.\n", PidFile); + if (!check_pid(PidFile)) { - dbgprintf("Writing pidfile.\n"); - if (!check_pid(PidFile)) + if (!write_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); + fputs("Can't write pid.\n", stderr); exit(1); /* exit during startup - questionable */ } - } /* if ( !Debug ) */ - myPid = getpid(); /* save our pid for further testing (also used for messages) */ - - - gethostname(LocalHostName, sizeof(LocalHostName)); - if ( (p = strchr(LocalHostName, '.')) ) { - *p++ = '\0'; - LocalDomain = p; } else { - LocalDomain = ""; - - /* 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(LocalHostName); - if(hent) { - snprintf(LocalHostName, sizeof(LocalHostName), "%s", hent->h_name); - - if ( (p = strchr(LocalHostName, '.')) ) - { - *p++ = '\0'; - LocalDomain = p; - } - } + fputs("Pidfile (and pid) already exist.\n", stderr); + exit(1); /* exit during startup - questionable */ } - - /* Convert to lower case to recognize the correct domain laterly - */ - for (p = (char *)LocalDomain; *p ; p++) - if (isupper((int) *p)) - *p = (char)tolower((int)*p); + myPid = getpid(); /* save our pid for further testing (also used for messages) */ 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; @@ -6565,44 +3454,40 @@ int main(int argc, char **argv) sigaction(SIGQUIT, &sigAct, NULL); sigAct.sa_handler = reapchild; sigaction(SIGCHLD, &sigAct, NULL); - sigAct.sa_handler = domarkAlarmHdlr; - sigaction(SIGALRM, &sigAct, NULL); sigAct.sa_handler = Debug ? debug_switch : SIG_IGN; sigaction(SIGUSR1, &sigAct, NULL); sigAct.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sigAct, NULL); sigaction(SIGXFSZ, &sigAct, NULL); /* do not abort if 2gig file limit is hit */ - (void) alarm(TIMERINTVL); mainThread(); -#if 0 - /* This commented-out code was once used to spawn a separate thread - * for the mainThread(). This was initially done to solve a problem that not - * really existed. Thus the code is now commented out. I do not remove it yet, - * because there may be use for it in the not too distant future. If it is - * still commented out in a year's time, that's a good indication it should - * be removed! -- rgerhards, 2007-10-17 - */ - i = pthread_create(&thrdMain, NULL, mainThread, NULL); - dbgprintf("\"main\" thread started with state %d.\n", i); + /* do any de-init's that need to be done AFTER this comment */ - /* we block all signals - they will be processed by the "main"-thread. This most - * closely resembles previous behaviour. TODO: think about optimizing it, some - * signals may better be delivered here. rgerhards, 2007-10-08 - */ - sigfillset(&sigSet); - pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + die(bFinished); - /* see comment in mainThread on why we start thread and then immediately - * do a blocking wait on it - it makese sense... ;) rgerhards, 2007-10-08 - */ - pthread_join(thrdMain, NULL); -#endif + thrdExit(); +finalize_it: + if(iRet != RS_RET_OK) + fprintf(stderr, "rsyslogd run failed with error %d\n(see rsyslog.h " + "or http://www.rsyslog.com/errcode to learn what that number means)\n", iRet); + + ENDfunc return 0; } -/* vi:set ai: +/* 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: */ @@ -1,19 +1,20 @@ /* common header for syslogd * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -22,6 +23,25 @@ #include "syslogd-types.h" #include "objomsr.h" +#include "modules.h" +#include "template.h" +#include "action.h" +#include "linkedlist.h" +#include "expr.h" + +/* 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 + #ifdef USE_NETZIP /* config param: minimum message size to try compression. The smaller @@ -42,35 +62,81 @@ /* Flags to logmsg(). */ +#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 */ #define SYNC_FILE 0x002 /* do fsync on file after printing */ #define ADDDATE 0x004 /* add a date to the message */ #define MARK 0x008 /* this message is a mark */ -void dbgprintf(char *, ...); -char *rs_strerror_r(int errnum, char *buf, size_t buflen); -void logerror(char *type); -void logerrorSz(char *type, char *errMsg); -void logerrorInt(char *type, int iErr); +/* This structure represents the files that will have log + * copies printed. + * RGerhards 2004-11-08: Each instance of the filed structure + * describes what I call an "output channel". This is important + * to mention as we now allow database connections to be + * present in the filed structure. If helps immensely, if we + * think of it as the abstraction of an output channel. + * rgerhards, 2005-10-26: The structure below provides ample + * opportunity for non-thread-safety. Each of the variable + * accesses must be carefully evaluated, many of them probably + * be guarded by mutexes. But beware of deadlocks... + * rgerhards, 2007-08-01: as you can see, the structure has shrunk pretty much. I will + * remove some of the comments some time. It's still the structure that controls much + * of the processing that goes on in syslogd, but it now has lots of helpers. + */ +struct filed { + struct filed *f_next; /* next in linked list */ + /* filter properties */ + enum { + FILTER_PRI = 0, /* traditional PRI based filer */ + FILTER_PROP = 1, /* extended filter, property based */ + FILTER_EXPR = 2 /* extended filter, expression based */ + } f_filter_type; + EHostnameCmpMode eHostnameCmpMode; + cstr_t *pCSHostnameComp; /* hostname to check */ + cstr_t *pCSProgNameComp; /* tag to check or NULL, if not to be checked */ + union { + u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */ + struct { + cstr_t *pCSPropName; + 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 regular expression? */ + } operation; + cstr_t *pCSCompValue; /* value to "compare" against */ + char isNegated; /* actually a boolean ;) */ + } prop; + expr_t *f_expr; /* expression object */ + } f_filterData; + + linkedList_t llActList; /* list of configured actions */ +}; +typedef struct filed selector_t; /* new type name */ -void printchopped(char *hname, char *msg, int len, int fd, int iSourceType); -void freeAllSockets(int **socks); -int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost); -void getCurrTime(struct syslogTime *t); -int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst); -int formatTimestampToPgSQL(struct syslogTime *ts, char* pDst, size_t iLenDst); -int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf); -int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf); + +#define MSG_PARSE_HOSTNAME 1 +#define MSG_DONT_PARSE_HOSTNAME 0 +rsRetVal parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType); +#include "net.h" /* TODO: remove when you remoe isAllowedSender from here! */ void untty(void); +rsRetVal selectorConstruct(selector_t **ppThis); 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); +rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl); int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); - +rsRetVal selectorDestruct(void *pVal); +rsRetVal selectorAddList(selector_t *f); +/* the following prototypes should go away once we have an input + * module interface -- rgerhards, 2007-12-12 + */ +rsRetVal logmsgInternal(int pri, char *msg, int flags); +void logmsg(msg_t *pMsg, int flags); +rsRetVal submitMsg(msg_t *pMsg); extern int glblHadMemShortage; /* indicates if we had memory shortage some time during the run */ -extern char LocalHostName[]; +extern uchar *LocalHostName; extern int family; extern int NoHops; -extern int *finet; extern int send_to_all; extern int option_DisallowWarning; extern int Debug; @@ -79,8 +145,22 @@ extern int DisableDNS; extern char **StripDomains; extern char *LocalDomain; extern int bDropMalPTRMsgs; -extern struct AllowedSenders *pAllowedSenders_TCP; -extern struct AllowedSenders *pAllowedSenders_GSS; -extern char ctty[]; +extern char ctty[]; +extern int MarkInterval; +extern int bReduceRepeatMsgs; +extern int bActExecWhenPrevSusp; +extern int iActExecOnceInterval; + +/* Intervals at which we flush out "message repeated" messages, + * in seconds after previous message is logged. After each flush, + * we move to the next interval until we reach the largest. + * TODO: move this to action object! + */ +extern int repeatinterval[2]; +#define MAXREPEAT ((int)((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)) +#define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) +#define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ + (f)->f_repeatcount = MAXREPEAT; \ + } #endif /* #ifndef SYSLOGD_H_INCLUDED */ diff --git a/sysvar.c b/sysvar.c new file mode 100644 index 00000000..5eec8f67 --- /dev/null +++ b/sysvar.c @@ -0,0 +1,202 @@ +/* sysvar.c - imlements rsyslog system variables + * + * At least for now, this class only has static functions and no + * instances. + * + * Module begun 2008-02-25 by Rainer Gerhards + * + * Copyright (C) 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. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "stringbuf.h" +#include "sysvar.h" +#include "datetime.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) +DEFobjCurrIf(datetime) + + +/* Standard-Constructor + */ +BEGINobjConstruct(sysvar) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(sysvar) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +sysvarConstructFinalize(sysvar_t __attribute__((unused)) *pThis) +{ + DEFiRet; + RETiRet; +} + + +/* destructor for the sysvar object */ +BEGINobjDestruct(sysvar) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(sysvar) +ENDobjDestruct(sysvar) + + +/* 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 + * 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, cstr_t **ppStr) +{ + DEFiRet; + uchar szBuf[16]; + struct syslogTime t; + + datetime.getCurrTime(&t); + switch(eNow) { + case NOW_NOW: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); + break; + case NOW_YEAR: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%4.4d", t.year); + break; + case NOW_MONTH: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.month); + break; + case NOW_DAY: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.day); + break; + case NOW_HOUR: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.hour); + break; + case NOW_MINUTE: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.minute); + break; + } + + /* now create a string object out of it and hand that over to the var */ + CHKiRet(rsCStrConstructFromszStr(ppStr, szBuf)); + +finalize_it: + RETiRet; +} + + +/* The function returns a system variable suitable for use with RainerScript. Most importantly, this means + * that the value is returned in a var_t object. The var_t is constructed inside this function and + * MUST be freed by the caller. + * rgerhards, 2008-02-25 + */ +static rsRetVal +GetVar(cstr_t *pstrVarName, var_t **ppVar) +{ + DEFiRet; + var_t *pVar; + cstr_t *pstrProp; + + ASSERT(pstrVarName != NULL); + ASSERT(ppVar != NULL); + + /* make sure we have a var_t instance */ + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + /* now begin the actual variable evaluation */ + if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"now", sizeof("now") - 1)) { + CHKiRet(getNOW(NOW_NOW, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"year", sizeof("year") - 1)) { + CHKiRet(getNOW(NOW_YEAR, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"month", sizeof("month") - 1)) { + CHKiRet(getNOW(NOW_MONTH, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"day", sizeof("day") - 1)) { + CHKiRet(getNOW(NOW_DAY, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"hour", sizeof("hour") - 1)) { + CHKiRet(getNOW(NOW_HOUR, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"minute", sizeof("minute") - 1)) { + CHKiRet(getNOW(NOW_MINUTE, &pstrProp)); + } else { + ABORT_FINALIZE(RS_RET_SYSVAR_NOT_FOUND); + } + + /* now hand the string over to the var object */ + CHKiRet(var.SetString(pVar, pstrProp)); + + /* finally store var */ + *ppVar = pVar; + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(sysvar) +CODESTARTobjQueryInterface(sysvar) + if(pIf->ifVersion != sysvarCURR_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). + */ + //xxxpIf->oID = "sysvar";//OBJsysvar; + + pIf->Construct = sysvarConstruct; + pIf->ConstructFinalize = sysvarConstructFinalize; + pIf->Destruct = sysvarDestruct; + pIf->GetVar = GetVar; +finalize_it: +ENDobjQueryInterface(sysvar) + + +/* Initialize the sysvar class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(sysvar, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, sysvarConstructFinalize); +ENDObjClassInit(sysvar) + +/* vi:set ai: + */ diff --git a/sysvar.h b/sysvar.h new file mode 100644 index 00000000..35051b64 --- /dev/null +++ b/sysvar.h @@ -0,0 +1,47 @@ +/* The sysvar object. So far, no instance can be defined (makes logically no + * sense). + * + * 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_SYSVAR_H +#define INCLUDED_SYSVAR_H + +/* the sysvar object - not really used... */ +typedef struct sysvar_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ +} sysvar_t; + + +/* interfaces */ +BEGINinterface(sysvar) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(sysvar); + rsRetVal (*Construct)(sysvar_t **ppThis); + rsRetVal (*ConstructFinalize)(sysvar_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(sysvar_t **ppThis); + rsRetVal (*GetVar)(cstr_t *pstrPropName, var_t **ppVar); +ENDinterface(sysvar) +#define sysvarCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(sysvar); + +#endif /* #ifndef INCLUDED_SYSVAR_H */ diff --git a/tcpclt.c b/tcpclt.c new file mode 100644 index 00000000..3a76e47d --- /dev/null +++ b/tcpclt.c @@ -0,0 +1,487 @@ +/* tcpclt.c + * + * This is the implementation of TCP-based syslog clients (the counterpart + * of the tcpsrv class). + * + * 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" +#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 "syslogd.h" +#include "syslogd-types.h" +#include "net.h" +#include "tcpsyslog.h" +#include "tcpclt.h" +#include "module-template.h" +#include "srUtils.h" + +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers + +/* Initialize TCP sockets (for sender) + * This is done once per selector line, if not yet initialized. + */ +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)); + + 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) { + /* 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 + */ + 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 + */ + 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 +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; +} + + +/* 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->SetSendInit = SetSendInit; + pIf->SetSendFrame = SetSendFrame; + pIf->SetSendPrepRetry = SetSendPrepRetry; + pIf->SetFraming = SetFraming; + +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..d2f1fe02 --- /dev/null +++ b/tcpclt.h @@ -0,0 +1,69 @@ +/* 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, 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 TCPCLT_H_INCLUDED +#define TCPCLT_H_INCLUDED 1 + +#include "tcpsyslog.h" +#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; + size_t lenPrevMsg; + /* session specific callbacks */ + 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 (*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); +ENDinterface(tcpclt) +#define tcpcltCURR_IF_VERSION 1 /* 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..74eac3af --- /dev/null +++ b/tcps_sess.c @@ -0,0 +1,437 @@ +/* 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) + * + * 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" +#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 "syslogd.h" +#include "module-template.h" +#include "net.h" +#include "tcpsrv.h" +#include "tcps_sess.h" +#include "obj.h" +#include "errmsg.h" + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + +/* 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->sock = -1; /* no sock */ + pThis->iMsg = 0; /* just make sure... */ + pThis->bAtStrtOfFram = 1; /* indicate frame header expected */ + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; /* just make sure... */ +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) + if(pThis->sock != -1) + Close(pThis); + + if(pThis->pSrv->pOnSessDestruct != NULL) { + pThis->pSrv->pOnSessDestruct(&pThis->pUsr); + } + /* now destruct our own properties */ + if(pThis->fromHost != NULL) + free(pThis->fromHost); +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 */ +static rsRetVal +SetHost(tcps_sess_t *pThis, uchar *pszHost) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->fromHost != NULL) { + free(pThis->fromHost); + pThis->fromHost = NULL; + } + + if((pThis->fromHost = strdup((char*)pszHost)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + +finalize_it: + RETiRet; +} + +static rsRetVal +SetSock(tcps_sess_t *pThis, int sock) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + pThis->sock = sock; + RETiRet; +} + +static rsRetVal +SetMsgIdx(tcps_sess_t *pThis, int idx) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + pThis->iMsg = idx; + RETiRet; +} + + +/* set out 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; +} + + +static rsRetVal +SetUsrP(tcps_sess_t *pThis, void *pUsr) +{ + DEFiRet; + pThis->pUsr = pUsr; + 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) +{ + 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(NO_ERRCODE, "Incomplete frame at end of stream in session %d - " + "ignoring extra data (a message may be lost).\n", + pThis->sock); + /* 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"); + parseAndSubmitMessage(pThis->fromHost, pThis->msg, pThis->iMsg, MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_LIGHT_DELAY); + pThis->bAtStrtOfFram = 1; + } + +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; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + close(pThis->sock); + pThis->sock = -1; + free(pThis->fromHost); + pThis->fromHost = NULL; /* not really needed, but... */ + + 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) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + + 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(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(NO_ERRCODE, "Framing Error in received TCP message: " + "invalid octet count %d.\n", pThis->iOctetsRemain); + } else if(pThis->iOctetsRemain > MAXLINE) { + /* 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 - MAXLINE is %d\n", + pThis->iOctetsRemain, MAXLINE); + errmsg.LogError(NO_ERRCODE, "received oversize message: size is %d bytes, " + "MAXLINE is %d, truncating...\n", pThis->iOctetsRemain, MAXLINE); + } + pThis->inputState = eInMsg; + } + } else { + assert(pThis->inputState == eInMsg); + if(pThis->iMsg >= MAXLINE) { + /* emergency, we now need to flush, no matter if we are at end of message or not... */ + dbgprintf("error: message received is larger than MAXLINE, we split it\n"); + parseAndSubmitMessage(pThis->fromHost, pThis->msg, pThis->iMsg, MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_LIGHT_DELAY); + pThis->iMsg = 0; + /* 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->eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delemiter? */ + parseAndSubmitMessage(pThis->fromHost, pThis->msg, pThis->iMsg, MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_LIGHT_DELAY); + pThis->iMsg = 0; + 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 MAXLINE, we truncate it. This is the best + * we can do in light of what the engine supports. -- rgerhards, 2008-03-14 + */ + if(pThis->iMsg < MAXLINE) { + *(pThis->msg + 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! */ + parseAndSubmitMessage(pThis->fromHost, pThis->msg, pThis->iMsg, MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_LIGHT_DELAY); + pThis->iMsg = 0; + 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 + */ +static rsRetVal +DataRcvd(tcps_sess_t *pThis, char *pData, size_t iLen) +{ + DEFiRet; + char *pMsg; + char *pEnd; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + assert(pData != NULL); + assert(iLen > 0); + + /* We now copy the message to the session buffer. As + * it looks, we need to do this in any case because + * we might run into multiple messages inside a single + * buffer. Of course, we could think about optimizations, + * but as this code is to be replaced by liblogging, it + * probably doesn't make so much sense... + * rgerhards 2005-07-04 + * + * Algo: + * - copy message to buffer until the first LF is found + * - printline() the buffer + * - continue with copying + */ + pMsg = pThis->msg; /* just a shortcut */ + pEnd = pData + iLen; /* this is one off, which is intensional */ + + while(pData < pEnd) { + CHKiRet(processDataRcvd(pThis, *pData++)); + } + +finalize_it: + RETiRet; +} + + +/* 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->SetHost = SetHost; + pIf->SetSock = SetSock; + pIf->SetMsgIdx = SetMsgIdx; +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); +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)); + + /* 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..0433fdfb --- /dev/null +++ b/tcps_sess.h @@ -0,0 +1,80 @@ +/* Definitions for tcps_sess 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 INCLUDED_TCPS_SESS_H +#define INCLUDED_TCPS_SESS_H + +#include "obj.h" + +/* a forward-definition, we are somewhat cyclic */ +struct tcpsrv_s; + +/* framing modes for TCP */ +typedef enum _TCPFRAMINGMODE { + TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ + TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ + } TCPFRAMINGMODE; + +/* the tcps_sess object */ +typedef struct tcps_sess_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + struct tcpsrv_s *pSrv; /* pointer back to my server (e.g. for callbacks) */ + int sock; + 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; + char msg[MAXLINE+1]; + char *fromHost; + void *pUsr; /* a user-pointer */ +} tcps_sess_t; + + +/* 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 (*SetUsrP)(tcps_sess_t*, void*); + rsRetVal (*SetHost)(tcps_sess_t *pThis, uchar*); + rsRetVal (*SetSock)(tcps_sess_t *pThis, int); + rsRetVal (*SetMsgIdx)(tcps_sess_t *pThis, int); +ENDinterface(tcps_sess) +#define tcps_sessCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(tcps_sess); + + +#endif /* #ifndef INCLUDED_TCPS_SESS_H */ diff --git a/tcpsrv.c b/tcpsrv.c new file mode 100644 index 00000000..924426af --- /dev/null +++ b/tcpsrv.c @@ -0,0 +1,843 @@ +/* tcpsrv.c + * + * Common code for plain TCP based servers. This is currently being + * utilized by imtcp and imgssapi. I suspect that when we implement + * SSL/TLS, that module could also use tcpsrv. + * + * 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) + * + * 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" +#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 "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "srUtils.h" +#include "conf.h" +#include "tcpsrv.h" +#include "obj.h" +#include "errmsg.h" + +MODULE_TYPE_LIB + +/* defines */ +#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */ + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(conf) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) + + + +/* code to free all sockets within a socket table. + * A socket table is a descriptor table where the zero + * element has the count of elements. This is used for + * listening sockets. The socket table itself is also + * freed. + * A POINTER to this structure must be provided, thus + * double indirection! + * rgerhards, 2007-06-28 + */ +static void freeAllSockets(int **socks) +{ + assert(socks != NULL); + assert(*socks != NULL); + while(**socks) { + dbgprintf("Closing socket %d.\n", (*socks)[**socks]); + close((*socks)[**socks]); + (**socks)--; + } + free(*socks); + *socks = NULL; +} + + +/* configure TCP listener settings. This is called during command + * line parsing. The argument following -t is supplied as an argument. + * The format of this argument is + * "<port-to-use>, <nbr-of-sessions>" + * Typically, there is no whitespace between port and session number. + * (but it may be...). + * NOTE: you can not use dbgprintf() in here - the dbgprintf() system is + * not yet initilized when this function is called. + * rgerhards, 2007-06-21 + * The port in cOptarg is handed over to us - the caller MUST NOT free it! + * rgerhards, 2008-03-20 + */ +static void +configureTCPListen(tcpsrv_t *pThis, char *cOptarg) +{ + register int i; + register char *pArg = cOptarg; + + assert(cOptarg != NULL); + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* extract port */ + i = 0; + while(isdigit((int) *pArg)) { + i = i * 10 + *pArg++ - '0'; + } + + if(pThis->TCPLstnPort != NULL) { + free(pThis->TCPLstnPort); + pThis->TCPLstnPort = NULL; + } + + if( i >= 0 && i <= 65535) { + pThis->TCPLstnPort = cOptarg; + } else { + errmsg.LogError(NO_ERRCODE, "Invalid TCP listen port %s - changed to 514.\n", cOptarg); + } +} + + +/* 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; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) + if(pThis->pSessions[i] != NULL) + break; + + 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 iTCPSess; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pThis->pSessions != NULL); + + /* close all TCP connections! */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(iTCPSess != -1) { + tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); + /* now get next... */ + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + + /* we are done with the session table - so get rid of it... + */ + free(pThis->pSessions); + pThis->pSessions = NULL; /* just to make sure... */ + + if(pThis->TCPLstnPort != NULL) + free(pThis->TCPLstnPort); + + /* finally close the listen sockets themselfs */ + freeAllSockets(&pThis->pSocksLstn); +} + + +/* Initialize TCP sockets (for listener) + * This function returns either NULL (which means it failed) or + * a pointer to an array of file descriptiors. If the pointer is + * returned, the zeroest element [0] contains the count of valid + * descriptors. The descriptors themself follow in range + * [1] ... [num-descriptors]. It is guaranteed that each of these + * descriptors is valid, at least when this function returns. + * Please note that technically the array may be larger than the number + * of valid pointers stored in it. The memory overhead is minimal, so + * we do not bother to re-allocate an array of the exact size. Logically, + * the array still contains the exactly correct number of descriptors. + */ +static int *create_tcp_socket(tcpsrv_t *pThis) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks, on = 1; + char *TCPLstnPort; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + if(!strcmp(pThis->TCPLstnPort, "0")) + TCPLstnPort = "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 = pThis->TCPLstnPort; + dbgprintf("creating tcp socket on port %s\n", TCPLstnPort); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo(NULL, TCPLstnPort, &hints, &res); + if(error) { + errmsg.LogError(NO_ERRCODE, "%s", gai_strerror(error)); + 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(NO_ERRCODE, "couldn't allocate memory for TCP listen sockets, suspending TCP 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(NO_ERRCODE, "create_tcp_socket(), socket"); + /* 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(*s, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&iOn, sizeof (iOn)) < 0) { + errmsg.LogError(NO_ERRCODE, "TCP setsockopt"); + close(*s); + *s = -1; + continue; + } + } +#endif + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)) < 0 ) { + errmsg.LogError(NO_ERRCODE, "TCP 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. + */ +#ifndef OS_BSD + if(net.should_use_so_bsdcompat()) { + if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)"); + close(*s); + *s = -1; + continue; + } + } +#endif + + if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) +#ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) +#endif + ) { + errmsg.LogError(NO_ERRCODE, "TCP bind"); + close(*s); + *s = -1; + continue; + } + + if( listen(*s,pThis->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. + */ + errmsg.LogError(NO_ERRCODE, "listen with a backlog of %d failed - retrying with default of 32.", + pThis->iSessMax / 10 + 5); + if(listen(*s, 32) < 0) { + errmsg.LogError(NO_ERRCODE, "TCP listen, suspending tcp inet"); + close(*s); + *s = -1; + continue; + } + } + + (*socks)++; + s++; + } + + if(res != NULL) + freeaddrinfo(res); + + if(Debug && *socks != maxs) + dbgprintf("We could initialize %d TCP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", *socks, maxs); + + if(*socks == 0) { + errmsg.LogError(NO_ERRCODE, "No TCP listen socket could successfully be initialized, " + "message reception via TCP disabled.\n"); + free(socks); + return(NULL); + } + + /* 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(NO_ERRCODE, "Could not initialize TCP session table, suspending TCP message reception."); + freeAllSockets(&socks); /* prevent a socket leak */ + return(NULL); + } + + return(socks); +} + + +/* 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 succeds. + * 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, tcps_sess_t **ppSess, int fd) +{ + DEFiRet; + tcps_sess_t *pSess = NULL; + int newConn; + int iSess = -1; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(struct sockaddr_storage); + uchar fromHost[NI_MAXHOST]; + uchar fromHostFQDN[NI_MAXHOST]; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + newConn = accept(fd, (struct sockaddr*) &addr, &addrlen); + if (newConn < 0) { + errmsg.LogError(NO_ERRCODE, "tcp accept, ignoring error and connection request"); + ABORT_FINALIZE(RS_RET_ERR); // TODO: better error code + //was: return -1; + } + + /* Add to session list */ + iSess = TCPSessTblFindFreeSpot(pThis); + if(iSess == -1) { + errno = 0; + errmsg.LogError(NO_ERRCODE, "too many tcp sessions - dropping incoming request"); + close(newConn); + ABORT_FINALIZE(RS_RET_ERR); // TODO: better error code + //was: return -1; + } else { + /* we found a free spot and can construct our session object */ + CHKiRet(tcps_sess.Construct(&pSess)); + CHKiRet(tcps_sess.SetTcpsrv(pSess, pThis)); + } + + /* OK, we have a "good" index... */ + /* get the host name */ + if(net.cvthname(&addr, fromHost, fromHostFQDN) != RS_RET_OK) { + /* we seem to have something malicous - at least we + * are now told to discard the connection request. + * Error message has been generated by cvthname. + */ + close (newConn); + ABORT_FINALIZE(RS_RET_ERR); // TODO: better error code + //was: return -1; + } + + /* 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). + * rgerhards, 2005-09-26 + */ + if(!pThis->pIsPermittedHost((struct sockaddr*) &addr, (char*) fromHostFQDN, pThis->pUsr, pSess->pUsr)) { + dbgprintf("%s is not an allowed sender\n", (char *) fromHostFQDN); + if(option_DisallowWarning) { + errno = 0; + errmsg.LogError(NO_ERRCODE, "TCP message from disallowed sender %s discarded", + (char*)fromHost); + } + close(newConn); + 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, fromHost)); + CHKiRet(tcps_sess.SetSock(pSess, newConn)); + 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; + pThis->pSessions[iSess] = pSess; + pSess = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pSess != NULL) { + tcps_sess.Destruct(&pSess); + } + iSess = -1; // TODO: change this to be fully iRet compliant ;) + } + + RETiRet; +} + + +/* This function is called to gather input. + */ +static rsRetVal +Run(tcpsrv_t *pThis) +{ + DEFiRet; + int maxfds; + int nfds; + int i; + int iTCPSess; + fd_set readfds; + tcps_sess_t *pNewSess; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* 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) { + maxfds = 0; + FD_ZERO (&readfds); + + /* Add the TCP listen sockets to the list of read descriptors. + */ + if(pThis->pSocksLstn != NULL && *pThis->pSocksLstn) { + for (i = 0; i < *pThis->pSocksLstn; i++) { + /* The if() below is theoretically not needed, but I leave it in + * so that a socket may become unsuable during execution. That + * feature is not yet supported by the current code base. + */ + if (pThis->pSocksLstn[i+1] != -1) { + if(Debug) + net.debugListenInfo(pThis->pSocksLstn[i+1], "TCP"); + FD_SET(pThis->pSocksLstn[i+1], &readfds); + if(pThis->pSocksLstn[i+1]>maxfds) maxfds=pThis->pSocksLstn[i+1]; + } + } + /* do the sessions */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(iTCPSess != -1) { + int fdSess; + fdSess = pThis->pSessions[iTCPSess]->sock; // TODO: NOT CLEAN!, use method + dbgprintf("Adding TCP Session %d\n", fdSess); + FD_SET(fdSess, &readfds); + if (fdSess>maxfds) maxfds=fdSess; + /* now get next... */ + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + } + + if(Debug) { + // TODO: name in dbgprintf! + dbgprintf("--------<TCPSRV> 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); + + for (i = 0; i < *pThis->pSocksLstn; i++) { + if (FD_ISSET(pThis->pSocksLstn[i+1], &readfds)) { + dbgprintf("New connect on TCP inetd socket: #%d\n", pThis->pSocksLstn[i+1]); +RUNLOG_VAR("%p", &pNewSess); + SessAccept(pThis, &pNewSess, pThis->pSocksLstn[i+1]); + --nfds; /* indicate we have processed one */ + } + } + + /* now check the sessions */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(nfds && iTCPSess != -1) { + int fdSess; + int state; + fdSess = pThis->pSessions[iTCPSess]->sock; // TODO: not clean, use method + if(FD_ISSET(fdSess, &readfds)) { + char buf[MAXLINE]; + dbgprintf("tcp session socket with new data: #%d\n", fdSess); + + /* Receive message */ + state = pThis->pRcvData(pThis->pSessions[iTCPSess], buf, sizeof(buf)); + if(state == 0) { + pThis->pOnRegularClose(pThis->pSessions[iTCPSess]); + tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); + } else if(state == -1) { + errmsg.LogError(NO_ERRCODE, "TCP session %d will be closed, error ignored\n", fdSess); + pThis->pOnErrClose(pThis->pSessions[iTCPSess]); + tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); + } else { + /* valid data received, process it! */ + if(tcps_sess.DataRcvd(pThis->pSessions[iTCPSess], buf, state) != RS_RET_OK) { + /* in this case, something went awfully wrong. + * We are instructed to terminate the session. + */ + errmsg.LogError(NO_ERRCODE, "Tearing down TCP Session %d - see " + "previous messages for reason(s)\n", iTCPSess); + pThis->pOnErrClose(pThis->pSessions[iTCPSess]); + tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); + } + } + --nfds; /* indicate we have processed one */ + } + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + } + + RETiRet; +} + + +/* Standard-Constructor + */ +BEGINobjConstruct(tcpsrv) /* be sure to specify the object type also in END macro! */ + pThis->pSocksLstn = NULL; + pThis->iSessMax = 200; /* TODO: useful default ;) */ +ENDobjConstruct(tcpsrv) + + +/* ConstructionFinalizer + */ +static rsRetVal +tcpsrvConstructFinalize(tcpsrv_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->pSocksLstn = pThis->OpenLstnSocks(pThis); + + 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); +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, int (*pRcvData)(tcps_sess_t*, char*, size_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, int* (*pCB)(tcpsrv_t*)) +{ + DEFiRet; + pThis->OpenLstnSocks = pCB; + RETiRet; +} + +static rsRetVal +SetUsrP(tcpsrv_t *pThis, void *pUsr) +{ + DEFiRet; + pThis->pUsr = pUsr; + 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->SessAccept = SessAccept; + pIf->configureTCPListen = configureTCPListen; + pIf->create_tcp_socket = create_tcp_socket; + pIf->Run = Run; + + pIf->SetUsrP = SetUsrP; + 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; + +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(errmsg, CORE_COMPONENT); + 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(tcps_sess, DONT_LOAD_LIB)); + CHKiRet(objUse(conf, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, tcpsrvDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcpsrvConstructFinalize); +ENDObjClassInit(tcpsrv) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + /* 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 */ + + /* 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..20a7ea25 --- /dev/null +++ b/tcpsrv.h @@ -0,0 +1,84 @@ +/* Definitions for tcpsrv class. + * + * 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 INCLUDED_TCPSRV_H +#define INCLUDED_TCPSRV_H + +#include "obj.h" +#include "tcps_sess.h" + +/* the tcpsrv object */ +typedef struct tcpsrv_s { + BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */ + int *pSocksLstn; /**< listen socket array for server [0] holds count */ + int iSessMax; /**< max number of sessions supported */ + char *TCPLstnPort; /**< the port the listener shall listen on */ + 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); + int (*pRcvData)(tcps_sess_t*, char*, size_t); + int* (*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)(struct tcpsrv_s *, tcps_sess_t*); + rsRetVal (*OnSessConstructFinalize)(void*); + rsRetVal (*pOnSessDestruct)(void*); +} tcpsrv_t; + + +/* 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); + void (*configureTCPListen)(tcpsrv_t*, char *cOptarg); + int (*SessAccept)(tcpsrv_t *pThis, tcps_sess_t**ppSess, int fd); + int* (*create_tcp_socket)(tcpsrv_t *pThis); + rsRetVal (*Run)(tcpsrv_t *pThis); + /* set methods */ + rsRetVal (*SetUsrP)(tcpsrv_t*, void*); + rsRetVal (*SetCBIsPermittedHost)(tcpsrv_t*, int (*) (struct sockaddr *addr, char*, void*, void*)); + rsRetVal (*SetCBOpenLstnSocks)(tcpsrv_t *, int* (*)(tcpsrv_t*)); + rsRetVal (*SetCBRcvData)(tcpsrv_t *, int (*)(tcps_sess_t*, char*, size_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*)); + /* session specifics */ + rsRetVal (*SetCBOnSessAccept)(tcpsrv_t*, rsRetVal (*) (tcpsrv_t*, tcps_sess_t*)); + rsRetVal (*SetCBOnSessDestruct)(tcpsrv_t*, rsRetVal (*) (void*)); + rsRetVal (*SetCBOnSessConstructFinalize)(tcpsrv_t*, rsRetVal (*) (void*)); +ENDinterface(tcpsrv) +#define tcpsrvCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(tcpsrv); + +/* the name of our library binary */ +#define LM_TCPSRV_FILENAME "lmtcpsrv" + +#endif /* #ifndef INCLUDED_TCPSRV_H */ diff --git a/tcpsyslog.c b/tcpsyslog.c index 6b1c446c..d00731d3 100644 --- a/tcpsyslog.c +++ b/tcpsyslog.c @@ -1,5 +1,6 @@ /* tcpsyslog.c - * This is the implementation of TCP-based syslog. + * This is the implementation of TCP-based syslog. It includes those + * (few) things that both clients and servers need. * * 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 @@ -9,19 +10,20 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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. */ @@ -42,1226 +44,12 @@ #if HAVE_FCNTL_H #include <fcntl.h> #endif -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) -#include <gssapi/gssapi.h> -#endif #include "syslogd.h" #include "syslogd-types.h" #include "net.h" -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) -#include "gss-misc.h" -#endif #include "tcpsyslog.h" -/******************************************************************** - * ### SYSLOG/TCP CODE ### - * This is code for syslog/tcp. This code would belong to a separate - * file - but I have put it here to avoid hassle with CVS. Over - * time, I expect rsyslog to utilize liblogging for actual network - * I/O. So the tcp code will be (re)moved some time. I don't like - * to add a new file to cvs that I will push to the attic in just - * a few weeks (month at most...). So I simply add the code here. - * - * Place no unrelated code between this comment and the - * END tcp comment! - * - * 2005-07-04 RGerhards (Happy independence day to our US friends!) - ********************************************************************/ -#ifdef SYSLOG_INET - -#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */ - -static int iTCPSessMax = TCPSESS_MAX_DEFAULT; /* actual number of sessions */ -char *TCPLstnPort = "0"; /* read-only after startup */ -int bEnableTCP = 0; /* read-only after startup */ -int *sockTCPLstn = NULL; /* read-only after startup, modified by restart */ -struct TCPSession *pTCPSessions; -/* The thread-safeness of the sesion table is doubtful */ -#ifdef USE_GSSAPI -static gss_cred_id_t gss_server_creds = GSS_C_NO_CREDENTIAL; -char *gss_listen_service_name = NULL; -#endif - -/* configure TCP listener settings. This is called during command - * line parsing. The argument following -t is supplied as an argument. - * The format of this argument is - * "<port-to-use>, <nbr-of-sessions>" - * Typically, there is no whitespace between port and session number. - * (but it may be...). - * NOTE: you can not use dbgprintf() in here - the dbgprintf() system is - * not yet initilized when this function is called. - * rgerhards, 2007-06-21 - * We can also not use logerror(), as that system is also not yet - * initialized... rgerhards, 2007-06-28 - */ -void configureTCPListen(char *cOptarg) -{ - register int i; - register char *pArg = cOptarg; - - assert(cOptarg != NULL); - - /* extract port */ - i = 0; - while(isdigit((int) *pArg)) { - i = i * 10 + *pArg++ - '0'; - } - - if( i >= 0 && i <= 65535) { - TCPLstnPort = cOptarg; - } else { - fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i); - TCPLstnPort = "514"; - } - - /* number of sessions */ - if(*pArg == ','){ - *pArg = '\0'; /* hack: terminates port (see a few lines above, same buffer!) */ - ++pArg; - while(isspace((int) *pArg)) - ++pArg; - /* ok, here should be the number... */ - i = 0; - while(isdigit((int) *pArg)) { - i = i * 10 + *pArg++ - '0'; - } - if(i > 1) - iTCPSessMax = i; - else { - /* too small, need to adjust */ - fprintf(stderr, - "rsyslogd: TCP session max configured to %d [-t %s] - changing to 1.\n", - i, cOptarg); - iTCPSessMax = 1; - } - } else if(*pArg == '\0') { - /* use default for session number - that's already set...*/ - /*EMPTY BY INTENSION*/ - } else { - fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", cOptarg); - } -} - - -/* Initialize the session table - * returns 0 if OK, somewhat else otherwise - */ -static int TCPSessInit(void) -{ - register int i; - - assert(pTCPSessions == NULL); - dbgprintf("Allocating buffer for %d TCP sessions.\n", iTCPSessMax); - if((pTCPSessions = (struct TCPSession *) malloc(sizeof(struct TCPSession) * iTCPSessMax)) - == NULL) { - dbgprintf("Error: TCPSessInit() could not alloc memory for TCP session table.\n"); - return(1); - } - - for(i = 0 ; i < iTCPSessMax ; ++i) { - pTCPSessions[i].sock = -1; /* no sock */ - pTCPSessions[i].iMsg = 0; /* just make sure... */ - pTCPSessions[i].bAtStrtOfFram = 1; /* indicate frame header expected */ - pTCPSessions[i].eFraming = TCP_FRAMING_OCTET_STUFFING; /* just make sure... */ -#ifdef USE_GSSAPI - pTCPSessions[i].gss_flags = 0; - pTCPSessions[i].gss_context = GSS_C_NO_CONTEXT; - pTCPSessions[i].allowedMethods = 0; -#endif - } - return(0); -} - - -/* 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 TCPSessFindFreeSpot(void) -{ - register int i; - - for(i = 0 ; i < iTCPSessMax ; ++i) { - if(pTCPSessions[i].sock == -1) - break; - } - - return((i < iTCPSessMax) ? 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. - */ -int TCPSessGetNxtSess(int iCurr) -{ - register int i; - - for(i = iCurr + 1 ; i < iTCPSessMax ; ++i) - if(pTCPSessions[i].sock != -1) - break; - - return((i < iTCPSessMax) ? 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 - */ -void deinit_tcp_listener(void) -{ - int iTCPSess; - - assert(pTCPSessions != NULL); - /* close all TCP connections! */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fd; - fd = pTCPSessions[iTCPSess].sock; - dbgprintf("Closing TCP Session %d\n", fd); - close(fd); - free(pTCPSessions[iTCPSess].fromHost); -#ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) { - OM_uint32 maj_stat, min_stat; - maj_stat = gss_delete_sec_context(&min_stat, &pTCPSessions[iTCPSess].gss_context, GSS_C_NO_BUFFER); - if (maj_stat != GSS_S_COMPLETE) - display_status("deleting context", maj_stat, min_stat); - } -#endif - /* now get next... */ - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - - /* we are done with the session table - so get rid of it... - */ - free(pTCPSessions); - pTCPSessions = NULL; /* just to make sure... */ - - /* finally close the listen sockets themselfs */ - freeAllSockets(&sockTCPLstn); -} - - -/* Initialize TCP sockets (for listener) - * This function returns either NULL (which means it failed) or - * a pointer to an array of file descriptiors. If the pointer is - * returned, the zeroest element [0] contains the count of valid - * descriptors. The descriptors themself follow in range - * [1] ... [num-descriptors]. It is guaranteed that each of these - * descriptors is valid, at least when this function returns. - * Please note that technically the array may be larger than the number - * of valid pointers stored in it. The memory overhead is minimal, so - * we do not bother to re-allocate an array of the exact size. Logically, - * the array still contains the exactly correct number of descriptors. - */ -int *create_tcp_socket(void) -{ - struct addrinfo hints, *res, *r; - int error, maxs, *s, *socks, on = 1; - - if(!strcmp(TCPLstnPort, "0")) - TCPLstnPort = "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 - */ - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; - hints.ai_family = family; - hints.ai_socktype = SOCK_STREAM; - - error = getaddrinfo(NULL, TCPLstnPort, &hints, &res); - if(error) { - logerror((char*) gai_strerror(error)); - 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) { - logerror("couldn't allocate memory for TCP listen sockets, suspending TCP 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)) - logerror("create_udp_socket(), socket"); - /* 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(*s, IPPROTO_IPV6, IPV6_V6ONLY, - (char *)&iOn, sizeof (iOn)) < 0) { - logerror("TCP setsockopt"); - close(*s); - *s = -1; - continue; - } - } -#endif - if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, - (char *) &on, sizeof(on)) < 0 ) { - logerror("TCP 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. - */ -#ifndef BSD - if (should_use_so_bsdcompat()) { - if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, - (char *) &on, sizeof(on)) < 0) { - logerror("TCP setsockopt(BSDCOMPAT)"); - close(*s); - *s = -1; - continue; - } - } -#endif - - if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) -#ifndef IPV6_V6ONLY - && (errno != EADDRINUSE) -#endif - ) { - logerror("TCP bind"); - close(*s); - *s = -1; - continue; - } - - if( listen(*s,iTCPSessMax / 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. - */ - logerrorInt("listen with a backlog of %d failed - retrying with default of 32.", - iTCPSessMax / 10 + 5); - if(listen(*s, 32) < 0) { - logerror("TCP listen, suspending tcp inet"); - close(*s); - *s = -1; - continue; - } - } - - (*socks)++; - s++; - } - - if(res != NULL) - freeaddrinfo(res); - - if(Debug && *socks != maxs) - dbgprintf("We could initialize %d TCP listen sockets out of %d we received " - "- this may or may not be an error indication.\n", *socks, maxs); - - if(*socks == 0) { - logerror("No TCP listen socket could successfully be initialized, " - "message reception via TCP disabled.\n"); - free(socks); - return(NULL); - } - - /* OK, we had success. Now it is also time to - * initialize our connections - */ - if(TCPSessInit() != 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... - */ - logerror("Could not initialize TCP session table, suspending TCP message reception."); - freeAllSockets(&socks); /* prevent a socket leak */ - return(NULL); - } - - return(socks); -} - - -/* 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. - */ -int TCPSessAccept(int fd) -{ - int newConn; - int iSess; - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(struct sockaddr_storage); - size_t lenHostName; - uchar fromHost[NI_MAXHOST]; - uchar fromHostFQDN[NI_MAXHOST]; - char *pBuf; -#ifdef USE_GSSAPI - char allowedMethods = 0; -#endif - - newConn = accept(fd, (struct sockaddr*) &addr, &addrlen); - if (newConn < 0) { - logerror("tcp accept, ignoring error and connection request"); - return -1; - } - - /* Add to session list */ - iSess = TCPSessFindFreeSpot(); - if(iSess == -1) { - errno = 0; - logerror("too many tcp sessions - dropping incoming request"); - close(newConn); - return -1; - } - - /* OK, we have a "good" index... */ - /* get the host name */ - if(cvthname(&addr, fromHost, fromHostFQDN) != RS_RET_OK) { - /* we seem to have something malicous - at least we - * are now told to discard the connection request. - * Error message has been generated by cvthname. - */ - close (newConn); - return -1; - } - - /* 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). - * rgerhards, 2005-09-26 - */ -#ifdef USE_GSSAPI - if((bEnableTCP & ALLOWEDMETHOD_TCP) && - isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) - allowedMethods |= ALLOWEDMETHOD_TCP; - if((bEnableTCP & ALLOWEDMETHOD_GSS) && - isAllowedSender(pAllowedSenders_GSS, (struct sockaddr *)&addr, (char*)fromHostFQDN)) - allowedMethods |= ALLOWEDMETHOD_GSS; - if(allowedMethods) - pTCPSessions[iSess].allowedMethods = allowedMethods; - else -#else - if(!isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) -#endif - { - dbgprintf("%s is not an allowed sender\n", (char *) fromHostFQDN); - if(option_DisallowWarning) { - errno = 0; - logerrorSz("TCP message from disallowed sender %s discarded", - (char*)fromHost); - } - close(newConn); - return -1; - } - - /* OK, we have an allowed sender, so let's continue */ - lenHostName = strlen((char*)fromHost) + 1; /* for \0 byte */ - if((pBuf = (char*) malloc(sizeof(char) * lenHostName)) == NULL) { - glblHadMemShortage = 1; - pTCPSessions[iSess].fromHost = "NO-MEMORY-FOR-HOSTNAME"; - } else { - memcpy(pBuf, fromHost, lenHostName); - pTCPSessions[iSess].fromHost = pBuf; - } - - pTCPSessions[iSess].sock = newConn; - pTCPSessions[iSess].iMsg = 0; /* init msg buffer! */ - return iSess; -} - - -/* 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 - */ -void TCPSessPrepareClose(int iTCPSess) -{ - if(iTCPSess < 0 || iTCPSess > iTCPSessMax) { - errno = 0; - logerror("internal error, trying to close an invalid TCP session!"); - return; - } - - if(pTCPSessions[iTCPSess].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. - */ - return; - } - - /* we have some data left! */ - if(pTCPSessions[iTCPSess].eFraming == TCP_FRAMING_OCTET_COUNTING) { - /* In this case, we have an invalid frame count and thus - * generate an error message and discard the frame. - */ - logerrorInt("Incomplete frame at end of stream in session %d - " - "ignoring extra data (a message may be lost).\n", - pTCPSessions[iTCPSess].sock); - /* 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"); - printchopped(pTCPSessions[iTCPSess].fromHost, pTCPSessions[iTCPSess].msg, - pTCPSessions[iTCPSess].iMsg, pTCPSessions[iTCPSess].sock, 1); - pTCPSessions[iTCPSess].bAtStrtOfFram = 1; - } -} - - -/* Closes a TCP session and marks its slot in the session - * table as unused. No attention is paid to the return code - * of close, so potential-double closes are not detected. - */ -void TCPSessClose(int iSess) -{ - if(iSess < 0 || iSess > iTCPSessMax) { - errno = 0; - logerror("internal error, trying to close an invalid TCP session!"); - return; - } - - close(pTCPSessions[iSess].sock); - pTCPSessions[iSess].sock = -1; - free(pTCPSessions[iSess].fromHost); - pTCPSessions[iSess].fromHost = NULL; /* not really needed, but... */ -} - - -/* 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 - * Changed this functions interface. We now return a status of - * what shall happen with the session. This is information for - * the caller. If 1 is returned, the session should remain open - * and additional data be accepted. If we return 0, the TCP - * session is to be closed by the caller. This functionality is - * needed in order to support framing errors, from which there - * is no recovery possible other than session termination and - * re-establishment. The need for this functionality thus is - * primarily rooted in support for -transport-tls I-D framing. - * rgerhards, 2006-12-07 - */ -int TCPSessDataRcvd(int iTCPSess, char *pData, int iLen) -{ - register int iMsg; - char *pMsg; - char *pEnd; - assert(pData != NULL); - assert(iLen > 0); - assert(iTCPSess >= 0); - assert(iTCPSess < iTCPSessMax); - assert(pTCPSessions[iTCPSess].sock != -1); - - /* We now copy the message to the session buffer. As - * it looks, we need to do this in any case because - * we might run into multiple messages inside a single - * buffer. Of course, we could think about optimizations, - * but as this code is to be replaced by liblogging, it - * probably doesn't make so much sense... - * rgerhards 2005-07-04 - * - * Algo: - * - copy message to buffer until the first LF is found - * - printline() the buffer - * - continue with copying - */ - iMsg = pTCPSessions[iTCPSess].iMsg; /* copy for speed */ - pMsg = pTCPSessions[iTCPSess].msg; /* just a shortcut */ - pEnd = pData + iLen; /* this is one off, which is intensional */ - - while(pData < pEnd) { - /* Check if we are at a new frame */ - if(pTCPSessions[iTCPSess].bAtStrtOfFram) { - /* we need to look at the message and detect - * the framing mode used - *//* - * Contrary to -transport-tls, we accept leading zeros in the message - * length. We do this in the spirit of "Be liberal in what you accept, - * and conservative in what you send". We expect that including leading - * zeros could be a common coding error. - * rgerhards, 2006-12-07 - * 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 - */ - if(isdigit((int) *pData)) { - int iCnt; /* the frame count specified */ - pTCPSessions[iTCPSess].eFraming = TCP_FRAMING_OCTET_COUNTING; - /* in this mode, we have OCTET-COUNT SP MSG - so we now need - * to extract the OCTET-COUNT and the SP and then extract - * the msg. - */ - iCnt = 0; - /* IETF20061218 int iNbrOctets = 0; / * number of octets already consumed */ - while(isdigit((int) *pData)) { - iCnt = iCnt * 10 + *pData - '0'; - /* IETF20061218 ++iNbrOctets; */ - ++pData; - } - dbgprintf("TCP Message with octet-counter, size %d.\n", iCnt); - if(*pData == ' ') { - ++pData; /* skip over SP */ - /* IETF20061218 ++iNbrOctets; */ - } else { - /* TODO: handle "invalid frame" case */ - logerrorInt("Framing Error in received TCP message: " - "delimiter is not SP but has ASCII value %d.\n", - *pData); - return(0); /* unconditional error exit */ - } - /* IETF20061218 pTCPSessions[iTCPSess].iOctetsRemain = iCnt - iNbrOctets; */ - pTCPSessions[iTCPSess].iOctetsRemain = iCnt; - if(pTCPSessions[iTCPSess].iOctetsRemain < 1) { - /* TODO: handle the case where the octet count is 0 or negative! */ - dbgprintf("Framing Error: invalid octet count\n"); - logerrorInt("Framing Error in received TCP message: " - "invalid octet count %d.\n", - pTCPSessions[iTCPSess].iOctetsRemain); - return(0); /* unconditional error exit */ - } - } else { - pTCPSessions[iTCPSess].eFraming = TCP_FRAMING_OCTET_STUFFING; - /* No need to do anything else here in this case */ - } - pTCPSessions[iTCPSess].bAtStrtOfFram = 0; /* done frame header */ - } - - /* now copy message until end of record */ - - if(iMsg >= MAXLINE) { - /* emergency, we now need to flush, no matter if - * we are at end of message or not... - */ - printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg, - pTCPSessions[iTCPSess].sock, 1); - iMsg = 0; - /* 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(*pData == '\n' && - pTCPSessions[iTCPSess].eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delemiter? */ - printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg, - pTCPSessions[iTCPSess].sock, 1); - iMsg = 0; - pTCPSessions[iTCPSess].bAtStrtOfFram = 1; - ++pData; - } else { - /* IMPORTANT: here we copy the actual frame content to the message! */ - *(pMsg + iMsg++) = *pData++; - } - - if(pTCPSessions[iTCPSess].eFraming == TCP_FRAMING_OCTET_COUNTING) { - /* do we need to find end-of-frame via octet counting? */ - pTCPSessions[iTCPSess].iOctetsRemain--; - if(pTCPSessions[iTCPSess].iOctetsRemain < 1) { - /* we have end of frame! */ - printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg, - pTCPSessions[iTCPSess].sock, 1); - iMsg = 0; - pTCPSessions[iTCPSess].bAtStrtOfFram = 1; - } - } - } - - pTCPSessions[iTCPSess].iMsg = iMsg; /* persist value */ - - return(1); /* successful return */ -} - - -#ifdef USE_GSSAPI -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) { - 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) { - display_status("acquiring credentials", maj_stat, min_stat); - return -1; - } - - gss_release_name(&min_stat, &server_name); - dbgprintf("GSS-API initialized\n"); - return 0; -} - - -int TCPSessGSSAccept(int fd) -{ - gss_buffer_desc send_tok, recv_tok; - gss_name_t client; - OM_uint32 maj_stat, min_stat, acc_sec_min_stat; - int iSess; - gss_ctx_id_t *context; - OM_uint32 *sess_flags; - int fdSess; - char allowedMethods; - - if ((iSess = TCPSessAccept(fd)) == -1) - return -1; - - allowedMethods = pTCPSessions[iSess].allowedMethods; - if (allowedMethods & ALLOWEDMETHOD_GSS) { - /* Buffer to store raw message in case that - * gss authentication fails halfway through. - */ - char buf[MAXLINE]; - int ret = 0; - - dbgprintf("GSS-API Trying to accept TCP session %d\n", iSess); - - fdSess = pTCPSessions[iSess].sock; - if (allowedMethods & ALLOWEDMETHOD_TCP) { - int len; - fd_set fds; - struct timeval tv; - - do { - FD_ZERO(&fds); - FD_SET(fdSess, &fds); - tv.tv_sec = 1; - tv.tv_usec = 0; - ret = select(fdSess + 1, &fds, NULL, NULL, &tv); - } while (ret < 0 && errno == EINTR); - if (ret < 0) { - logerrorInt("TCP session %d will be closed, error ignored\n", iSess); - TCPSessClose(iSess); - return -1; - } else if (ret == 0) { - dbgprintf("GSS-API Reverting to plain TCP\n"); - pTCPSessions[iSess].allowedMethods = ALLOWEDMETHOD_TCP; - return 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 - logerrorInt("TCP session %d will be closed, error ignored\n", iSess); - TCPSessClose(iSess); - return -1; - } - - if (ret < 4) { - dbgprintf("GSS-API Reverting to plain TCP\n"); - pTCPSessions[iSess].allowedMethods = ALLOWEDMETHOD_TCP; - return 0; - } else if (ret == 4) { - /* The client might has been interupted after sending - * the data length (4B), give him another chance. - */ - sleep(1); - 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 - logerrorInt("TCP session %d will be closed, error ignored\n", iSess); - TCPSessClose(iSess); - return -1; - } - } - - 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"); - pTCPSessions[iSess].allowedMethods = ALLOWEDMETHOD_TCP; - return 0; - } - } - - context = &pTCPSessions[iSess].gss_context; - *context = GSS_C_NO_CONTEXT; - sess_flags = &pTCPSessions[iSess].gss_flags; - do { - if (recv_token(fdSess, &recv_tok) <= 0) { - logerrorInt("TCP session %d will be closed, error ignored\n", iSess); - TCPSessClose(iSess); - return -1; - } - 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(TCPSessDataRcvd(iSess, buf, ret) == 0) { - logerrorInt("Tearing down TCP Session %d - see " - "previous messages for reason(s)\n", - iSess); - TCPSessClose(iSess); - return -1; - } - pTCPSessions[iSess].allowedMethods = ALLOWEDMETHOD_TCP; - return 0; - } - display_status("accepting context", maj_stat, - acc_sec_min_stat); - TCPSessClose(iSess); - return -1; - } - if (send_tok.length != 0) { - if (send_token(fdSess, &send_tok) < 0) { - gss_release_buffer(&min_stat, &send_tok); - logerrorInt("TCP session %d will be closed, error ignored\n", iSess); - if (*context != GSS_C_NO_CONTEXT) - gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); - TCPSessClose(iSess); - return -1; - } - 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) - display_status("displaying name", maj_stat, min_stat); - else - dbgprintf("GSS-API Accepted connection from: %s\n", recv_tok.value); - gss_release_name(&min_stat, &client); - gss_release_buffer(&min_stat, &recv_tok); - - dbgprintf("GSS-API Provided context flags:\n"); - display_ctx_flags(*sess_flags); - pTCPSessions[iSess].allowedMethods = ALLOWEDMETHOD_GSS; - } - - return 0; -} - - -int TCPSessGSSRecv(int iSess, void *buf, size_t buf_len) -{ - 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, len; - - fdSess = pTCPSessions[iSess].sock; - if ((state = recv_token(fdSess, &xmit_buf)) <= 0) - return state; - - context = &pTCPSessions[iSess].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) { - display_status("unsealing message", maj_stat, min_stat); - if (xmit_buf.value) { - free(xmit_buf.value); - xmit_buf.value = 0; - } - return (-1); - } - if (xmit_buf.value) { - free(xmit_buf.value); - xmit_buf.value = 0; - } - - len = msg_buf.length < buf_len ? msg_buf.length : buf_len; - memcpy(buf, msg_buf.value, len); - gss_release_buffer(&min_stat, &msg_buf); - - return len; -} - - -void TCPSessGSSClose(int iSess) { - OM_uint32 maj_stat, min_stat; - gss_ctx_id_t *context; - - if(iSess < 0 || iSess > iTCPSessMax) { - errno = 0; - logerror("internal error, trying to close an invalid TCP session!"); - return; - } - - context = &pTCPSessions[iSess].gss_context; - maj_stat = gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); - if (maj_stat != GSS_S_COMPLETE) - display_status("deleting context", maj_stat, min_stat); - *context = GSS_C_NO_CONTEXT; - pTCPSessions[iSess].gss_flags = 0; - pTCPSessions[iSess].allowedMethods = 0; - - TCPSessClose(iSess); -} - - -void TCPSessGSSDeinit(void) { - OM_uint32 maj_stat, min_stat; - - maj_stat = gss_release_cred(&min_stat, &gss_server_creds); - if (maj_stat != GSS_S_COMPLETE) - display_status("releasing credentials", maj_stat, min_stat); -} -#endif /* #ifdef USE_GSSAPI */ - - -#endif -/******************************************************************** - * ### END OF SYSLOG/TCP CODE ### - ********************************************************************/ - -/* ----------------------------------------------------------------- * - * CODE THAT SHALL GO INTO ITS OWN MODULE (SENDING) * - * ----------------------------------------------------------------- */ - -/* Initialize TCP sockets (for sender) - * This is done once per selector line, if not yet initialized. - */ -int TCPSendCreateSocket(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\n", - rs_strerror_r(errno, errStr, sizeof(errStr))); - } - - } - else { - return fd; - } - close(fd); - } - else { - char errStr[1024]; - dbgprintf("couldn't create send socket, reason %s\n", rs_strerror_r(errno, errStr, sizeof(errStr))); - } - r = r->ai_next; - } - - dbgprintf("no working socket could be obtained\n"); - - 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(TCPFRAMINGMODE rqdFraming, 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 : rqdFraming; - - /* 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: - return iRet; -} - - -/* 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. - */ -int TCPSend(void *pData, char *msg, size_t len, TCPFRAMINGMODE rqdFraming, - rsRetVal (*initFunc)(void*), - rsRetVal (*sendFunc)(void*, char*, size_t), - rsRetVal (*prepRetryFunc)(void*)) -{ - DEFiRet; - int bDone = 0; - int retry = 0; - int bMsgMustBeFreed = 0;/* must msg be freed at end of function? 0 - no, 1 - yes */ - - assert(pData != NULL); - assert(msg != NULL); - assert(len > 0); - - CHKiRet(TCPSendBldFrame(rqdFraming, &msg, &len, &bMsgMustBeFreed)); - - while(!bDone) { /* loop is broken when send succeeds or error occurs */ - CHKiRet(initFunc(pData)); - iRet = sendFunc(pData, msg, len); - - if(iRet == RS_RET_OK || retry > 0) { - /* we are done - either we succeeded or the retry failed */ - bDone = 1; - } else { /* OK, one retry */ - ++retry; - CHKiRet(prepRetryFunc(pData)); /* try to recover */ - } - } - -finalize_it: - if(bMsgMustBeFreed) - free(msg); - return iRet; -} - - -/* ----------------------------------------------------------------- * - * END OF CODE THAT SHALL GO INTO ITS OWN MODULE * - * ----------------------------------------------------------------- */ - +#include "srUtils.h" -/* - * vi:set ai: +/* vi:set ai: */ diff --git a/tcpsyslog.h b/tcpsyslog.h index de818df1..13c40a92 100644 --- a/tcpsyslog.h +++ b/tcpsyslog.h @@ -5,80 +5,33 @@ * * Copyright 2007 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 file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * 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 TCPSYSLOG_H_INCLUDED #define TCPSYSLOG_H_INCLUDED 1 -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) -#include <gssapi/gssapi.h> -#endif - -struct TCPSession { - int sock; - int iMsg; /* index of next char to store in msg */ - int bAtStrtOfFram; /* are we at the very beginning of a new frame? */ - int iOctetsRemain; /* Number of Octets remaining in message */ - TCPFRAMINGMODE eFraming; - char msg[MAXLINE+1]; - char *fromHost; -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) - OM_uint32 gss_flags; - gss_ctx_id_t gss_context; - char allowedMethods; -#endif -}; - -/* static data */ -extern int *sockTCPLstn; -extern char *TCPLstnPort; -extern int bEnableTCP; -extern struct TCPSession *pTCPSessions; -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) -extern char *gss_listen_service_name; - -#define ALLOWEDMETHOD_GSS 2 -#endif - -#define ALLOWEDMETHOD_TCP 1 +#include <netdb.h> -/* prototypes */ -void deinit_tcp_listener(void); -int *create_tcp_socket(void); -int TCPSessGetNxtSess(int iCurr); -int TCPSessAccept(int fd); -void TCPSessPrepareClose(int iTCPSess); -void TCPSessClose(int iSess); -int TCPSessDataRcvd(int iTCPSess, char *pData, int iLen); -void configureTCPListen(char *cOptarg); -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) -int TCPSessGSSInit(void); -int TCPSessGSSAccept(int fd); -int TCPSessGSSRecv(int fd, void *buf, size_t buf_len); -void TCPSessGSSClose(int sess); -void TCPSessGSSDeinit(void); -#endif +typedef enum _TCPFRAMINGMODE { + TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ + TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ + } TCPFRAMINGMODE; -/* TCP Send support (shall go into its own module later) */ -int TCPSendCreateSocket(struct addrinfo *addrDest); -int TCPSend(void *pData, char *msg, size_t len, TCPFRAMINGMODE rqdFraming, - rsRetVal (*initFunc)(void*), - rsRetVal (*sendFunc)(void*, char*, size_t), - rsRetVal (*prepRetryFunc)(void*)); #endif /* #ifndef TCPSYSLOG_H_INCLUDED */ /* * vi:set ai: @@ -1,14 +1,28 @@ /* This is the template processing code of rsyslog. * Please see syslogd.c for license information. - * This code is placed under the GPL. * begun 2004-11-17 rgerhards + * + * Copyright 2004, 2007 Rainer Gerhards and Adiscon + * + * 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 __FreeBSD__ -#define BSD -#endif - #include "rsyslog.h" #include <stdio.h> #include <stdlib.h> @@ -20,7 +34,15 @@ #include "template.h" #include "msg.h" #include "syslogd.h" +#include "obj.h" +#include "errmsg.h" + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(regexp) +static int bFirstRegexpErrmsg = 1; /**< did we already do a "can't load regexp" error message? */ static struct template *tplRoot = NULL; /* the root of the template list */ static struct template *tplLast = NULL; /* points to the last element of the template list */ static struct template *tplLastStatic = NULL; /* last static element of the template list */ @@ -50,7 +72,7 @@ rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz) { DEFiRet; struct templateEntry *pTpe; - rsCStrObj *pCStr; + cstr_t *pCStr; unsigned short bMustBeFreed; uchar *pVal; size_t iLenVal; @@ -64,10 +86,7 @@ rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz) * free the obtained value (if requested). We continue this * loop until we got hold of all values. */ - if((pCStr = rsCStrConstruct()) == NULL) { - dbgprintf("memory shortage, tplToString failed\n"); - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } + CHKiRet(rsCStrConstruct(&pCStr)); pTpe = pTpl->pEntryRoot; while(pTpe != NULL) { @@ -78,7 +97,7 @@ rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz) ) { dbgprintf("error %d during tplToString()\n", iRet); /* it does not make sense to continue now */ - rsCStrDestruct(pCStr); + rsCStrDestruct(&pCStr); FINALIZE; } } else if(pTpe->eEntryType == FIELD) { @@ -98,7 +117,7 @@ rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz) CHKiRet_Hdlr(rsCStrAppendStrWithLen(pCStr, (uchar*) pVal, iLenVal)) { dbgprintf("error %d during tplToString()\n", iRet); /* it does not make sense to continue now */ - rsCStrDestruct(pCStr); + rsCStrDestruct(&pCStr); if(bMustBeFreed) free(pVal); FINALIZE; @@ -118,7 +137,7 @@ rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz) finalize_it: *ppSz = (iRet == RS_RET_OK) ? pVal : NULL; - return iRet; + RETiRet; } /* Helper to doSQLEscape. This is called if doSQLEscape @@ -169,11 +188,13 @@ static void doSQLEmergencyEscape(register uchar *p, int escapeMode) * new parameter escapeMode is 0 - standard sql, 1 - "smart" engines * 2005-09-22 rgerhards */ -void doSQLEscape(uchar **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode) +rsRetVal +doSQLEscape(uchar **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode) { + DEFiRet; uchar *p; int iLen; - rsCStrObj *pStrB; + cstr_t *pStrB = NULL; uchar *pszGenerated; assert(pp != NULL); @@ -191,44 +212,25 @@ void doSQLEscape(uchar **pp, size_t *pLen, unsigned short *pbMustBeFreed, int es /* when we get out of the loop, we are either at the * string terminator or the first \'. */ if(*p == '\0') - return; /* nothing to do in this case! */ + FINALIZE; /* nothing to do in this case! */ p = *pp; iLen = *pLen; - if((pStrB = rsCStrConstruct()) == NULL) { - /* oops - no mem ... Do emergency... */ - doSQLEmergencyEscape(p, escapeMode); - return; - } + CHKiRet(rsCStrConstruct(&pStrB)); while(*p) { if(*p == '\'') { - if(rsCStrAppendChar(pStrB, (escapeMode == 0) ? '\'' : '\\') != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - rsCStrDestruct(pStrB); - return; - } + CHKiRet(rsCStrAppendChar(pStrB, (escapeMode == 0) ? '\'' : '\\')); iLen++; /* reflect the extra character */ } else if((escapeMode == 1) && (*p == '\\')) { - if(rsCStrAppendChar(pStrB, '\\') != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - rsCStrDestruct(pStrB); - return; - } + CHKiRet(rsCStrAppendChar(pStrB, '\\')); iLen++; /* reflect the extra character */ } - if(rsCStrAppendChar(pStrB, *p) != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - rsCStrDestruct(pStrB); - return; - } + CHKiRet(rsCStrAppendChar(pStrB, *p)); ++p; } - rsCStrFinish(pStrB); - if(rsCStrConvSzStrAndDestruct(pStrB, &pszGenerated, 0) != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - return; - } + CHKiRet(rsCStrFinish(pStrB)); + CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &pszGenerated, 0)); if(*pbMustBeFreed) free(*pp); /* discard previous value */ @@ -236,6 +238,15 @@ void doSQLEscape(uchar **pp, size_t *pLen, unsigned short *pbMustBeFreed, int es *pp = pszGenerated; *pLen = iLen; *pbMustBeFreed = 1; + +finalize_it: + if(iRet != RS_RET_OK) { + doSQLEmergencyEscape(*pp, escapeMode); + if(pStrB != NULL) + rsCStrDestruct(&pStrB); + } + + RETiRet; } @@ -299,7 +310,7 @@ struct template* tplConstruct(void) static int do_Constant(unsigned char **pp, struct template *pTpl) { register unsigned char *p; - rsCStrObj *pStrB; + cstr_t *pStrB; struct templateEntry *pTpe; int i; @@ -309,7 +320,7 @@ static int do_Constant(unsigned char **pp, struct template *pTpl) p = *pp; - if((pStrB = rsCStrConstruct()) == NULL) + if(rsCStrConstruct(&pStrB) != RS_RET_OK) return 1; rsCStrSetAllocIncrement(pStrB, 32); /* process the message and expand escapes @@ -366,7 +377,6 @@ static int do_Constant(unsigned char **pp, struct template *pTpl) if((pTpe = tpeConstruct(pTpl)) == NULL) { /* OK, we are out of luck. Let's invalidate the * entry and that's it. - * TODO: add panic message once we have a mechanism for this */ pTpe->eEntryType = UNDEFINED; return 1; @@ -434,6 +444,8 @@ static void doOptions(unsigned char **pp, struct templateEntry *pTpe) 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")) { @@ -462,14 +474,15 @@ static void doOptions(unsigned char **pp, struct templateEntry *pTpe) static int do_Parameter(unsigned char **pp, struct template *pTpl) { unsigned char *p; - rsCStrObj *pStrB; + cstr_t *pStrB; struct templateEntry *pTpe; int iNum; /* to compute numbers */ + rsRetVal iRetLocal; #ifdef FEATURE_REGEXP - /* APR: variables for regex */
- int longitud;
- unsigned char *regex_char;
+ /* APR: variables for regex */ + int longitud; + unsigned char *regex_char; unsigned char *regex_end; #endif @@ -479,7 +492,7 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) p = (unsigned char*) *pp; - if((pStrB = rsCStrConstruct()) == NULL) + if(rsCStrConstruct(&pStrB) != RS_RET_OK) return 1; if((pTpe = tpeConstruct(pTpl)) == NULL) { @@ -490,7 +503,8 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) pTpe->eEntryType = FIELD; while(*p && *p != '%' && *p != ':') { - rsCStrAppendChar(pStrB, *p++); + rsCStrAppendChar(pStrB, tolower(*p)); + ++p; /* do NOT do this in tolower()! */ } /* got the name*/ @@ -509,8 +523,7 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) if (*p != ':') { /* There is something more than an R , this is invalid ! */ /* Complain on extra characters */ - logerrorSz - ("error: invalid character in frompos after \"R\", property: '%%%s'", + errmsg.LogError(NO_ERRCODE, "error: invalid character in frompos after \"R\", property: '%%%s'", (char*) *pp); } else { pTpe->data.field.has_regex = 1; @@ -534,8 +547,7 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) pTpe->data.field.has_fields = 1; if(!isdigit((int)*p)) { /* complain and use default */ - logerrorSz - ("error: invalid character in frompos after \"F,\", property: '%%%s' - using 9 (HT) as field delimiter", + errmsg.LogError(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 { @@ -543,8 +555,7 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) while(isdigit((int)*p)) iNum = iNum * 10 + *p++ - '0'; if(iNum < 0 || iNum > 255) { - logerrorInt - ("error: non-USASCII delimiter character value in template - using 9 (HT) as substitute", iNum); + errmsg.LogError(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; @@ -554,8 +565,7 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) /* invalid character after F, so we need to reject * this. */ - logerrorSz - ("error: invalid character in frompos after \"F\", property: '%%%s'", + errmsg.LogError(NO_ERRCODE, "error: invalid character in frompos after \"F\", property: '%%%s'", (char*) *pp); } } else { @@ -596,9 +606,8 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) 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"); + if(regex_char == NULL) { + dbgprintf("Could not allocate memory for template parameter!\n"); pTpe->data.field.has_regex = 0; return 1; /* TODO: RGer: check if we can recover better... (probably not) */ @@ -612,8 +621,21 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) /* Now i compile the regex */ /* Remember that the re is an attribute of the Template entry */ - if(regcomp(&(pTpe->data.field.re), (char*) regex_char, 0) != 0) { - dbgprintf("error: can not compile regex: '%s'\n", regex_char); + if((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { +dbgprintf("compile data.field.re ptr: %p (pTpe %p)\n", (&(pTpe->data.field.re)), pTpe); + if(regexp.regcomp(&(pTpe->data.field.re), (char*) regex_char, 0) != 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(NO_ERRCODE, "regexp library could not be loaded (error %d), " + "regexp ignored", iRetLocal); + } pTpe->data.field.has_regex = 2; } @@ -651,7 +673,6 @@ static int do_Parameter(unsigned char **pp, struct template *pTpl) #endif /* #ifdef FEATURE_REGEXP */ } - /* TODO: add more sanity checks. For now, we do the bare minimum */ if((pTpe->data.field.has_fields == 0) && (pTpe->data.field.iToPos < pTpe->data.field.iFromPos)) { iNum = pTpe->data.field.iToPos; pTpe->data.field.iToPos = pTpe->data.field.iFromPos; @@ -822,6 +843,8 @@ void tplDeleteAll(void) { struct template *pTpl, *pTplDel; struct templateEntry *pTpe, *pTpeDel; + rsRetVal iRetLocal; + BEGINfunc pTpl = tplRoot; while(pTpl != NULL) { @@ -841,6 +864,12 @@ void tplDeleteAll(void) free(pTpeDel->data.constant.pConstant); break; case FIELD: + /* check if we have a regexp and, if so, delete it */ + if(pTpeDel->data.field.has_regex != 0) { + if((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { + regexp.regfree(&(pTpeDel->data.field.re)); + } + } /*dbgprintf("(FIELD), value: '%s'", pTpeDel->data.field.pPropRepl);*/ free(pTpeDel->data.field.pPropRepl); break; @@ -854,15 +883,20 @@ void tplDeleteAll(void) free(pTplDel->pszName); free(pTplDel); } + ENDfunc } + /* Destroy all templates obtained from conf file - * preserving hadcoded ones. This is called from init(). + * preserving hardcoded ones. This is called from init(). */ void tplDeleteNew(void) { struct template *pTpl, *pTplDel; struct templateEntry *pTpe, *pTpeDel; + rsRetVal iRetLocal; + + BEGINfunc if(tplRoot == NULL || tplLastStatic == NULL) return; @@ -887,6 +921,12 @@ void tplDeleteNew(void) free(pTpeDel->data.constant.pConstant); break; case FIELD: + /* check if we have a regexp and, if so, delete it */ + if(pTpeDel->data.field.has_regex != 0) { + if((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { + regexp.regfree(&(pTpeDel->data.field.re)); + } + } /*dbgprintf("(FIELD), value: '%s'", pTpeDel->data.field.pPropRepl);*/ free(pTpeDel->data.field.pPropRepl); break; @@ -900,6 +940,7 @@ void tplDeleteNew(void) free(pTplDel->pszName); free(pTplDel); } + ENDfunc } /* Store the pointer to the last hardcoded teplate */ @@ -974,6 +1015,15 @@ void tplPrintList(void) 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.bDropLastLF) { dbgprintf("[drop last LF in msg] "); } @@ -1000,6 +1050,19 @@ int tplGetEntryCount(struct template *pTpl) assert(pTpl != NULL); return(pTpl->tpenElements); } + +/* our init function. TODO: remove once converted to a class + */ +rsRetVal templateInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +finalize_it: + RETiRet; +} + /* * vi:set ai: */ @@ -1,20 +1,33 @@ /* This is the header for template processing code of rsyslog. * Please see syslogd.c for license information. - * This code is placed under the GPL. * begun 2004-11-17 rgerhards + * + * Copyright (C) 2004 by 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 TEMPLATE_H_INCLUDED #define TEMPLATE_H_INCLUDED 1 - +#include "regexp.h" #include "stringbuf.h" -#ifdef FEATURE_REGEXP -/* Include regular expressions */ -#include <regex.h> -#endif - struct template { struct template *pNext; char *pszName; @@ -67,11 +80,22 @@ struct templateEntry { 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; /* replace slashes, replace dots, empty 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(char* pName, unsigned char** pRestOfConfLine); struct template *tplFind(char *pName, int iLenName); @@ -86,9 +110,10 @@ void tplLastStaticInit(struct template *tpl); * rgerhards, 2007-08-06 */ rsRetVal tplToString(struct template *pTpl, msg_t *pMsg, uchar** ppSz); -void doSQLEscape(uchar **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode); +rsRetVal doSQLEscape(uchar **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode); + +rsRetVal templateInit(); #endif /* #ifndef TEMPLATE_H_INCLUDED */ -/* - * vi:set ai: +/* vim:set ai: */ diff --git a/threads.c b/threads.c new file mode 100644 index 00000000..03f19ff9 --- /dev/null +++ b/threads.c @@ -0,0 +1,220 @@ +/* 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 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 <string.h> +#include <signal.h> +#include <pthread.h> +#include <assert.h> + +#include "rsyslog.h" +#include "syslogd.h" +#include "linkedlist.h" +#include "threads.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); + + if((pThis = calloc(1, sizeof(thrdInfo_t))) == NULL) + return RS_RET_OUT_OF_MEMORY; + + /* OK, we got the element, now initialize members that should + * not be zero-filled. + */ + pThis->mutTermOK = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init (pThis->mutTermOK, NULL); + + *ppThis = pThis; + 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); + } + free(pThis->mutTermOK); + free(pThis); + + RETiRet; +} + + +/* terminate a thread gracefully. + */ +rsRetVal thrdTerminate(thrdInfo_t *pThis) +{ + DEFiRet; + assert(pThis != NULL); + + pthread_cancel(pThis->thrdID); + pthread_join(pThis->thrdID, NULL); /* wait for cancel to complete */ + pThis->bIsActive = 0; + + /* 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; + + assert(pThis != NULL); + assert(pThis->pUsrThrdMain != NULL); + + /* block all signalsi */ + sigset_t sigSet; + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &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 0x%lx returned with iRet %d, exiting now.\n", (unsigned long) pThis->thrdID, iRet); + 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 *)) +{ + DEFiRet; + thrdInfo_t *pThis; + int i; + + assert(thrdMain != NULL); + + CHKiRet(thrdConstruct(&pThis)); + pThis->bIsActive = 1; + pThis->pUsrThrdMain = thrdMain; + pThis->pAfterRun = afterRun; + i = pthread_create(&pThis->thrdID, NULL, 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; +} + + +/* thrdSleep() - a fairly portable way to put a thread to sleep. It + * will wake up when + * a) the wake-time is over + * b) the thread shall be terminated + * Returns RS_RET_OK if all went well, RS_RET_TERMINATE_NOW if the calling + * thread shall be terminated and any other state if an error happened. + * rgerhards, 2007-12-17 + */ +rsRetVal +thrdSleep(thrdInfo_t *pThis, int iSeconds, int iuSeconds) +{ + DEFiRet; + struct timeval tvSelectTimeout; + + assert(pThis != NULL); + tvSelectTimeout.tv_sec = iSeconds; + tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */ + select(1, NULL, NULL, NULL, &tvSelectTimeout); + if(pThis->bShallStop) + iRet = RS_RET_TERMINATE_NOW; + RETiRet; +} + + +/* + * vi:set ai: + */ diff --git a/threads.h b/threads.h new file mode 100644 index 00000000..aa6a5c28 --- /dev/null +++ b/threads.h @@ -0,0 +1,47 @@ +/* Definition of the threading support module. + * + * 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 THREADS_H_INCLUDED +#define THREADS_H_INCLUDED + + +/* the thread object */ +typedef struct thrdInfo { + pthread_mutex_t *mutTermOK; /* Is it ok to terminate that thread now? */ + 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; +} thrdInfo_t; + +/* 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 *)); +rsRetVal thrdSleep(thrdInfo_t *pThis, int iSeconds, int iuSeconds); + +/* macros (replace inline functions) */ + +#endif /* #ifndef THREADS_H_INCLUDED */ @@ -0,0 +1,414 @@ +/* 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 <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) + + +/* duplicates a var instance + * rgerhards, 2008-02-25 + */ +static rsRetVal +Duplicate(var_t *pThis, var_t **ppNew) +{ + DEFiRet; + var_t *pNew = NULL; + cstr_t *pstr; + + ISOBJ_TYPE_assert(pThis, var); + assert(ppNew != NULL); + + CHKiRet(varConstruct(&pNew)); + CHKiRet(varConstructFinalize(pNew)); + + /* we have the object, now copy value */ + pNew->varType = pThis->varType; + if(pThis->varType == VARTYPE_NUMBER) { + pNew->val.num = pThis->val.num; + } else if(pThis->varType == VARTYPE_STR) { + CHKiRet(rsCStrConstructFromCStr(&pstr, pThis->val.pStr)); + pNew->val.pStr = pstr; + } + + *ppNew = pNew; + +finalize_it: + if(iRet != RS_RET_OK && pNew != NULL) + varDestruct(&pNew); + + RETiRet; +} + + +/* free the current values (destructs objects if necessary) + */ +static rsRetVal +varUnsetValues(var_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + if(pThis->varType == VARTYPE_STR) + rsCStrDestruct(&pThis->val.pStr); + + pThis->varType = VARTYPE_NONE; + + RETiRet; +} + + +/* set a string value + * The caller hands over the string and must n longer use it after this method + * has been called. + */ +static rsRetVal +varSetString(var_t *pThis, cstr_t *pStr) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + + CHKiRet(varUnsetValues(pThis)); + pThis->varType = VARTYPE_STR; + pThis->val.pStr = pStr; + +finalize_it: + RETiRet; +} + + +/* set an int64 value */ +static rsRetVal +varSetNumber(var_t *pThis, number_t iVal) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + + CHKiRet(varUnsetValues(pThis)); + pThis->varType = VARTYPE_NUMBER; + pThis->val.num = iVal; + +finalize_it: + RETiRet; +} + + +/* Change the provided object to be of type number. + * rgerhards, 2008-02-22 + */ +rsRetVal +ConvToNumber(var_t *pThis) +{ + DEFiRet; + number_t n; + + if(pThis->varType == VARTYPE_NUMBER) { + FINALIZE; + } else if(pThis->varType == VARTYPE_STR) { + iRet = rsCStrConvertToNumber(pThis->val.pStr, &n); + if(iRet == RS_RET_NOT_A_NUMBER) { + n = 0; + iRet = RS_RET_OK; /* we accept this as part of the language definition */ + } else if (iRet != RS_RET_OK) { + FINALIZE; + } + + /* we need to destruct the string first, because string and number are + * inside a union and share the memory area! -- rgerhards, 2008-04-03 + */ + rsCStrDestruct(&pThis->val.pStr); + + pThis->val.num = n; + pThis->varType = VARTYPE_NUMBER; + } + +finalize_it: + RETiRet; +} + + +/* convert the provided var to type string. This is always possible + * (except, of course, for things like out of memory...) + * TODO: finish implementation!!!!!!!!! + * rgerhards, 2008-02-24 + */ +rsRetVal +ConvToString(var_t *pThis) +{ + DEFiRet; + uchar szNumBuf[64]; + + if(pThis->varType == VARTYPE_STR) { + FINALIZE; + } else if(pThis->varType == VARTYPE_NUMBER) { + CHKiRet(srUtilItoA((char*)szNumBuf, sizeof(szNumBuf)/sizeof(uchar), pThis->val.num)); + CHKiRet(rsCStrConstructFromszStr(&pThis->val.pStr, szNumBuf)); + pThis->varType = VARTYPE_STR; + } + +finalize_it: + RETiRet; +} + + +/* convert (if necessary) the value to a boolean. In essence, this means the + * value must be a number, but in case of a string special logic is used as + * some string-values may represent a boolean (e.g. "true"). + * rgerhards, 2008-02-25 + */ +rsRetVal +ConvToBool(var_t *pThis) +{ + DEFiRet; + number_t n; + + if(pThis->varType == VARTYPE_NUMBER) { + FINALIZE; + } else if(pThis->varType == VARTYPE_STR) { + iRet = rsCStrConvertToBool(pThis->val.pStr, &n); + if(iRet == RS_RET_NOT_A_NUMBER) { + n = 0; + iRet = RS_RET_OK; /* we accept this as part of the language definition */ + } else if (iRet != RS_RET_OK) { + FINALIZE; + } + + /* we need to destruct the string first, because string and number are + * inside a union and share the memory area! -- rgerhards, 2008-04-03 + */ + rsCStrDestruct(&pThis->val.pStr); + pThis->val.num = n; + pThis->varType = VARTYPE_NUMBER; + } + +finalize_it: + RETiRet; +} + + +/* This function is used to prepare two var_t objects for a common operation, + * e.g before they are added, compared. The function looks at + * the data types of both operands and finds the best data type suitable for + * the operation (in respect to current types). Then, it converts those + * operands that need conversion. Please note that the passed-in var objects + * *are* modified and returned as new type. So do call this function only if + * you actually need the conversion. + * + * This is how the common data type is selected. Note that op1 and op2 are + * just the two operands, their order is irrelevant (this would just take up + * more table space - so string/number is the same thing as number/string). + * + * Common Types: + * op1 op2 operation data type + * string string string + * string number number if op1 can be converted to number, string else + * date string date if op1 can be converted to date, string else + * number number number + * date number string (maybe we can do better?) + * date date date + * none n/a error + * + * If a boolean value is required, we need to have a number inside the + * operand. If it is not, conversion rules to number apply. Once we + * have a number, things get easy: 0 is false, anything else is true. + * Please note that due to this conversion rules, "0" becomes false + * while "-4712" becomes true. Using a date as boolen is not a good + * idea. Depending on the ultimate conversion rules, it may always + * become true or false. As such, using dates as booleans is + * prohibited and the result defined to be undefined. + * + * rgerhards, 2008-02-22 + */ +static rsRetVal +ConvForOperation(var_t *pThis, var_t *pOther) +{ + DEFiRet; + + if(pThis->varType == VARTYPE_NONE || pOther->varType == VARTYPE_NONE) + ABORT_FINALIZE(RS_RET_INVALID_VAR); + + switch(pThis->varType) { + case VARTYPE_NONE: + ABORT_FINALIZE(RS_RET_INVALID_VAR); + break; + case VARTYPE_STR: + switch(pOther->varType) { + case VARTYPE_NONE: + ABORT_FINALIZE(RS_RET_INVALID_VAR); + break; + case VARTYPE_STR: + FINALIZE; /* two strings, we are all set */ + break; + case VARTYPE_NUMBER: + /* check if we can convert pThis to a number, if so use number format. */ + iRet = ConvToNumber(pThis); + if(iRet != RS_RET_NOT_A_NUMBER) { + CHKiRet(ConvToString(pOther)); + } else { + FINALIZE; /* OK or error */ + } + break; + case VARTYPE_SYSLOGTIME: + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + } + break; + case VARTYPE_NUMBER: + switch(pOther->varType) { + case VARTYPE_NONE: + ABORT_FINALIZE(RS_RET_INVALID_VAR); + break; + case VARTYPE_STR: + iRet = ConvToNumber(pOther); + if(iRet != RS_RET_NOT_A_NUMBER) { + CHKiRet(ConvToString(pThis)); + } else { + FINALIZE; /* OK or error */ + } + break; + case VARTYPE_NUMBER: + FINALIZE; /* two numbers, so we are all set */ + break; + case VARTYPE_SYSLOGTIME: + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + } + break; + case VARTYPE_SYSLOGTIME: + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + } + +finalize_it: + RETiRet; +} + + +/* 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; + pIf->SetNumber = varSetNumber; + pIf->SetString = varSetString; + pIf->ConvForOperation = ConvForOperation; + pIf->ConvToNumber = ConvToNumber; + pIf->ConvToBool = ConvToBool; + pIf->ConvToString = ConvToString; + pIf->Duplicate = Duplicate; +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: + */ @@ -0,0 +1,70 @@ +/* The var object. + * + * 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_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; + 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); + rsRetVal (*SetNumber)(var_t *pThis, number_t iVal); + rsRetVal (*SetString)(var_t *pThis, cstr_t *pCStr); + rsRetVal (*ConvForOperation)(var_t *pThis, var_t *pOther); + rsRetVal (*ConvToNumber)(var_t *pThis); + rsRetVal (*ConvToBool)(var_t *pThis); + rsRetVal (*ConvToString)(var_t *pThis); + rsRetVal (*Duplicate)(var_t *pThis, var_t **ppNew); +ENDinterface(var) +#define varCURR_IF_VERSION 1 /* increment whenever you change the interface above! */ + + +/* prototypes */ +PROTOTYPEObj(var); + +#endif /* #ifndef INCLUDED_VAR_H */ @@ -0,0 +1,528 @@ +/* vm.c - the arithmetic stack of a virtual machine. + * + * Module begun 2008-02-22 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 <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vm.h" +#include "sysvar.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmstk) +DEFobjCurrIf(var) +DEFobjCurrIf(sysvar) + + +/* ------------------------------ instruction set implementation ------------------------------ * + * The following functions implement the VM's instruction set. + */ +#define BEGINop(instruction) \ + static rsRetVal op##instruction(vm_t *pThis, __attribute__((unused)) vmop_t *pOp) \ + { \ + DEFiRet; + +#define CODESTARTop(instruction) \ + ISOBJ_TYPE_assert(pThis, vm); + +#define PUSHRESULTop(operand, res) \ + /* we have a result, so let's push it */ \ + var.SetNumber(operand, res); \ + vmstk.Push(pThis->pStk, operand); /* result */ + +#define ENDop(instruction) \ + RETiRet; \ + } + +/* code generator for boolean operations */ +#define BOOLOP(name, OPERATION) \ +BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ + var_t *operand1; \ + var_t *operand2; \ +CODESTARTop(name) \ + vmstk.PopBool(pThis->pStk, &operand1); \ + vmstk.PopBool(pThis->pStk, &operand2); \ + if(operand1->val.num OPERATION operand2->val.num) { \ + CHKiRet(var.SetNumber(operand1, 1)); \ + } else { \ + CHKiRet(var.SetNumber(operand1, 0)); \ + } \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +finalize_it: \ +ENDop(name) +BOOLOP(OR, ||) +BOOLOP(AND, &&) +#undef BOOLOP + + +/* code generator for numerical operations */ +#define NUMOP(name, OPERATION) \ +BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ + var_t *operand1; \ + var_t *operand2; \ +CODESTARTop(name) \ + vmstk.PopNumber(pThis->pStk, &operand1); \ + vmstk.PopNumber(pThis->pStk, &operand2); \ + operand1->val.num = operand1->val.num OPERATION operand2->val.num; \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +ENDop(name) +NUMOP(PLUS, +) +NUMOP(MINUS, -) +NUMOP(TIMES, *) +NUMOP(DIV, /) +NUMOP(MOD, %) +#undef BOOLOP + + +/* code generator for compare operations */ +#define BEGINCMPOP(name) \ +BEGINop(name) \ + var_t *operand1; \ + var_t *operand2; \ + number_t bRes; \ +CODESTARTop(name) \ + CHKiRet(vmstk.Pop2CommOp(pThis->pStk, &operand1, &operand2)); \ + /* data types are equal (so we look only at operand1), but we must \ + * check which type we have to deal with... \ + */ \ + switch(operand1->varType) { +#define ENDCMPOP(name) \ + default: \ + bRes = 0; /* we do not abort just so that we have a value. TODO: reconsider */ \ + break; \ + } \ + \ + /* we have a result, so let's push it */ \ + var.SetNumber(operand1, bRes); \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +finalize_it: \ +ENDop(name) + +BEGINCMPOP(CMP_EQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num == operand2->val.num; + break; + case VARTYPE_STR: + bRes = !rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); + break; +ENDCMPOP(CMP_EQ) + +BEGINCMPOP(CMP_NEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num != operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); + break; +ENDCMPOP(CMP_EQ) + +BEGINCMPOP(CMP_LT) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num < operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) < 0; + break; +ENDCMPOP(CMP_LT) + +BEGINCMPOP(CMP_GT) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num > operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) > 0; + break; +ENDCMPOP(CMP_GT) + +BEGINCMPOP(CMP_LTEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num <= operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) <= 0; + break; +ENDCMPOP(CMP_LTEQ) + +BEGINCMPOP(CMP_GTEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num >= operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) >= 0; + break; +ENDCMPOP(CMP_GTEQ) + +#undef BEGINCMPOP +#undef ENDCMPOP +/* end regular compare operations */ + +/* comare operations that work on strings, only */ +BEGINop(CMP_CONTAINS) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_CONTAINS) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_CONTAINS) + + +BEGINop(CMP_CONTAINSI) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_CONTAINSI) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); +var.DebugPrint(operand1); \ +var.DebugPrint(operand2); \ + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrCaseInsensitiveLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_CONTAINSI) + + +BEGINop(CMP_STARTSWITH) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_STARTSWITH) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), + rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_STARTSWITH) + + +BEGINop(CMP_STARTSWITHI) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_STARTSWITHI) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrCaseInsensitveStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), + rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; + + /* we have a result, so let's push it */ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_STARTSWITHI) + +/* end comare operations that work on strings, only */ + +BEGINop(STRADD) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; +CODESTARTop(STRADD) + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + + CHKiRet(rsCStrAppendCStr(operand1->val.pStr, operand2->val.pStr)); + + /* we have a result, so let's push it */ + vmstk.Push(pThis->pStk, operand1); + var.Destruct(&operand2); /* no longer needed */ +finalize_it: +ENDop(STRADD) + +BEGINop(NOT) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand; +CODESTARTop(NOT) + vmstk.PopBool(pThis->pStk, &operand); + PUSHRESULTop(operand, !operand->val.num); +ENDop(NOT) + +BEGINop(UNARY_MINUS) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand; +CODESTARTop(UNARY_MINUS) + vmstk.PopNumber(pThis->pStk, &operand); + PUSHRESULTop(operand, -operand->val.num); +ENDop(UNARY_MINUS) + + +BEGINop(PUSHCONSTANT) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVarDup; /* we need to duplicate the var, as we need to hand it over */ +CODESTARTop(PUSHCONSTANT) + CHKiRet(var.Duplicate(pOp->operand.pVar, &pVarDup)); + vmstk.Push(pThis->pStk, pVarDup); +finalize_it: +ENDop(PUSHCONSTANT) + + +BEGINop(PUSHMSGVAR) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVal; /* the value to push */ + cstr_t *pstrVal; +CODESTARTop(PUSHMSGVAR) + if(pThis->pMsg == NULL) { + /* TODO: flag an error message! As a work-around, we permit + * execution to continue here with an empty string + */ + /* TODO: create a method in var to create a string var? */ + CHKiRet(var.Construct(&pVal)); + CHKiRet(var.ConstructFinalize(pVal)); + CHKiRet(rsCStrConstructFromszStr(&pstrVal, (uchar*)"")); + CHKiRet(var.SetString(pVal, pstrVal)); + } else { + /* we have a message, so pull value from there */ + CHKiRet(msgGetMsgVar(pThis->pMsg, pOp->operand.pVar->val.pStr, &pVal)); + } + + /* if we reach this point, we have a valid pVal and can push it */ + vmstk.Push(pThis->pStk, pVal); +finalize_it: +ENDop(PUSHMSGVAR) + + +BEGINop(PUSHSYSVAR) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVal; /* the value to push */ +CODESTARTop(PUSHSYSVAR) + CHKiRet(sysvar.GetVar(pOp->operand.pVar->val.pStr, &pVal)); + vmstk.Push(pThis->pStk, pVal); +finalize_it: +ENDop(PUSHSYSVAR) + + +/* ------------------------------ end instruction set implementation ------------------------------ */ + + +/* Standard-Constructor + */ +BEGINobjConstruct(vm) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vm) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmConstructFinalize(vm_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vm); + + CHKiRet(vmstk.Construct(&pThis->pStk)); + CHKiRet(vmstk.ConstructFinalize(pThis->pStk)); + +finalize_it: + RETiRet; +} + + +/* destructor for the vm object */ +BEGINobjDestruct(vm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vm) + if(pThis->pStk != NULL) + vmstk.Destruct(&pThis->pStk); + if(pThis->pMsg != NULL) + msgDestruct(&pThis->pMsg); +ENDobjDestruct(vm) + + +/* debugprint for the vm object */ +BEGINobjDebugPrint(vm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(vm) + dbgoprint((obj_t*) pThis, "rsyslog virtual machine, currently no state info available\n"); +ENDobjDebugPrint(vm) + + +/* execute a program + */ +static rsRetVal +execProg(vm_t *pThis, vmprg_t *pProg) +{ + DEFiRet; + vmop_t *pCurrOp; /* virtual instruction pointer */ + + ISOBJ_TYPE_assert(pThis, vm); + ISOBJ_TYPE_assert(pProg, vmprg); + +#define doOP(OP) case opcode_##OP: CHKiRet(op##OP(pThis, pCurrOp)); break + pCurrOp = pProg->vmopRoot; /* TODO: do this via a method! */ + while(pCurrOp != NULL && pCurrOp->opcode != opcode_END_PROG) { + switch(pCurrOp->opcode) { + doOP(OR); + doOP(AND); + doOP(CMP_EQ); + doOP(CMP_NEQ); + doOP(CMP_LT); + doOP(CMP_GT); + doOP(CMP_LTEQ); + doOP(CMP_GTEQ); + doOP(CMP_CONTAINS); + doOP(CMP_CONTAINSI); + doOP(CMP_STARTSWITH); + doOP(CMP_STARTSWITHI); + doOP(NOT); + doOP(PUSHCONSTANT); + doOP(PUSHMSGVAR); + doOP(PUSHSYSVAR); + doOP(STRADD); + doOP(PLUS); + doOP(MINUS); + doOP(TIMES); + doOP(DIV); + doOP(MOD); + doOP(UNARY_MINUS); + default: + ABORT_FINALIZE(RS_RET_INVALID_VMOP); + dbgoprint((obj_t*) pThis, "invalid instruction %d in vmprg\n", pCurrOp->opcode); + break; + } + /* so far, we have plain sequential execution, so on to next... */ + pCurrOp = pCurrOp->pNext; + } +#undef doOP + + /* if we reach this point, our program has intintionally terminated + * (no error state). + */ + +finalize_it: + RETiRet; +} + + +/* Set the current message object for the VM. It *is* valid to set a + * NULL message object, what simply means there is none. Message + * objects are properly reference counted. + */ +static rsRetVal +SetMsg(vm_t *pThis, msg_t *pMsg) +{ + DEFiRet; + if(pThis->pMsg != NULL) { + msgDestruct(&pThis->pMsg); + } + + if(pMsg != NULL) { + pThis->pMsg = MsgAddRef(pMsg); + } + + RETiRet; +} + + +/* Pop a var from the stack and return it to caller. The variable type is not + * changed, it is taken from the stack as is. This functionality is + * partly needed. We may (or may not ;)) be able to remove it once we have + * full RainerScript support. -- rgerhards, 2008-02-25 + */ +static rsRetVal +PopVarFromStack(vm_t *pThis, var_t **ppVar) +{ + DEFiRet; + CHKiRet(vmstk.Pop(pThis->pStk, ppVar)); +finalize_it: + RETiRet; +} + + +/* Pop a boolean from the stack and return it to caller. This functionality is + * partly needed. We may (or may not ;)) be able to remove it once we have + * full RainerScript support. -- rgerhards, 2008-02-25 + */ +static rsRetVal +PopBoolFromStack(vm_t *pThis, var_t **ppVar) +{ + DEFiRet; + CHKiRet(vmstk.PopBool(pThis->pStk, ppVar)); +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vm) +CODESTARTobjQueryInterface(vm) + if(pIf->ifVersion != vmCURR_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 = vmConstruct; + pIf->ConstructFinalize = vmConstructFinalize; + pIf->Destruct = vmDestruct; + pIf->DebugPrint = vmDebugPrint; + pIf->ExecProg = execProg; + pIf->PopBoolFromStack = PopBoolFromStack; + pIf->PopVarFromStack = PopVarFromStack; + pIf->SetMsg = SetMsg; +finalize_it: +ENDobjQueryInterface(vm) + + +/* Initialize the vm class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vm, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmstk, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(sysvar, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmConstructFinalize); +ENDObjClassInit(vm) + +/* vi:set ai: + */ @@ -0,0 +1,65 @@ +/* The vm object. + * + * This implements the rsyslog virtual machine. The initial implementation is + * done to support complex user-defined expressions, but it may evolve into a + * much more useful thing over time. + * + * The virtual machine uses rsyslog variables as its memory storage system. + * All computation is done on a stack (vmstk). The vm supports a given + * instruction set and executes programs of type vmprg, which consist of + * single operations defined in vmop (which hold the instruction and the + * data). + * + * 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_VM_H +#define INCLUDED_VM_H + +#include "msg.h" +#include "vmstk.h" +#include "vmprg.h" + +/* the vm object */ +typedef struct vm_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + vmstk_t *pStk; /* The stack */ + msg_t *pMsg; /* the current message (or NULL, if we have none) */ +} vm_t; + + +/* interfaces */ +BEGINinterface(vm) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vm); + rsRetVal (*Construct)(vm_t **ppThis); + rsRetVal (*ConstructFinalize)(vm_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vm_t **ppThis); + rsRetVal (*ExecProg)(vm_t *pThis, vmprg_t *pProg); + rsRetVal (*PopBoolFromStack)(vm_t *pThis, var_t **ppVar); /* there are a few cases where we need this... */ + rsRetVal (*PopVarFromStack)(vm_t *pThis, var_t **ppVar); /* there are a few cases where we need this... */ + rsRetVal (*SetMsg)(vm_t *pThis, msg_t *pMsg); /* there are a few cases where we need this... */ +ENDinterface(vm) +#define vmCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(vm); + +#endif /* #ifndef INCLUDED_VM_H */ @@ -0,0 +1,235 @@ +/* vmop.c - abstracts an operation (instructed) supported by the + * rsyslog virtual machine + * + * Module begun 2008-02-20 by Rainer Gerhards + * + * 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 <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vmop.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) + + +/* forward definitions */ +static rsRetVal vmopOpcode2Str(vmop_t *pThis, uchar **ppName); + +/* Standard-Constructor + */ +BEGINobjConstruct(vmop) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vmop) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal vmopConstructFinalize(vmop_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + RETiRet; +} + + +/* destructor for the vmop object */ +BEGINobjDestruct(vmop) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vmop) + if( pThis->opcode == opcode_PUSHSYSVAR + || pThis->opcode == opcode_PUSHMSGVAR + || pThis->opcode == opcode_PUSHCONSTANT) { + if(pThis->operand.pVar != NULL) + var.Destruct(&pThis->operand.pVar); + } +ENDobjDestruct(vmop) + + +/* DebugPrint support for the vmop object */ +BEGINobjDebugPrint(vmop) /* be sure to specify the object type also in END and CODESTART macros! */ + uchar *pOpcodeName; +CODESTARTobjDebugPrint(vmop) + vmopOpcode2Str(pThis, &pOpcodeName); + dbgoprint((obj_t*) pThis, "opcode: %d\t(%s), next %p, var in next line\n", (int) pThis->opcode, pOpcodeName, + pThis->pNext); + if(pThis->operand.pVar != NULL) + var.DebugPrint(pThis->operand.pVar); +ENDobjDebugPrint(vmop) + + +/* set operand (variant case) + * rgerhards, 2008-02-20 + */ +static rsRetVal +vmopSetVar(vmop_t *pThis, var_t *pVar) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + ISOBJ_TYPE_assert(pVar, var); + pThis->operand.pVar = pVar; + RETiRet; +} + + +/* set operation + * rgerhards, 2008-02-20 + */ +static rsRetVal +vmopSetOpcode(vmop_t *pThis, opcode_t opcode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + pThis->opcode = opcode; + RETiRet; +} + + +/* a way to turn an opcode into a readable string + */ +static rsRetVal +vmopOpcode2Str(vmop_t *pThis, uchar **ppName) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + + switch(pThis->opcode) { + case opcode_OR: + *ppName = (uchar*) "or"; + break; + case opcode_AND: + *ppName = (uchar*) "and"; + break; + case opcode_PLUS: + *ppName = (uchar*) "+"; + break; + case opcode_MINUS: + *ppName = (uchar*) "-"; + break; + case opcode_TIMES: + *ppName = (uchar*) "*"; + break; + case opcode_DIV: + *ppName = (uchar*) "/"; + break; + case opcode_MOD: + *ppName = (uchar*) "%"; + break; + case opcode_NOT: + *ppName = (uchar*) "not"; + break; + case opcode_CMP_EQ: + *ppName = (uchar*) "=="; + break; + case opcode_CMP_NEQ: + *ppName = (uchar*) "!="; + break; + case opcode_CMP_LT: + *ppName = (uchar*) "<"; + break; + case opcode_CMP_GT: + *ppName = (uchar*) ">"; + break; + case opcode_CMP_LTEQ: + *ppName = (uchar*) "<="; + break; + case opcode_CMP_CONTAINS: + *ppName = (uchar*) "contains"; + break; + case opcode_CMP_STARTSWITH: + *ppName = (uchar*) "startswith"; + break; + case opcode_CMP_GTEQ: + *ppName = (uchar*) ">="; + break; + case opcode_PUSHSYSVAR: + *ppName = (uchar*) "PUSHSYSVAR"; + break; + case opcode_PUSHMSGVAR: + *ppName = (uchar*) "PUSHMSGVAR"; + break; + case opcode_PUSHCONSTANT: + *ppName = (uchar*) "PUSHCONSTANT"; + break; + case opcode_POP: + *ppName = (uchar*) "POP"; + break; + case opcode_UNARY_MINUS: + *ppName = (uchar*) "UNARY_MINUS"; + break; + case opcode_STRADD: + *ppName = (uchar*) "STRADD"; + break; + default: + *ppName = (uchar*) "INVALID opcode"; + break; + } + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vmop) +CODESTARTobjQueryInterface(vmop) + if(pIf->ifVersion != vmopCURR_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). + */ + //xxxpIf->oID = OBJvmop; + + pIf->Construct = vmopConstruct; + pIf->ConstructFinalize = vmopConstructFinalize; + pIf->Destruct = vmopDestruct; + pIf->DebugPrint = vmopDebugPrint; + pIf->SetOpcode = vmopSetOpcode; + pIf->SetVar = vmopSetVar; + pIf->Opcode2Str = vmopOpcode2Str; +finalize_it: +ENDobjQueryInterface(vmop) + + +/* Initialize the vmop class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vmop, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmopDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmopConstructFinalize); +ENDObjClassInit(vmop) + +/* vi:set ai: + */ @@ -0,0 +1,92 @@ +/* The vmop object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * 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_VMOP_H +#define INCLUDED_VMOP_H + +#include "ctok_token.h" + +/* machine instructions types */ +typedef enum { /* do NOT start at 0 to detect uninitialized types after calloc() */ + opcode_INVALID = 0, + /* for simplicity of debugging and reading dumps, we use the same IDs + * that the tokenizer uses where this applicable. + */ + opcode_OR = ctok_OR, + opcode_AND = ctok_AND, + opcode_STRADD= ctok_STRADD, + opcode_PLUS = ctok_PLUS, + opcode_MINUS = ctok_MINUS, + opcode_TIMES = ctok_TIMES, /* "*" */ + opcode_DIV = ctok_DIV, + opcode_MOD = ctok_MOD, + opcode_NOT = ctok_NOT, + opcode_CMP_EQ = ctok_CMP_EQ, /* all compare operations must be in a row */ + opcode_CMP_NEQ = ctok_CMP_NEQ, + opcode_CMP_LT = ctok_CMP_LT, + opcode_CMP_GT = ctok_CMP_GT, + opcode_CMP_LTEQ = ctok_CMP_LTEQ, + opcode_CMP_CONTAINS = ctok_CMP_CONTAINS, + opcode_CMP_STARTSWITH = ctok_CMP_STARTSWITH, + opcode_CMP_CONTAINSI = ctok_CMP_CONTAINSI, + opcode_CMP_STARTSWITHI = ctok_CMP_STARTSWITHI, + opcode_CMP_GTEQ = ctok_CMP_GTEQ, /* end compare operations */ + /* here we start our own codes */ + opcode_POP = 1000, /* requires var operand to receive result */ + opcode_PUSHSYSVAR = 1001, /* requires var operand */ + opcode_PUSHMSGVAR = 1002, /* requires var operand */ + opcode_PUSHCONSTANT = 1003, /* requires var operand */ + opcode_UNARY_MINUS = 1010, + opcode_END_PROG = 1011 +} opcode_t; + + +/* the vmop object */ +typedef struct vmop_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + opcode_t opcode; + union { + var_t *pVar; + /* TODO: add function pointer */ + } operand; + struct vmop_s *pNext; /* next operation or NULL, if end of program (logically this belongs to vmprg) */ +} vmop_t; + + +/* interfaces */ +BEGINinterface(vmop) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vmop); + rsRetVal (*Construct)(vmop_t **ppThis); + rsRetVal (*ConstructFinalize)(vmop_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vmop_t **ppThis); + rsRetVal (*SetOpcode)(vmop_t *pThis, opcode_t opcode); + rsRetVal (*SetVar)(vmop_t *pThis, var_t *pVar); + rsRetVal (*Opcode2Str)(vmop_t *pThis, uchar **ppName); +ENDinterface(vmop) +#define vmopCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* the remaining prototypes */ +PROTOTYPEObj(vmop); + +#endif /* #ifndef INCLUDED_VMOP_H */ diff --git a/vmprg.c b/vmprg.c new file mode 100644 index 00000000..a2b744d7 --- /dev/null +++ b/vmprg.c @@ -0,0 +1,175 @@ +/* vmprg.c - abstracts a program (bytecode) for the rsyslog virtual machine + * + * Module begun 2008-02-20 by Rainer Gerhards + * + * 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 <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vmprg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmop) + + +/* Standard-Constructor + */ +BEGINobjConstruct(vmprg) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vmprg) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmprgConstructFinalize(vmprg_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmprg); + RETiRet; +} + + +/* destructor for the vmprg object */ +BEGINobjDestruct(vmprg) /* be sure to specify the object type also in END and CODESTART macros! */ + vmop_t *pOp; + vmop_t *pTmp; +CODESTARTobjDestruct(vmprg) + /* we need to destruct the program elements! */ + for(pOp = pThis->vmopRoot ; pOp != NULL ; ) { + pTmp = pOp; + pOp = pOp->pNext; + vmop.Destruct(&pTmp); + } +ENDobjDestruct(vmprg) + + +/* destructor for the vmop object */ +BEGINobjDebugPrint(vmprg) /* be sure to specify the object type also in END and CODESTART macros! */ + vmop_t *pOp; +CODESTARTobjDebugPrint(vmprg) + dbgoprint((obj_t*) pThis, "program contents:\n"); + for(pOp = pThis->vmopRoot ; pOp != NULL ; pOp = pOp->pNext) { + vmop.DebugPrint(pOp); + } +ENDobjDebugPrint(vmprg) + + +/* add an operation (instruction) to the end of the current program. This + * function is expected to be called while creating the program, but never + * again after this is done and it is being executed. Results are undefined if + * it is called after execution. + */ +static rsRetVal +vmprgAddOperation(vmprg_t *pThis, vmop_t *pOp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, vmprg); + ISOBJ_TYPE_assert(pOp, vmop); + + if(pThis->vmopRoot == NULL) { + pThis->vmopRoot = pOp; + } else { + pThis->vmopLast->pNext = pOp; + } + pThis->vmopLast = pOp; + + RETiRet; +} + + +/* this is a shortcut for high-level callers. It creates a new vmop, sets its + * parameters and adds it to the program - all in one big step. If there is no + * var associated with this operation, the caller can simply supply NULL as + * pVar. + */ +static rsRetVal +vmprgAddVarOperation(vmprg_t *pThis, opcode_t opcode, var_t *pVar) +{ + DEFiRet; + vmop_t *pOp; + + ISOBJ_TYPE_assert(pThis, vmprg); + + /* construct and fill vmop */ + CHKiRet(vmop.Construct(&pOp)); + CHKiRet(vmop.ConstructFinalize(pOp)); + CHKiRet(vmop.ConstructFinalize(pOp)); + CHKiRet(vmop.SetOpcode(pOp, opcode)); + if(pVar != NULL) + CHKiRet(vmop.SetVar(pOp, pVar)); + + /* and add it to the program */ + CHKiRet(vmprgAddOperation(pThis, pOp)); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vmprg) +CODESTARTobjQueryInterface(vmprg) + if(pIf->ifVersion != vmprgCURR_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). + */ + //xxxpIf->oID = OBJvmprg; + + pIf->Construct = vmprgConstruct; + pIf->ConstructFinalize = vmprgConstructFinalize; + pIf->Destruct = vmprgDestruct; + pIf->DebugPrint = vmprgDebugPrint; + pIf->AddOperation = vmprgAddOperation; + pIf->AddVarOperation = vmprgAddVarOperation; +finalize_it: +ENDobjQueryInterface(vmprg) + + +/* Initialize the vmprg class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vmprg, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmop, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmprgDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmprgConstructFinalize); +ENDObjClassInit(vmprg) + +/* vi:set ai: + */ diff --git a/vmprg.h b/vmprg.h new file mode 100644 index 00000000..db1f62f0 --- /dev/null +++ b/vmprg.h @@ -0,0 +1,66 @@ +/* The vmprg object. + * + * The program is made up of vmop_t's, one after another. When we support + * branching (or user-defined functions) at some time, well do this via + * special branch opcodes. They will then contain the actual memory + * address of a logical program entry that we shall branch to. Other than + * that, all execution is serial - that is one opcode is executed after + * the other. This class implements a logical program store, modelled + * after real main memory. A linked list of opcodes is used to implement it. + * In the future, we may use linked lists of array's to enhance performance, + * but for the time being we have taken the simplistic approach (which also + * reduces risk of bugs during initial development). The necessary pointers + * for this are already implemented in vmop. Though this is not the 100% + * correct place, we have opted this time in favor of performance, which + * made them go there. + * + * 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_VMPRG_H +#define INCLUDED_VMPRG_H + +#include "vmop.h" + + +/* the vmprg object */ +typedef struct vmprg_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + vmop_t *vmopRoot; /* start of program */ + vmop_t *vmopLast; /* last vmop of program (for adding new ones) */ +} vmprg_t; + + +/* interfaces */ +BEGINinterface(vmprg) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vmprg); + rsRetVal (*Construct)(vmprg_t **ppThis); + rsRetVal (*ConstructFinalize)(vmprg_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vmprg_t **ppThis); + rsRetVal (*AddOperation)(vmprg_t *pThis, vmop_t *pOp); + rsRetVal (*AddVarOperation)(vmprg_t *pThis, opcode_t opcode, var_t *pVar); +ENDinterface(vmprg) +#define vmprgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(vmprg); + +#endif /* #ifndef INCLUDED_VMPRG_H */ diff --git a/vmstk.c b/vmstk.c new file mode 100644 index 00000000..1ee3d485 --- /dev/null +++ b/vmstk.c @@ -0,0 +1,234 @@ +/* vmstk.c - the arithmetic stack of a virtual machine. + * + * Module begun 2008-02-21 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 <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vmstk.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) + + +/* Standard-Constructor + */ +BEGINobjConstruct(vmstk) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vmstk) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmstkConstructFinalize(vmstk_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmstk); + RETiRet; +} + + +/* destructor for the vmstk object */ +BEGINobjDestruct(vmstk) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vmstk) +ENDobjDestruct(vmstk) + + +/* debugprint for the vmstk object */ +BEGINobjDebugPrint(vmstk) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(vmstk) + dbgoprint((obj_t*) pThis, "stack contents:\n"); +ENDobjDebugPrint(vmstk) + + +/* push a value on the stack. The provided pVar is now owned + * by the stack. If the user intends to continue use it, it + * must be duplicated. + */ +static rsRetVal +push(vmstk_t *pThis, var_t *pVar) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, vmstk); + ISOBJ_TYPE_assert(pVar, var); + + if(pThis->iStkPtr >= VMSTK_SIZE) + ABORT_FINALIZE(RS_RET_OUT_OF_STACKSPACE); + + pThis->vStk[pThis->iStkPtr++] = pVar; + +finalize_it: + RETiRet; +} + + +/* pop a value from the stack + * IMPORTANT: the stack pointer always points to the NEXT FREE entry. So in + * order to pop, we must access the element one below the stack pointer. + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +pop(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, vmstk); + ASSERT(ppVar != NULL); + + if(pThis->iStkPtr == 0) + ABORT_FINALIZE(RS_RET_STACK_EMPTY); + + *ppVar = pThis->vStk[--pThis->iStkPtr]; + +finalize_it: + RETiRet; +} + + +/* pop a boolean value from the stack + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +popBool(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + CHKiRet(pop(pThis, ppVar)); + CHKiRet(var.ConvToBool(*ppVar)); + +finalize_it: + RETiRet; +} + + +/* pop a number value from the stack + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +popNumber(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + CHKiRet(pop(pThis, ppVar)); + CHKiRet(var.ConvToNumber(*ppVar)); + +finalize_it: + RETiRet; +} + + +/* pop a number value from the stack + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +popString(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + CHKiRet(pop(pThis, ppVar)); + CHKiRet(var.ConvToString(*ppVar)); + +finalize_it: + RETiRet; +} + + +/* pop two variables for a common operation, e.g. a compare. When this + * functions returns, both variables have the same type, but the type + * is not set to anything specific. + * The user is responsible for destructing the ppVar's returned. + * A quick note on the name: it means pop 2 variable for a common + * opertion - just in case you wonder (I don't really like the name, + * but I didn't come up with a better one...). + * rgerhards, 2008-02-25 + */ +static rsRetVal +pop2CommOp(vmstk_t *pThis, var_t **ppVar1, var_t **ppVar2) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + /* operand two must be popped first, because it is at the top of stack */ + CHKiRet(pop(pThis, ppVar2)); + CHKiRet(pop(pThis, ppVar1)); + CHKiRet(var.ConvForOperation(*ppVar1, *ppVar2)); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vmstk) +CODESTARTobjQueryInterface(vmstk) + if(pIf->ifVersion != vmstkCURR_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 = vmstkConstruct; + pIf->ConstructFinalize = vmstkConstructFinalize; + pIf->Destruct = vmstkDestruct; + pIf->DebugPrint = vmstkDebugPrint; + pIf->Push = push; + pIf->Pop = pop; + pIf->PopBool = popBool; + pIf->PopNumber = popNumber; + pIf->PopString = popString; + pIf->Pop2CommOp = pop2CommOp; + +finalize_it: +ENDobjQueryInterface(vmstk) + + +/* Initialize the vmstk class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vmstk, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmstkDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmstkConstructFinalize); +ENDObjClassInit(vmstk) + +/* vi:set ai: + */ diff --git a/vmstk.h b/vmstk.h new file mode 100644 index 00000000..2d45ee4d --- /dev/null +++ b/vmstk.h @@ -0,0 +1,56 @@ +/* The vmstk object. + * + * 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_VMSTK_H +#define INCLUDED_VMSTK_H + +/* The max size of the stack - TODO: make configurable */ +#define VMSTK_SIZE 256 + +/* the vmstk object */ +typedef struct vmstk_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + var_t *vStk[VMSTK_SIZE];/* the actual stack */ + int iStkPtr; /* stack pointer, points to next free location, grows from 0 --> topend */ +} vmstk_t; + + +/* interfaces */ +BEGINinterface(vmstk) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vmstk); + rsRetVal (*Construct)(vmstk_t **ppThis); + rsRetVal (*ConstructFinalize)(vmstk_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vmstk_t **ppThis); + rsRetVal (*Push)(vmstk_t *pThis, var_t *pVar); + rsRetVal (*Pop)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*PopBool)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*PopNumber)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*PopString)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*Pop2CommOp)(vmstk_t *pThis, var_t **ppVar1, var_t **ppVar2); +ENDinterface(vmstk) +#define vmstkCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(vmstk); + +#endif /* #ifndef INCLUDED_VMSTK_H */ @@ -0,0 +1,480 @@ +/* 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 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 <errno.h> + +#include "rsyslog.h" +#include "syslogd.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "wtp.h" +#include "wti.h" +#include "obj.h" + +/* static data */ +DEFobjStaticHelpers + +/* 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; +} + + +/* get the current worker state. For simplicity and speed, we have + * NOT used our regular calling interface this time. I hope that won't + * bite in the long term... -- rgerhards, 2008-01-17 + * TODO: may be performance optimized by atomic operations + */ +qWrkCmd_t +wtiGetState(wti_t *pThis, int bLockMutex) +{ + DEFVARS_mutexProtection; + qWrkCmd_t tCmd; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wti); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + tCmd = pThis->tCurrCmd; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + ENDfunc + return tCmd; +} + + +/* send a command to a specific thread + * bActiveOnly specifies if the command should be sent only when the worker is + * in an active state. -- rgerhards, 2008-01-20 + */ +rsRetVal +wtiSetState(wti_t *pThis, qWrkCmd_t tCmd, int bActiveOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, wti); + assert(tCmd <= eWRKTHRD_SHUTDOWN_IMMEDIATE); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + + /* all worker states must be followed sequentially, only termination can be set in any state */ + if( (bActiveOnly && (pThis->tCurrCmd < eWRKTHRD_RUN_CREATED)) + || (pThis->tCurrCmd > tCmd && !(tCmd == eWRKTHRD_TERMINATING || tCmd == eWRKTHRD_STOPPED))) { + dbgprintf("%s: command %d can not be accepted in current %d processing state - ignored\n", + wtiGetDbgHdr(pThis), tCmd, pThis->tCurrCmd); + } else { + dbgprintf("%s: receiving command %d\n", wtiGetDbgHdr(pThis), tCmd); + switch(tCmd) { + case eWRKTHRD_TERMINATING: + /* TODO: re-enable meaningful debug msg! (via function callback?) + dbgprintf("%s: thread terminating with %d entries left in queue, %d workers running.\n", + wtiGetDbgHdr(pThis->pQueue), pThis->pQueue->iQueueSize, + pThis->pQueue->iCurNumWrkThrd); + */ + pthread_cond_signal(&pThis->condExitDone); + dbgprintf("%s: worker terminating\n", wtiGetDbgHdr(pThis)); + break; + case eWRKTHRD_RUNNING: + pthread_cond_signal(&pThis->condInitDone); + break; + /* these cases just to satisfy the compiler, we do (yet) not act an them: */ + case eWRKTHRD_STOPPED: + case eWRKTHRD_RUN_CREATED: + case eWRKTHRD_RUN_INIT: + case eWRKTHRD_SHUTDOWN: + case eWRKTHRD_SHUTDOWN_IMMEDIATE: + /* DO NOTHING */ + break; + } + pThis->tCurrCmd = tCmd; /* apply the new state */ + } + + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + RETiRet; +} + + +/* Cancel the thread. If the thread is already cancelled or termination, + * we do not again cancel it. But it is save and legal to call wtiCancelThrd() in + * such situations. + * rgerhards, 2008-02-26 + */ +rsRetVal +wtiCancelThrd(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + + d_pthread_mutex_lock(&pThis->mut); + + if(pThis->tCurrCmd >= eWRKTHRD_TERMINATING) { + dbgoprint((obj_t*) pThis, "canceling worker thread\n"); + pthread_cancel(pThis->thrdID); + wtiSetState(pThis, eWRKTHRD_TERMINATING, 0, MUTEX_ALREADY_LOCKED); + pThis->pWtp->bThrdStateChanged = 1; /* indicate change, so harverster will be called */ + } + + d_pthread_mutex_unlock(&pThis->mut); + + RETiRet; +} + + +/* Destructor */ +BEGINobjDestruct(wti) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(wti) + /* if we reach this point, we must make sure the associated worker has terminated. It is + * the callers duty to make sure the worker already knows it shall terminate. + * TODO: is it *really* the caller's duty? ...mmmhhhh.... smells bad... rgerhards, 2008-01-25 + */ + wtiProcessThrdChanges(pThis, LOCK_MUTEX); /* process state change one last time */ + + d_pthread_mutex_lock(&pThis->mut); + if(wtiGetState(pThis, MUTEX_ALREADY_LOCKED) != eWRKTHRD_STOPPED) { + dbgprintf("%s: WARNING: worker %p shall be destructed but is still running (might be OK) - joining it\n", + wtiGetDbgHdr(pThis), pThis); + /* let's hope the caller actually instructed it to shutdown... */ + pthread_cond_wait(&pThis->condExitDone, &pThis->mut); + wtiJoinThrd(pThis); + } + d_pthread_mutex_unlock(&pThis->mut); + + /* actual destruction */ + pthread_cond_destroy(&pThis->condInitDone); + pthread_cond_destroy(&pThis->condExitDone); + pthread_mutex_destroy(&pThis->mut); + + if(pThis->pszDbgHdr != NULL) + free(pThis->pszDbgHdr); +ENDobjDestruct(wti) + + +/* Standard-Constructor for the wti object + */ +BEGINobjConstruct(wti) /* be sure to specify the object type also in END macro! */ + pthread_cond_init(&pThis->condInitDone, NULL); + pthread_cond_init(&pThis->condExitDone, NULL); + pthread_mutex_init(&pThis->mut, NULL); +ENDobjConstruct(wti) + + +/* Construction finalizer + * rgerhards, 2008-01-17 + */ +rsRetVal +wtiConstructFinalize(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + + dbgprintf("%s: finalizing construction of worker instance data\n", wtiGetDbgHdr(pThis)); + + /* initialize our thread instance descriptor */ + pThis->pUsrp = NULL; + pThis->tCurrCmd = eWRKTHRD_STOPPED; + + RETiRet; +} + + +/* join a specific worker thread + * we do not lock the mutex, because join will sync anyways... + */ +rsRetVal +wtiJoinThrd(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + dbgprintf("waiting for worker %s termination, current state %d\n", wtiGetDbgHdr(pThis), pThis->tCurrCmd); + pthread_join(pThis->thrdID, NULL); + wtiSetState(pThis, eWRKTHRD_STOPPED, 0, MUTEX_ALREADY_LOCKED); /* back to virgin... */ + pThis->thrdID = 0; /* invalidate the thread ID so that we do not accidently find reused ones */ + dbgprintf("worker %s has stopped\n", wtiGetDbgHdr(pThis)); + + RETiRet; +} + +/* check if we had a worker thread changes and, if so, act + * on it. At a minimum, terminated threads are harvested (joined). + */ +rsRetVal +wtiProcessThrdChanges(wti_t *pThis, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, wti); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + switch(pThis->tCurrCmd) { + case eWRKTHRD_TERMINATING: + /* we need to at least temporarily release the mutex, because otherwise + * we may deadlock with the thread we intend to join (it aquires the mutex + * during termination processing). -- rgerhards, 2008-02-26 + */ + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + iRet = wtiJoinThrd(pThis); + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + break; + /* these cases just to satisfy the compiler, we do not act an them: */ + case eWRKTHRD_STOPPED: + case eWRKTHRD_RUN_CREATED: + case eWRKTHRD_RUN_INIT: + case eWRKTHRD_RUNNING: + case eWRKTHRD_SHUTDOWN: + case eWRKTHRD_SHUTDOWN_IMMEDIATE: + /* DO NOTHING */ + break; + } + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + RETiRet; +} + + +/* cancellation cleanup handler for queueWorker () + * Updates admin structure and frees ressources. + * rgerhards, 2008-01-16 + */ +static void +wtiWorkerCancelCleanup(void *arg) +{ + wti_t *pThis = (wti_t*) arg; + wtp_t *pWtp; + int iCancelStateSave; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wti); + pWtp = pThis->pWtp; + ISOBJ_TYPE_assert(pWtp, wtp); + + dbgprintf("%s: cancelation cleanup handler called.\n", wtiGetDbgHdr(pThis)); + + /* call user supplied handler (that one e.g. requeues the element) */ + pWtp->pfOnWorkerCancel(pThis->pWtp->pUsr, pThis->pUsrp); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pWtp->mut); + wtiSetState(pThis, eWRKTHRD_TERMINATING, 0, MUTEX_ALREADY_LOCKED); + /* TODO: sync access? I currently think it is NOT needed -- rgerhards, 2008-01-28 */ + pWtp->bThrdStateChanged = 1; /* indicate change, so harverster will be called */ + + d_pthread_mutex_unlock(&pWtp->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + ENDfunc +} + + +/* generic worker thread framework + * + * Some special comments below, so that they do not clutter the main function code: + * + * On the use of pthread_testcancel(): + * Now make sure we can get canceled - it is not specified if pthread_setcancelstate() is + * a cancellation point in itself. As we run most of the time without cancel enabled, I fear + * we may never get cancelled if we do not create a cancellation point ourselfs. + * + * On the use of pthread_yield(): + * We yield to give the other threads a chance to obtain the mutex. If we do not + * do that, this thread may very well aquire the mutex again before another thread + * has even a chance to run. The reason is that mutex operations are free to be + * implemented in the quickest possible way (and they typically are!). That is, the + * mutex lock/unlock most probably just does an atomic memory swap and does not necessarily + * schedule other threads waiting on the same mutex. That can lead to the same thread + * aquiring the mutex ever and ever again while all others are starving for it. We + * have exactly seen this behaviour when we deliberately introduced a long-running + * test action which basically did a sleep. I understand that with real actions the + * likelihood of this starvation condition is very low - but it could still happen + * and would be very hard to debug. The yield() is a sure fix, its performance overhead + * should be well accepted given the above facts. -- rgerhards, 2008-01-10 + */ +rsRetVal +wtiWorker(wti_t *pThis) +{ + DEFiRet; + DEFVARS_mutexProtection; + struct timespec t; + wtp_t *pWtp; /* our worker thread pool */ + int bInactivityTOOccured = 0; + + ISOBJ_TYPE_assert(pThis, wti); + pWtp = pThis->pWtp; /* shortcut */ + ISOBJ_TYPE_assert(pWtp, wtp); + + dbgSetThrdName(pThis->pszDbgHdr); + pThis->pUsrp = NULL; + pthread_cleanup_push(wtiWorkerCancelCleanup, pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr, LOCK_MUTEX); + pWtp->pfOnWorkerStartup(pWtp->pUsr); + END_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr); + + /* now we have our identity, on to real processing */ + while(1) { /* loop will be broken below - need to do mutex locks */ + /* process any pending thread requests */ + wtpProcessThrdChanges(pWtp); + pthread_testcancel(); /* see big comment in function header */ +# if !defined(__hpux) /* pthread_yield is missing there! */ + pthread_yield(); /* see big comment in function header */ +# endif + + /* if we have a rate-limiter set for this worker pool, let's call it. Please + * keep in mind that the rate-limiter may hold us for an extended period + * of time. -- rgerhards, 2008-04-02 + */ + if(pWtp->pfRateLimiter != NULL) { + pWtp->pfRateLimiter(pWtp->pUsr); + } + + wtpSetInactivityGuard(pThis->pWtp, 0, LOCK_MUTEX); /* must be set before usr mutex is locked! */ + BEGIN_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr, LOCK_MUTEX); + + if( (bInactivityTOOccured && pWtp->pfIsIdle(pWtp->pUsr, MUTEX_ALREADY_LOCKED)) + || wtpChkStopWrkr(pWtp, LOCK_MUTEX, MUTEX_ALREADY_LOCKED)) { + END_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr); + break; /* end worker thread run */ + } + bInactivityTOOccured = 0; /* reset for next run */ + + /* if we reach this point, we are still protected by the mutex */ + + if(pWtp->pfIsIdle(pWtp->pUsr, MUTEX_ALREADY_LOCKED)) { + dbgprintf("%s: worker IDLE, waiting for work.\n", wtiGetDbgHdr(pThis)); + pWtp->pfOnIdle(pWtp->pUsr, MUTEX_ALREADY_LOCKED); + + if(pWtp->toWrkShutdown == -1) { + /* 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)); + bInactivityTOOccured = 1; /* indicate we had a timeout */ + } + } + END_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr); + continue; /* request next iteration */ + } + + /* if we reach this point, we have a non-empty queue (and are still protected by mutex) */ + pWtp->pfDoWork(pWtp->pUsr, pThis, iCancelStateSave); + } + + /* indicate termination */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pThis->mut); + pthread_cleanup_pop(0); /* remove cleanup handler */ + + pWtp->pfOnWorkerShutdown(pWtp->pUsr); + + wtiSetState(pThis, eWRKTHRD_TERMINATING, 0, MUTEX_ALREADY_LOCKED); + pWtp->bThrdStateChanged = 1; /* indicate change, so harverster will be called */ + d_pthread_mutex_unlock(&pThis->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + + RETiRet; +} + + +/* 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); + 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 wtiQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + + +/* 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 */ +ENDObjClassInit(wti) + +/* + * vi:set ai: + */ @@ -0,0 +1,63 @@ +/* Definition of the worker thread instance (wti) class. + * + * 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 WTI_H_INCLUDED +#define WTI_H_INCLUDED + +#include <pthread.h> +#include "wtp.h" +#include "obj.h" + +/* the worker thread instance class */ +typedef struct wti_s { + BEGINobjInstance; + pthread_t thrdID; /* thread ID */ + qWrkCmd_t tCurrCmd; /* current command to be carried out by worker */ + obj_t *pUsrp; /* pointer to an object meaningful for current user pointer (e.g. queue pUsr data elemt) */ + wtp_t *pWtp; /* my worker thread pool (important if only the work thread instance is passed! */ + pthread_cond_t condInitDone; /* signaled when the thread startup is done (once per thread existance) */ + pthread_cond_t condExitDone; /* signaled when the thread exit is done (once per thread existance) */ + pthread_mutex_t mut; + int bShutdownRqtd; /* shutdown for this thread requested? 0 - no , 1 - yes */ + uchar *pszDbgHdr; /* header string for debug messages */ +} wti_t; + +/* some symbolic constants for easier reference */ + + +/* prototypes */ +rsRetVal wtiConstruct(wti_t **ppThis); +rsRetVal wtiConstructFinalize(wti_t *pThis); +rsRetVal wtiDestruct(wti_t **ppThis); +rsRetVal wtiWorker(wti_t *pThis); +rsRetVal wtiProcessThrdChanges(wti_t *pThis, int bLockMutex); +rsRetVal wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg); +rsRetVal wtiSetState(wti_t *pThis, qWrkCmd_t tCmd, int bActiveOnly, int bLockMutex); +rsRetVal wtiJoinThrd(wti_t *pThis); +rsRetVal wtiCancelThrd(wti_t *pThis); +qWrkCmd_t wtiGetState(wti_t *pThis, int bLockMutex); +PROTOTYPEObjClassInit(wti); +PROTOTYPEpropSetMeth(wti, pszDbgHdr, uchar*); +PROTOTYPEpropSetMeth(wti, pWtp, wtp_t*); + +#endif /* #ifndef WTI_H_INCLUDED */ @@ -0,0 +1,624 @@ +/* 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 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 "rsyslog.h" +#include "syslogd.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "wtp.h" +#include "wti.h" +#include "obj.h" + +/* static data */ +DEFobjStaticHelpers + +/* 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_OK; } +/* Standard-Constructor for the wtp object + */ +BEGINobjConstruct(wtp) /* be sure to specify the object type also in END macro! */ + pthread_mutex_init(&pThis->mut, NULL); + pthread_cond_init(&pThis->condThrdTrm, NULL); + /* set all function pointers to "not implemented" dummy so that we can safely call them */ + pThis->pfChkStopWrkr = NotImplementedDummy; + pThis->pfIsIdle = NotImplementedDummy; + pThis->pfDoWork = NotImplementedDummy; + pThis->pfOnIdle = NotImplementedDummy; + pThis->pfOnWorkerCancel = NotImplementedDummy; + pThis->pfOnWorkerStartup = NotImplementedDummy; + pThis->pfOnWorkerShutdown = NotImplementedDummy; +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 + */ + if((pThis->pWrkr = malloc(sizeof(wti_t*) * pThis->iNumWorkerThreads)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + 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) + wtpProcessThrdChanges(pThis); /* process thread changes one last time */ + + /* 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->mut); + + if(pThis->pszDbgHdr != NULL) + free(pThis->pszDbgHdr); +ENDobjDestruct(wtp) + + +/* wake up at least one worker thread. + * rgerhards, 2008-01-20 + */ +rsRetVal +wtpWakeupWrkr(wtp_t *pThis) +{ + DEFiRet; + + /* TODO; mutex? I think not needed, as we do not need predictable exec order -- rgerhards, 2008-01-28 */ + ISOBJ_TYPE_assert(pThis, wtp); + pthread_cond_signal(pThis->pcondBusy); + RETiRet; +} + +/* wake up all worker threads. + * rgerhards, 2008-01-16 + */ +rsRetVal +wtpWakeupAllWrkr(wtp_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + pthread_cond_broadcast(pThis->pcondBusy); + RETiRet; +} + + +/* check if we had any worker thread changes and, if so, act + * on them. At a minimum, terminated threads are harvested (joined). + * This function MUST NEVER block on the queue mutex! + */ +rsRetVal +wtpProcessThrdChanges(wtp_t *pThis) +{ + DEFiRet; + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + if(pThis->bThrdStateChanged == 0) + FINALIZE; + + /* go through all threads */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + wtiProcessThrdChanges(pThis->pWrkr[i], LOCK_MUTEX); + } + +finalize_it: + RETiRet; +} + + +/* Sent a specific state for the worker thread pool. + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpSetState(wtp_t *pThis, wtpState_t iNewState) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + pThis->wtpState = iNewState; + /* TODO: must wakeup workers? seen to be not needed -- rgerhards, 2008-01-28 */ + + RETiRet; +} + + +/* check if the worker shall shutdown (1 = yes, 0 = no) + * TODO: check if we can use atomic operations to enhance performance + * 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 bLockMutex, int bLockUsrMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, wtp); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + if( (pThis->wtpState == wtpState_SHUTDOWN_IMMEDIATE) + || ((pThis->wtpState == wtpState_SHUTDOWN) && pThis->pfIsIdle(pThis->pUsr, bLockUsrMutex))) + iRet = RS_RET_TERMINATE_NOW; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + /* try customer handler if one was set and we do not yet have a definite result */ + if(iRet == RS_RET_OK && pThis->pfChkStopWrkr != NULL) { + iRet = pThis->pfChkStopWrkr(pThis->pUsr, bLockUsrMutex); + } + + RETiRet; +} + + +/* Send a shutdown command to all workers and see if they terminate. + * A timeout may be specified. + * rgerhards, 2008-01-14 + */ +rsRetVal +wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout) +{ + DEFiRet; + int bTimedOut; + int iCancelStateSave; + + ISOBJ_TYPE_assert(pThis, wtp); + + wtpSetState(pThis, tShutdownCmd); + wtpWakeupAllWrkr(pThis); + + /* see if we need to harvest (join) any terminated threads (even in timeout case, + * some may have terminated... + */ + wtpProcessThrdChanges(pThis); + + /* and wait for their termination */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pThis->mut); + pthread_cleanup_push(mutexCancelCleanup, &pThis->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + bTimedOut = 0; + while(pThis->iCurNumWrkThrd > 0 && !bTimedOut) { + dbgprintf("%s: waiting %ldms on worker thread termination, %d still running\n", + wtpGetDbgHdr(pThis), timeoutVal(ptTimeout), pThis->iCurNumWrkThrd); + + if(d_pthread_cond_timedwait(&pThis->condThrdTrm, &pThis->mut, ptTimeout) != 0) { + dbgprintf("%s: timeout waiting on worker thread termination\n", wtpGetDbgHdr(pThis)); + bTimedOut = 1; /* we exit the loop on timeout */ + } + } + pthread_cleanup_pop(1); + + if(bTimedOut) + iRet = RS_RET_TIMED_OUT; + + /* see if we need to harvest (join) any terminated threads (even in timeout case, + * some may have terminated... + */ + wtpProcessThrdChanges(pThis); + + RETiRet; +} + + +/* indicate that a thread has terminated and awake anyone waiting on it + * rgerhards, 2008-01-23 + */ +rsRetVal wtpSignalWrkrTermination(wtp_t *pThis) +{ + DEFiRet; + /* I leave the mutex code here out as it give as deadlocks. I think it is not really + * needed and we are on the safe side. I leave this comment in if practice proves us + * wrong. The whole thing should be removed after half a your or year if we see there + * actually is no issue (or revisit it from a theoretical POV). + * rgerhards, 2008-01-28 + */ + /*TODO: mutex or not mutex, that's the question ;)DEFVARS_mutexProtection;*/ + + ISOBJ_TYPE_assert(pThis, wtp); + + /*BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX);*/ + pthread_cond_signal(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */ + /*END_MTX_PROTECTED_OPERATIONS(&pThis->mut);*/ + RETiRet; +} + + +/* Unconditionally cancel all running worker threads. + * rgerhards, 2008-01-14 + */ +rsRetVal +wtpCancelAll(wtp_t *pThis) +{ + DEFiRet; + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + /* process any pending thread requests so that we know who actually is still running */ + wtpProcessThrdChanges(pThis); + + /* go through all workers and cancel those that are active */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + dbgprintf("%s: try canceling worker thread %d\n", wtpGetDbgHdr(pThis), i); + wtiCancelThrd(pThis->pWrkr[i]); + } + + RETiRet; +} + + + +/* Set the Inactivity Guard + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpSetInactivityGuard(wtp_t *pThis, int bNewState, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + pThis->bInactivityGuard = bNewState; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + RETiRet; +} + + +/* cancellation cleanup handler for executing worker + * decrements the worker counter + * rgerhards, 2008-01-20 + */ +void +wtpWrkrExecCancelCleanup(void *arg) +{ + wtp_t *pThis = (wtp_t*) arg; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wtp); + pThis->iCurNumWrkThrd--; + wtpSignalWrkrTermination(pThis); + + dbgprintf("%s: thread CANCELED with %d workers running.\n", wtpGetDbgHdr(pThis), pThis->iCurNumWrkThrd); + ENDfunc +} + + +/* wtp worker shell. This is started and calls into the actual + * wti worker. + * rgerhards, 2008-01-21 + */ +static void * +wtpWorker(void *arg) /* the arg is actually a wti object, even though we are in wtp! */ +{ + DEFiRet; + DEFVARS_mutexProtection; + wti_t *pWti = (wti_t*) arg; + wtp_t *pThis; + sigset_t sigSet; + + ISOBJ_TYPE_assert(pWti, wti); + pThis = pWti->pWtp; + ISOBJ_TYPE_assert(pThis, wtp); + + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX); + + /* do some late initialization */ + + pthread_cleanup_push(wtpWrkrExecCancelCleanup, pThis); + + /* finally change to RUNNING state. We need to check if we actually should still run, + * because someone may have requested us to shut down even before we got a chance to do + * our init. That would be a bad race... -- rgerhards, 2008-01-16 + */ + wtiSetState(pWti, eWRKTHRD_RUNNING, 0, MUTEX_ALREADY_LOCKED); /* we are running now! */ + + do { + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + iRet = wtiWorker(pWti); /* just to make sure: this is NOT protected by the mutex! */ + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX); + } while(pThis->iCurNumWrkThrd == 1 && pThis->bInactivityGuard == 1); + /* inactivity guard prevents shutdown of all workers while one should be running due to race + * condition. It can lead to one more worker running than desired, but that is acceptable. After + * all, that worker will shutdown itself due to inactivity timeout. If, however, none were running + * when one was required, processing could come to a halt. -- rgerhards, 2008-01-21 + */ + + pthread_cleanup_pop(0); + pThis->iCurNumWrkThrd--; + wtpSignalWrkrTermination(pThis); + + dbgprintf("%s: Worker thread %lx, terminated, num workers now %d\n", + wtpGetDbgHdr(pThis), (unsigned long) pWti, pThis->iCurNumWrkThrd); + + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + ENDfunc + pthread_exit(0); +} + + +/* start a new worker */ +static rsRetVal +wtpStartWrkr(wtp_t *pThis, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + wti_t *pWti; + int i; + int iState; + + ISOBJ_TYPE_assert(pThis, wtp); + + wtpProcessThrdChanges(pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + + pThis->iCurNumWrkThrd++; + + /* find free spot in thread table. If we find at least one worker that is in initialization, + * we do NOT start a new one. Let's give the other one a chance, first. + */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + if(wtiGetState(pThis->pWrkr[i], LOCK_MUTEX) == eWRKTHRD_STOPPED) { + break; + } + } + + if(i == pThis->iNumWorkerThreads) + ABORT_FINALIZE(RS_RET_NO_MORE_THREADS); + + pWti = pThis->pWrkr[i]; + wtiSetState(pWti, eWRKTHRD_RUN_CREATED, 0, LOCK_MUTEX); + iState = pthread_create(&(pWti->thrdID), NULL, wtpWorker, (void*) pWti); + dbgprintf("%s: started with state %d, num workers now %d\n", + wtpGetDbgHdr(pThis), iState, pThis->iCurNumWrkThrd); + + /* we try to give the starting worker a little boost. It won't help much as we still + * hold the queue's mutex, but at least it has a chance to start on a single-CPU system. + */ +# if !defined(__hpux) /* pthread_yield is missing there! */ + pthread_yield(); +# endif + + /* indicate we just started a worker and would like to see it running */ + wtpSetInactivityGuard(pThis, 1, MUTEX_ALREADY_LOCKED); + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + 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; + DEFVARS_mutexProtection; + int nMissing; /* number workers missing to run */ + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + if(nMaxWrkr == 0) + FINALIZE; + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX); + + if(nMaxWrkr > pThis->iNumWorkerThreads) /* limit to configured maximum */ + nMaxWrkr = pThis->iNumWorkerThreads; + + nMissing = nMaxWrkr - pThis->iCurNumWrkThrd; + + 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, MUTEX_ALREADY_LOCKED)); + } + } else { + if(nMaxWrkr > 0) { + dbgprintf("wtpAdviseMaxWorkers signals busy\n"); + wtpWakeupWrkr(pThis); + } + } + + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + 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, pfIsIdle, rsRetVal(*pVal)(void*, int)); +DEFpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*, int)); +DEFpropSetMethFP(wtp, pfOnIdle, rsRetVal(*pVal)(void*, int)); +DEFpropSetMethFP(wtp, pfOnWorkerCancel, rsRetVal(*pVal)(void*, void*)); +DEFpropSetMethFP(wtp, pfOnWorkerStartup, rsRetVal(*pVal)(void*)); +DEFpropSetMethFP(wtp, pfOnWorkerShutdown, rsRetVal(*pVal)(void*)); + + +/* return the current number of worker threads. + * TODO: atomic operation would bring a nice performance + * enhancemcent + * rgerhards, 2008-01-27 + */ +int +wtpGetCurNumWrkr(wtp_t *pThis, int bLockMutex) +{ + DEFVARS_mutexProtection; + int iNumWrkr; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wtp); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + iNumWrkr = pThis->iCurNumWrkThrd; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + ENDfunc + return iNumWrkr; +} + + +/* 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; } + +/* 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 */ +ENDObjClassInit(wtp) + +/* + * vi:set ai: + */ @@ -0,0 +1,119 @@ +/* Definition of the worker thread pool (wtp) object. + * + * 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 WTP_H_INCLUDED +#define WTP_H_INCLUDED + +#include <pthread.h> +#include "obj.h" + +/* commands and states for worker threads. */ +typedef enum { + eWRKTHRD_STOPPED = 0, /* worker thread is not running (either actually never ran or was shut down) */ + eWRKTHRD_TERMINATING = 1,/* worker thread has shut down, but some finalzing is still needed */ + /* ALL active states MUST be numerically higher than eWRKTHRD_TERMINATED and NONE must be lower! */ + eWRKTHRD_RUN_CREATED = 2,/* worker thread has been created, but not yet begun initialization (prob. not yet scheduled) */ + eWRKTHRD_RUN_INIT = 3, /* worker thread is initializing, but not yet fully running */ + eWRKTHRD_RUNNING = 4, /* worker thread is up and running and shall continue to do so */ + eWRKTHRD_SHUTDOWN = 5, /* worker thread is running but shall terminate when wtp is empty */ + eWRKTHRD_SHUTDOWN_IMMEDIATE = 6/* worker thread is running but shall terminate even if wtp is full */ + /* SHUTDOWN_IMMEDIATE MUST alsways be the numerically highest state! */ +} qWrkCmd_t; + + +/* 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 */ +typedef 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) */ + int bInactivityGuard;/* prevents inactivity due to race condition */ + rsRetVal (*pConsumer)(void *); /* user-supplied consumer function for dewtpd messages */ + /* synchronization variables */ + pthread_mutex_t mut; /* mutex for the wtp's thread management */ + pthread_cond_t condThrdTrm;/* signalled when threads terminate */ + int bThrdStateChanged; /* at least one thread state has changed if 1 */ + /* end sync variables */ + /* user objects */ + void *pUsr; /* pointer to user object */ + 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 (*pfRateLimiter)(void *pUsr); + rsRetVal (*pfIsIdle)(void *pUsr, int); + rsRetVal (*pfDoWork)(void *pUsr, void *pWti, int); + rsRetVal (*pfOnIdle)(void *pUsr, int); + rsRetVal (*pfOnWorkerCancel)(void *pUsr, void*pWti); + rsRetVal (*pfOnWorkerStartup)(void *pUsr); + rsRetVal (*pfOnWorkerShutdown)(void *pUsr); + /* end user objects */ + uchar *pszDbgHdr; /* header string for debug messages */ +} wtp_t; + +/* 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 wtpSetInactivityGuard(wtp_t *pThis, int bNewState, int bLockMutex); +rsRetVal wtpChkStopWrkr(wtp_t *pThis, int bLockMutex, int bLockUsrMutex); +rsRetVal wtpSetState(wtp_t *pThis, wtpState_t iNewState); +rsRetVal wtpWakeupWrkr(wtp_t *pThis); +rsRetVal wtpWakeupAllWrkr(wtp_t *pThis); +rsRetVal wtpCancelAll(wtp_t *pThis); +rsRetVal wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg); +rsRetVal wtpSignalWrkrTermination(wtp_t *pWtp); +rsRetVal wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout); +int wtpGetCurNumWrkr(wtp_t *pThis, int bLockMutex); +PROTOTYPEObjClassInit(wtp); +PROTOTYPEpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*)); +PROTOTYPEpropSetMethFP(wtp, pfIsIdle, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfOnIdle, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfOnWorkerCancel, rsRetVal(*pVal)(void*,void*)); +PROTOTYPEpropSetMethFP(wtp, pfOnWorkerStartup, rsRetVal(*pVal)(void*)); +PROTOTYPEpropSetMethFP(wtp, pfOnWorkerShutdown, rsRetVal(*pVal)(void*)); +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 */ |