mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-08-13 13:26:01 +03:00
Import ModSecurity 2.1.0-rc7
This commit is contained in:
commit
3f80fdac3b
144
CHANGES
Normal file
144
CHANGES
Normal file
@ -0,0 +1,144 @@
|
||||
|
||||
05 Feb 2006 - 2.1.0-rc7
|
||||
------------------------
|
||||
|
||||
* Fixed a problem with incorrectly setting requestBodyProcessor using
|
||||
the ctl action.
|
||||
|
||||
|
||||
24 Jan 2007 - 2.1.0-rc6
|
||||
-----------------------
|
||||
|
||||
* Bundled Core Rules 2.1-1.3.2b4.
|
||||
|
||||
* Updates to the reference manual.
|
||||
|
||||
* Reversed the return values of @validateDTD and @validateSchema, to
|
||||
make them consistent with other operators.
|
||||
|
||||
* Added a few helpful debug messages in the XML validation area.
|
||||
|
||||
|
||||
05 Jan 2007 - 2.1.0-rc5
|
||||
-----------------------
|
||||
|
||||
* Updates to the reference manual.
|
||||
|
||||
|
||||
29 Dec 2006 - 2.1.0-rc4
|
||||
-----------------------
|
||||
|
||||
* Fixed the validateByteRange operator.
|
||||
|
||||
* Default value for the status action is now 403 (as it was supposed to
|
||||
be but it was effectively 500).
|
||||
|
||||
* Rule exceptions (removing using an ID range or an regular expression)
|
||||
is now applied to the current context too. (Previously it only worked
|
||||
on rules that are inherited from the parent context.)
|
||||
|
||||
* Fix of a bug with expired variables.
|
||||
|
||||
* Fixed regular expression variable selectors for many collections.
|
||||
|
||||
* Performance improvements - up to two times for real-life work loads!
|
||||
|
||||
* Memory consumption improvements (not measured but significant).
|
||||
|
||||
* The allow action did not work in phases 3 and 4. Fixed.
|
||||
|
||||
* Unlocked collections GLOBAL and RESOURCE.
|
||||
|
||||
* Added support for variable expansion in the msg action.
|
||||
|
||||
* New feature: It is now possible to make relative changes to the
|
||||
audit log parts with the ctl action. For example: "ctl:auditLogParts=+E".
|
||||
|
||||
* New feature: "tag" action. To be used for event categorisation.
|
||||
|
||||
* XML parser was not reporting errors that occured at the end
|
||||
of XML payload.
|
||||
|
||||
* Files were not extracted from request if SecUploadKeepFiles was
|
||||
Off. Fixed.
|
||||
|
||||
* Regular expressions that are too long are truncated to 256
|
||||
characters before used in error messages. (In order to keep
|
||||
the error messages in the log at a reasonable size.)
|
||||
|
||||
* Fixed the sha1 transformation function.
|
||||
|
||||
* Fixed the skip action.
|
||||
|
||||
* Fixed REQUEST_PROTOCOL, REMOTE_USER, and AUTH_TYPE.
|
||||
|
||||
* SecRuleEngine did not work in child configuration contexts
|
||||
(e.g. <Location>).
|
||||
|
||||
* Fixed base64Decode and base64Encode.
|
||||
|
||||
|
||||
15 Nov 2006 - 2.0.4
|
||||
-------------------
|
||||
|
||||
* Fixed the "deprecatevar" action.
|
||||
|
||||
* Decreasing variable values did not work.
|
||||
|
||||
* Made "nolog" do what it is supposed to do - cause a rule match to
|
||||
not be logged. Also "nolog" now implies "noauditlog" but it's
|
||||
possible to follow "nolog" with "auditlog" and have the match
|
||||
not logged to the error log but logged to the auditlog. (Not
|
||||
something that strikes me as useful but it's possible.)
|
||||
|
||||
* Relative paths given to SecDataDir will now be treated as relative
|
||||
to the Apache server root.
|
||||
|
||||
* Added checks to make sure only correct actions are specified in
|
||||
SecDefaultAction (some actions are required, some don't make any
|
||||
sense) and in rules that are not chain starters (same). This should
|
||||
make the unhelpful "Internal Error: Failed to add rule to the ruleset"
|
||||
message go away.
|
||||
|
||||
* Fixed the problem when "SecRuleInheritance Off" is used in a context
|
||||
with no rules defined.
|
||||
|
||||
* Fixed a problem of lost input (request body) data on some redirections,
|
||||
for example when mod_rewrite is used.
|
||||
|
||||
|
||||
26 Oct 2006 - 2.0.3
|
||||
-------------------
|
||||
|
||||
* Fixed a memory leak (all platforms) and a concurrency control
|
||||
problem that could cause a crash (multithreaded platforms only).
|
||||
|
||||
* Fixed a SecAuditLogRelevantStatus problem, which would not work
|
||||
properly unless the regular expression contained a subexpression.
|
||||
|
||||
|
||||
19 Oct 2006 - 2.0.2
|
||||
-------------------
|
||||
|
||||
* Fixed incorrect permissions on the global mutex, which prevented
|
||||
the mutex from working properly.
|
||||
|
||||
* Fixed incorrect actionset merging where the status was copied from
|
||||
the child actionset even though it was not defined.
|
||||
|
||||
* Fixed missing metadata information (in the logs) for warnings.
|
||||
|
||||
|
||||
16 Oct 2006 - 2.0.1
|
||||
-------------------
|
||||
|
||||
* Rules that used operator negation did not work. Fixed.
|
||||
|
||||
* Fixed bug that prevented invalid regular expressions from being reported.
|
||||
|
||||
|
||||
16 Oct 2006 - 2.0.0
|
||||
-------------------
|
||||
|
||||
* First stable 2.x release.
|
||||
|
340
LICENSE
Normal file
340
LICENSE
Normal file
@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 19yy <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
8
README.TXT
Normal file
8
README.TXT
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
ModSecurity for Apache (http://www.modsecurity.org)
|
||||
Copyright (C) 2004-2006 Breach Security, Inc. (http://www.breach.com)
|
||||
|
||||
DOCUMENTATION
|
||||
|
||||
Please refer to the documentation folder (/doc) for
|
||||
the reference manual.
|
0
apache2/.deps
Normal file
0
apache2/.deps
Normal file
340
apache2/LICENSE
Normal file
340
apache2/LICENSE
Normal file
@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 19yy <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
40
apache2/Makefile
Normal file
40
apache2/Makefile
Normal file
@ -0,0 +1,40 @@
|
||||
builddir = .
|
||||
|
||||
# To successfully build ModSecurity for Apache you will
|
||||
# need to configure the "top_dir" variable properly. The
|
||||
# correct value will differ from system to system.
|
||||
#
|
||||
# If you've installed Apache manually simply point to the
|
||||
# installation directory. Most pre-packaged installations
|
||||
# consist of two parts. One contains the binaries, and the
|
||||
# other contains the "development" files. You will typically
|
||||
# need both.
|
||||
#
|
||||
# The list below may help:
|
||||
#
|
||||
# Fedora Core - /usr/lib/httpd (the httpd-devel package must be installed)
|
||||
#
|
||||
# Debian - /usr/share/apache2 (apache2-prefork-dev or apache2-threaded-dev
|
||||
# needed, depending on your installation type)
|
||||
#
|
||||
top_dir = /home/ivanr/apache22
|
||||
|
||||
top_srcdir = ${top_dir}
|
||||
top_builddir = ${top_dir}
|
||||
|
||||
include ${top_builddir}/build/special.mk
|
||||
|
||||
APXS = apxs
|
||||
APACHECTL = apachectl
|
||||
|
||||
INCLUDES = -I /usr/include/libxml2
|
||||
DEFS = -DWITH_LIBXML2
|
||||
#LIBS = -Lmy/lib/dir -lmylib
|
||||
|
||||
CFLAGS = -O2 -g -Wuninitialized -Wall -Wmissing-prototypes -Wshadow -Wunused-variable -Wunused-value -Wchar-subscripts -Wsign-compare
|
||||
|
||||
all: local-shared-build
|
||||
|
||||
clean:
|
||||
-rm -f *.o *.lo *.slo *.la *~ .libs
|
||||
|
42
apache2/Makefile.win
Normal file
42
apache2/Makefile.win
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
# Path to Apache installation
|
||||
BASE = "C:/Program Files/Apache Group/Apache2/"
|
||||
|
||||
CC = cl
|
||||
|
||||
# Add -DWITH_LIBXML2 below if you want to link against libxml2
|
||||
DEFS = /nologo /Od /LD /W3 -DWIN32 -DWINNT
|
||||
DLL = mod_security2.dll
|
||||
|
||||
# Path to the headers - configure the libxml2 include path
|
||||
INCLUDES = -I. -I$(BASE)\include -IC:\libxml2\include
|
||||
|
||||
CFLAGS= -O $(INCLUDES) $(DEFS)
|
||||
|
||||
# Paths to the required libraries
|
||||
# Use the line below if you want to link against libxml2
|
||||
# LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.lib $(BASE)\lib\pcre.lib C:\libxml2\lib\libxml2.lib
|
||||
|
||||
# Use the line belof if you don't want to link against libxml2
|
||||
LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.lib $(BASE)\lib\pcre.lib
|
||||
|
||||
OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \
|
||||
re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \
|
||||
msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj msc_parsers.obj \
|
||||
msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj
|
||||
|
||||
all: $(DLL)
|
||||
|
||||
dll: $(DLL)
|
||||
|
||||
.c.obj:
|
||||
$(CC) $(CFLAGS) -c $< -Fo$@
|
||||
|
||||
.cpp.obj:
|
||||
$(CC) $(CFLAGS) -c $< -Fo$@
|
||||
|
||||
$(DLL): $(OBJS)
|
||||
$(CC) $(CFLAGS) -LD $(OBJS) -Fe$(DLL) $(LIBS) /link /NODEFAULTLIB:MSVCRT
|
||||
|
||||
clean:
|
||||
del $(OBJS) *.dll *.lib *.pdb *.idb *.ilk *.exp *.res *.rc *.bin
|
70
apache2/apache2.h
Normal file
70
apache2/apache2.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: apache2.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _APACHE2_H_
|
||||
#define _APACHE2_H_
|
||||
|
||||
#include "http_core.h"
|
||||
#include "http_request.h"
|
||||
|
||||
#include <apr_general.h>
|
||||
#include <apr_optional.h>
|
||||
|
||||
|
||||
/* Optional functions. */
|
||||
|
||||
APR_DECLARE_OPTIONAL_FN(void, modsec_register_tfn, (const char *name, void *fn));
|
||||
APR_DECLARE_OPTIONAL_FN(void, modsec_register_operator, (const char *name, void *fn_init, void *fn_exec));
|
||||
|
||||
|
||||
/* Configuration functions. */
|
||||
|
||||
void DSOLOCAL *create_directory_config(apr_pool_t *mp, char *path);
|
||||
|
||||
void DSOLOCAL *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child);
|
||||
|
||||
void DSOLOCAL init_directory_config(directory_config *dcfg);
|
||||
|
||||
|
||||
/* IO functions. */
|
||||
|
||||
apr_status_t DSOLOCAL input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out,
|
||||
ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes);
|
||||
|
||||
apr_status_t DSOLOCAL output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in);
|
||||
|
||||
apr_status_t DSOLOCAL read_request_body(modsec_rec *msr, char **error_msg);
|
||||
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
int DSOLOCAL perform_interception(modsec_rec *msr);
|
||||
|
||||
int DSOLOCAL apache2_exec(modsec_rec *msr, const char *command, const char **argv, char **output);
|
||||
|
||||
void DSOLOCAL record_time_checkpoint(modsec_rec *msr, int checkpoint_no);
|
||||
|
||||
char DSOLOCAL *get_apr_error(apr_pool_t *p, apr_status_t rc);
|
||||
|
||||
char DSOLOCAL *get_env_var(request_rec *r, char *name);
|
||||
|
||||
void DSOLOCAL internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr,
|
||||
int level, const char *text, va_list ap);
|
||||
|
||||
void DSOLOCAL msr_log(modsec_rec *msr, int level, const char *text, ...);
|
||||
|
||||
char DSOLOCAL *format_error_log_message(apr_pool_t *mp, error_message *em);
|
||||
|
||||
const DSOLOCAL char *get_response_protocol(request_rec *r);
|
||||
|
||||
#endif
|
||||
|
1326
apache2/apache2_config.c
Normal file
1326
apache2/apache2_config.c
Normal file
File diff suppressed because it is too large
Load Diff
588
apache2/apache2_io.c
Normal file
588
apache2/apache2_io.c
Normal file
@ -0,0 +1,588 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: apache2_io.c,v 1.6 2007/01/23 16:08:15 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "modsecurity.h"
|
||||
#include "apache2.h"
|
||||
|
||||
|
||||
/* -- Input filter -- */
|
||||
|
||||
#if 0
|
||||
static void dummy_free_func(void *data) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This request filter will forward the previously stored
|
||||
* request body further down the chain (most likely to the
|
||||
* processing module).
|
||||
*/
|
||||
apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out,
|
||||
ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes)
|
||||
{
|
||||
modsec_rec *msr = (modsec_rec *)f->ctx;
|
||||
msc_data_chunk *chunk = NULL;
|
||||
apr_bucket *bucket;
|
||||
apr_status_t rc;
|
||||
|
||||
if (msr == NULL) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server,
|
||||
"ModSecurity: Internal error in input filter: msr is null.");
|
||||
ap_remove_input_filter(f);
|
||||
return APR_EGENERAL;
|
||||
}
|
||||
|
||||
if ((msr->if_status == IF_STATUS_COMPLETE)||(msr->if_status == IF_STATUS_NONE)) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Input forwarding already complete, skipping (f %x, r %x).", f, f->r);
|
||||
}
|
||||
ap_remove_input_filter(f);
|
||||
return ap_get_brigade(f->next, bb_out, mode, block, nbytes);
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Forwarding input: mode=%i, block=%i, nbytes=%" APR_OFF_T_FMT
|
||||
" (f %x, r %x).", mode, block, nbytes, f, f->r);
|
||||
}
|
||||
|
||||
if (msr->if_started_forwarding == 0) {
|
||||
msr->if_started_forwarding = 1;
|
||||
rc = modsecurity_request_body_retrieve_start(msr);
|
||||
if (rc == -1) {
|
||||
// TODO err
|
||||
return APR_EGENERAL;
|
||||
}
|
||||
}
|
||||
|
||||
rc = modsecurity_request_body_retrieve(msr, &chunk, (unsigned int)nbytes);
|
||||
if (rc == -1) {
|
||||
// TODO err
|
||||
return APR_EGENERAL;
|
||||
}
|
||||
|
||||
if (chunk) {
|
||||
/* Copy the data we received in the chunk */
|
||||
bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL,
|
||||
f->r->connection->bucket_alloc);
|
||||
|
||||
#if 0
|
||||
|
||||
It would seem that we cannot prevent other filters in the chain
|
||||
from modifying data in-place. Hence we copy.
|
||||
|
||||
if (chunk->is_permanent) {
|
||||
/* Do not make a copy of the data we received in the chunk. */
|
||||
bucket = apr_bucket_heap_create(chunk->data, chunk->length, dummy_free_func,
|
||||
f->r->connection->bucket_alloc);
|
||||
} else {
|
||||
/* Copy the data we received in the chunk. */
|
||||
bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL,
|
||||
f->r->connection->bucket_alloc);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (bucket == NULL) return APR_EGENERAL;
|
||||
APR_BRIGADE_INSERT_TAIL(bb_out, bucket);
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Forwarded %lu bytes.", chunk->length);
|
||||
}
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
modsecurity_request_body_retrieve_end(msr);
|
||||
|
||||
bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc);
|
||||
if (bucket == NULL) return APR_EGENERAL;
|
||||
APR_BRIGADE_INSERT_TAIL(bb_out, bucket);
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Sent EOS.");
|
||||
}
|
||||
|
||||
/* We're done */
|
||||
msr->if_status = IF_STATUS_COMPLETE;
|
||||
ap_remove_input_filter(f);
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Input forwarding complete.");
|
||||
}
|
||||
}
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads request body from a client.
|
||||
*/
|
||||
apr_status_t read_request_body(modsec_rec *msr, char **error_msg) {
|
||||
request_rec *r = msr->r;
|
||||
unsigned int seen_eos;
|
||||
apr_bucket_brigade *bb_in;
|
||||
apr_bucket *bucket;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if (msr->reqbody_should_exist != 1) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: This request does not have a body.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msr->txcfg->reqbody_access != 1) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Request body access not enabled.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Reading request body.");
|
||||
}
|
||||
|
||||
if (modsecurity_request_body_start(msr) < 0) {
|
||||
// TODO err
|
||||
return -1;
|
||||
}
|
||||
|
||||
seen_eos = 0;
|
||||
bb_in = apr_brigade_create(msr->mp, r->connection->bucket_alloc);
|
||||
if (bb_in == NULL) return -1;
|
||||
do {
|
||||
apr_status_t rc;
|
||||
|
||||
rc = ap_get_brigade(r->input_filters, bb_in, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
|
||||
if (rc != APR_SUCCESS) {
|
||||
/* NOTE Apache returns -3 here when the request is too large
|
||||
* and APR_EGENERAL when the client disconnects.
|
||||
*/
|
||||
switch(rc) {
|
||||
case APR_TIMEUP :
|
||||
return -4;
|
||||
break;
|
||||
case -3 :
|
||||
*error_msg = apr_psprintf(msr->mp, "Error reading request body: HTTP Error 413 - Request entity too large. (Most likely.)");
|
||||
rc = -3;
|
||||
break;
|
||||
case APR_EGENERAL :
|
||||
*error_msg = apr_psprintf(msr->mp, "Error reading request body: Client went away.");
|
||||
rc = -2;
|
||||
break;
|
||||
default :
|
||||
*error_msg = apr_psprintf(msr->mp, "Error reading request body: %s", get_apr_error(msr->mp, rc));
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*error_msg) msr_log(msr, 1, "%s", *error_msg);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Loop through the buckets in the brigade in order
|
||||
* to extract the size of the data available.
|
||||
*/
|
||||
for(bucket = APR_BRIGADE_FIRST(bb_in);
|
||||
bucket != APR_BRIGADE_SENTINEL(bb_in);
|
||||
bucket = APR_BUCKET_NEXT(bucket))
|
||||
{
|
||||
const char *buf;
|
||||
apr_size_t buflen;
|
||||
|
||||
rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Input filter: Failed reading input / bucket (%i): %s",
|
||||
rc, get_apr_error(msr->mp, rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "Input filter: Bucket type %s contains %i bytes.",
|
||||
bucket->type->name, buflen);
|
||||
}
|
||||
|
||||
/* Check request body limit (should only trigger on chunked requests). */
|
||||
if (msr->reqbody_length + buflen > (apr_size_t)msr->txcfg->reqbody_limit) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Requests body is larger than the "
|
||||
"configured limit (%lu).", msr->txcfg->reqbody_limit);
|
||||
return -5;
|
||||
}
|
||||
|
||||
if (buflen != 0) {
|
||||
if (modsecurity_request_body_store(msr, buf, buflen) < 0) {
|
||||
// TODO err
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr->reqbody_length += buflen;
|
||||
}
|
||||
|
||||
if (APR_BUCKET_IS_EOS(bucket)) {
|
||||
seen_eos = 1;
|
||||
}
|
||||
}
|
||||
|
||||
apr_brigade_cleanup(bb_in);
|
||||
} while(!seen_eos);
|
||||
|
||||
modsecurity_request_body_end(msr);
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Input filter: Completed receiving request body (length %lu).",
|
||||
msr->reqbody_length);
|
||||
}
|
||||
|
||||
msr->if_status = IF_STATUS_WANTS_TO_RUN;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* -- Output filter -- */
|
||||
|
||||
/**
|
||||
* Sends a brigade with an error bucket down the filter chain.
|
||||
*/
|
||||
static apr_status_t send_error_bucket(ap_filter_t *f, int status) {
|
||||
apr_bucket_brigade *brigade = NULL;
|
||||
apr_bucket *bucket = NULL;
|
||||
|
||||
brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
|
||||
if (brigade == NULL) return APR_EGENERAL;
|
||||
bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc);
|
||||
if (bucket == NULL) return APR_EGENERAL;
|
||||
APR_BRIGADE_INSERT_TAIL(brigade, bucket);
|
||||
bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc);
|
||||
if (bucket == NULL) return APR_EGENERAL;
|
||||
APR_BRIGADE_INSERT_TAIL(brigade, bucket);
|
||||
|
||||
return ap_pass_brigade(f->next, brigade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines the configuration and the response MIME type
|
||||
* in order to determine whether output buffering should
|
||||
* run or not.
|
||||
*/
|
||||
static int output_filter_should_run(modsec_rec *msr, request_rec *r) {
|
||||
char *content_type = NULL;
|
||||
|
||||
/* Check configuration. */
|
||||
if (msr->txcfg->resbody_access != 1) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Output filter: Response body buffering is not enabled.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check MIME type. */
|
||||
|
||||
if ((msr->txcfg->of_mime_types == NULL)||(msr->txcfg->of_mime_types == NOT_SET_P)) {
|
||||
msr_log(msr, 1, "Output filter: MIME type structures are corrupted (internal error).");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (r->content_type != NULL) {
|
||||
char *p = NULL;
|
||||
|
||||
content_type = apr_pstrdup(msr->mp, r->content_type);
|
||||
if (content_type == NULL) return -1;
|
||||
|
||||
/* Hide the character encoding information
|
||||
* if present. Sometimes the content type header
|
||||
* looks like this "text/html; charset=xyz" ...
|
||||
*/
|
||||
p = strstr(content_type, ";");
|
||||
if (p != NULL) {
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
strtolower_inplace(content_type);
|
||||
|
||||
if (strcmp(content_type, "text/html") == 0) {
|
||||
/* Useful information to have should we later
|
||||
* decide to do something with the HTML output.
|
||||
*/
|
||||
msr->resbody_contains_html = 1;
|
||||
}
|
||||
} else {
|
||||
content_type = "null";
|
||||
}
|
||||
|
||||
if (apr_table_get(msr->txcfg->of_mime_types, content_type) != NULL) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the output filter.
|
||||
*/
|
||||
static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f,
|
||||
apr_bucket_brigade *bb_in)
|
||||
{
|
||||
request_rec *r = f->r;
|
||||
const char *s_content_length = NULL;
|
||||
apr_status_t rc;
|
||||
|
||||
msr->of_brigade = apr_brigade_create(msr->mp, f->c->bucket_alloc);
|
||||
if (msr->of_brigade == NULL) return -1;
|
||||
msr->of_status = OF_STATUS_IN_PROGRESS;
|
||||
|
||||
rc = output_filter_should_run(msr, r);
|
||||
if (rc < 0) return -1;
|
||||
if (rc == 0) return 0;
|
||||
|
||||
/* Look up the Content-Length header to see if we know
|
||||
* the amount of data coming our way. If we do and if
|
||||
* it's too much we might want to stop processing right here.
|
||||
*/
|
||||
s_content_length = apr_table_get(r->headers_out, "Content-Length");
|
||||
if (s_content_length == NULL) {
|
||||
/* Try this too, mod_cgi seems to put headers there */
|
||||
s_content_length = apr_table_get(r->err_headers_out, "Content-Length");
|
||||
}
|
||||
|
||||
if (s_content_length != NULL) {
|
||||
long int len;
|
||||
|
||||
len = strtol(s_content_length, NULL, 10);
|
||||
if ((len == LONG_MIN)||(len == LONG_MAX)||(len < 0)||(len >= 1073741824)) {
|
||||
msr_log(msr, 1, "Output filter: Invalid Content-Length: %s", log_escape_nq(r->pool, (char *)s_content_length));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Output filter: Skipping response since Content-Length is zero.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (len > msr->txcfg->of_limit) {
|
||||
msr_log(msr, 1, "Output filter: Content-Length (%s) over the limit (%lu).", log_escape_nq(r->pool, (char *)s_content_length), msr->txcfg->of_limit);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output filter.
|
||||
*/
|
||||
apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) {
|
||||
request_rec *r = f->r;
|
||||
modsec_rec *msr = (modsec_rec *)f->ctx;
|
||||
apr_bucket *bucket;
|
||||
apr_status_t rc;
|
||||
|
||||
if (msr == NULL) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server,
|
||||
"ModSecurity: Internal Error: msr is null in output filter.");
|
||||
ap_remove_output_filter(f);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Output filter: Receiving output (f %x, r %x).", f, f->r);
|
||||
}
|
||||
|
||||
/* Initialise on first invocation */
|
||||
if (msr->of_status == OF_STATUS_NOT_STARTED) {
|
||||
/* Update our context from the request structure. */
|
||||
msr->r = r;
|
||||
msr->response_status = r->status;
|
||||
msr->status_line = ((r->status_line != NULL)
|
||||
? r->status_line : ap_get_status_line(r->status));
|
||||
msr->response_protocol = get_response_protocol(r);
|
||||
msr->response_headers = apr_table_overlay(msr->mp, r->err_headers_out, r->headers_out);
|
||||
|
||||
/* Process phase RESPONSE_HEADERS */
|
||||
rc = modsecurity_process_phase(msr, PHASE_RESPONSE_HEADERS);
|
||||
if (rc < 0) { /* error */
|
||||
ap_remove_output_filter(f);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
if (rc > 0) { /* transaction needs to be interrupted */
|
||||
int status = perform_interception(msr);
|
||||
if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
|
||||
ap_remove_output_filter(f);
|
||||
return send_error_bucket(f, status);
|
||||
}
|
||||
}
|
||||
|
||||
/* Decide whether to observe the response body. */
|
||||
rc = output_filter_init(msr, f, bb_in);
|
||||
switch(rc) {
|
||||
case -2 : /* response too large */
|
||||
case -1 : /* error */
|
||||
/* there's something wrong with this response */
|
||||
ap_remove_output_filter(f);
|
||||
msr->of_status = OF_STATUS_COMPLETE;
|
||||
msr->resbody_status = RESBODY_STATUS_ERROR;
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
case 0 :
|
||||
/* We do not want to observe this response body
|
||||
* but we need to remain attached to observe
|
||||
* when it is completed so that we can run
|
||||
* the RESPONSE_BODY phase.
|
||||
*/
|
||||
msr->of_skipping = 1;
|
||||
msr->resbody_status = RESBODY_STATUS_NOT_READ;
|
||||
break;
|
||||
default :
|
||||
/* Continue (observe the response body). */
|
||||
break;
|
||||
}
|
||||
} else
|
||||
if (msr->of_status == OF_STATUS_COMPLETE) {
|
||||
msr_log(msr, 1, "Output filter: Internal error: output filtering complete yet filter was invoked.");
|
||||
ap_remove_output_filter(f);
|
||||
return APR_EGENERAL;
|
||||
}
|
||||
|
||||
/* Loop through the buckets in the brigade in order
|
||||
* to extract the size of the data available.
|
||||
*/
|
||||
for(bucket = APR_BRIGADE_FIRST(bb_in);
|
||||
bucket != APR_BRIGADE_SENTINEL(bb_in);
|
||||
bucket = APR_BUCKET_NEXT(bucket)) {
|
||||
const char *buf;
|
||||
apr_size_t buflen;
|
||||
|
||||
if (msr->of_skipping == 0) {
|
||||
rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr->of_status = OF_STATUS_COMPLETE;
|
||||
msr->resbody_status = RESBODY_STATUS_ERROR;
|
||||
msr_log(msr, 1, "Output filter: Failed to read bucket (rc %i): %s",
|
||||
rc, get_apr_error(r->pool, rc));
|
||||
ap_remove_output_filter(f);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "Output filter: Bucket type %s contains %i bytes.",
|
||||
bucket->type->name, buflen);
|
||||
}
|
||||
|
||||
if (msr->resbody_length > (apr_size_t)msr->txcfg->of_limit) {
|
||||
msr_log(msr, 1, "Output filter: Response body too large (over limit of %lu, total length not known).",
|
||||
msr->txcfg->of_limit);
|
||||
msr->of_status = OF_STATUS_COMPLETE;
|
||||
msr->resbody_status = RESBODY_STATUS_PARTIAL;
|
||||
ap_remove_output_filter(f);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
msr->resbody_length += buflen;
|
||||
}
|
||||
|
||||
if (APR_BUCKET_IS_EOS(bucket)) {
|
||||
msr->of_done_reading = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add buckets in this brigade to the brigade
|
||||
* we have in the context, but only if we actually
|
||||
* want to keep the response body.
|
||||
*/
|
||||
if (msr->of_skipping == 0) {
|
||||
ap_save_brigade(f, &msr->of_brigade, &bb_in, msr->mp);
|
||||
|
||||
if (msr->of_done_reading == 0) {
|
||||
/* We are done for now. We will be called again with more data. */
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Output filter: Completed receiving response (length %lu).",
|
||||
msr->resbody_length);
|
||||
}
|
||||
} else {
|
||||
if (msr->of_done_reading == 0) {
|
||||
return ap_pass_brigade(f->next, bb_in);
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Output filter: Completed receiving response.");
|
||||
}
|
||||
}
|
||||
|
||||
/* We're not coming back here. */
|
||||
msr->of_status = OF_STATUS_COMPLETE;
|
||||
ap_remove_output_filter(f);
|
||||
|
||||
if (msr->of_skipping == 0) {
|
||||
/* We've done with reading, it's time to inspect the data. */
|
||||
msr->resbody_status = RESBODY_STATUS_READ_BRIGADE;
|
||||
|
||||
if (msr->resbody_length + 1 <= 0) {
|
||||
msr_log(msr, 1, "Output filter: Invalid response length: %lu", msr->resbody_length);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
msr->resbody_data = apr_palloc(msr->mp, msr->resbody_length + 1);
|
||||
if (msr->resbody_data == NULL) {
|
||||
msr_log(msr, 1, "Output filter: Response body data memory allocation failed. Asked for: %li",
|
||||
msr->resbody_length + 1);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// TODO Why does the function below take pointer to length? Will it modify it?
|
||||
|
||||
rc = apr_brigade_flatten(msr->of_brigade, msr->resbody_data, &msr->resbody_length);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Output filter: Failed to flatten brigade (%i): %s", rc,
|
||||
get_apr_error(r->pool, rc));
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
msr->resbody_data[msr->resbody_length] = '\0';
|
||||
msr->resbody_status = RESBODY_STATUS_READ;
|
||||
}
|
||||
|
||||
/* Process phase RESPONSE_BODY */
|
||||
rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY);
|
||||
if (rc < 0) {
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
if (rc > 0) {
|
||||
int status = perform_interception(msr);
|
||||
if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
|
||||
return send_error_bucket(f, status);
|
||||
}
|
||||
}
|
||||
|
||||
if (msr->of_skipping == 0) {
|
||||
record_time_checkpoint(msr, 3);
|
||||
|
||||
rc = ap_pass_brigade(f->next, msr->of_brigade);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Output filter: Error while forwarding response data (%i): %s",
|
||||
rc, get_apr_error(msr->mp, rc));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Another job well done! */
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Output filter: Output forwarding complete.");
|
||||
}
|
||||
|
||||
if (msr->of_skipping == 0) {
|
||||
return APR_SUCCESS;
|
||||
} else {
|
||||
return ap_pass_brigade(f->next, bb_in);
|
||||
}
|
||||
}
|
322
apache2/apache2_util.c
Normal file
322
apache2/apache2_util.c
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: apache2_util.c,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "modsecurity.h"
|
||||
#include "apache2.h"
|
||||
#include "http_core.h"
|
||||
#include "util_script.h"
|
||||
|
||||
/**
|
||||
* Execute system command. First line of the output will be returned in
|
||||
* the "output" parameter.
|
||||
*/
|
||||
int apache2_exec(modsec_rec *msr, const char *command, const char **argv, char **output) {
|
||||
apr_procattr_t *procattr = NULL;
|
||||
apr_proc_t *procnew = NULL;
|
||||
apr_status_t rc = APR_SUCCESS;
|
||||
const char *const *env = NULL;
|
||||
apr_file_t *script_out = NULL;
|
||||
request_rec *r = msr->r;
|
||||
|
||||
if (argv == NULL) {
|
||||
argv = apr_pcalloc(r->pool, 3 * sizeof(char *));
|
||||
argv[0] = command;
|
||||
argv[1] = NULL;
|
||||
}
|
||||
|
||||
ap_add_cgi_vars(r);
|
||||
ap_add_common_vars(r);
|
||||
|
||||
/* PHP hack, getting around its silly security checks. */
|
||||
apr_table_add(r->subprocess_env, "PATH_TRANSLATED", command);
|
||||
apr_table_add(r->subprocess_env, "REDIRECT_STATUS", "302");
|
||||
|
||||
env = (const char * const *)ap_create_environment(r->pool, r->subprocess_env);
|
||||
if (env == NULL) {
|
||||
msr_log(msr, 1, "Exec: Unable to create environment.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
procnew = apr_pcalloc(r->pool, sizeof(*procnew));
|
||||
if (procnew == NULL) {
|
||||
msr_log(msr, 1, "Exec: Unable to allocate %i bytes.", sizeof(*procnew));
|
||||
return -1;
|
||||
}
|
||||
|
||||
apr_procattr_create(&procattr, r->pool);
|
||||
if (procattr == NULL) {
|
||||
msr_log(msr, 1, "Exec: Unable to create procattr.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
apr_procattr_io_set(procattr, APR_NO_PIPE, APR_FULL_BLOCK, APR_NO_PIPE);
|
||||
|
||||
rc = apr_proc_create(procnew, command, argv, env, procattr, r->pool);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Exec: Execution failed: %s (%s)", log_escape_nq(r->pool, command),
|
||||
get_apr_error(r->pool, rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
apr_pool_note_subprocess(r->pool, procnew, APR_KILL_AFTER_TIMEOUT);
|
||||
|
||||
script_out = procnew->out;
|
||||
if (!script_out) {
|
||||
msr_log(msr, 1, "Exec: Failed to get script output pipe.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
apr_file_pipe_timeout_set(script_out, r->server->timeout);
|
||||
|
||||
/* Now read from the pipe. */
|
||||
{
|
||||
char buf[260] = "";
|
||||
char *p = buf;
|
||||
apr_size_t nbytes = 255;
|
||||
apr_status_t rc2;
|
||||
|
||||
rc2 = apr_file_read(script_out, buf, &nbytes);
|
||||
if (rc2 == APR_SUCCESS) {
|
||||
buf[nbytes] = 0;
|
||||
|
||||
/* if there is more than one line ignore them */
|
||||
while(*p != 0) {
|
||||
if (*p == 0x0a) *p = 0;
|
||||
p++;
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "Exec: First line from script output: \"%s\"",
|
||||
log_escape(r->pool, buf));
|
||||
|
||||
if (output != NULL) *output = apr_pstrdup(r->pool, buf);
|
||||
|
||||
/* Soak up the remaining data. */
|
||||
nbytes = 255;
|
||||
while(apr_file_read(script_out, buf, &nbytes) == APR_SUCCESS) nbytes = 255;
|
||||
} else {
|
||||
msr_log(msr, 1, "Exec: Execution failed: %s (%s)", log_escape_nq(r->pool, command),
|
||||
get_apr_error(r->pool, rc2));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
apr_proc_wait(procnew, NULL, NULL, APR_WAIT);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the current time and store for later.
|
||||
*/
|
||||
void record_time_checkpoint(modsec_rec *msr, int checkpoint_no) {
|
||||
char note[100], note_name[100];
|
||||
apr_time_t now;
|
||||
|
||||
now = apr_time_now();
|
||||
switch(checkpoint_no) {
|
||||
case 1 :
|
||||
msr->time_checkpoint_1 = now;
|
||||
break;
|
||||
case 2 :
|
||||
msr->time_checkpoint_2 = now;
|
||||
break;
|
||||
case 3 :
|
||||
msr->time_checkpoint_3 = now;
|
||||
break;
|
||||
default :
|
||||
msr_log(msr, 1, "Internal Error: Unknown checkpoint: %i", checkpoint_no);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Apache-specific stuff. */
|
||||
apr_snprintf(note, 99, "%" APR_TIME_T_FMT, (now - msr->request_time));
|
||||
apr_snprintf(note_name, 99, "mod_security-time%i", checkpoint_no);
|
||||
apr_table_set(msr->r->notes, note_name, note);
|
||||
|
||||
msr_log(msr, 4, "Time #%i: %s", checkpoint_no, note);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new string that contains the error
|
||||
* message for the given return code.
|
||||
*/
|
||||
char *get_apr_error(apr_pool_t *p, apr_status_t rc) {
|
||||
char *text = apr_pcalloc(p, 201);
|
||||
if (text == NULL) return NULL;
|
||||
apr_strerror(rc, text, 200);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve named environment variable.
|
||||
*/
|
||||
char *get_env_var(request_rec *r, char *name) {
|
||||
char *result = (char *)apr_table_get(r->notes, name);
|
||||
|
||||
if (result == NULL) {
|
||||
result = (char *)apr_table_get(r->subprocess_env, name);
|
||||
}
|
||||
|
||||
if (result == NULL) {
|
||||
result = getenv(name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal log helper function. Use msr_log instead.
|
||||
*/
|
||||
void internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr,
|
||||
int level, const char *text, va_list ap)
|
||||
{
|
||||
apr_size_t nbytes, nbytes_written;
|
||||
apr_file_t *debuglog_fd = NULL;
|
||||
int filter_debug_level = 0;
|
||||
char str1[1024] = "";
|
||||
char str2[1256] = "";
|
||||
|
||||
/* Find the logging FD and look up the logging level in the configuration. */
|
||||
if (dcfg != NULL) {
|
||||
if ((dcfg->debuglog_fd != NULL)&&(dcfg->debuglog_fd != NOT_SET_P)) {
|
||||
debuglog_fd = dcfg->debuglog_fd;
|
||||
}
|
||||
if (dcfg->debuglog_level != NOT_SET) filter_debug_level = dcfg->debuglog_level;
|
||||
}
|
||||
|
||||
/* Return immediately if we don't have where to write
|
||||
* or if the log level of the message is higher than
|
||||
* wanted in the log.
|
||||
*/
|
||||
if ((level > 3)&&( (debuglog_fd == NULL) || (level > filter_debug_level) )) return;
|
||||
|
||||
/* Construct the message. */
|
||||
apr_vsnprintf(str1, sizeof(str1), text, ap);
|
||||
apr_snprintf(str2, sizeof(str2), "[%s] [%s/sid#%lx][rid#%lx][%s][%i] %s\n",
|
||||
current_logtime(msr->mp), ap_get_server_name(r), (unsigned long)(r->server),
|
||||
(unsigned long)r, ((r->uri == NULL) ? "" : log_escape_nq(msr->mp, r->uri)),
|
||||
level, str1);
|
||||
|
||||
/* Write to the debug log. */
|
||||
if ((debuglog_fd != NULL)&&(level <= filter_debug_level)) {
|
||||
nbytes = strlen(str2);
|
||||
apr_file_write_full(debuglog_fd, str2, nbytes, &nbytes_written);
|
||||
}
|
||||
|
||||
/* Send message levels 1-3 to the Apache error log too. */
|
||||
if (level <= 3) {
|
||||
char *unique_id = (char *)get_env_var(r, "UNIQUE_ID");
|
||||
char *hostname = (char *)r->hostname;
|
||||
|
||||
if (unique_id != NULL) {
|
||||
unique_id = apr_psprintf(msr->mp, " [unique_id \"%s\"]",
|
||||
log_escape(msr->mp, unique_id));
|
||||
}
|
||||
else unique_id = "";
|
||||
|
||||
if (hostname != NULL) {
|
||||
hostname = apr_psprintf(msr->mp, " [hostname \"%s\"]",
|
||||
log_escape(msr->mp, hostname));
|
||||
}
|
||||
else hostname = "";
|
||||
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server,
|
||||
"[client %s] ModSecurity: %s%s [uri \"%s\"]%s", r->connection->remote_ip, str1,
|
||||
hostname, log_escape(msr->mp, r->unparsed_uri), unique_id);
|
||||
|
||||
/* Add this message to the list. */
|
||||
if (msr != NULL) {
|
||||
*(const char **)apr_array_push(msr->alerts) = apr_pstrdup(msr->mp, str1);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs one message at the given level to the debug log (and to the
|
||||
* Apache error log if the message is important enough.
|
||||
*/
|
||||
void msr_log(modsec_rec *msr, int level, const char *text, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, text);
|
||||
internal_log(msr->r, msr->txcfg, msr, level, text, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Apache error log message into one line of text.
|
||||
*/
|
||||
char *format_error_log_message(apr_pool_t *mp, error_message *em) {
|
||||
char *s_file = "", *s_line = "", *s_level = "";
|
||||
char *s_status = "", *s_message = "";
|
||||
char *msg = NULL;
|
||||
|
||||
if (em == NULL) return NULL;
|
||||
|
||||
if (em->file != NULL) {
|
||||
s_file = apr_psprintf(mp, "[file \"%s\"] ",
|
||||
log_escape(mp, (char *)em->file));
|
||||
if (s_file == NULL) return NULL;
|
||||
}
|
||||
|
||||
if (em->line > 0) {
|
||||
s_line = apr_psprintf(mp, "[line %i] ", em->line);
|
||||
if (s_line == NULL) return NULL;
|
||||
}
|
||||
|
||||
s_level = apr_psprintf(mp, "[level %i] ", em->level);
|
||||
if (s_level == NULL) return NULL;
|
||||
|
||||
if (em->status != 0) {
|
||||
s_status = apr_psprintf(mp, "[status %i] ", em->status);
|
||||
if (s_status == NULL) return NULL;
|
||||
}
|
||||
|
||||
if (em->message != NULL) {
|
||||
s_message = log_escape_nq(mp, em->message);
|
||||
if (s_message == NULL) return NULL;
|
||||
}
|
||||
|
||||
msg = apr_psprintf(mp, "%s%s%s%s%s", s_file, s_line, s_level, s_status, s_message);
|
||||
if (msg == NULL) return NULL;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the reponse protocol Apache will use (or has used)
|
||||
* to respond to the given request.
|
||||
*/
|
||||
const char *get_response_protocol(request_rec *r) {
|
||||
int proto_num = r->proto_num;
|
||||
|
||||
if (r->assbackwards) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (proto_num > HTTP_VERSION(1,0)
|
||||
&& apr_table_get(r->subprocess_env, "downgrade-1.0"))
|
||||
{
|
||||
proto_num = HTTP_VERSION(1,0);
|
||||
}
|
||||
|
||||
if (proto_num == HTTP_VERSION(1,0)
|
||||
&& apr_table_get(r->subprocess_env, "force-response-1.0"))
|
||||
{
|
||||
return "HTTP/1.0";
|
||||
}
|
||||
|
||||
return AP_SERVER_PROTOCOL;
|
||||
}
|
26
apache2/api/README
Normal file
26
apache2/api/README
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
This directory contains two examples how you can extend
|
||||
ModSecurity without having to touch it directly, simply
|
||||
by creating custom Apache modules.
|
||||
|
||||
1)
|
||||
|
||||
Module mod_tfn_reverse.c creates a custom transformation
|
||||
function "reverse" that reverses the content it receives
|
||||
on input.
|
||||
|
||||
To compile simply do:
|
||||
|
||||
apxs -cia mod_tfn_reverse.c
|
||||
|
||||
2)
|
||||
|
||||
Module mod_op_strstr.c creates a custom operator "strstr"
|
||||
that implements fast matching using the Boyer-Moore-Horspool
|
||||
algorithm.
|
||||
|
||||
Compiling this module is more involved because it requires
|
||||
access to ModSecurity structures. For example:
|
||||
|
||||
apxs -I<MODSECURITY_SOURCE_CODE> -I/usr/include/libxml2 -cia mod_op_strstr.c
|
||||
|
176
apache2/api/mod_op_strstr.c
Normal file
176
apache2/api/mod_op_strstr.c
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
#include "httpd.h"
|
||||
#include "http_core.h"
|
||||
#include "http_config.h"
|
||||
#include "http_log.h"
|
||||
#include "http_protocol.h"
|
||||
#include "ap_config.h"
|
||||
#include "apr_optional.h"
|
||||
|
||||
#include "modsecurity.h"
|
||||
|
||||
#define ALPHABET_SIZE 256
|
||||
#define MAX_PATTERN_SIZE 64
|
||||
|
||||
APR_DECLARE_OPTIONAL_FN(void, modsec_register_operator,
|
||||
(const char *name, void *fn_init, void *fn_exec));
|
||||
|
||||
static void initBoyerMooreHorspool(const char *pattern, int patlength,
|
||||
int *bm_badcharacter_array);
|
||||
|
||||
static int BoyerMooreHorspool(const char *pattern, int patlength,
|
||||
const char *text, int textlen, int *bm_badcharacter_array);
|
||||
|
||||
/**
|
||||
* Operator parameter initialisation entry point.
|
||||
*/
|
||||
static int op_strstr_init(msre_rule *rule, char **error_msg) {
|
||||
/* Operator initialisation function will be called once per
|
||||
* statement where operator is used. It is meant to be used
|
||||
* to check the parameters to see whether they are present
|
||||
* and if they are in the correct format.
|
||||
*/
|
||||
|
||||
/* In this example we just look for a simple non-empty parameter. */
|
||||
if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'strstr'.");
|
||||
return 0; /* ERROR */
|
||||
}
|
||||
|
||||
/* If you need to transform the data in the parameter into something
|
||||
* else you should do that here. Simply create a new structure to hold
|
||||
* the transformed data and place the pointer to it into rule->op_param_data.
|
||||
* You will have access to this pointer later on.
|
||||
*/
|
||||
rule->op_param_data = apr_pcalloc(rule->ruleset->mp, ALPHABET_SIZE * sizeof(int));
|
||||
initBoyerMooreHorspool(rule->op_param, strlen(rule->op_param), (int *)rule->op_param_data);
|
||||
|
||||
/* OK */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Operator execution entry point.
|
||||
*/
|
||||
static int op_strstr_exec(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
|
||||
char *valuecopy = NULL;
|
||||
|
||||
/* Here we need to inspect the contents of the supplied variable. */
|
||||
|
||||
/* In a general case it is possible for the value
|
||||
* to be NULL. What you need to do in this case
|
||||
* depends on your operator. In this example we return
|
||||
* a "no match" response.
|
||||
*/
|
||||
if (var->value == NULL) return 0; /* No match. */
|
||||
|
||||
/* Another thing to note is that variables are not C strings,
|
||||
* meaning the NULL byte is not used to determine the end
|
||||
* of the string. Variable length var->value_len should be
|
||||
* used for this purpose.
|
||||
*/
|
||||
|
||||
if (BoyerMooreHorspool(rule->op_param, strlen(rule->op_param),
|
||||
var->value, var->value_len, (int *)rule->op_param_data) >= 0)
|
||||
{
|
||||
return 1; /* Match. */
|
||||
}
|
||||
|
||||
return 0; /* No match. */
|
||||
}
|
||||
|
||||
static int hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_temp) {
|
||||
void (*fn)(const char *name, void *fn_init, void *fn_exec);
|
||||
|
||||
/* Look for the registration function
|
||||
* exported by ModSecurity.
|
||||
*/
|
||||
fn = APR_RETRIEVE_OPTIONAL_FN(modsec_register_operator);
|
||||
if (fn) {
|
||||
/* Use it to register our new
|
||||
* transformation function under the
|
||||
* name "reverse".
|
||||
*/
|
||||
fn("strstr", (void *)op_strstr_init, (void *)op_strstr_exec);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static void register_hooks(apr_pool_t *p) {
|
||||
ap_hook_pre_config(hook_pre_config, NULL, NULL, APR_HOOK_LAST);
|
||||
}
|
||||
|
||||
/* Dispatch list for API hooks */
|
||||
module AP_MODULE_DECLARE_DATA op_strstr_module = {
|
||||
STANDARD20_MODULE_STUFF,
|
||||
NULL, /* create per-dir config structures */
|
||||
NULL, /* merge per-dir config structures */
|
||||
NULL, /* create per-server config structures */
|
||||
NULL, /* merge per-server config structures */
|
||||
NULL, /* table of config file commands */
|
||||
register_hooks /* register hooks */
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
This example uses an implementation Boyer-Moore-Horspool
|
||||
matching algorithm as implemented in Streamline (http://ffpf.sourceforge.net).
|
||||
|
||||
Copyright (c) 2004-2006, Vrije Universiteit Amsterdam
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
Neither the name of the Vrije Universiteit nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
*/
|
||||
|
||||
static void precompute_badcharacter(const char *pattern, int patlength,
|
||||
int bm_badcharacter_array[])
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ALPHABET_SIZE; ++i) {
|
||||
bm_badcharacter_array[i] = patlength;
|
||||
}
|
||||
|
||||
for (i = 0; i < patlength - 1; ++i){
|
||||
bm_badcharacter_array[(uint8_t)pattern[i]] = patlength - i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void initBoyerMooreHorspool(const char *pattern, int patlength,
|
||||
int *bm_badcharacter_array)
|
||||
{
|
||||
precompute_badcharacter(pattern,
|
||||
(patlength < MAX_PATTERN_SIZE ? patlength : MAX_PATTERN_SIZE), bm_badcharacter_array);
|
||||
}
|
||||
|
||||
static int BoyerMooreHorspool(const char *pattern, int patlength,
|
||||
const char *text, int textlen, int *bm_badcharacter_array)
|
||||
{
|
||||
int j;
|
||||
char c;
|
||||
|
||||
j = 0;
|
||||
while (j <= textlen - patlength) {
|
||||
c = text[j + patlength - 1];
|
||||
if (pattern[patlength - 1] == c && memcmp(pattern, text + j, patlength - 1) == 0) {
|
||||
return j;
|
||||
}
|
||||
j += bm_badcharacter_array[(uint8_t)c];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
86
apache2/api/mod_tfn_reverse.c
Normal file
86
apache2/api/mod_tfn_reverse.c
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
#include "httpd.h"
|
||||
#include "http_core.h"
|
||||
#include "http_config.h"
|
||||
#include "http_log.h"
|
||||
#include "http_protocol.h"
|
||||
#include "ap_config.h"
|
||||
#include "apr_optional.h"
|
||||
|
||||
APR_DECLARE_OPTIONAL_FN(void, modsec_register_tfn, (const char *name, void *fn));
|
||||
|
||||
/**
|
||||
* This function will be invoked by
|
||||
* ModSecurity to transform input.
|
||||
*/
|
||||
static int reverse(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
/* Transformation functions can choose to do their
|
||||
* thing in-place, overwriting the existing content. This
|
||||
* is normally possible only if the transformed content
|
||||
* is of equal length or shorter.
|
||||
*
|
||||
* If you need to expand the content use the temporary
|
||||
* memory pool mptmp to allocate the space.
|
||||
*/
|
||||
|
||||
/* Reverse the string in place, but only if it's long enough. */
|
||||
if (input_len > 1) {
|
||||
long int i = 0;
|
||||
long int j = input_len - 1;
|
||||
while(i < j) {
|
||||
char c = input[i];
|
||||
input[i] = input[j];
|
||||
input[j] = c;
|
||||
i++;
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tell ModSecurity about the content
|
||||
* we have generated. In this case we
|
||||
* merely point back to the input buffer.
|
||||
*/
|
||||
*rval = (char *)input;
|
||||
*rval_len = input_len;
|
||||
|
||||
/* Must return 1 if the content was
|
||||
* changed, or 0 otherwise.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_temp) {
|
||||
void (*fn)(const char *name, void *fn);
|
||||
|
||||
/* Look for the registration function
|
||||
* exported by ModSecurity.
|
||||
*/
|
||||
fn = APR_RETRIEVE_OPTIONAL_FN(modsec_register_tfn);
|
||||
if (fn) {
|
||||
/* Use it to register our new
|
||||
* transformation function under the
|
||||
* name "reverse".
|
||||
*/
|
||||
fn("reverse", (void *)reverse);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static void register_hooks(apr_pool_t *p) {
|
||||
ap_hook_pre_config(hook_pre_config, NULL, NULL, APR_HOOK_LAST);
|
||||
}
|
||||
|
||||
/* Dispatch list for API hooks */
|
||||
module AP_MODULE_DECLARE_DATA tfn_reverse_module = {
|
||||
STANDARD20_MODULE_STUFF,
|
||||
NULL, /* create per-dir config structures */
|
||||
NULL, /* merge per-dir config structures */
|
||||
NULL, /* create per-server config structures */
|
||||
NULL, /* merge per-server config structures */
|
||||
NULL, /* table of config file commands */
|
||||
register_hooks /* register hooks */
|
||||
};
|
||||
|
991
apache2/mod_security2.c
Normal file
991
apache2/mod_security2.c
Normal file
@ -0,0 +1,991 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: mod_security2.c,v 1.11 2006/12/15 15:06:04 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include <limits.h>
|
||||
|
||||
#include "http_core.h"
|
||||
#include "http_request.h"
|
||||
|
||||
#include "modsecurity.h"
|
||||
#include "apache2.h"
|
||||
#include "msc_logging.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
|
||||
/* ModSecurity structure */
|
||||
|
||||
msc_engine DSOLOCAL *modsecurity = NULL;
|
||||
|
||||
|
||||
/* Global module variables; these are used for the Apache-specific functionality */
|
||||
|
||||
char DSOLOCAL *chroot_dir = NULL;
|
||||
|
||||
unsigned int DSOLOCAL chroot_completed = 0;
|
||||
|
||||
char DSOLOCAL *new_server_signature = NULL;
|
||||
|
||||
char DSOLOCAL *real_server_signature = NULL;
|
||||
|
||||
char DSOLOCAL *guardianlog_name = NULL;
|
||||
|
||||
apr_file_t DSOLOCAL *guardianlog_fd = NULL;
|
||||
|
||||
char DSOLOCAL *guardianlog_condition = NULL;
|
||||
|
||||
|
||||
/* -- Miscellaneous functions -- */
|
||||
|
||||
/**
|
||||
* Intercepts transaction, using the method specified
|
||||
* in the structure itself. MUST return an HTTP status code,
|
||||
* which will be used to terminate the transaction.
|
||||
*/
|
||||
int perform_interception(modsec_rec *msr) {
|
||||
msre_actionset *actionset = NULL;
|
||||
const char *message = NULL;
|
||||
const char *phase_text = "";
|
||||
int status = DECLINED;
|
||||
int log_level = 1;
|
||||
|
||||
/* Sanity checks first. */
|
||||
|
||||
if (msr->was_intercepted == 0) {
|
||||
msr_log(msr, 1, "Internal Error: Asked to intercept request but was_intercepted is zero");
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if (msr->phase > 4) {
|
||||
msr_log(msr, 1, "Internal Error: Asked to intercept request in phase %i.", msr->phase);
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
/* OK, we're good to go. */
|
||||
|
||||
actionset = msr->intercept_actionset;
|
||||
phase_text = apr_psprintf(msr->mp, " (phase %i)", msr->phase);
|
||||
|
||||
/* By default we log at level 1 but we switch to 4
|
||||
* if a nolog action was used to hide the message.
|
||||
*/
|
||||
log_level = (actionset->log != 1) ? 4 : 1;
|
||||
|
||||
/* Pause the request first (if configured to do so). */
|
||||
if (actionset->intercept_pause) {
|
||||
msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for "
|
||||
"%i msec.", actionset->intercept_pause);
|
||||
/* apr_sleep accepts microseconds */
|
||||
apr_sleep((apr_interval_time_t)(actionset->intercept_pause * 1000));
|
||||
}
|
||||
|
||||
/* Determine how to respond and prepare the log message. */
|
||||
switch(actionset->intercept_action) {
|
||||
case ACTION_DENY :
|
||||
if (actionset->intercept_status != 0) {
|
||||
status = actionset->intercept_status;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code %i%s.", status,
|
||||
phase_text);
|
||||
} else {
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Internal Error: Invalid status code requested %i).", phase_text,
|
||||
actionset->intercept_status);
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_PROXY :
|
||||
if (msr->phase < 3) {
|
||||
if (ap_find_linked_module("mod_proxy.c") == NULL) {
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Configuration Error: Proxy action to %s requested but mod_proxy not found).",
|
||||
phase_text, log_escape_nq(msr->mp, actionset->intercept_uri));
|
||||
} else {
|
||||
msr->r->filename = apr_psprintf(msr->mp, "proxy:%s", actionset->intercept_uri);
|
||||
msr->r->proxyreq = PROXYREQ_REVERSE;
|
||||
msr->r->handler = "proxy-server";
|
||||
status = OK;
|
||||
message = apr_psprintf(msr->mp, "Access denied using proxy to %s%s.",
|
||||
phase_text, log_escape_nq(msr->mp, actionset->intercept_uri));
|
||||
}
|
||||
} else {
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Configuration Error: Proxy action requested but it does not work in output phases).",
|
||||
phase_text);
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_DROP :
|
||||
/* ENH This does not seem to work on Windows. Is there a
|
||||
* better way to drop a connection anyway?
|
||||
*/
|
||||
#ifndef WIN32
|
||||
{
|
||||
extern module core_module;
|
||||
apr_socket_t *csd = ap_get_module_config(msr->r->connection->conn_config,
|
||||
&core_module);
|
||||
|
||||
if (csd) {
|
||||
if (apr_socket_close(csd) == APR_SUCCESS) {
|
||||
status = HTTP_FORBIDDEN;
|
||||
message = apr_psprintf(msr->mp, "Access denied with connection close%s.",
|
||||
phase_text);
|
||||
} else {
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Error: Connection drop requested but failed to close the "
|
||||
" socket).", phase_text);
|
||||
}
|
||||
} else {
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Error: Connection drop requested but socket not found.",
|
||||
phase_text);
|
||||
}
|
||||
}
|
||||
#else
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Error: Connection drop not implemented on this platform).",
|
||||
phase_text);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ACTION_REDIRECT :
|
||||
apr_table_setn(msr->r->headers_out, "Location", actionset->intercept_uri);
|
||||
if ((actionset->intercept_status == 301)||(actionset->intercept_status == 302)
|
||||
||(actionset->intercept_status == 303)||(actionset->intercept_status == 307))
|
||||
{
|
||||
status = actionset->intercept_status;
|
||||
} else {
|
||||
status = HTTP_MOVED_TEMPORARILY;
|
||||
}
|
||||
message = apr_psprintf(msr->mp, "Access denied with redirection to %s using "
|
||||
"status %i%s.", log_escape_nq(msr->mp, actionset->intercept_uri), status,
|
||||
phase_text);
|
||||
break;
|
||||
|
||||
case ACTION_ALLOW :
|
||||
status = DECLINED;
|
||||
message = apr_psprintf(msr->mp, "Access allowed%s.", phase_text);
|
||||
break;
|
||||
|
||||
default :
|
||||
log_level = 1;
|
||||
status = HTTP_INTERNAL_SERVER_ERROR;
|
||||
message = apr_psprintf(msr->mp, "Access denied with code 500%s "
|
||||
"(Internal Error: invalid interception action %i).",
|
||||
phase_text, actionset->intercept_action);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Log the message now. */
|
||||
msc_alert(msr, log_level, actionset, message, msr->intercept_message);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a previously stored transaction context by
|
||||
* looking at the main request, and the previous requests.
|
||||
*/
|
||||
static modsec_rec *retrieve_tx_context(const request_rec *r) {
|
||||
modsec_rec *msr = NULL;
|
||||
request_rec *rx = NULL;
|
||||
|
||||
/* Look in the current request first. */
|
||||
msr = (modsec_rec *)apr_table_get(r->notes, NOTE_MSR);
|
||||
if (msr != NULL) {
|
||||
return msr;
|
||||
}
|
||||
|
||||
/* If this is a subrequest then look in the main request. */
|
||||
if (r->main != NULL) {
|
||||
msr = (modsec_rec *)apr_table_get(r->main->notes, NOTE_MSR);
|
||||
if (msr != NULL) {
|
||||
return msr;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the request was redirected then look in the previous requests. */
|
||||
rx = r->prev;
|
||||
while(rx != NULL) {
|
||||
msr = (modsec_rec *)apr_table_get(rx->notes, NOTE_MSR);
|
||||
if (msr != NULL) {
|
||||
return msr;
|
||||
}
|
||||
rx = rx->prev;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores transaction context where it can be found in subsequent
|
||||
* phases, redirections, or subrequests.
|
||||
*/
|
||||
static void store_tx_context(modsec_rec *msr, request_rec *r) {
|
||||
apr_table_setn(r->notes, NOTE_MSR, (void *)msr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new transaction context.
|
||||
*/
|
||||
static modsec_rec *create_tx_context(request_rec *r) {
|
||||
apr_allocator_t *allocator = NULL;
|
||||
modsec_rec *msr = NULL;
|
||||
|
||||
msr = (modsec_rec *)apr_pcalloc(r->pool, sizeof(modsec_rec));
|
||||
if (msr == NULL) return NULL;
|
||||
|
||||
apr_allocator_create(&allocator);
|
||||
apr_allocator_max_free_set(allocator, 1024);
|
||||
apr_pool_create_ex(&msr->mp, r->pool, NULL, allocator);
|
||||
if (msr->mp == NULL) return NULL;
|
||||
apr_allocator_owner_set(allocator, msr->mp);
|
||||
|
||||
msr->modsecurity = modsecurity;
|
||||
msr->r = r;
|
||||
msr->r_early = r;
|
||||
msr->request_time = r->request_time;
|
||||
msr->dcfg1 = (directory_config *)ap_get_module_config(r->per_dir_config,
|
||||
&security2_module);
|
||||
|
||||
/**
|
||||
* Create a special user configuration. This is where
|
||||
* explicit instructions will be stored. This will be used
|
||||
* to override the default settings (and to override the
|
||||
* configuration in the second phase, dcfg2, with the user
|
||||
* setting executed in the first phase.
|
||||
*/
|
||||
msr->usercfg = create_directory_config(msr->mp, NULL);
|
||||
if (msr->usercfg == NULL) return NULL;
|
||||
|
||||
/* Create a transaction context and populate
|
||||
* it using the directory config we just
|
||||
* got from Apache.
|
||||
*/
|
||||
msr->txcfg = create_directory_config(msr->mp, NULL);
|
||||
if (msr->txcfg == NULL) return NULL;
|
||||
|
||||
if (msr->dcfg1 != NULL) {
|
||||
msr->txcfg = merge_directory_configs(msr->mp, msr->txcfg, msr->dcfg1);
|
||||
if (msr->txcfg == NULL) return NULL;
|
||||
}
|
||||
init_directory_config(msr->txcfg);
|
||||
|
||||
msr->txid = get_env_var(r, "UNIQUE_ID");
|
||||
if (msr->txid == NULL) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server,
|
||||
"ModSecurity: ModSecurity requires mod_unique_id to be installed.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Initialising transaction (txid %s).", msr->txid);
|
||||
}
|
||||
|
||||
/* Populate tx fields */
|
||||
msr->error_messages = apr_array_make(msr->mp, 5, sizeof(error_message *));
|
||||
msr->alerts = apr_array_make(msr->mp, 5, sizeof(char *));
|
||||
|
||||
msr->server_software = real_server_signature;
|
||||
msr->local_addr = r->connection->local_ip;
|
||||
msr->local_port = r->connection->local_addr->port;
|
||||
|
||||
msr->remote_addr = r->connection->remote_ip;
|
||||
msr->remote_port = r->connection->remote_addr->port;
|
||||
|
||||
msr->request_line = r->the_request;
|
||||
msr->request_uri = r->uri;
|
||||
msr->request_method = r->method;
|
||||
msr->query_string = r->args;
|
||||
msr->request_protocol = r->protocol;
|
||||
msr->request_headers = apr_table_copy(msr->mp, r->headers_in);
|
||||
msr->hostname = ap_get_server_name(r);
|
||||
|
||||
/* Invoke the engine to continue with initialisation */
|
||||
if (modsecurity_tx_init(msr) < 0) return NULL;
|
||||
|
||||
store_tx_context(msr, r);
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Transaction context created (dcfg %x).", msr->dcfg1);
|
||||
}
|
||||
|
||||
return msr;
|
||||
}
|
||||
|
||||
|
||||
/* -- Hooks -- */
|
||||
|
||||
/*
|
||||
* Change the signature of the server if change was requested in
|
||||
* configuration. We do this by locating the signature in server
|
||||
* memory and writing over it.
|
||||
*/
|
||||
static apr_status_t change_server_signature(server_rec *s) {
|
||||
char *server_version = NULL;
|
||||
|
||||
if (new_server_signature == NULL) return 0;
|
||||
|
||||
server_version = (char *)ap_get_server_version();
|
||||
if (server_version == NULL) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s,
|
||||
"SecServerSignature: Apache returned null as signature.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(server_version) >= strlen(new_server_signature)) {
|
||||
strcpy(server_version, new_server_signature);
|
||||
}
|
||||
else {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s,
|
||||
"SecServerSignature: original signature too short. Please set "
|
||||
"ServerTokens to Full.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed at the end of server lifetime to cleanup the allocated resources.
|
||||
*/
|
||||
static apr_status_t module_cleanup(void *data) {
|
||||
modsecurity_shutdown(modsecurity);
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-configuration initialisation hook.
|
||||
*/
|
||||
static int hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_temp) {
|
||||
/* Initialise ModSecurity engine */
|
||||
modsecurity = modsecurity_create(mp, MODSEC_ONLINE);
|
||||
if (modsecurity == NULL) {
|
||||
/* ENH Since s not available, how do we log from here? stderr?
|
||||
* ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s,
|
||||
* "ModSecurity: Failed to initialise engine.");
|
||||
*/
|
||||
return HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main (post-configuration) module initialisation.
|
||||
*/
|
||||
static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_temp, server_rec *s) {
|
||||
void *init_flag = NULL;
|
||||
int first_time = 0;
|
||||
|
||||
/* Figure out if we are here for the first time */
|
||||
apr_pool_userdata_get(&init_flag, "modsecurity-init-flag", s->process->pool);
|
||||
if (init_flag == NULL) {
|
||||
first_time = 1;
|
||||
apr_pool_userdata_set((const void *)1, "modsecurity-init-flag",
|
||||
apr_pool_cleanup_null, s->process->pool);
|
||||
} else {
|
||||
modsecurity_init(modsecurity, mp);
|
||||
}
|
||||
|
||||
/* Store the original server signature */
|
||||
real_server_signature = apr_pstrdup(mp, ap_get_server_version());
|
||||
|
||||
/* Make some space in the server signature for later */
|
||||
if (new_server_signature != NULL) {
|
||||
ap_add_version_component(mp, new_server_signature);
|
||||
change_server_signature(s);
|
||||
}
|
||||
|
||||
#if (!(defined(WIN32) || defined(NETWARE)))
|
||||
|
||||
/* Internal chroot functionality */
|
||||
|
||||
if (chroot_dir != NULL) {
|
||||
|
||||
/* ENH Is it safe to simply return with an error, instead
|
||||
* of using exit()?
|
||||
*/
|
||||
|
||||
if (first_time == 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity: chroot checkpoint #2 (pid=%i ppid=%i)", getpid(), getppid());
|
||||
|
||||
if (chdir(chroot_dir) < 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity: chroot failed, unable to chdir to %s, errno=%d (%s)",
|
||||
chroot_dir, errno, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (chroot(chroot_dir) < 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity: chroot failed, path=%s, errno=%d(%s)",
|
||||
chroot_dir, errno, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (chdir("/") < 0) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity: chdoot failed, unable to chdir to /, errno=%d (%s)",
|
||||
errno, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
chroot_completed = 1;
|
||||
|
||||
ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity: chroot successful, path=%s", chroot_dir);
|
||||
} else {
|
||||
ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity: chroot checkpoint #1 (pid=%i ppid=%i)", getpid(), getppid());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Schedule main cleanup for later, when the main pool is destroyed. */
|
||||
apr_pool_cleanup_register(mp, (void *)s, module_cleanup, apr_pool_cleanup_null);
|
||||
|
||||
/* Log our presence to the error log. */
|
||||
if (first_time) {
|
||||
if (new_server_signature != NULL) {
|
||||
ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity for Apache %s configured - %s", MODULE_RELEASE, real_server_signature);
|
||||
}
|
||||
else {
|
||||
ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s,
|
||||
"ModSecurity for Apache %s configured", MODULE_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
srand((unsigned int)(time(NULL) * getpid()));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisation performed for every new child process.
|
||||
*/
|
||||
static void hook_child_init(apr_pool_t *mp, server_rec *s) {
|
||||
modsecurity_child_init(modsecurity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial request processing, executed immediatelly after
|
||||
* Apache receives the request headers.
|
||||
*/
|
||||
static int hook_request_early(request_rec *r) {
|
||||
modsec_rec *msr = NULL;
|
||||
int rc;
|
||||
|
||||
/* This function needs to run only once per transaction
|
||||
* (i.e. subrequests and redirects are excluded).
|
||||
*/
|
||||
if ((r->main != NULL)||(r->prev != NULL)) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
/* Initialise transaction context and
|
||||
* create the initial configuration.
|
||||
*/
|
||||
msr = create_tx_context(r);
|
||||
if (msr == NULL) return DECLINED;
|
||||
|
||||
/* Are we allowed to continue? */
|
||||
if (msr->txcfg->is_enabled == MODSEC_DISABLED) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Processing disabled, skipping (hook request_early).");
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
/* Check request body limit (should only trigger on non-chunked requests). */
|
||||
if (msr->request_content_length > msr->txcfg->reqbody_limit) {
|
||||
msr_log(msr, 1, "Request body is larger than the "
|
||||
"configured limit (%lu).", msr->txcfg->reqbody_limit);
|
||||
return HTTP_REQUEST_ENTITY_TOO_LARGE;
|
||||
}
|
||||
|
||||
/* Process phase REQUEST_HEADERS */
|
||||
rc = DECLINED;
|
||||
if (modsecurity_process_phase(msr, PHASE_REQUEST_HEADERS)) {
|
||||
rc = perform_interception(msr);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked as the first hook in the handler chain, this function
|
||||
* executes the second phase of ModSecurity request processing.
|
||||
*/
|
||||
static int hook_request_late(request_rec *r) {
|
||||
char *my_error_msg = NULL;
|
||||
modsec_rec *msr = NULL;
|
||||
int rc;
|
||||
|
||||
/* Find the transaction context and make sure
|
||||
* we are supposed to proceed.
|
||||
*/
|
||||
msr = retrieve_tx_context(r);
|
||||
if (msr == NULL) {
|
||||
/* If we can't find the context that probably means it's
|
||||
* a subrequest that was not initiated from the outside.
|
||||
*/
|
||||
return DECLINED;
|
||||
}
|
||||
msr->r = r;
|
||||
msr->remote_user = r->user;
|
||||
|
||||
/* Has this phase been completed already? */
|
||||
if (msr->phase_request_body_complete) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Phase REQUEST_BODY already complete, skipping.");
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
msr->phase_request_body_complete = 1;
|
||||
|
||||
/* Get the second configuration context. */
|
||||
msr->dcfg2 = (directory_config *)ap_get_module_config(r->per_dir_config,
|
||||
&security2_module);
|
||||
|
||||
/* Create a transaction context. */
|
||||
msr->txcfg = create_directory_config(msr->mp, NULL);
|
||||
if (msr->txcfg == NULL) return DECLINED;
|
||||
if (msr->dcfg2 != NULL) {
|
||||
msr->txcfg = merge_directory_configs(msr->mp, msr->txcfg, msr->dcfg2);
|
||||
if (msr->txcfg == NULL) return DECLINED;
|
||||
}
|
||||
|
||||
/* Update with the explicit user settings. */
|
||||
msr->txcfg = merge_directory_configs(msr->mp, msr->txcfg, msr->usercfg);
|
||||
|
||||
init_directory_config(msr->txcfg);
|
||||
|
||||
if (msr->txcfg->is_enabled == 0) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Processing disabled, skipping (hook request_late).");
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Second phase starting (dcfg %x).", msr->dcfg2);
|
||||
}
|
||||
|
||||
/* Figure out whether or not to extract multipart files. */
|
||||
if ((msr->txcfg->upload_keep_files != KEEP_FILES_OFF) /* user might want to keep them */
|
||||
|| (msr->txcfg->upload_validates_files)) /* user might want to validate them */
|
||||
{
|
||||
msr->upload_extract_files = 1;
|
||||
msr->upload_remove_files = 1;
|
||||
}
|
||||
|
||||
rc = read_request_body(msr, &my_error_msg);
|
||||
if (rc < 0) {
|
||||
switch(rc) {
|
||||
case -1 :
|
||||
msr_log(msr, 1, "%s", my_error_msg);
|
||||
return HTTP_INTERNAL_SERVER_ERROR;
|
||||
break;
|
||||
case -4 : /* Timeout. */
|
||||
r->connection->keepalive = AP_CONN_CLOSE;
|
||||
return HTTP_REQUEST_TIME_OUT;
|
||||
break;
|
||||
case -5 : /* Request body limit reached. */
|
||||
r->connection->keepalive = AP_CONN_CLOSE;
|
||||
return HTTP_REQUEST_ENTITY_TOO_LARGE;
|
||||
break;
|
||||
default :
|
||||
/* allow through */
|
||||
break;
|
||||
}
|
||||
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
}
|
||||
|
||||
/* Update the request headers. They might have changed after
|
||||
* the body was read (trailers).
|
||||
*/
|
||||
// TODO We still need to keep a copy of the original headers
|
||||
// to log in the audit log.
|
||||
msr->request_headers = apr_table_copy(msr->mp, r->headers_in);
|
||||
|
||||
/* Process phase REQUEST_BODY */
|
||||
record_time_checkpoint(msr, 1);
|
||||
|
||||
rc = DECLINED;
|
||||
if (modsecurity_process_phase(msr, PHASE_REQUEST_BODY)) {
|
||||
rc = perform_interception(msr);
|
||||
}
|
||||
|
||||
record_time_checkpoint(msr, 2);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked every time Apache has something to write to the error log.
|
||||
*/
|
||||
static void hook_error_log(const char *file, int line, int level, apr_status_t status,
|
||||
const server_rec *s, const request_rec *r, apr_pool_t *mp, const char *fmt)
|
||||
{
|
||||
modsec_rec *msr = NULL;
|
||||
error_message *em = NULL;
|
||||
|
||||
if (r == NULL) return;
|
||||
msr = retrieve_tx_context(r);
|
||||
if (msr == NULL) return;
|
||||
|
||||
/* Store the error message for later */
|
||||
em = (error_message *)apr_pcalloc(msr->mp, sizeof(error_message));
|
||||
if (em == NULL) return;
|
||||
|
||||
if (file != NULL) em->file = apr_pstrdup(msr->mp, file);
|
||||
em->line = line;
|
||||
em->level = level;
|
||||
em->status = status;
|
||||
if (fmt != NULL) em->message = apr_pstrdup(msr->mp, fmt);
|
||||
|
||||
/* Remove \n from the end of the message */
|
||||
if (em->message != NULL) {
|
||||
char *p = (char *)em->message;
|
||||
while(*p != '\0') {
|
||||
if ((*(p + 1) == '\0')&&(*p == '\n')) {
|
||||
*p = '\0';
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
*(const error_message **)apr_array_push(msr->error_messages) = em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardian logger is used to interface to the external
|
||||
* script for web server protection - httpd_guardian.
|
||||
*/
|
||||
static void sec_guardian_logger(request_rec *r, request_rec *origr, modsec_rec *msr) {
|
||||
char *str1, *str2, *text;
|
||||
char *modsec_message = "-";
|
||||
int modsec_rating = 0; /* not used yet */
|
||||
apr_size_t nbytes, nbytes_written;
|
||||
apr_time_t duration = (apr_time_now() - origr->request_time);
|
||||
int limit, was_limited;
|
||||
|
||||
/* bail out if we do not have where to write */
|
||||
if ((guardianlog_name == NULL)||(guardianlog_fd == NULL)) return;
|
||||
|
||||
/* process the condition, if we have one */
|
||||
if (guardianlog_condition != NULL) {
|
||||
if (*guardianlog_condition == '!') {
|
||||
if (apr_table_get(r->subprocess_env, guardianlog_condition + 1) != NULL) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (apr_table_get(r->subprocess_env, guardianlog_condition) == NULL) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Log format is as follows:
|
||||
*
|
||||
* %V %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i" %{UNIQUE_ID}e
|
||||
* "SESSION_ID" %T %D "MODSEC_MESSAGE" MODSEC_RATING
|
||||
*
|
||||
* The fields SESSION_ID, MODSEC_MESSAGE, and MODSEC_RATING are not used at the moment.
|
||||
*/
|
||||
|
||||
str2 = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT " %" APR_TIME_T_FMT " \"%s\" %i",
|
||||
duration, apr_time_sec(duration), log_escape(msr->mp, modsec_message), modsec_rating);
|
||||
if (str2 == NULL) return;
|
||||
|
||||
/* We do not want the index line to be longer than 3980 bytes. */
|
||||
limit = 3980;
|
||||
was_limited = 0;
|
||||
|
||||
/* If we are logging to a pipe we need to observe and
|
||||
* obey the pipe atomic write limit - PIPE_BUF. For
|
||||
* more details see the discussion in sec_guardian_logger,
|
||||
* above.
|
||||
*/
|
||||
if (msr->txcfg->auditlog_name[0] == '|') {
|
||||
if (PIPE_BUF < limit) {
|
||||
limit = PIPE_BUF;
|
||||
}
|
||||
}
|
||||
|
||||
limit = limit - strlen(str2) - 5;
|
||||
if (limit <= 0) {
|
||||
msr_log(msr, 1, "Audit Log: Atomic PIPE write buffer too small: %i", PIPE_BUF);
|
||||
return;
|
||||
}
|
||||
|
||||
str1 = construct_log_vcombinedus_limited(msr, limit, &was_limited);
|
||||
if (str1 == NULL) return;
|
||||
|
||||
if (was_limited == 0) {
|
||||
text = apr_psprintf(msr->mp, "%s %s \n", str1, str2);
|
||||
} else {
|
||||
text = apr_psprintf(msr->mp, "%s %s L\n", str1, str2);
|
||||
}
|
||||
if (text == NULL) return;
|
||||
|
||||
nbytes = strlen(text);
|
||||
apr_file_write_full(guardianlog_fd, text, nbytes, &nbytes_written);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked at the end of each transaction.
|
||||
*/
|
||||
static int hook_log_transaction(request_rec *r) {
|
||||
const apr_array_header_t *arr = NULL;
|
||||
request_rec *origr = NULL;
|
||||
modsec_rec *msr = NULL;
|
||||
|
||||
msr = retrieve_tx_context(r);
|
||||
if (msr == NULL) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Initialising logging.");
|
||||
}
|
||||
|
||||
/* Find the first (origr) and the last (r) request */
|
||||
origr = r;
|
||||
while(origr->prev) {
|
||||
origr = origr->prev;
|
||||
}
|
||||
while(r->next) {
|
||||
r = r->next;
|
||||
}
|
||||
|
||||
/* At this point r is the last request in the
|
||||
* chain. However, we now need to detect a case when
|
||||
* a bad ErrorDocument was used and back out of it. That's
|
||||
* how Apache does it internally. Except where Apache knows
|
||||
* exactly what is happening we will have to rely on the missing
|
||||
* headers in the final request to detect this condition.
|
||||
*/
|
||||
arr = apr_table_elts(r->headers_out);
|
||||
while ((arr->nelts == 0)&&(r->prev != NULL)) {
|
||||
r = r->prev;
|
||||
arr = apr_table_elts(r->headers_out);
|
||||
}
|
||||
|
||||
msr->r = r;
|
||||
msr->response_status = r->status;
|
||||
msr->status_line = ((r->status_line != NULL)
|
||||
? r->status_line : ap_get_status_line(r->status));
|
||||
msr->response_protocol = get_response_protocol(origr);
|
||||
msr->response_headers = apr_table_copy(msr->mp, r->headers_out);
|
||||
if (!r->assbackwards) msr->response_headers_sent = 1;
|
||||
msr->bytes_sent = r->bytes_sent;
|
||||
msr->local_user = r->user;
|
||||
msr->remote_user = r->connection->remote_logname;
|
||||
|
||||
/* -- Guardian -- */
|
||||
|
||||
sec_guardian_logger(r, origr, msr);
|
||||
|
||||
/* Invoke the engine to do the rest of the work now. */
|
||||
modsecurity_process_phase(msr, PHASE_LOGGING);
|
||||
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked right before request processing begins. This is
|
||||
* when we need to decide if we want to hook into the output
|
||||
* filter chain.
|
||||
*/
|
||||
static void hook_insert_filter(request_rec *r) {
|
||||
modsec_rec *msr = NULL;
|
||||
|
||||
/* Find the transaction context and make sure we are
|
||||
* supposed to proceed.
|
||||
*/
|
||||
msr = retrieve_tx_context(r);
|
||||
if (msr == NULL) return;
|
||||
|
||||
if (msr->txcfg->is_enabled == 0) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Processing disabled, skipping.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msr->if_status == IF_STATUS_WANTS_TO_RUN) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Adding input forwarding filter (r %x).", r);
|
||||
}
|
||||
ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection);
|
||||
}
|
||||
|
||||
/* We always add the output filter because that's where we need to
|
||||
* initiate our 3rd and 4th processing phases from. The filter is
|
||||
* smart enough not to buffer the data if it is not supposed to.
|
||||
*/
|
||||
if (msr->of_status != OF_STATUS_COMPLETE) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Adding output filter (r %x).", r);
|
||||
}
|
||||
ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* Invoked whenever Apache starts processing an error. A chance
|
||||
* to insert ourselves into the output filter chain.
|
||||
*/
|
||||
static void hook_insert_error_filter(request_rec *r) {
|
||||
modsec_rec *msr = NULL;
|
||||
|
||||
/* Find the transaction context and make sure we are
|
||||
* supposed to proceed.
|
||||
*/
|
||||
|
||||
/* TODO Insert filter but make a note that it's the error
|
||||
* response the filter would be receiving.
|
||||
*/
|
||||
|
||||
msr = retrieve_tx_context(r);
|
||||
if (msr == NULL) return;
|
||||
|
||||
if (msr->txcfg->is_enabled == 0) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_error_filter: Processing disabled, skipping.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msr->of_status != OF_STATUS_COMPLETE) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_error_filter: Adding output filter (r %x).", r);
|
||||
}
|
||||
ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection);
|
||||
} else {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_error_filter: Output buffering already complete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This function is exported for other Apache modules to
|
||||
* register new transformation functions.
|
||||
*/
|
||||
static void modsec_register_tfn(const char *name, void *fn) {
|
||||
if (modsecurity != NULL) {
|
||||
msre_engine_tfn_register(modsecurity->msre, name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is exported for other Apache modules to
|
||||
* register new operators.
|
||||
*/
|
||||
static void modsec_register_operator(const char *name, void *fn_init, void *fn_exec) {
|
||||
if (modsecurity != NULL) {
|
||||
msre_engine_op_register(modsecurity->msre, name, fn_init, fn_exec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers module hooks with Apache.
|
||||
*/
|
||||
static void register_hooks(apr_pool_t *mp) {
|
||||
static const char *postconfig_beforeme_list[] = {
|
||||
"mod_unique_id.c",
|
||||
"mod_ssl.c",
|
||||
NULL
|
||||
};
|
||||
static const char *postconfig_afterme_list[] = {
|
||||
"mod_fcgid.c",
|
||||
"mod_cgid.c",
|
||||
NULL
|
||||
};
|
||||
static const char *postread_beforeme_list[] = {
|
||||
"mod_unique_id.c",
|
||||
NULL
|
||||
};
|
||||
static const char *postread_afterme_list[] = {
|
||||
"mod_log_forensic.c",
|
||||
NULL
|
||||
};
|
||||
|
||||
/* Export optional functions. */
|
||||
APR_REGISTER_OPTIONAL_FN(modsec_register_tfn);
|
||||
APR_REGISTER_OPTIONAL_FN(modsec_register_operator);
|
||||
|
||||
/* Main hooks */
|
||||
ap_hook_pre_config(hook_pre_config, NULL, NULL, APR_HOOK_FIRST);
|
||||
ap_hook_post_config(hook_post_config, postconfig_beforeme_list,
|
||||
postconfig_afterme_list, APR_HOOK_REALLY_LAST);
|
||||
ap_hook_child_init(hook_child_init, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
|
||||
/* Our own hook to handle RPC transactions (not used at the moment).
|
||||
* // ap_hook_handler(hook_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
*/
|
||||
|
||||
/* Transaction processing hooks */
|
||||
ap_hook_post_read_request(hook_request_early,
|
||||
postread_beforeme_list, postread_afterme_list, APR_HOOK_REALLY_FIRST);
|
||||
|
||||
ap_hook_fixups(hook_request_late, NULL, NULL, APR_HOOK_REALLY_FIRST);
|
||||
|
||||
/* Logging */
|
||||
ap_hook_error_log(hook_error_log, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
ap_hook_log_transaction(hook_log_transaction, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
|
||||
/* Filter hooks */
|
||||
ap_hook_insert_filter(hook_insert_filter, NULL, NULL, APR_HOOK_FIRST);
|
||||
/* ap_hook_insert_error_filter(hook_insert_error_filter, NULL, NULL, APR_HOOK_FIRST); */
|
||||
|
||||
ap_register_input_filter("MODSECURITY_IN", input_filter,
|
||||
NULL, AP_FTYPE_CONTENT_SET);
|
||||
ap_register_output_filter("MODSECURITY_OUT", output_filter,
|
||||
NULL, AP_FTYPE_CONTENT_SET);
|
||||
}
|
||||
|
||||
/* Defined in apache2_config.c */
|
||||
extern const command_rec module_directives[];
|
||||
|
||||
/* Module entry points */
|
||||
module AP_MODULE_DECLARE_DATA security2_module = {
|
||||
STANDARD20_MODULE_STUFF,
|
||||
create_directory_config,
|
||||
merge_directory_configs,
|
||||
NULL, /* create_server_config */
|
||||
NULL, /* merge_server_configs */
|
||||
module_directives,
|
||||
register_hooks
|
||||
};
|
468
apache2/modsecurity.c
Normal file
468
apache2/modsecurity.c
Normal file
@ -0,0 +1,468 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: modsecurity.c,v 1.7 2006/12/28 10:39:13 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "apr_global_mutex.h"
|
||||
|
||||
#include "modsecurity.h"
|
||||
#include "msc_parsers.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
/**
|
||||
* Log an alert message to the log, adding the rule metadata at the end.
|
||||
*/
|
||||
void msc_alert(modsec_rec *msr, int level, msre_actionset *actionset, const char *action_message,
|
||||
const char *rule_message)
|
||||
{
|
||||
const char *message = NULL;
|
||||
|
||||
if (rule_message == NULL) rule_message = "Unknown error.";
|
||||
|
||||
message = apr_psprintf(msr->mp, "%s %s%s", action_message,
|
||||
rule_message, msre_format_metadata(msr, actionset));
|
||||
|
||||
msr_log(msr, level, "%s", message);
|
||||
}
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* Return phase name associated with the given phase number.
|
||||
*/
|
||||
static const char *phase_name(int phase) {
|
||||
switch(phase) {
|
||||
case 1 :
|
||||
return "REQUEST_HEADERS";
|
||||
break;
|
||||
case 2 :
|
||||
return "REQUEST_BODY";
|
||||
break;
|
||||
case 3 :
|
||||
return "RESPONSE_HEADERS";
|
||||
break;
|
||||
case 4 :
|
||||
return "RESPONSE_BODY";
|
||||
break;
|
||||
case 5 :
|
||||
return "LOGGING";
|
||||
break;
|
||||
}
|
||||
return "INVALID";
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Creates and initialises a ModSecurity engine instance.
|
||||
*/
|
||||
msc_engine *modsecurity_create(apr_pool_t *mp, int processing_mode) {
|
||||
msc_engine *msce = NULL;
|
||||
|
||||
msce = apr_pcalloc(mp, sizeof(msc_engine));
|
||||
if (msce == NULL) return NULL;
|
||||
|
||||
msce->mp = mp;
|
||||
msce->processing_mode = processing_mode;
|
||||
|
||||
msce->msre = msre_engine_create(msce->mp);
|
||||
if (msce->msre == NULL) return NULL;
|
||||
msre_engine_register_default_variables(msce->msre);
|
||||
msre_engine_register_default_operators(msce->msre);
|
||||
msre_engine_register_default_tfns(msce->msre);
|
||||
msre_engine_register_default_actions(msce->msre);
|
||||
|
||||
return msce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the modsecurity engine. This function must be invoked
|
||||
* after configuration processing is complete as Apache needs to know the
|
||||
* username it is running as.
|
||||
*/
|
||||
int modsecurity_init(msc_engine *msce, apr_pool_t *mp) {
|
||||
apr_status_t rc;
|
||||
|
||||
/* Serial audit log mutext */
|
||||
rc = apr_global_mutex_create(&msce->auditlog_lock, NULL, APR_LOCK_DEFAULT, mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
//ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not create modsec_auditlog_lock");
|
||||
//return HTTP_INTERNAL_SERVER_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef __SET_MUTEX_PERMS
|
||||
rc = unixd_set_global_mutex_perms(msce->auditlog_lock);
|
||||
if (rc != APR_SUCCESS) {
|
||||
// ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, "mod_security: Could not set permissions on modsec_auditlog_lock; check User and Group directives");
|
||||
// return HTTP_INTERNAL_SERVER_ERROR;
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs per-child (new process) initialisation.
|
||||
*/
|
||||
void modsecurity_child_init(msc_engine *msce) {
|
||||
if (msce->auditlog_lock != NULL) {
|
||||
apr_status_t rc = apr_global_mutex_child_init(&msce->auditlog_lock, NULL, msce->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
// ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases resources held by engine instance.
|
||||
*/
|
||||
void modsecurity_shutdown(msc_engine *msce) {
|
||||
if (msce == NULL) return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_tx_cleanup(void *data) {
|
||||
modsec_rec *msr = (modsec_rec *)data;
|
||||
const apr_array_header_t *arr;
|
||||
apr_table_entry_t *te;
|
||||
int collect_garbage = 0;
|
||||
int i;
|
||||
|
||||
if (msr == NULL) return APR_SUCCESS;
|
||||
|
||||
if (rand() < RAND_MAX/100) {
|
||||
collect_garbage = 1;
|
||||
}
|
||||
|
||||
/* Collections, store & remove stale. */
|
||||
arr = apr_table_elts(msr->collections);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
apr_table_t *col = (apr_table_t *)te[i].val;
|
||||
|
||||
/* Only store those collections that changed. */
|
||||
if (apr_table_get(msr->collections_dirty, te[i].key)) {
|
||||
collection_store(msr, col);
|
||||
}
|
||||
|
||||
if (collect_garbage) {
|
||||
collections_remove_stale(msr, te[i].key);
|
||||
}
|
||||
}
|
||||
|
||||
/* Multipart processor cleanup. */
|
||||
if (msr->mpd != NULL) multipart_cleanup(msr);
|
||||
|
||||
#ifdef WITH_LIBXML2
|
||||
/* XML processor cleanup. */
|
||||
if (msr->xml != NULL) xml_cleanup(msr);
|
||||
#endif
|
||||
|
||||
modsecurity_request_body_clear(msr);
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apr_status_t modsecurity_tx_init(modsec_rec *msr) {
|
||||
const char *s = NULL;
|
||||
const apr_array_header_t *arr;
|
||||
apr_table_entry_t *te;
|
||||
int i;
|
||||
|
||||
/* Register TX cleanup */
|
||||
apr_pool_cleanup_register(msr->mp, msr, modsecurity_tx_cleanup, apr_pool_cleanup_null);
|
||||
|
||||
/* Initialise C-L */
|
||||
msr->request_content_length = -1;
|
||||
s = apr_table_get(msr->request_headers, "Content-Length");
|
||||
if (s != NULL) {
|
||||
msr->request_content_length = strtol(s, NULL, 10);
|
||||
}
|
||||
|
||||
/* Figure out whether this request has a body */
|
||||
msr->reqbody_chunked = 0;
|
||||
msr->reqbody_should_exist = 0;
|
||||
if (msr->request_content_length == -1) {
|
||||
/* There's no C-L, but is chunked encoding used? */
|
||||
char *transfer_encoding = (char *)apr_table_get(msr->request_headers, "Transfer-Encoding");
|
||||
if ((transfer_encoding != NULL)&&(strstr(transfer_encoding, "chunked") != NULL)) {
|
||||
msr->reqbody_should_exist = 1;
|
||||
msr->reqbody_chunked = 1;
|
||||
}
|
||||
} else {
|
||||
/* C-L found */
|
||||
msr->reqbody_should_exist = 1;
|
||||
}
|
||||
|
||||
/* Initialise C-T */
|
||||
msr->request_content_type = NULL;
|
||||
s = apr_table_get(msr->request_headers, "Content-Type");
|
||||
if (s != NULL) msr->request_content_type = s;
|
||||
|
||||
/* Decide what to do with the request body. */
|
||||
if ((msr->request_content_type != NULL)
|
||||
&& (strncasecmp(msr->request_content_type, "application/x-www-form-urlencoded", 33) == 0))
|
||||
{
|
||||
/* Always place POST requests with
|
||||
* "application/x-www-form-urlencoded" payloads in memory.
|
||||
*/
|
||||
msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
|
||||
msr->msc_reqbody_spilltodisk = 0;
|
||||
msr->msc_reqbody_processor = "URLENCODED";
|
||||
} else {
|
||||
/* If the C-L is known and there's more data than
|
||||
* our limit go to disk straight away.
|
||||
*/
|
||||
if ((msr->request_content_length != -1)
|
||||
&& (msr->request_content_length > msr->txcfg->reqbody_inmemory_limit))
|
||||
{
|
||||
msr->msc_reqbody_storage = MSC_REQBODY_DISK;
|
||||
}
|
||||
|
||||
/* In all other cases, try using the memory first
|
||||
* but switch over to disk for larger bodies.
|
||||
*/
|
||||
msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
|
||||
msr->msc_reqbody_spilltodisk = 1;
|
||||
|
||||
if (msr->request_content_type != NULL) {
|
||||
if (strncasecmp(msr->request_content_type, "multipart/form-data", 19) == 0) {
|
||||
msr->msc_reqbody_processor = "MULTIPART";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialise arguments */
|
||||
msr->arguments = apr_table_make(msr->mp, 32);
|
||||
if (msr->arguments == NULL) return -1;
|
||||
if (msr->query_string != NULL) {
|
||||
int invalid_count = 0;
|
||||
|
||||
if (parse_arguments(msr, msr->query_string, msr->txcfg->argument_separator,
|
||||
"QUERY_STRING", msr->arguments, &invalid_count) < 0) {
|
||||
msr_log(msr, 1, "Initialisation: Error occurred while parsing QUERY_STRING arguments.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
msr->arguments_to_sanitise = apr_table_make(msr->mp, 16);
|
||||
if (msr->arguments_to_sanitise == NULL) return -1;
|
||||
msr->request_headers_to_sanitise = apr_table_make(msr->mp, 16);
|
||||
if (msr->request_headers_to_sanitise == NULL) return -1;
|
||||
msr->response_headers_to_sanitise = apr_table_make(msr->mp, 16);
|
||||
if (msr->response_headers_to_sanitise == NULL) return -1;
|
||||
|
||||
/* Initialise cookies */
|
||||
msr->request_cookies = apr_table_make(msr->mp, 16);
|
||||
if (msr->request_cookies == NULL) return -1;
|
||||
|
||||
/* Locate the cookie headers and parse them */
|
||||
arr = apr_table_elts(msr->request_headers);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
if (strcasecmp(te[i].key, "Cookie") == 0) {
|
||||
if (msr->txcfg->cookie_format == COOKIES_V0) {
|
||||
parse_cookies_v0(msr, te[i].val, msr->request_cookies);
|
||||
} else {
|
||||
parse_cookies_v1(msr, te[i].val, msr->request_cookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Collections. */
|
||||
msr->tx_vars = apr_table_make(msr->mp, 32);
|
||||
if (msr->tx_vars == NULL) return -1;
|
||||
|
||||
msr->collections = apr_table_make(msr->mp, 8);
|
||||
if (msr->collections == NULL) return -1;
|
||||
msr->collections_dirty = apr_table_make(msr->mp, 8);
|
||||
if (msr->collections_dirty == NULL) return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int is_response_status_relevant(modsec_rec *msr, int status) {
|
||||
char *my_error_msg = NULL;
|
||||
apr_status_t rc;
|
||||
char buf[32];
|
||||
|
||||
if ((msr->txcfg->auditlog_relevant_regex == NULL)
|
||||
||(msr->txcfg->auditlog_relevant_regex == NOT_SET_P))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
apr_snprintf(buf, sizeof(buf), "%i", status);
|
||||
|
||||
rc = msc_regexec(msr->txcfg->auditlog_relevant_regex, buf, strlen(buf), &my_error_msg);
|
||||
if (rc >= 0) return 1;
|
||||
if (rc == PCRE_ERROR_NOMATCH) return 0;
|
||||
|
||||
msr_log(msr, 1, "Regex processing failed (rc %i): %s", rc, my_error_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_process_phase_request_headers(modsec_rec *msr) {
|
||||
msr_log(msr, 4, "Starting phase REQUEST_HEADERS.");
|
||||
|
||||
if (msr->txcfg->ruleset != NULL) {
|
||||
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_process_phase_request_body(modsec_rec *msr) {
|
||||
msr_log(msr, 4, "Starting phase REQUEST_BODY.");
|
||||
|
||||
if (msr->txcfg->ruleset != NULL) {
|
||||
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_process_phase_response_headers(modsec_rec *msr) {
|
||||
msr_log(msr, 4, "Starting phase RESPONSE_HEADERS.");
|
||||
|
||||
if (msr->txcfg->ruleset != NULL) {
|
||||
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_process_phase_response_body(modsec_rec *msr) {
|
||||
msr_log(msr, 4, "Starting phase RESPONSE_BODY.");
|
||||
|
||||
if (msr->txcfg->ruleset != NULL) {
|
||||
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_process_phase_logging(modsec_rec *msr) {
|
||||
msr_log(msr, 4, "Starting phase LOGGING.");
|
||||
|
||||
if (msr->txcfg->ruleset != NULL) {
|
||||
msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
|
||||
}
|
||||
|
||||
/* Is this request relevant for logging purposes? */
|
||||
if (msr->is_relevant == 0) {
|
||||
/* Check the status */
|
||||
msr->is_relevant += is_response_status_relevant(msr, msr->r->status);
|
||||
|
||||
/* If we processed two requests and statuses are different then
|
||||
* check the other status too.
|
||||
*/
|
||||
if (msr->r_early->status != msr->r->status) {
|
||||
msr->is_relevant += is_response_status_relevant(msr, msr->r_early->status);
|
||||
}
|
||||
}
|
||||
|
||||
/* Figure out if we want to keep the files (if there are any, of course). */
|
||||
if ((msr->txcfg->upload_keep_files == KEEP_FILES_ON)
|
||||
|| ((msr->txcfg->upload_keep_files == KEEP_FILES_RELEVANT_ONLY)&&(msr->is_relevant)))
|
||||
{
|
||||
msr->upload_remove_files = 0;
|
||||
} else {
|
||||
msr->upload_remove_files = 1;
|
||||
}
|
||||
|
||||
/* Are we configured for audit logging? */
|
||||
switch(msr->txcfg->auditlog_flag) {
|
||||
case AUDITLOG_OFF :
|
||||
msr_log(msr, 4, "Audit log: Not configured to run for this request.");
|
||||
return DECLINED;
|
||||
break;
|
||||
|
||||
case AUDITLOG_RELEVANT :
|
||||
if (msr->is_relevant == 0) {
|
||||
msr_log(msr, 4, "Audit log: Ignoring a non-relevant request.");
|
||||
return DECLINED;
|
||||
}
|
||||
break;
|
||||
|
||||
case AUDITLOG_ON :
|
||||
/* All right, do nothing */
|
||||
break;
|
||||
|
||||
default :
|
||||
return HTTP_INTERNAL_SERVER_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Invoke the Audit logger */
|
||||
msr_log(msr, 4, "Audit log: Logging this transaction.");
|
||||
|
||||
sec_audit_logger(msr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes one transaction phase. The phase number does not
|
||||
* need to be explicitly provided since it's already available
|
||||
* in the modsec_rec structure.
|
||||
*/
|
||||
apr_status_t modsecurity_process_phase(modsec_rec *msr, int phase) {
|
||||
msr->phase = phase;
|
||||
|
||||
switch(phase) {
|
||||
case 1 :
|
||||
return modsecurity_process_phase_request_headers(msr);
|
||||
break;
|
||||
case 2 :
|
||||
return modsecurity_process_phase_request_body(msr);
|
||||
break;
|
||||
case 3 :
|
||||
return modsecurity_process_phase_response_headers(msr);
|
||||
break;
|
||||
case 4 :
|
||||
return modsecurity_process_phase_response_body(msr);
|
||||
break;
|
||||
case 5 :
|
||||
return modsecurity_process_phase_logging(msr);
|
||||
break;
|
||||
default :
|
||||
msr_log(msr, 1, "Invalid processing phase: %i", msr->phase);
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
469
apache2/modsecurity.h
Normal file
469
apache2/modsecurity.h
Normal file
@ -0,0 +1,469 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: modsecurity.h,v 1.27 2007/02/05 12:44:40 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MODSECURITY_H_
|
||||
#define _MODSECURITY_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
typedef struct rule_exception rule_exception;
|
||||
typedef struct modsec_rec modsec_rec;
|
||||
typedef struct directory_config directory_config;
|
||||
typedef struct error_message error_message;
|
||||
typedef struct msc_engine msc_engine;
|
||||
typedef struct msc_data_chunk msc_data_chunk;
|
||||
typedef struct msc_arg msc_arg;
|
||||
typedef struct msc_string msc_string;
|
||||
|
||||
#ifndef WIN32
|
||||
#define DSOLOCAL __attribute__((visibility("hidden")))
|
||||
#else
|
||||
#define DSOLOCAL
|
||||
#endif
|
||||
|
||||
#include "msc_logging.h"
|
||||
#include "msc_multipart.h"
|
||||
#include "msc_pcre.h"
|
||||
#include "msc_util.h"
|
||||
#ifdef WITH_LIBXML2
|
||||
#include "msc_xml.h"
|
||||
#endif
|
||||
#include "re.h"
|
||||
|
||||
#include "ap_config.h"
|
||||
#include "apr_md5.h"
|
||||
#include "apr_strings.h"
|
||||
#include "httpd.h"
|
||||
#include "http_config.h"
|
||||
#include "http_log.h"
|
||||
#include "http_protocol.h"
|
||||
|
||||
#define MODULE_NAME "ModSecurity"
|
||||
#define MODULE_RELEASE "2.1.0-rc7"
|
||||
#define MODULE_NAME_FULL (MODULE_NAME " v" MODULE_RELEASE " (Apache 2.x)")
|
||||
|
||||
#define PHASE_REQUEST_HEADERS 1
|
||||
#define PHASE_REQUEST_BODY 2
|
||||
#define PHASE_RESPONSE_HEADERS 3
|
||||
#define PHASE_RESPONSE_BODY 4
|
||||
#define PHASE_LOGGING 5
|
||||
|
||||
#define NOT_SET -1
|
||||
#define NOT_SET_P (void *)-1
|
||||
|
||||
#define CREATEMODE ( APR_UREAD | APR_UWRITE | APR_GREAD )
|
||||
#define CREATEMODE_DIR ( APR_UREAD | APR_UWRITE | APR_UEXECUTE | APR_GREAD | APR_GEXECUTE )
|
||||
|
||||
#if defined(NETWARE)
|
||||
#define CREATEMODE_UNISTD ( S_IREAD | S_IWRITE )
|
||||
#elif defined(WIN32)
|
||||
#define CREATEMODE_UNISTD ( _S_IREAD | _S_IWRITE )
|
||||
#else
|
||||
#define CREATEMODE_UNISTD ( S_IRUSR | S_IWUSR | S_IRGRP )
|
||||
#endif
|
||||
|
||||
#if !defined(O_BINARY)
|
||||
#define O_BINARY (0)
|
||||
#endif
|
||||
|
||||
#ifndef PIPE_BUF
|
||||
#define PIPE_BUF (512)
|
||||
#endif
|
||||
|
||||
#define REQUEST_BODY_HARD_LIMIT 1073741824L
|
||||
#define REQUEST_BODY_DEFAULT_INMEMORY_LIMIT 131072
|
||||
#define REQUEST_BODY_DEFAULT_LIMIT 134217728
|
||||
#define RESPONSE_BODY_DEFAULT_LIMIT 524288
|
||||
#define RESPONSE_BODY_HARD_LIMIT 1073741824L
|
||||
|
||||
#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
|
||||
#include "unixd.h"
|
||||
#define __SET_MUTEX_PERMS
|
||||
#endif
|
||||
|
||||
#define COOKIES_V0 0
|
||||
#define COOKIES_V1 1
|
||||
|
||||
#ifdef WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define NOTE_MSR "modsecurity-tx-context"
|
||||
|
||||
#define FATAL_ERROR "ModSecurity: Fatal error (memory allocation or unexpected internal error)!"
|
||||
|
||||
extern DSOLOCAL char *new_server_signature;
|
||||
extern DSOLOCAL char *real_server_signature;
|
||||
extern DSOLOCAL char *chroot_dir;
|
||||
|
||||
extern module AP_MODULE_DECLARE_DATA security2_module;
|
||||
|
||||
extern DSOLOCAL const command_rec module_directives[];
|
||||
|
||||
#define RESBODY_STATUS_NOT_READ 0 /* we were not configured to read the body */
|
||||
#define RESBODY_STATUS_ERROR 1 /* error occured while we were reading the body */
|
||||
#define RESBODY_STATUS_PARTIAL 2 /* partial body content available in the brigade */
|
||||
#define RESBODY_STATUS_READ_BRIGADE 3 /* body was read but not flattened */
|
||||
#define RESBODY_STATUS_READ 4 /* body was read and flattened */
|
||||
|
||||
#define IF_STATUS_NONE 0
|
||||
#define IF_STATUS_WANTS_TO_RUN 1
|
||||
#define IF_STATUS_COMPLETE 2
|
||||
|
||||
#define OF_STATUS_NOT_STARTED 0
|
||||
#define OF_STATUS_IN_PROGRESS 1
|
||||
#define OF_STATUS_COMPLETE 2
|
||||
|
||||
#define MSC_REQBODY_NONE 0
|
||||
#define MSC_REQBODY_MEMORY 1
|
||||
#define MSC_REQBODY_DISK 2
|
||||
|
||||
#define ACTION_NONE 0
|
||||
#define ACTION_DENY 1
|
||||
#define ACTION_REDIRECT 2
|
||||
#define ACTION_PROXY 3
|
||||
#define ACTION_DROP 4
|
||||
#define ACTION_ALLOW 5
|
||||
|
||||
#define MODSEC_DISABLED 0
|
||||
#define MODSEC_DETECTION_ONLY 1
|
||||
#define MODSEC_ENABLED 2
|
||||
|
||||
#define MODSEC_OFFLINE 0
|
||||
#define MODSEC_ONLINE 1
|
||||
|
||||
#define REGEX_CAPTURE_BUFLEN 1024
|
||||
|
||||
#define KEEP_FILES_OFF 0
|
||||
#define KEEP_FILES_ON 1
|
||||
#define KEEP_FILES_RELEVANT_ONLY 2
|
||||
|
||||
#define RULE_EXCEPTION_IMPORT_ID 1
|
||||
#define RULE_EXCEPTION_IMPORT_MSG 2
|
||||
#define RULE_EXCEPTION_REMOVE_ID 3
|
||||
#define RULE_EXCEPTION_REMOVE_MSG 4
|
||||
|
||||
#define NBSP 160
|
||||
|
||||
struct rule_exception {
|
||||
int type;
|
||||
const char *param;
|
||||
void *param_data;
|
||||
};
|
||||
|
||||
struct modsec_rec {
|
||||
apr_pool_t *mp;
|
||||
msc_engine *modsecurity;
|
||||
|
||||
request_rec *r_early;
|
||||
request_rec *r;
|
||||
directory_config *dcfg1;
|
||||
directory_config *dcfg2;
|
||||
directory_config *usercfg;
|
||||
directory_config *txcfg;
|
||||
|
||||
unsigned int reqbody_should_exist;
|
||||
unsigned int reqbody_chunked;
|
||||
|
||||
unsigned int phase;
|
||||
unsigned int phase_request_headers_complete;
|
||||
unsigned int phase_request_body_complete;
|
||||
|
||||
apr_bucket_brigade *if_brigade;
|
||||
unsigned int if_status;
|
||||
unsigned int if_started_forwarding;
|
||||
|
||||
apr_size_t reqbody_length;
|
||||
unsigned int reqbody_status;
|
||||
|
||||
apr_bucket_brigade *of_brigade;
|
||||
unsigned int of_status;
|
||||
unsigned int of_done_reading;
|
||||
unsigned int of_skipping;
|
||||
|
||||
unsigned int resbody_status;
|
||||
apr_size_t resbody_length;
|
||||
char *resbody_data;
|
||||
unsigned int resbody_contains_html;
|
||||
|
||||
apr_array_header_t *error_messages;
|
||||
apr_array_header_t *alerts;
|
||||
|
||||
const char *txid;
|
||||
const char *sessionid;
|
||||
const char *userid;
|
||||
|
||||
const char *server_software;
|
||||
const char *local_addr;
|
||||
unsigned int local_port;
|
||||
const char *local_user;
|
||||
|
||||
/* client */
|
||||
|
||||
const char *remote_addr;
|
||||
unsigned int remote_port;
|
||||
const char *remote_user;
|
||||
|
||||
/* request */
|
||||
|
||||
const char *request_line;
|
||||
const char *request_method;
|
||||
const char *request_uri;
|
||||
const char *query_string;
|
||||
const char *request_protocol;
|
||||
|
||||
const char *hostname;
|
||||
|
||||
apr_table_t *request_headers;
|
||||
|
||||
apr_off_t request_content_length;
|
||||
const char *request_content_type;
|
||||
|
||||
apr_table_t *arguments;
|
||||
apr_table_t *arguments_to_sanitise;
|
||||
apr_table_t *request_headers_to_sanitise;
|
||||
apr_table_t *response_headers_to_sanitise;
|
||||
apr_table_t *request_cookies;
|
||||
|
||||
unsigned int is_relevant;
|
||||
|
||||
apr_table_t *tx_vars;
|
||||
|
||||
/* response */
|
||||
unsigned int response_status;
|
||||
const char *status_line;
|
||||
const char *response_protocol;
|
||||
apr_table_t *response_headers;
|
||||
unsigned int response_headers_sent;
|
||||
apr_off_t bytes_sent;
|
||||
|
||||
/* modsecurity request body processing stuff */
|
||||
|
||||
unsigned int msc_reqbody_storage; /* on disk or in memory */
|
||||
unsigned int msc_reqbody_spilltodisk;
|
||||
unsigned int msc_reqbody_read;
|
||||
|
||||
apr_pool_t *msc_reqbody_mp; /* this is where chunks are allocated from */
|
||||
apr_array_header_t *msc_reqbody_chunks; /* data chunks when stored in memory */
|
||||
unsigned int msc_reqbody_length; /* the amount of data received */
|
||||
int msc_reqbody_chunk_position; /* used when retrieving the body */
|
||||
unsigned int msc_reqbody_chunk_offset; /* offset of the chunk currently in use */
|
||||
msc_data_chunk *msc_reqbody_chunk_current; /* current chunk */
|
||||
char *msc_reqbody_buffer;
|
||||
|
||||
const char *msc_reqbody_filename; /* when stored on disk */
|
||||
int msc_reqbody_fd;
|
||||
msc_data_chunk *msc_reqbody_disk_chunk;
|
||||
|
||||
const char *msc_reqbody_processor;
|
||||
int msc_reqbody_error;
|
||||
const char *msc_reqbody_error_msg;
|
||||
|
||||
multipart_data *mpd; /* MULTIPART processor data structure */
|
||||
|
||||
#ifdef WITH_LIBXML2
|
||||
xml_data *xml; /* XML processor data structure */
|
||||
#endif
|
||||
|
||||
/* audit logging */
|
||||
char *new_auditlog_boundary;
|
||||
char *new_auditlog_filename;
|
||||
apr_file_t *new_auditlog_fd;
|
||||
unsigned int new_auditlog_size;
|
||||
apr_md5_ctx_t new_auditlog_md5ctx;
|
||||
|
||||
unsigned int was_intercepted;
|
||||
unsigned int intercept_phase;
|
||||
msre_actionset *intercept_actionset;
|
||||
const char *intercept_message;
|
||||
|
||||
/* performance measurement */
|
||||
apr_time_t request_time;
|
||||
apr_time_t time_checkpoint_1;
|
||||
apr_time_t time_checkpoint_2;
|
||||
apr_time_t time_checkpoint_3;
|
||||
|
||||
const char *matched_var;
|
||||
|
||||
/* upload */
|
||||
int upload_extract_files;
|
||||
int upload_remove_files;
|
||||
|
||||
/* other */
|
||||
apr_table_t *collections;
|
||||
apr_table_t *collections_dirty;
|
||||
};
|
||||
|
||||
struct directory_config {
|
||||
apr_pool_t *mp;
|
||||
|
||||
msre_ruleset *ruleset;
|
||||
|
||||
int is_enabled;
|
||||
int reqbody_access;
|
||||
long int reqbody_inmemory_limit;
|
||||
long int reqbody_limit;
|
||||
int resbody_access;
|
||||
|
||||
long int of_limit;
|
||||
apr_table_t *of_mime_types;
|
||||
int of_mime_types_cleared;
|
||||
|
||||
const char *debuglog_name;
|
||||
int debuglog_level;
|
||||
apr_file_t *debuglog_fd;
|
||||
|
||||
int cookie_format;
|
||||
int argument_separator;
|
||||
|
||||
int rule_inheritance;
|
||||
apr_array_header_t *rule_exceptions;
|
||||
|
||||
|
||||
/* -- Audit log -- */
|
||||
|
||||
/* Whether audit log should be enabled in the context or not */
|
||||
int auditlog_flag;
|
||||
|
||||
/* AUDITLOG_SERIAL (single file) or AUDITLOG_CONCURRENT (multiple files) */
|
||||
int auditlog_type;
|
||||
|
||||
/* The name of the audit log file (for the old type), or the
|
||||
* name of the index file (for the new audit log type)
|
||||
*/
|
||||
char *auditlog_name;
|
||||
|
||||
/* The file descriptor for the file above */
|
||||
apr_file_t *auditlog_fd;
|
||||
|
||||
/* For the new-style audit log only, the path where
|
||||
* audit log entries will be stored
|
||||
*/
|
||||
char *auditlog_storage_dir;
|
||||
|
||||
/* A list of parts to include in the new-style audit log
|
||||
* entry. By default, it contains 'ABCFHZ'. Have a look at
|
||||
* the AUDITLOG_PART_* constants above to decipher the
|
||||
* meaning.
|
||||
*/
|
||||
char *auditlog_parts;
|
||||
|
||||
/* A regular expression that determines if a response
|
||||
* status is treated as relevant.
|
||||
*/
|
||||
msc_regex_t *auditlog_relevant_regex;
|
||||
|
||||
/* Upload */
|
||||
const char *tmp_dir;
|
||||
const char *upload_dir;
|
||||
int upload_keep_files;
|
||||
int upload_validates_files;
|
||||
|
||||
/* Used only in the configuration phase. */
|
||||
msre_rule *tmp_chain_starter;
|
||||
msre_actionset *tmp_default_actionset;
|
||||
|
||||
/* Misc */
|
||||
const char *data_dir;
|
||||
const char *webappid;
|
||||
};
|
||||
|
||||
struct error_message {
|
||||
const char *file;
|
||||
int line;
|
||||
int level;
|
||||
apr_status_t status;
|
||||
const char *message;
|
||||
};
|
||||
|
||||
struct msc_engine {
|
||||
apr_pool_t *mp;
|
||||
apr_global_mutex_t *auditlog_lock;
|
||||
msre_engine *msre;
|
||||
unsigned int processing_mode;
|
||||
};
|
||||
|
||||
struct msc_data_chunk {
|
||||
char *data;
|
||||
apr_size_t length;
|
||||
unsigned int is_permanent;
|
||||
};
|
||||
|
||||
struct msc_arg {
|
||||
const char *name;
|
||||
unsigned int name_len;
|
||||
unsigned int name_origin_offset;
|
||||
unsigned int name_origin_len;
|
||||
const char *value;
|
||||
unsigned int value_len;
|
||||
unsigned int value_origin_offset;
|
||||
unsigned int value_origin_len;
|
||||
const char *origin;
|
||||
};
|
||||
|
||||
struct msc_string {
|
||||
char *name;
|
||||
unsigned int name_len;
|
||||
char *value;
|
||||
unsigned int value_len;
|
||||
};
|
||||
|
||||
|
||||
/* Engine functions */
|
||||
|
||||
msc_engine DSOLOCAL *modsecurity_create(apr_pool_t *mp, int processing_mode);
|
||||
|
||||
int DSOLOCAL modsecurity_init(msc_engine *msce, apr_pool_t *mp);
|
||||
|
||||
void DSOLOCAL modsecurity_child_init(msc_engine *msce);
|
||||
|
||||
void DSOLOCAL modsecurity_shutdown(msc_engine *msce);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_tx_init(modsec_rec *msr);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_process_phase(modsec_rec *msr, int phase);
|
||||
|
||||
|
||||
/* Request body functions */
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_start(modsec_rec *msr);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_store(modsec_rec *msr,
|
||||
const char *data, apr_size_t length);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_end(modsec_rec *msr);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_retrieve_start(modsec_rec *msr);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_retrieve_end(modsec_rec *msr);
|
||||
|
||||
/* Retrieves up to nbytes bytes of the request body. Returns 1 on
|
||||
* success, 0 when there is no more data, or -1 on error. On return
|
||||
* nbytes will contain the number of bytes stored in the buffer.
|
||||
*/
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_retrieve(modsec_rec *msr, msc_data_chunk **chunk,
|
||||
long int nbytes);
|
||||
|
||||
void DSOLOCAL msc_add(modsec_rec *msr, int level, msre_actionset *actionset,
|
||||
const char *action_message, const char *rule_message);
|
||||
|
||||
void DSOLOCAL msc_alert(modsec_rec *msr, int level, msre_actionset *actionset, const char *action_message,
|
||||
const char *rule_message);
|
||||
|
||||
apr_status_t DSOLOCAL modsecurity_request_body_clear(modsec_rec *msr);
|
||||
|
||||
#endif
|
19
apache2/modules.mk
Normal file
19
apache2/modules.mk
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \
|
||||
re re_operators re_actions re_tfns re_variables \
|
||||
msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \
|
||||
persist_dbm msc_reqbody
|
||||
|
||||
H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \
|
||||
msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h
|
||||
|
||||
${MOD_SECURITY2:=.slo}: ${H}
|
||||
${MOD_SECURITY2:=.lo}: ${H}
|
||||
${MOD_SECURITY2:=.o}: ${H}
|
||||
|
||||
mod_security2.la: ${MOD_SECURITY2:=.slo}
|
||||
$(SH_LINK) -rpath $(libexecdir) -module -avoid-version ${MOD_SECURITY2:=.lo}
|
||||
|
||||
DISTCLEAN_TARGETS = modules.mk
|
||||
|
||||
shared = mod_security2.la
|
890
apache2/msc_logging.c
Normal file
890
apache2/msc_logging.c
Normal file
@ -0,0 +1,890 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_logging.c,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "msc_logging.h"
|
||||
#include "httpd.h"
|
||||
#include "apr_strings.h"
|
||||
#include "apr_global_mutex.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
|
||||
/**
|
||||
* Write the supplied data to the audit log (if the FD is ready), update
|
||||
* the size counters, update the hash context.
|
||||
*/
|
||||
static int sec_auditlog_write(modsec_rec *msr, const char *data, unsigned int len) {
|
||||
apr_size_t nbytes_written, nbytes = len;
|
||||
apr_status_t rc;
|
||||
|
||||
if ((msr->new_auditlog_fd == NULL)||(data == NULL)) return -1;
|
||||
|
||||
rc = apr_file_write_full(msr->new_auditlog_fd, data, nbytes, &nbytes_written);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Audit log: Failed writing (requested %ui bytes, written %ui)",
|
||||
nbytes, nbytes_written);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Note the following will only take into account the actual
|
||||
* amount of bytes we've written.
|
||||
*/
|
||||
msr->new_auditlog_size += nbytes_written;
|
||||
apr_md5_update(&msr->new_auditlog_md5ctx, data, nbytes_written);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a log line in the vcombinedus format (see below).
|
||||
*/
|
||||
char *construct_log_vcombinedus(modsec_rec *msr) {
|
||||
const char *local_user, *remote_user;
|
||||
const char *referer, *user_agent, *uniqueid;
|
||||
const char *sessionid;
|
||||
|
||||
/* remote log name */
|
||||
if (msr->remote_user == NULL) remote_user = "-";
|
||||
else remote_user = msr->remote_user;
|
||||
|
||||
/* authenticated user */
|
||||
if (msr->local_user == NULL) local_user = "-";
|
||||
else local_user = msr->local_user;
|
||||
|
||||
/* unique id */
|
||||
uniqueid = msr->txid;
|
||||
if (uniqueid == NULL) uniqueid = "-";
|
||||
|
||||
/* referer */
|
||||
referer = "-";
|
||||
/* Logging Referer is a waste of space.
|
||||
referer = (char *)apr_table_get(msr->request_headers, "Referer");
|
||||
if (referer == NULL) referer = "-";
|
||||
*/
|
||||
|
||||
/* user agent */
|
||||
user_agent = "-";
|
||||
/* Logging User-Agent is a waste of space too.
|
||||
user_agent = (char *)apr_table_get(msr->request_headers, "User-Agent");
|
||||
if (user_agent == NULL) user_agent = "-";
|
||||
*/
|
||||
|
||||
/* sessionid */
|
||||
sessionid = (msr->sessionid == NULL ? "-" : msr->sessionid);
|
||||
|
||||
return apr_psprintf(msr->mp, "%s %s %s %s [%s] \"%s\" %i %" APR_OFF_T_FMT " \"%s\" \"%s\" %s \"%s\"",
|
||||
log_escape_nq(msr->mp, msr->hostname), msr->remote_addr, log_escape_nq(msr->mp, remote_user),
|
||||
log_escape_nq(msr->mp, local_user), current_logtime(msr->mp),
|
||||
((msr->request_line == NULL) ? "" : log_escape(msr->mp, msr->request_line)),
|
||||
msr->response_status, msr->bytes_sent, log_escape(msr->mp, referer),
|
||||
log_escape(msr->mp, user_agent), log_escape(msr->mp, uniqueid), sessionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a log line in vcombined log format trying to truncate
|
||||
* some of the fields to make the log line shorter than _limit bytes.
|
||||
*/
|
||||
char *construct_log_vcombinedus_limited(modsec_rec *msr, int _limit, int *was_limited) {
|
||||
char *hostname;
|
||||
char *local_user, *remote_user;
|
||||
char *referer, *user_agent, *uniqueid;
|
||||
const char *sessionid;
|
||||
char *the_request, *bytes_sent;
|
||||
int limit = _limit;
|
||||
|
||||
/* hostname */
|
||||
hostname = (msr->hostname == NULL ? "-" : log_escape_nq(msr->mp, msr->hostname));
|
||||
|
||||
/* remote log name */
|
||||
if (msr->remote_user == NULL) remote_user = "-";
|
||||
else remote_user = log_escape_nq(msr->mp, msr->remote_user);
|
||||
|
||||
/* authenticated user */
|
||||
if (msr->local_user == NULL) local_user = "-";
|
||||
else local_user = log_escape_nq(msr->mp, msr->local_user);
|
||||
|
||||
/* unique id */
|
||||
if (msr->txid == NULL) uniqueid = "-";
|
||||
else uniqueid = log_escape(msr->mp, msr->txid);
|
||||
|
||||
/* referer */
|
||||
referer = "-";
|
||||
/*
|
||||
referer = (char *)apr_table_get(msr->request_headers, "Referer");
|
||||
if (referer == NULL) referer = "-";
|
||||
else referer = log_escape(msr->mp, referer);
|
||||
*/
|
||||
|
||||
/* user agent */
|
||||
user_agent = "-";
|
||||
/*
|
||||
user_agent = (char *)apr_table_get(msr->request_headers, "User-Agent");
|
||||
if (user_agent == NULL) user_agent = "-";
|
||||
else user_agent = log_escape(msr->mp, user_agent);
|
||||
*/
|
||||
|
||||
/* sessionid */
|
||||
sessionid = (msr->sessionid == NULL) ? "-" : log_escape(msr->mp, msr->sessionid);
|
||||
|
||||
the_request = (msr->request_line == NULL) ? "" : log_escape(msr->mp, msr->request_line);
|
||||
|
||||
bytes_sent = apr_psprintf(msr->mp, "%" APR_OFF_T_FMT, msr->bytes_sent);
|
||||
|
||||
/* first take away the size of the
|
||||
* information we must log
|
||||
*/
|
||||
limit -= 22; /* spaces and double quotes */
|
||||
limit -= strlen(hostname); /* server name or IP */
|
||||
limit -= strlen(msr->remote_addr); /* remote IP */
|
||||
limit -= 28; /* current_logtime */
|
||||
limit -= 3; /* status */
|
||||
limit -= strlen(bytes_sent); /* bytes sent */
|
||||
limit -= strlen(uniqueid); /* unique id */
|
||||
limit -= strlen(sessionid); /* session id */
|
||||
|
||||
if (limit <= 0) {
|
||||
msr_log(msr, 1, "GuardianLog: Atomic pipe write size too small: %i", PIPE_BUF);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we hope to be able to squeeze everything in */
|
||||
if (limit < (int)(strlen(remote_user) + strlen(local_user) + strlen(referer)
|
||||
+ strlen(user_agent) + strlen(the_request)))
|
||||
{
|
||||
/* Boo hoo hoo, there's not enough space available. */
|
||||
*was_limited = 1;
|
||||
|
||||
/* Let's see if we can reduce the size of something. This
|
||||
* is a very crude approach but it seems to work for our
|
||||
* needs.
|
||||
*/
|
||||
if (strlen(remote_user) > 32) {
|
||||
msr_log(msr, 9, "GuardianLog: Reduced remote_user to 32.");
|
||||
remote_user[32] = '\0';
|
||||
}
|
||||
limit -= strlen(remote_user);
|
||||
|
||||
if (strlen(local_user) > 32) {
|
||||
msr_log(msr, 9, "GuardianLog: Reduced local_user to 32.");
|
||||
local_user[32] = '\0';
|
||||
}
|
||||
limit -= strlen(local_user);
|
||||
|
||||
if (strlen(referer) > 64) {
|
||||
msr_log(msr, 9, "GuardianLog: Reduced referer to 64.");
|
||||
referer[64] = '\0';
|
||||
}
|
||||
limit -= strlen(referer);
|
||||
|
||||
if (strlen(user_agent) > 64) {
|
||||
msr_log(msr, 9, "GuardianLog: Reduced user_agent to 64.");
|
||||
user_agent[64] = '\0';
|
||||
}
|
||||
limit -= strlen(user_agent);
|
||||
|
||||
if (limit <= 0) {
|
||||
msr_log(msr, 1, "GuardianLog: Atomic pipe write size too small: %i.", PIPE_BUF);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* use what's left for the request line */
|
||||
if ((int)strlen(the_request) > limit) {
|
||||
the_request[limit] = '\0';
|
||||
msr_log(msr, 9, "GuardianLog: Reduced the_request to %i bytes.", limit);
|
||||
}
|
||||
} else {
|
||||
/* Yay! We have enough space! */
|
||||
*was_limited = 0;
|
||||
}
|
||||
|
||||
return apr_psprintf(msr->mp, "%s %s %s %s [%s] \"%s\" %i %s \"%s\" \"%s\" %s \"%s\"",
|
||||
hostname, msr->remote_addr, remote_user,
|
||||
local_user, current_logtime(msr->mp), the_request,
|
||||
msr->response_status, bytes_sent, referer, user_agent,
|
||||
uniqueid, sessionid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid audit log parts specification.
|
||||
*/
|
||||
int is_valid_parts_specification(char *p) {
|
||||
char c, *t = p;
|
||||
|
||||
while((c = *t++) != '\0') {
|
||||
if ((c != AUDITLOG_PART_ENDMARKER)&&((c < AUDITLOG_PART_FIRST)||(c > AUDITLOG_PART_LAST))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a filename that will be used to store an
|
||||
* audit log entry.
|
||||
*/
|
||||
static char *construct_auditlog_filename(apr_pool_t *mp, const char *uniqueid) {
|
||||
apr_time_exp_t t;
|
||||
char tstr[300];
|
||||
apr_size_t len;
|
||||
|
||||
apr_time_exp_lt(&t, apr_time_now());
|
||||
|
||||
apr_strftime(tstr, &len, 299, "/%Y%m%d/%Y%m%d-%H%M/%Y%m%d-%H%M%S", &t);
|
||||
return apr_psprintf(mp, "%s-%s", tstr, uniqueid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a random 8-character string that
|
||||
* consists of hexadecimal numbers, to be used
|
||||
* as an audit log boundary.
|
||||
*/
|
||||
static char *create_auditlog_boundary(request_rec *r) {
|
||||
unsigned long data = rand();
|
||||
/* Do note that I tried using apr_generate_random_bytes but it turned
|
||||
* out to be terribly slow for some reason. Needs further investigation.
|
||||
*/
|
||||
return bytes2hex(r->pool, (void *)&data, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitises the request line by removing the parameters
|
||||
* that have been marked as sensitive.
|
||||
*/
|
||||
static void sanitise_request_line(modsec_rec *msr) {
|
||||
const apr_array_header_t *tarr;
|
||||
const apr_table_entry_t *telts;
|
||||
int i;
|
||||
char *qspos;
|
||||
|
||||
/* Locate the query string. */
|
||||
qspos = strstr(msr->request_line, "?");
|
||||
if (qspos == NULL) return;
|
||||
qspos++;
|
||||
|
||||
/* Loop through the list of sensitive parameters. */
|
||||
tarr = apr_table_elts(msr->arguments_to_sanitise);
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
for (i = 0; i < tarr->nelts; i++) {
|
||||
msc_arg *arg = (msc_arg *)telts[i].val;
|
||||
/* Only look at the parameters that appeared in the query string. */
|
||||
if (strcmp(arg->origin, "QUERY_STRING") == 0) {
|
||||
char *p;
|
||||
int j;
|
||||
|
||||
/* Go to the beginning of the parameter. */
|
||||
p = qspos;
|
||||
j = arg->value_origin_offset;
|
||||
while((*p != '\0')&&(j--)) p++;
|
||||
if (*p == '\0') {
|
||||
msr_log(msr, 1, "Unable to sanitise variable \"%s\" at offset %i of QUERY_STRING"
|
||||
"because the request line is too short.",
|
||||
log_escape_ex(msr->mp, arg->name, arg->name_len),
|
||||
arg->value_origin_offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Write over the value. */
|
||||
j = arg->value_origin_len;
|
||||
while((*p != '\0')&&(j--)) {
|
||||
*p++ = '*';
|
||||
}
|
||||
if (*p == '\0') {
|
||||
msr_log(msr, 1, "Unable to sanitise variable \"%s\" at offset %i (size %i) "
|
||||
"of QUERY_STRING because the request line is too short.",
|
||||
log_escape_ex(msr->mp, arg->name, arg->name_len),
|
||||
arg->value_origin_offset, arg->value_origin_len);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an audit log entry.
|
||||
*/
|
||||
void sec_audit_logger(modsec_rec *msr) {
|
||||
const apr_array_header_t *arr = NULL;
|
||||
apr_table_entry_t *te = NULL;
|
||||
char *str1 = NULL, *str2 = NULL, *text = NULL;
|
||||
apr_size_t nbytes, nbytes_written;
|
||||
unsigned char md5hash[APR_MD5_DIGESTSIZE];
|
||||
int was_limited = 0;
|
||||
int wrote_response_body = 0;
|
||||
char *entry_filename, *entry_basename;
|
||||
apr_status_t rc;
|
||||
int i, limit;
|
||||
|
||||
/* the boundary is used by both audit log types */
|
||||
msr->new_auditlog_boundary = create_auditlog_boundary(msr->r);
|
||||
|
||||
/* Return silently if we don't have a request line. This
|
||||
* means we will not be logging request timeouts.
|
||||
*/
|
||||
if (msr->request_line == NULL) {
|
||||
msr_log(msr, 4, "Audit log: Skipping request whose request_line is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Also return silently if we don't have a file descriptor. */
|
||||
if (msr->txcfg->auditlog_fd == NULL) {
|
||||
msr_log(msr, 4, "Audit log: Skipping request since there is nowhere to write to.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msr->txcfg->auditlog_type != AUDITLOG_CONCURRENT) {
|
||||
/* Serial logging - we already have an open file
|
||||
* descriptor to write to.
|
||||
*/
|
||||
msr->new_auditlog_fd = msr->txcfg->auditlog_fd;
|
||||
} else {
|
||||
/* Concurrent logging - we need to create a brand
|
||||
* new file for this request.
|
||||
*/
|
||||
apr_md5_init(&msr->new_auditlog_md5ctx);
|
||||
|
||||
msr->new_auditlog_filename = construct_auditlog_filename(msr->mp, msr->txid);
|
||||
if (msr->new_auditlog_filename == NULL) return;
|
||||
|
||||
/* The audit log storage directory should be explicitly
|
||||
* defined. But if it isn't try to write to the same
|
||||
* directory where the index file is placed. Of course,
|
||||
* it is *very* bad practice to allow the Apache user
|
||||
* to write to the same directory where a root user is
|
||||
* writing to but it's not us that's causing the problem
|
||||
* and there isn't anything we can do about that.
|
||||
*
|
||||
* TODO Actually there is something we can do! We will make
|
||||
* SecAuditStorageDir mandatory, ask the user to explicitly
|
||||
* define the storage location *and* refuse to work if the
|
||||
* index and the storage location are in the same folder.
|
||||
*/
|
||||
if (msr->txcfg->auditlog_storage_dir == NULL) {
|
||||
entry_filename = file_dirname(msr->mp, msr->txcfg->auditlog_name);
|
||||
}
|
||||
else {
|
||||
entry_filename = msr->txcfg->auditlog_storage_dir;
|
||||
}
|
||||
if (entry_filename == NULL) return;
|
||||
|
||||
entry_filename = apr_psprintf(msr->mp, "%s%s", entry_filename, msr->new_auditlog_filename);
|
||||
if (entry_filename == NULL) return;
|
||||
entry_basename = file_dirname(msr->mp, entry_filename);
|
||||
if (entry_basename == NULL) return;
|
||||
|
||||
/* IMP1 Surely it would be more efficient to check the folders for
|
||||
* the audit log repository base path in the configuration phase, to reduce
|
||||
* the work we do on every request. Also, since our path depends on time,
|
||||
* we could cache the time we last checked and don't check if we know
|
||||
* the folder is there.
|
||||
*/
|
||||
rc = apr_dir_make_recursive(entry_basename, CREATEMODE_DIR, msr->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Audit log: Failed to create subdirectories: %s (%s)",
|
||||
entry_basename, get_apr_error(msr->mp, rc));
|
||||
return;
|
||||
}
|
||||
|
||||
rc = apr_file_open(&msr->new_auditlog_fd, entry_filename,
|
||||
APR_WRITE | APR_TRUNCATE | APR_CREATE | APR_BINARY | APR_FILE_NOCLEANUP,
|
||||
CREATEMODE, msr->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Audit log: Failed to create file: %s (%s)",
|
||||
entry_filename, get_apr_error(msr->mp, rc));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lock the mutex, but only if we are using serial format. */
|
||||
if (msr->txcfg->auditlog_type != AUDITLOG_CONCURRENT) {
|
||||
rc = apr_global_mutex_lock(msr->modsecurity->auditlog_lock);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Audit log: Failed to lock global mutex: %s",
|
||||
get_apr_error(msr->mp, rc));
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_HEADER */
|
||||
|
||||
text = apr_psprintf(msr->mp, "--%s-A--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Format: time transaction_id remote_addr remote_port local_addr local_port */
|
||||
|
||||
text = apr_psprintf(msr->mp, "[%s] %s %s %i %s %i",
|
||||
current_logtime(msr->mp), msr->txid, msr->remote_addr, msr->remote_port,
|
||||
msr->local_addr, msr->local_port);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
|
||||
/* AUDITLOG_PART_REQUEST_HEADERS */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_REQUEST_HEADERS) != NULL) {
|
||||
text = apr_psprintf(msr->mp, "\n--%s-B--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
sanitise_request_line(msr);
|
||||
|
||||
sec_auditlog_write(msr, msr->request_line, strlen(msr->request_line));
|
||||
sec_auditlog_write(msr, "\n", 1);
|
||||
|
||||
arr = apr_table_elts(msr->request_headers);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, te[i].val);
|
||||
/* Do we need to sanitise this request header? */
|
||||
if (apr_table_get(msr->request_headers_to_sanitise, te[i].key) != NULL) {
|
||||
/* Yes, sanitise it. */
|
||||
memset(text + strlen(te[i].key) + 2, '*', strlen(te[i].val));
|
||||
}
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_REQUEST_BODY */
|
||||
|
||||
/* Output this part of it was explicitly requested (C) or if it was the faked
|
||||
* request body that was requested (I) but we have no reason to fake it (it's
|
||||
* already in the correct format).
|
||||
*/
|
||||
if ( (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_REQUEST_BODY) != NULL)
|
||||
|| ( (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_FAKE_REQUEST_BODY) != NULL)
|
||||
&& (msr->mpd == NULL) ) )
|
||||
{
|
||||
if (msr->msc_reqbody_read) {
|
||||
const apr_array_header_t *tarr;
|
||||
const apr_table_entry_t *telts;
|
||||
apr_array_header_t *sorted_args;
|
||||
unsigned int offset = 0, last_offset = 0;
|
||||
msc_arg *nextarg = NULL;
|
||||
int sanitise = 0; /* IMP1 Use constants for "sanitise" values. */
|
||||
|
||||
sorted_args = apr_array_make(msr->mp, 25, sizeof(const msc_arg *));
|
||||
|
||||
/* First we need to sort the arguments that need to be
|
||||
* sanitised in descending order (we are using a stack structure
|
||||
* to store then so the order will be ascending when we start
|
||||
* popping them out). This is because we will
|
||||
* be reading the request body sequentially and must
|
||||
* sanitise it as we go.
|
||||
*/
|
||||
|
||||
for(;;) {
|
||||
nextarg = NULL;
|
||||
|
||||
/* Find the next largest offset (excluding
|
||||
* the ones we've used up already).
|
||||
*/
|
||||
tarr = apr_table_elts(msr->arguments_to_sanitise);
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
msc_arg *arg = (msc_arg *)telts[i].val;
|
||||
if (strcmp(arg->origin, "BODY") != 0) continue;
|
||||
|
||||
if (last_offset == 0) { /* The first time we're here. */
|
||||
if (arg->value_origin_offset > offset) {
|
||||
offset = arg->value_origin_offset;
|
||||
nextarg = arg;
|
||||
}
|
||||
} else { /* Not the first time. */
|
||||
if ((arg->value_origin_offset > offset)
|
||||
&&(arg->value_origin_offset < last_offset))
|
||||
{
|
||||
offset = arg->value_origin_offset;
|
||||
nextarg = arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we don't have the next argument that means
|
||||
* we're done here.
|
||||
*/
|
||||
if (nextarg == NULL) break;
|
||||
|
||||
sanitise = 2; /* Means time to pop the next argument out. */
|
||||
last_offset = offset;
|
||||
offset = 0;
|
||||
{ /* IMP1 Fix this ugly bit here. */
|
||||
msc_arg **x = apr_array_push(sorted_args);
|
||||
*x = nextarg;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now start retrieving the body chunk by chunk and
|
||||
* sanitise data in pieces.
|
||||
*/
|
||||
|
||||
rc = modsecurity_request_body_retrieve_start(msr);
|
||||
if (rc < 0) {
|
||||
msr_log(msr, 1, "Audit log: Failed retrieving request body.");
|
||||
} else {
|
||||
msc_data_chunk *chunk = NULL;
|
||||
unsigned int chunk_offset = 0;
|
||||
unsigned int sanitise_offset = 0;
|
||||
unsigned int sanitise_length = 0;
|
||||
|
||||
text = apr_psprintf(msr->mp, "\n--%s-C--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
for(;;) {
|
||||
rc = modsecurity_request_body_retrieve(msr, &chunk, -1);
|
||||
if (chunk != NULL) {
|
||||
/* Anything greater than 1 means we have more data to sanitise. */
|
||||
while (sanitise > 1) {
|
||||
msc_arg **arg = NULL;
|
||||
|
||||
if (sanitise == 2) {
|
||||
/* Get the next argument from the stack. */
|
||||
arg = (msc_arg **)apr_array_pop(sorted_args);
|
||||
if (arg == NULL) sanitise = 0; /* We're done sanitising. */
|
||||
else {
|
||||
/* Continue with sanitation to process the
|
||||
* retrieved argument.
|
||||
*/
|
||||
sanitise = 1;
|
||||
sanitise_offset = (*arg)->value_origin_offset;
|
||||
sanitise_length = (*arg)->value_origin_len;
|
||||
}
|
||||
}
|
||||
|
||||
if (sanitise) {
|
||||
/* Check if the data we want to sanitise is
|
||||
* stored in the current chunk.
|
||||
*/
|
||||
if (chunk_offset + chunk->length > sanitise_offset) {
|
||||
unsigned int soff; /* data offset within chunk */
|
||||
unsigned int len; /* amount in this chunk to sanitise */
|
||||
|
||||
soff = sanitise_offset - chunk_offset;
|
||||
|
||||
if (soff + sanitise_length <= chunk->length) {
|
||||
/* The entire argument resides in the current chunk. */
|
||||
len = sanitise_length;
|
||||
sanitise = 2; /* Get another parameter to sanitise. */
|
||||
} else {
|
||||
/* Some work to do here but we'll need to seek
|
||||
* another chunk.
|
||||
*/
|
||||
len = chunk->length - soff;
|
||||
sanitise_offset += len;
|
||||
sanitise_length -= len;
|
||||
sanitise = 1; /* It's OK to go to the next chunk. */
|
||||
}
|
||||
|
||||
/* Yes, we actually write over the original data.
|
||||
* We shouldn't be needing it any more.
|
||||
*/
|
||||
if (soff + len <= chunk->length) { /* double check */
|
||||
memset((char *)chunk->data + soff, '*', len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the sanitised chunk to the log
|
||||
* and advance to the next chunk. */
|
||||
sec_auditlog_write(msr, chunk->data, chunk->length);
|
||||
chunk_offset += chunk->length;
|
||||
}
|
||||
|
||||
if (rc <= 0) break;
|
||||
}
|
||||
|
||||
modsecurity_request_body_retrieve_end(msr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_FAKE_REQUEST_BODY */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_FAKE_REQUEST_BODY) != NULL) {
|
||||
if ((msr->msc_reqbody_read)&&(msr->mpd != NULL)) {
|
||||
char *buffer = NULL;
|
||||
|
||||
buffer = multipart_reconstruct_urlencoded_body_sanitise(msr);
|
||||
if (buffer == NULL) {
|
||||
msr_log(msr, 1, "Audit log: Failed to reconstruct request body.");
|
||||
} else {
|
||||
text = apr_psprintf(msr->mp, "\n--%s-I--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
sec_auditlog_write(msr, buffer, strlen(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_A_RESPONSE_HEADERS */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_A_RESPONSE_HEADERS) != NULL) {
|
||||
text = apr_psprintf(msr->mp, "\n--%s-F--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* There are no response headers (or the status line) in HTTP 0.9 */
|
||||
if (msr->response_headers_sent) {
|
||||
if (msr->status_line != NULL) {
|
||||
text = apr_psprintf(msr->mp, "%s %s\n", msr->response_protocol,
|
||||
msr->status_line);
|
||||
} else {
|
||||
text = apr_psprintf(msr->mp, "%s %i\n", msr->response_protocol,
|
||||
msr->response_status);
|
||||
}
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Output headers */
|
||||
|
||||
arr = apr_table_elts(msr->response_headers);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, te[i].val);
|
||||
/* Do we need to sanitise this response header? */
|
||||
if (apr_table_get(msr->response_headers_to_sanitise, te[i].key) != NULL) {
|
||||
/* Yes, sanitise it. */
|
||||
memset(text + strlen(te[i].key) + 2, '*', strlen(te[i].val));
|
||||
}
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_RESPONSE_BODY */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_RESPONSE_BODY) != NULL) {
|
||||
if (msr->resbody_data != NULL) {
|
||||
text = apr_psprintf(msr->mp, "\n--%s-E--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
sec_auditlog_write(msr, msr->resbody_data, msr->resbody_length);
|
||||
wrote_response_body = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_TRAILER */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_TRAILER) != NULL) {
|
||||
apr_time_t now = apr_time_now();
|
||||
|
||||
text = apr_psprintf(msr->mp, "\n--%s-H--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Messages */
|
||||
for(i = 0; i < msr->alerts->nelts; i++) {
|
||||
text = apr_psprintf(msr->mp, "Message: %s\n", ((char **)msr->alerts->elts)[i]);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
/* Apache error messages */
|
||||
for(i = 0; i < msr->error_messages->nelts; i++) {
|
||||
error_message *em = (((error_message**)msr->error_messages->elts)[i]);
|
||||
text = apr_psprintf(msr->mp, "Apache-Error: %s\n",
|
||||
format_error_log_message(msr->mp, em));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
/* Action */
|
||||
if (msr->was_intercepted) {
|
||||
text = apr_psprintf(msr->mp, "Action: Intercepted (phase %i)\n", msr->intercept_phase);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
/* Apache-Handler */
|
||||
if (msr->r->handler != NULL) {
|
||||
text = apr_psprintf(msr->mp, "Apache-Handler: %s\n", msr->r->handler);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
/* Processing times */
|
||||
if (msr->time_checkpoint_1 == 0) {
|
||||
text = apr_psprintf(msr->mp, "Stopwatch: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT
|
||||
" (- - -)\n", (msr->request_time), (now - msr->request_time));
|
||||
} else {
|
||||
char sw_str2[101] = "-";
|
||||
char sw_str3[101] = "-";
|
||||
|
||||
if (msr->time_checkpoint_2 != 0) {
|
||||
apr_snprintf(sw_str2, sizeof(sw_str2), "%" APR_TIME_T_FMT,
|
||||
(msr->time_checkpoint_2 - msr->request_time));
|
||||
}
|
||||
|
||||
if (msr->time_checkpoint_3 != 0) {
|
||||
apr_snprintf(sw_str3, sizeof(sw_str3), "%" APR_TIME_T_FMT,
|
||||
(msr->time_checkpoint_3 - msr->request_time));
|
||||
}
|
||||
|
||||
text = apr_psprintf(msr->mp, "Stopwatch: %" APR_TIME_T_FMT
|
||||
" %" APR_TIME_T_FMT " (%" APR_TIME_T_FMT
|
||||
"%s %s %s)\n",
|
||||
(msr->request_time), (now - msr->request_time),
|
||||
(msr->time_checkpoint_1 - msr->request_time),
|
||||
((msr->msc_reqbody_read == 0) ? "" : "*"),
|
||||
sw_str2, sw_str3
|
||||
);
|
||||
}
|
||||
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Our response body does not contain chunks */
|
||||
/* ENH Only write this when the output was chunked. */
|
||||
/* ENH Add info when request body was decompressed, dechunked too. */
|
||||
if (wrote_response_body) {
|
||||
text = apr_psprintf(msr->mp, "Response-Body-Transformed: Dechunked\n");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
/* Producer */
|
||||
text = apr_psprintf(msr->mp, "Producer: %s\n", MODULE_NAME_FULL);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Server */
|
||||
if (msr->server_software != NULL) {
|
||||
text = apr_psprintf(msr->mp, "Server: %s\n", msr->server_software);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
/* Sanitised arguments */
|
||||
{
|
||||
const apr_array_header_t *tarr;
|
||||
const apr_table_entry_t *telts;
|
||||
|
||||
tarr = apr_table_elts(msr->arguments_to_sanitise);
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
text = apr_psprintf(msr->mp, "Sanitised-Args: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
msc_arg *arg = (msc_arg *)telts[i].val;
|
||||
text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, arg->name), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
}
|
||||
|
||||
/* Sanitised request headers */
|
||||
{
|
||||
const apr_array_header_t *tarr;
|
||||
const apr_table_entry_t *telts;
|
||||
|
||||
tarr = apr_table_elts(msr->request_headers_to_sanitise);
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
text = apr_psprintf(msr->mp, "Sanitised-Request-Headers: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, telts[i].key), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
}
|
||||
|
||||
/* Sanitised response headers */
|
||||
{
|
||||
const apr_array_header_t *tarr;
|
||||
const apr_table_entry_t *telts;
|
||||
|
||||
tarr = apr_table_elts(msr->response_headers_to_sanitise);
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
text = apr_psprintf(msr->mp, "Sanitised-Response-Headers: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, telts[i].key), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
}
|
||||
|
||||
/* Web application info. */
|
||||
if ( ((msr->txcfg->webappid != NULL)&&(strcmp(msr->txcfg->webappid, "default") != 0))
|
||||
|| (msr->sessionid != NULL) || (msr->userid != NULL))
|
||||
{
|
||||
text = apr_psprintf(msr->mp, "WebApp-Info: \"%s\" \"%s\" \"%s\"\n",
|
||||
msr->txcfg->webappid == NULL ? "-" : log_escape(msr->mp, msr->txcfg->webappid),
|
||||
msr->sessionid == NULL ? "-" : log_escape(msr->mp, msr->sessionid),
|
||||
msr->userid == NULL ? "-" : log_escape(msr->mp, msr->userid));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* AUDITLOG_PART_ENDMARKER */
|
||||
|
||||
text = apr_psprintf(msr->mp, "\n--%s-Z--\n", msr->new_auditlog_boundary);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Return here if we were writing to a serial log
|
||||
* as it does not need an index file.
|
||||
*/
|
||||
if (msr->txcfg->auditlog_type != AUDITLOG_CONCURRENT) {
|
||||
sec_auditlog_write(msr, "\n", 1);
|
||||
|
||||
/* Unlock the mutex we used to serialise access to the audit log file. */
|
||||
rc = apr_global_mutex_unlock(msr->modsecurity->auditlog_lock);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Audit log: Failed to unlock global mutex: %s",
|
||||
get_apr_error(msr->mp, rc));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* From here on only concurrent-style processing. */
|
||||
|
||||
apr_file_close(msr->new_auditlog_fd);
|
||||
|
||||
/* Write an entry to the index file */
|
||||
|
||||
/* Calculate hash of the entry. */
|
||||
apr_md5_final(md5hash, &msr->new_auditlog_md5ctx);
|
||||
|
||||
str2 = apr_psprintf(msr->mp, "%s %i %i md5:%s", msr->new_auditlog_filename, 0,
|
||||
msr->new_auditlog_size, bytes2hex(msr->mp, md5hash, 16));
|
||||
if (str2 == NULL) return;
|
||||
|
||||
/* We do not want the index line to be longer than 3980 bytes. */
|
||||
limit = 3980;
|
||||
was_limited = 0;
|
||||
|
||||
/* If we are logging to a pipe we need to observe and
|
||||
* obey the pipe atomic write limit - PIPE_BUF. For
|
||||
* more details see the discussion in sec_guardian_logger code.
|
||||
*/
|
||||
if (msr->txcfg->auditlog_name[0] == '|') {
|
||||
if (PIPE_BUF < limit) {
|
||||
limit = PIPE_BUF;
|
||||
}
|
||||
}
|
||||
|
||||
limit = limit - strlen(str2) - 5;
|
||||
if (limit <= 0) {
|
||||
msr_log(msr, 1, "Audit Log: Atomic PIPE write buffer too small: %i", PIPE_BUF);
|
||||
return;
|
||||
}
|
||||
|
||||
str1 = construct_log_vcombinedus_limited(msr, limit, &was_limited);
|
||||
if (str1 == NULL) return;
|
||||
|
||||
if (was_limited == 0) {
|
||||
text = apr_psprintf(msr->mp, "%s %s \n", str1, str2);
|
||||
} else {
|
||||
text = apr_psprintf(msr->mp, "%s %s L\n", str1, str2);
|
||||
}
|
||||
if (text == NULL) return;
|
||||
|
||||
nbytes = strlen(text);
|
||||
apr_file_write_full(msr->txcfg->auditlog_fd, text, nbytes, &nbytes_written);
|
||||
}
|
46
apache2/msc_logging.h
Normal file
46
apache2/msc_logging.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_logging.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MSC_LOGGING_H_
|
||||
#define _MSC_LOGGING_H_
|
||||
|
||||
#define AUDITLOG_OFF 0
|
||||
#define AUDITLOG_ON 1
|
||||
#define AUDITLOG_RELEVANT 2
|
||||
|
||||
#define AUDITLOG_SERIAL 0
|
||||
#define AUDITLOG_CONCURRENT 1
|
||||
|
||||
#define AUDITLOG_PART_FIRST 'A'
|
||||
#define AUDITLOG_PART_HEADER 'A'
|
||||
#define AUDITLOG_PART_REQUEST_HEADERS 'B'
|
||||
#define AUDITLOG_PART_REQUEST_BODY 'C'
|
||||
#define AUDITLOG_PART_RESPONSE_HEADERS 'D'
|
||||
#define AUDITLOG_PART_RESPONSE_BODY 'E'
|
||||
#define AUDITLOG_PART_A_RESPONSE_HEADERS 'F'
|
||||
#define AUDITLOG_PART_A_RESPONSE_BODY 'G'
|
||||
#define AUDITLOG_PART_TRAILER 'H'
|
||||
#define AUDITLOG_PART_FAKE_REQUEST_BODY 'I'
|
||||
#define AUDITLOG_PART_LAST 'I'
|
||||
#define AUDITLOG_PART_ENDMARKER 'Z'
|
||||
|
||||
#include "modsecurity.h"
|
||||
|
||||
int DSOLOCAL is_valid_parts_specification(char *p);
|
||||
|
||||
char DSOLOCAL *construct_log_vcombinedus(modsec_rec *msr);
|
||||
|
||||
char DSOLOCAL *construct_log_vcombinedus_limited(modsec_rec *msr, int _limit, int *was_limited);
|
||||
|
||||
void DSOLOCAL sec_audit_logger(modsec_rec *msr);
|
||||
|
||||
#endif
|
856
apache2/msc_multipart.c
Normal file
856
apache2/msc_multipart.c
Normal file
@ -0,0 +1,856 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_multipart.c,v 1.2 2006/10/16 04:41:51 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include <ctype.h>
|
||||
|
||||
#include "msc_multipart.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
|
||||
#if 0
|
||||
static char *multipart_construct_filename(modsec_rec *msr) {
|
||||
char c, *p, *q = msr->mpd->mpp->filename;
|
||||
char *filename;
|
||||
|
||||
/* find the last backward slash and consider the
|
||||
* filename to be only what's right from it
|
||||
*/
|
||||
p = strrchr(q, '\\');
|
||||
if (p != NULL) q = p + 1;
|
||||
|
||||
/* do the same for the forward slash */
|
||||
p = strrchr(q, '/');
|
||||
if (p != NULL) q = p + 1;
|
||||
|
||||
/* allow letters, digits and dots, replace
|
||||
* everything else with underscores
|
||||
*/
|
||||
p = filename = apr_pstrdup(msr->mp, q);
|
||||
while((c = *p) != 0) {
|
||||
if (!( isalnum(c)||(c == '.') )) *p = '_';
|
||||
p++;
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) {
|
||||
char *p = NULL, *t = NULL;
|
||||
|
||||
/* accept only what we understand */
|
||||
if (strncmp(c_d_value, "form-data", 9) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* see if there are any other parts to parse */
|
||||
|
||||
p = c_d_value + 9;
|
||||
while((*p == '\t')||(*p == ' ')) p++;
|
||||
if (*p == '\0') return 1; /* this is OK */
|
||||
|
||||
if (*p != ';') return -2;
|
||||
p++;
|
||||
|
||||
/* parse the appended parts */
|
||||
|
||||
while(*p != '\0') {
|
||||
char *name = NULL, *value = NULL, *start = NULL;
|
||||
|
||||
/* go over the whitespace */
|
||||
while((*p == '\t')||(*p == ' ')) p++;
|
||||
if (*p == '\0') return -3;
|
||||
|
||||
start = p;
|
||||
while((*p != '\0')&&(*p != '=')&&(*p != '\t')&&(*p != ' ')) p++;
|
||||
if (*p == '\0') return -4;
|
||||
|
||||
name = apr_pstrmemdup(msr->mp, start, (p - start));
|
||||
|
||||
while((*p == '\t')||(*p == ' ')) p++;
|
||||
if (*p == '\0') return -5;
|
||||
|
||||
if (*p != '=') return -13;
|
||||
p++;
|
||||
|
||||
while((*p == '\t')||(*p == ' ')) p++;
|
||||
if (*p == '\0') return -6;
|
||||
|
||||
if (*p == '"') {
|
||||
/* quoted */
|
||||
|
||||
p++;
|
||||
if (*p == '\0') return -7;
|
||||
|
||||
start = p;
|
||||
value = apr_pstrdup(msr->mp, p);
|
||||
t = value;
|
||||
|
||||
while(*p != '\0') {
|
||||
if (*p == '\\') {
|
||||
if (*(p + 1) == '\0') {
|
||||
/* improper escaping */
|
||||
return -8;
|
||||
}
|
||||
/* only " and \ can be escaped */
|
||||
if ((*(p + 1) == '"')||(*(p + 1) == '\\')) {
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
/* improper escaping */
|
||||
|
||||
/* We allow for now because IE sends
|
||||
* improperly escaped content and there's
|
||||
* nothing we can do about it.
|
||||
*
|
||||
* return -9;
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
if (*p == '"') {
|
||||
*t = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
*t++ = *p++;
|
||||
}
|
||||
if (*p == '\0') return -10;
|
||||
|
||||
p++; /* go over the quote at the end */
|
||||
|
||||
} else {
|
||||
/* not quoted */
|
||||
|
||||
start = p;
|
||||
while((*p != '\0')&&(is_token_char(*p))) p++;
|
||||
value = apr_pstrmemdup(msr->mp, start, (p - start));
|
||||
}
|
||||
|
||||
/* evaluate part */
|
||||
|
||||
if (strcmp(name, "name") == 0) {
|
||||
if (msr->mpd->mpp->name != NULL) return -14;
|
||||
msr->mpd->mpp->name = value;
|
||||
msr_log(msr, 9, "Multipart: Content-Disposition name: %s",
|
||||
log_escape_nq(msr->mp, value));
|
||||
}
|
||||
else
|
||||
if (strcmp(name, "filename") == 0) {
|
||||
if (msr->mpd->mpp->filename != NULL) return -15;
|
||||
msr->mpd->mpp->filename = value;
|
||||
msr_log(msr, 9, "Multipart: Content-Disposition filename: %s",
|
||||
log_escape_nq(msr->mp, value));
|
||||
}
|
||||
else return -11;
|
||||
|
||||
if (*p != '\0') {
|
||||
while((*p == '\t')||(*p == ' ')) p++;
|
||||
/* the next character must be a zero or a semi-colon */
|
||||
if (*p == '\0') return 1; /* this is OK */
|
||||
if (*p != ';') return -12;
|
||||
p++; /* move over the semi-colon */
|
||||
}
|
||||
|
||||
/* loop will stop when (*p == '\0') */
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
||||
int rc;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if ((msr->mpd->buf[0] == '\r')
|
||||
&&(msr->mpd->buf[1] == '\n')
|
||||
&&(msr->mpd->buf[2] == '\0'))
|
||||
{
|
||||
char *header_value;
|
||||
|
||||
/* empty line */
|
||||
|
||||
header_value = (char *)apr_table_get(msr->mpd->mpp->headers, "Content-Disposition");
|
||||
if (header_value == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Part is missing the Content-Disposition header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = multipart_parse_content_disposition(msr, header_value);
|
||||
if (rc < 0) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (%i): %s",
|
||||
rc, log_escape_nq(msr->mp, header_value));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (msr->mpd->mpp->name == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Part name missing");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (msr->mpd->mpp->filename != NULL) {
|
||||
msr->mpd->mpp->type = MULTIPART_FILE;
|
||||
} else {
|
||||
msr->mpd->mpp->type = MULTIPART_FORMDATA;
|
||||
}
|
||||
|
||||
msr->mpd->mpp_state = 1;
|
||||
msr->mpd->mpp->last_header_name = NULL;
|
||||
} else {
|
||||
/* header line */
|
||||
if ((msr->mpd->buf[0] == '\t')||(msr->mpd->buf[0] == ' ')) {
|
||||
char *header_value, *new_value, *data;
|
||||
|
||||
/* header folding, add data to the header we are building */
|
||||
|
||||
if (msr->mpd->mpp->last_header_name == NULL) {
|
||||
/* we are not building a header at this moment */
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: invalid part header (invalid folding)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* locate the beginning of the data */
|
||||
data = msr->mpd->buf;
|
||||
while((*data == '\t')||(*data == ' ')) data++;
|
||||
|
||||
new_value = apr_pstrdup(msr->mp, data);
|
||||
remove_lf_crlf_inplace(new_value);
|
||||
|
||||
/* update the header value in the table */
|
||||
header_value = (char *)apr_table_get(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name);
|
||||
new_value = apr_pstrcat(msr->mp, header_value, " ", new_value, NULL);
|
||||
apr_table_set(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name, new_value);
|
||||
|
||||
msr_log(msr, 9, "Multipart: Continued folder header \"%s\" with \"%s\"",
|
||||
log_escape(msr->mp, msr->mpd->mpp->last_header_name),
|
||||
log_escape(msr->mp, data));
|
||||
|
||||
if (strlen(new_value) > 4096) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multpart: invalid part header (too long)");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
char *header_name, *header_value, *data;
|
||||
|
||||
/* new header */
|
||||
|
||||
data = msr->mpd->buf;
|
||||
while((*data != ':')&&(*data != '\0')) data++;
|
||||
if (*data == '\0') {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: invalid part header (missing colon): %s",
|
||||
log_escape_nq(msr->mp, msr->mpd->buf));
|
||||
return -1;
|
||||
}
|
||||
|
||||
header_name = apr_pstrmemdup(msr->mp, msr->mpd->buf, (data - msr->mpd->buf));
|
||||
|
||||
/* extract the value value */
|
||||
data++;
|
||||
while((*data == '\t')||(*data == ' ')) data++;
|
||||
header_value = apr_pstrdup(msr->mp, data);
|
||||
remove_lf_crlf_inplace(header_value);
|
||||
|
||||
/* error if the name already exists */
|
||||
if (apr_table_get(msr->mpd->mpp->headers, header_name) != NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: part header already exists: %s",
|
||||
log_escape_nq(msr->mp, header_name));
|
||||
return -1;
|
||||
}
|
||||
|
||||
apr_table_setn(msr->mpd->mpp->headers, header_name, header_value);
|
||||
msr->mpd->mpp->last_header_name = header_name;
|
||||
|
||||
msr_log(msr, 9, "Multipart: Added part header \"%s\" \"%s\"",
|
||||
log_escape(msr->mp, header_name),
|
||||
log_escape(msr->mp, header_value));
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int multipart_process_part_data(modsec_rec *msr, char **error_msg) {
|
||||
char *p = msr->mpd->buf + (MULTIPART_BUF_SIZE - msr->mpd->bufleft) - 2;
|
||||
char localreserve[2];
|
||||
int bytes_reserved = 0;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* preserve the last two bytes for later */
|
||||
if (MULTIPART_BUF_SIZE - msr->mpd->bufleft >= 2) {
|
||||
bytes_reserved = 1;
|
||||
localreserve[0] = *p;
|
||||
localreserve[1] = *(p + 1);
|
||||
msr->mpd->bufleft += 2;
|
||||
*p = 0;
|
||||
}
|
||||
|
||||
/* add data to the part we are building */
|
||||
if (msr->mpd->mpp->type == MULTIPART_FILE) {
|
||||
|
||||
/* remember where we started */
|
||||
if (msr->mpd->mpp->length == 0) {
|
||||
msr->mpd->mpp->offset = msr->mpd->buf_offset;
|
||||
}
|
||||
|
||||
/* only store individual files on disk if we are going
|
||||
* to keep them or if we need to have them approved later
|
||||
*/
|
||||
if (msr->upload_extract_files) {
|
||||
/* first create a temporary file if we don't have it already */
|
||||
if (msr->mpd->mpp->tmp_file_fd == 0) {
|
||||
// char *filename = multipart_construct_filename(msr);
|
||||
|
||||
/* construct temporary file name */
|
||||
msr->mpd->mpp->tmp_file_name = apr_psprintf(msr->mp, "%s/%s-%s-file-XXXXXX",
|
||||
msr->txcfg->tmp_dir, current_filetime(msr->mp), msr->txid);
|
||||
msr->mpd->mpp->tmp_file_fd = msc_mkstemp(msr->mpd->mpp->tmp_file_name);
|
||||
|
||||
/* do we have an opened file? */
|
||||
if (msr->mpd->mpp->tmp_file_fd < 0) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Failed to create file: %s",
|
||||
log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name));
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "Multipart: Created temporary file: %s",
|
||||
log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name));
|
||||
}
|
||||
|
||||
/* write the reserve first */
|
||||
if (msr->mpd->reserve[0] == 1) {
|
||||
if (write(msr->mpd->mpp->tmp_file_fd, &msr->mpd->reserve[1], 2) != 2) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
|
||||
log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
|
||||
return -1;
|
||||
}
|
||||
msr->mpd->mpp->tmp_file_size += 2;
|
||||
msr->mpd->mpp->length += 2;
|
||||
}
|
||||
|
||||
/* write data to the file */
|
||||
if (write(msr->mpd->mpp->tmp_file_fd, msr->mpd->buf, MULTIPART_BUF_SIZE - msr->mpd->bufleft)
|
||||
!= (MULTIPART_BUF_SIZE - msr->mpd->bufleft))
|
||||
{
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
|
||||
log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
|
||||
msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
|
||||
} else {
|
||||
/* just keep track of the file size */
|
||||
if (msr->mpd->reserve[0] == 1) {
|
||||
msr->mpd->mpp->tmp_file_size += 2;
|
||||
}
|
||||
msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
|
||||
msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
|
||||
}
|
||||
}
|
||||
else if (msr->mpd->mpp->type == MULTIPART_FORMDATA) {
|
||||
value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t));
|
||||
|
||||
/* add this part to the list of parts */
|
||||
|
||||
/* remember where we started */
|
||||
if (msr->mpd->mpp->length == 0) {
|
||||
msr->mpd->mpp->offset = msr->mpd->buf_offset;
|
||||
}
|
||||
|
||||
if (msr->mpd->reserve[0] == 1) {
|
||||
value_part->data = apr_palloc(msr->mp, (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + 2);
|
||||
memcpy(value_part->data, &(msr->mpd->reserve[1]), 2);
|
||||
memcpy(value_part->data + 2, msr->mpd->buf, (MULTIPART_BUF_SIZE - msr->mpd->bufleft));
|
||||
value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + 2;
|
||||
msr->mpd->mpp->length += value_part->length;
|
||||
} else {
|
||||
value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
|
||||
value_part->data = apr_pstrmemdup(msr->mp, msr->mpd->buf, value_part->length);
|
||||
msr->mpd->mpp->length += value_part->length;
|
||||
}
|
||||
|
||||
*(value_part_t **)apr_array_push(msr->mpd->mpp->value_parts) = value_part;
|
||||
|
||||
msr_log(msr, 9, "Multipart: Added data to variable: %s",
|
||||
log_escape_nq_ex(msr->mp, value_part->data, value_part->length));
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: unknown part type %i", msr->mpd->mpp->type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* store the reserved bytes to the multipart
|
||||
* context so that they don't get lost
|
||||
*/
|
||||
if (bytes_reserved) {
|
||||
msr->mpd->reserve[0] = 1;
|
||||
msr->mpd->reserve[1] = localreserve[0];
|
||||
msr->mpd->reserve[2] = localreserve[1];
|
||||
msr->mpd->buf_offset += 2;
|
||||
}
|
||||
else {
|
||||
msr->mpd->reserve[0] = 0;
|
||||
msr->mpd->buf_offset -= 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *multipart_combine_value_parts(modsec_rec *msr, apr_array_header_t *value_parts) {
|
||||
value_part_t **parts = NULL;
|
||||
char *rval = apr_palloc(msr->mp, msr->mpd->mpp->length + 1);
|
||||
unsigned long int offset;
|
||||
int i;
|
||||
|
||||
if (rval == NULL) return NULL;
|
||||
|
||||
offset = 0;
|
||||
parts = (value_part_t **)value_parts->elts;
|
||||
for(i = 0; i < value_parts->nelts; i++) {
|
||||
if (offset + parts[i]->length <= msr->mpd->mpp->length) {
|
||||
memcpy(rval + offset, parts[i]->data, parts[i]->length);
|
||||
offset += parts[i]->length;
|
||||
}
|
||||
}
|
||||
rval[offset] = '\0';
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int multipart_process_boundary(modsec_rec *msr, int last_part, char **error_log) {
|
||||
/* if there was a part being built finish it */
|
||||
if (msr->mpd->mpp != NULL) {
|
||||
/* close the temp file */
|
||||
if ((msr->mpd->mpp->type == MULTIPART_FILE)
|
||||
&&(msr->mpd->mpp->tmp_file_name != NULL)
|
||||
&&(msr->mpd->mpp->tmp_file_fd != 0))
|
||||
{
|
||||
close(msr->mpd->mpp->tmp_file_fd);
|
||||
}
|
||||
|
||||
if (msr->mpd->mpp->type != MULTIPART_FILE) {
|
||||
/* now construct a single string out of the parts */
|
||||
msr->mpd->mpp->value = multipart_combine_value_parts(msr, msr->mpd->mpp->value_parts);
|
||||
if (msr->mpd->mpp->value == NULL) return -1;
|
||||
}
|
||||
|
||||
/* add the part to the list of parts */
|
||||
*(multipart_part **)apr_array_push(msr->mpd->parts) = msr->mpd->mpp;
|
||||
if (msr->mpd->mpp->type == MULTIPART_FILE) {
|
||||
msr_log(msr, 9, "Multipart: Added file part %x to the list: name \"%s\" "
|
||||
"file name \"%s\" (offset %u, length %u)",
|
||||
msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name),
|
||||
log_escape(msr->mp, msr->mpd->mpp->filename),
|
||||
msr->mpd->mpp->offset, msr->mpd->mpp->length);
|
||||
}
|
||||
else {
|
||||
msr_log(msr, 9, "Multipart: Added part %x to the list: name \"%s\" "
|
||||
"(offset %u, length %u)", msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name),
|
||||
msr->mpd->mpp->offset, msr->mpd->mpp->length);
|
||||
}
|
||||
msr->mpd->mpp = NULL;
|
||||
}
|
||||
|
||||
if (last_part == 0) {
|
||||
/* start building a new part */
|
||||
msr->mpd->mpp = (multipart_part *)apr_pcalloc(msr->mp, sizeof(multipart_part));
|
||||
if (msr->mpd->mpp == NULL) return -1;
|
||||
msr->mpd->mpp->type = MULTIPART_FORMDATA;
|
||||
msr->mpd->mpp_state = 0;
|
||||
|
||||
msr->mpd->mpp->headers = apr_table_make(msr->mp, 10);
|
||||
if (msr->mpd->mpp->headers == NULL) return -1;
|
||||
msr->mpd->mpp->last_header_name = NULL;
|
||||
|
||||
msr->mpd->reserve[0] = 0;
|
||||
msr->mpd->reserve[1] = 0;
|
||||
msr->mpd->reserve[2] = 0;
|
||||
msr->mpd->reserve[3] = 0;
|
||||
|
||||
msr->mpd->mpp->value_parts = apr_array_make(msr->mp, 10, sizeof(value_part_t *));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int multipart_init(modsec_rec *msr, char **error_msg) {
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
msr->mpd = (multipart_data *)apr_pcalloc(msr->mp, sizeof(multipart_data));
|
||||
if (msr->mpd == NULL) return -1;
|
||||
|
||||
if (msr->request_content_type == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Content-Type header not available.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr->mpd->boundary = strstr(msr->request_content_type, "boundary=");
|
||||
if ((msr->mpd->boundary != NULL)&&(*(msr->mpd->boundary + 9) != 0)) {
|
||||
msr->mpd->boundary = msr->mpd->boundary + 9;
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Multipart Boundary not found or invalid.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr->mpd->parts = apr_array_make(msr->mp, 10, sizeof(multipart_part *));
|
||||
msr->mpd->bufleft = MULTIPART_BUF_SIZE;
|
||||
msr->mpd->bufptr = msr->mpd->buf;
|
||||
msr->mpd->buf_contains_line = 1;
|
||||
msr->mpd->mpp = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int multipart_complete(modsec_rec *msr, char **error_log) {
|
||||
if (msr->mpd == NULL) return 1;
|
||||
|
||||
if ((msr->mpd->seen_data != 0)&&(msr->mpd->is_complete == 0)) {
|
||||
*error_log = apr_psprintf(msr->mp, "Multipart: final boundary missing");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
||||
unsigned int size, char **error_msg)
|
||||
{
|
||||
char *inptr = (char *)buf;
|
||||
unsigned int inleft = size;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if (size == 0) return 1;
|
||||
|
||||
if (msr->mpd->seen_data == 0) msr->mpd->seen_data = 1;
|
||||
|
||||
if (msr->mpd->is_complete) {
|
||||
msr_log(msr, 4, "Multipart: Ignoring data after last boundary (received %i bytes)", size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (msr->mpd->bufleft == 0) {
|
||||
*error_msg = apr_psprintf(msr->mp,
|
||||
"Multipart: Internal error in process_chunk: no space left in the buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* here we loop through the data available, byte by byte */
|
||||
while(inleft > 0) {
|
||||
char c = *inptr;
|
||||
int process_buffer = 0;
|
||||
|
||||
if ((c == 0x0d)&&(msr->mpd->bufleft == 1)) {
|
||||
/* we don't want to take 0x0d as the last byte in the buffer */
|
||||
process_buffer = 1;
|
||||
} else {
|
||||
inptr++;
|
||||
inleft = inleft - 1;
|
||||
|
||||
*(msr->mpd->bufptr) = c;
|
||||
msr->mpd->bufptr++;
|
||||
msr->mpd->bufleft--;
|
||||
}
|
||||
|
||||
/* until we either reach the end of the line
|
||||
* or the end of our internal buffer
|
||||
*/
|
||||
if ((c == 0x0a)||(msr->mpd->bufleft == 0)||(process_buffer)) {
|
||||
*(msr->mpd->bufptr) = 0;
|
||||
|
||||
/* boundary preconditions: length of the line greater than
|
||||
* the length of the boundary + the first two characters
|
||||
* are dashes "-"
|
||||
*/
|
||||
if ( msr->mpd->buf_contains_line
|
||||
&& (strlen(msr->mpd->buf) > strlen(msr->mpd->boundary) + 2)
|
||||
&& (((*(msr->mpd->buf) == '-'))&&(*(msr->mpd->buf + 1) == '-'))
|
||||
&& (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) ) {
|
||||
|
||||
char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary);
|
||||
|
||||
if ( (*boundary_end == '\r')
|
||||
&&(*(boundary_end + 1) == '\n')
|
||||
&&(*(boundary_end + 2) == '\0')
|
||||
) {
|
||||
/* simple boundary */
|
||||
if (multipart_process_boundary(msr, 0, error_msg) < 0) return -1;
|
||||
}
|
||||
else
|
||||
if ( (*boundary_end == '-')
|
||||
&&(*(boundary_end + 1) == '-')
|
||||
&&(*(boundary_end + 2) == '\r')
|
||||
&&(*(boundary_end + 3) == '\n')
|
||||
&&(*(boundary_end + 4) == '\0')
|
||||
) {
|
||||
/* final boundary */
|
||||
msr->mpd->is_complete = 1;
|
||||
if (multipart_process_boundary(msr, 1, error_msg) < 0) return -1;
|
||||
}
|
||||
else {
|
||||
/* error */
|
||||
*error_msg = apr_psprintf(msr->mp,
|
||||
"Multipart: Invalid boundary detected: %s",
|
||||
log_escape_nq(msr->mp, msr->mpd->buf));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (msr->mpd->mpp == NULL) {
|
||||
msr_log(msr, 4, "Multipart: Ignoring data before first boundary.");
|
||||
} else {
|
||||
if (msr->mpd->mpp_state == 0) {
|
||||
if ((msr->mpd->bufleft == 0)||(process_buffer)) {
|
||||
/* part header lines must be shorter than
|
||||
* MULTIPART_BUF_SIZE bytes
|
||||
*/
|
||||
*error_msg = apr_psprintf(msr->mp,
|
||||
"Multipart: Part header line over %i bytes long",
|
||||
MULTIPART_BUF_SIZE);
|
||||
return -1;
|
||||
}
|
||||
if (multipart_process_part_header(msr, error_msg) < 0) return -1;
|
||||
} else {
|
||||
if (multipart_process_part_data(msr, error_msg) < 0) return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the offset of the data we are about
|
||||
* to process. This is to allow us to know the
|
||||
* offsets of individual files and variables.
|
||||
*/
|
||||
msr->mpd->buf_offset += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
|
||||
|
||||
/* reset the pointer to the beginning of the buffer
|
||||
* and continue to accept input data
|
||||
*/
|
||||
msr->mpd->bufptr = msr->mpd->buf;
|
||||
msr->mpd->bufleft = MULTIPART_BUF_SIZE;
|
||||
msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0;
|
||||
}
|
||||
|
||||
if ((msr->mpd->is_complete)&&(inleft != 0)) {
|
||||
msr_log(msr, 4, "Multipart: Ignoring data after last boundary (%i bytes left)", inleft);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apr_status_t multipart_cleanup(modsec_rec *msr) {
|
||||
int keep_files = 0;
|
||||
|
||||
if (msr->mpd == NULL) return -1;
|
||||
|
||||
msr_log(msr, 4, "Multipart: Cleanup started (remove files %i).", msr->upload_remove_files);
|
||||
|
||||
if (msr->upload_remove_files == 0) {
|
||||
if (msr->txcfg->upload_dir == NULL) {
|
||||
msr_log(msr, 1, "Input filter: SecUploadDir is undefined, unable to store "
|
||||
"multipart files.");
|
||||
} else {
|
||||
keep_files = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through the list of parts
|
||||
* and delete the temporary files, but only if
|
||||
* file storage was not requested, or if storage
|
||||
* of relevant files was requested and this isn't
|
||||
* such a request.
|
||||
*/
|
||||
if (keep_files == 0) {
|
||||
multipart_part **parts;
|
||||
int i;
|
||||
|
||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
||||
if (parts[i]->type == MULTIPART_FILE) {
|
||||
if (parts[i]->tmp_file_name != NULL) {
|
||||
if (unlink(parts[i]->tmp_file_name) < 0) {
|
||||
msr_log(msr, 1, "Multipart: Failed to delete file (part) \"%s\" because %d(%s)",
|
||||
log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno));
|
||||
} else {
|
||||
msr_log(msr, 4, "Multipart: Deleted file (part) \"%s\"",
|
||||
log_escape(msr->mp, parts[i]->tmp_file_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* delete empty files, move the others to the upload dir */
|
||||
multipart_part **parts;
|
||||
int i;
|
||||
|
||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
||||
if ((parts[i]->type == MULTIPART_FILE)&&(parts[i]->tmp_file_size == 0)) {
|
||||
/* Delete empty file. */
|
||||
if (parts[i]->tmp_file_name != NULL) {
|
||||
if (unlink(parts[i]->tmp_file_name) < 0) {
|
||||
msr_log(msr, 1, "Multipart: Failed to delete empty file (part) \"%s\" because %d(%s)",
|
||||
log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno));
|
||||
} else {
|
||||
msr_log(msr, 4, "Multipart: Deleted empty file (part) \"%s\"",
|
||||
log_escape(msr->mp, parts[i]->tmp_file_name));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Move file to the upload dir. */
|
||||
if (parts[i]->tmp_file_name != NULL) {
|
||||
const char *new_filename = NULL;
|
||||
const char *new_basename = NULL;
|
||||
|
||||
new_basename = file_basename(msr->mp, parts[i]->tmp_file_name);
|
||||
if (new_basename == NULL) return -1;
|
||||
new_filename = apr_psprintf(msr->mp, "%s/%s", msr->txcfg->upload_dir,
|
||||
new_basename);
|
||||
if (new_filename == NULL) return -1;
|
||||
|
||||
if (apr_file_rename(parts[i]->tmp_file_name, new_filename,
|
||||
msr->msc_reqbody_mp) != APR_SUCCESS)
|
||||
{
|
||||
msr_log(msr, 1, "Input filter: Failed to rename file from \"%s\" to \"%s\".",
|
||||
log_escape(msr->mp, parts[i]->tmp_file_name),
|
||||
log_escape(msr->mp, new_filename));
|
||||
return -1;
|
||||
} else {
|
||||
msr_log(msr, 4, "Input filter: Moved file from \"%s\" to \"%s\".",
|
||||
log_escape(msr->mp, parts[i]->tmp_file_name),
|
||||
log_escape(msr->mp, new_filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int multipart_get_arguments(modsec_rec *msr, char *origin, apr_table_t *arguments) {
|
||||
multipart_part **parts;
|
||||
int i;
|
||||
|
||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
||||
if (parts[i]->type == MULTIPART_FORMDATA) {
|
||||
msc_arg *arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg));
|
||||
if (arg == NULL) return -1;
|
||||
|
||||
arg->name = parts[i]->name;
|
||||
arg->name_len = strlen(parts[i]->name); // TODO
|
||||
arg->value = parts[i]->value;
|
||||
arg->value_len = parts[i]->length;
|
||||
arg->value_origin_offset = parts[i]->offset;
|
||||
arg->value_origin_len = parts[i]->length;
|
||||
arg->origin = origin;
|
||||
|
||||
apr_table_addn(arguments, arg->name, (void *)arg);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr) {
|
||||
multipart_part **parts;
|
||||
char *body;
|
||||
unsigned int body_len;
|
||||
int i;
|
||||
|
||||
if (msr->mpd == NULL) return NULL;
|
||||
|
||||
// TODO Cache this data somewhere in the structure
|
||||
|
||||
/* calculate the size of the buffer */
|
||||
body_len = 1;
|
||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
||||
if (parts[i]->type == MULTIPART_FORMDATA) {
|
||||
body_len += 4;
|
||||
body_len += strlen(parts[i]->name) * 3;
|
||||
body_len += strlen(parts[i]->value) * 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* allocate the buffer */
|
||||
body = apr_palloc(msr->mp, body_len + 1);
|
||||
if ((body == NULL)||(body_len + 1 == 0)) return NULL;
|
||||
*body = 0;
|
||||
|
||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
||||
if (parts[i]->type == MULTIPART_FORMDATA) {
|
||||
if (*body != 0) {
|
||||
strncat(body, "&", body_len - strlen(body));
|
||||
}
|
||||
strnurlencat(body, parts[i]->name, body_len - strlen(body));
|
||||
strncat(body, "=", body_len - strlen(body));
|
||||
|
||||
/* Sanitise the variable. Since we are only doing this for
|
||||
* the logging we will actually write over the data we keep
|
||||
* in the memory.
|
||||
*/
|
||||
if (msr->phase >= PHASE_LOGGING) {
|
||||
if (apr_table_get(msr->arguments_to_sanitise, parts[i]->name) != NULL) {
|
||||
memset(parts[i]->value, '*', strlen(parts[i]->value));
|
||||
}
|
||||
}
|
||||
strnurlencat(body, parts[i]->value, body_len - strlen(body));
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
120
apache2/msc_multipart.h
Normal file
120
apache2/msc_multipart.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_multipart.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MSC_MULTIPART_H_
|
||||
#define _MSC_MULTIPART_H_
|
||||
|
||||
#define MULTIPART_BUF_SIZE 4096
|
||||
|
||||
#define MULTIPART_FORMDATA 1
|
||||
#define MULTIPART_FILE 2
|
||||
|
||||
typedef struct multipart_part multipart_part;
|
||||
typedef struct multipart_data multipart_data;
|
||||
|
||||
#include "apr_general.h"
|
||||
#include "apr_tables.h"
|
||||
#include "modsecurity.h"
|
||||
|
||||
typedef struct value_part_t value_part_t;
|
||||
struct value_part_t {
|
||||
char *data;
|
||||
long int length;
|
||||
};
|
||||
|
||||
struct multipart_part {
|
||||
/* part type, can be MULTIPART_FORMDATA or MULTIPART_FILE */
|
||||
int type;
|
||||
/* the name */
|
||||
char *name;
|
||||
|
||||
/* variables only, variable value */
|
||||
char *value;
|
||||
apr_array_header_t *value_parts;
|
||||
|
||||
/* files only, the content type (where available) */
|
||||
char *content_type;
|
||||
|
||||
/* files only, the name of the temporary file holding data */
|
||||
char *tmp_file_name;
|
||||
int tmp_file_fd;
|
||||
unsigned tmp_file_size;
|
||||
/* files only, filename as supplied by the browser */
|
||||
char *filename;
|
||||
|
||||
char *last_header_name;
|
||||
apr_table_t *headers;
|
||||
|
||||
unsigned int offset;
|
||||
unsigned int length;
|
||||
};
|
||||
|
||||
struct multipart_data {
|
||||
/* this array keeps parts */
|
||||
apr_array_header_t *parts;
|
||||
|
||||
/* mime boundary used to detect when
|
||||
* parts end and new begin
|
||||
*/
|
||||
char *boundary;
|
||||
|
||||
/* internal buffer and other variables
|
||||
* used while parsing
|
||||
*/
|
||||
char buf[MULTIPART_BUF_SIZE + 2];
|
||||
int buf_contains_line;
|
||||
char *bufptr;
|
||||
int bufleft;
|
||||
|
||||
unsigned int buf_offset;
|
||||
|
||||
/* pointer that keeps track of a part while
|
||||
* it is being built
|
||||
*/
|
||||
multipart_part *mpp;
|
||||
|
||||
|
||||
/* part parsing state; 0 means we are reading
|
||||
* headers, 1 means we are collecting data
|
||||
*/
|
||||
int mpp_state;
|
||||
|
||||
/* because of the way this parsing algorithm
|
||||
* works we hold back the last two bytes of
|
||||
* each data chunk so that we can discard it
|
||||
* later if the next data chunk proves to be
|
||||
* a boundary; the first byte is an indicator
|
||||
* 0 - no content, 1 - two data bytes available
|
||||
*/
|
||||
char reserve[4];
|
||||
|
||||
int seen_data;
|
||||
int is_complete;
|
||||
};
|
||||
|
||||
|
||||
/* Functions */
|
||||
|
||||
int DSOLOCAL multipart_init(modsec_rec *msr, char **error_msg);
|
||||
|
||||
int DSOLOCAL multipart_complete(modsec_rec *msr, char **error_msg);
|
||||
|
||||
int DSOLOCAL multipart_process_chunk(modsec_rec *msr, const char *buf,
|
||||
unsigned int size, char **error_msg);
|
||||
|
||||
apr_status_t DSOLOCAL multipart_cleanup(modsec_rec *msr);
|
||||
|
||||
int DSOLOCAL multipart_get_arguments(modsec_rec *msr, char *origin, apr_table_t *arguments);
|
||||
|
||||
char DSOLOCAL *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr);
|
||||
|
||||
#endif
|
305
apache2/msc_parsers.c
Normal file
305
apache2/msc_parsers.c
Normal file
@ -0,0 +1,305 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_parsers.c,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "msc_parsers.h"
|
||||
#include <ctype.h>
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int parse_cookies_v0(modsec_rec *msr, char *_cookie_header, apr_table_t *cookies) {
|
||||
char *attr_name = NULL, *attr_value = NULL;
|
||||
char *cookie_header;
|
||||
char *saveptr = NULL;
|
||||
int cookie_count = 0;
|
||||
char *p = NULL;
|
||||
|
||||
if (_cookie_header == NULL) {
|
||||
msr_log(msr, 1, "Cookie parser: Received null for argument.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cookie_header = strdup(_cookie_header);
|
||||
if (cookie_header == NULL) return -1;
|
||||
|
||||
p = apr_strtok(cookie_header, ";", &saveptr);
|
||||
while(p != NULL) {
|
||||
attr_name = NULL;
|
||||
attr_value = NULL;
|
||||
|
||||
/* ignore whitespace at the beginning of cookie name */
|
||||
while(isspace(*p)) p++;
|
||||
attr_name = p;
|
||||
|
||||
attr_value = strstr(p, "=");
|
||||
if (attr_value != NULL) {
|
||||
/* terminate cookie name */
|
||||
*attr_value = 0;
|
||||
/* move over to the beginning of the value */
|
||||
attr_value++;
|
||||
}
|
||||
|
||||
/* we ignore cookies with empty names */
|
||||
if ((attr_name != NULL)&&(strlen(attr_name) != 0)) {
|
||||
if (attr_value != NULL) {
|
||||
msr_log(msr, 5, "Adding request cookie: name \"%s\", value \"%s\"", log_escape(msr->mp, attr_name), log_escape(msr->mp, attr_value));
|
||||
apr_table_add(cookies, attr_name, attr_value);
|
||||
} else {
|
||||
msr_log(msr, 5, "Adding request cookie: name \"%s\", value empty", log_escape(msr->mp, attr_name));
|
||||
apr_table_add(cookies, attr_name, "");
|
||||
}
|
||||
|
||||
cookie_count++;
|
||||
}
|
||||
|
||||
p = apr_strtok(NULL, ";", &saveptr);
|
||||
}
|
||||
|
||||
free(cookie_header);
|
||||
return cookie_count;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int parse_cookies_v1(modsec_rec *msr, char *_cookie_header, apr_table_t *cookies) {
|
||||
char *attr_name = NULL, *attr_value = NULL, *p = NULL;
|
||||
char *prev_attr_name = NULL;
|
||||
char *cookie_header = NULL;
|
||||
int cookie_count = 0;
|
||||
|
||||
if (_cookie_header == NULL) return -1;
|
||||
cookie_header = strdup(_cookie_header);
|
||||
if (cookie_header == NULL) return -1;
|
||||
|
||||
p = cookie_header;
|
||||
while(*p != 0) {
|
||||
attr_name = NULL;
|
||||
attr_value = NULL;
|
||||
|
||||
/* attribute name */
|
||||
|
||||
/* remove space from the beginning */
|
||||
while((isspace(*p))&&(*p != 0)) p++;
|
||||
attr_name = p;
|
||||
while((*p != 0)&&(*p != '=')&&(*p != ';')&&(*p != ',')) p++;
|
||||
|
||||
/* if we've reached the end of string */
|
||||
if (*p == 0) goto add_cookie;
|
||||
|
||||
/* if there is no cookie value supplied */
|
||||
if ((*p == ';')||(*p == ',')) {
|
||||
*p++ = 0; /* terminate the name */
|
||||
goto add_cookie;
|
||||
}
|
||||
|
||||
/* terminate the attribute name,
|
||||
* writing over the = character
|
||||
*/
|
||||
*p++ = 0;
|
||||
|
||||
/* attribute value */
|
||||
|
||||
/* skip over the whitespace at the beginning */
|
||||
while((isspace(*p))&&(*p != 0)) p++;
|
||||
|
||||
/* no value supplied */
|
||||
if (*p == 0) goto add_cookie;
|
||||
|
||||
if (*p == '"') {
|
||||
if (*++p == 0) goto add_cookie;
|
||||
attr_value = p;
|
||||
while((*p != 0)&&(*p != '"')) p++;
|
||||
if (*p != 0) *p++ = 0;
|
||||
else {
|
||||
/* Do nothing about this. */
|
||||
}
|
||||
} else {
|
||||
attr_value = p;
|
||||
while((*p != 0)&&(*p != ',')&&(*p != ';')) p++;
|
||||
if (*p != 0) *p++ = 0;
|
||||
|
||||
/* remove the whitespace from the end of cookie value */
|
||||
if (attr_value != NULL) {
|
||||
char *t = attr_value;
|
||||
int i = 0;
|
||||
|
||||
while(*t != 0) {
|
||||
t++;
|
||||
i++;
|
||||
}
|
||||
|
||||
while((i-- > 0)&&(isspace(*(--t)))) *t = 0;
|
||||
}
|
||||
}
|
||||
|
||||
add_cookie:
|
||||
|
||||
/* remove the whitespace from the end of cookie name */
|
||||
if (attr_name != NULL) {
|
||||
char *t = attr_name;
|
||||
int i = 0;
|
||||
|
||||
while(*t != 0) {
|
||||
t++;
|
||||
i++;
|
||||
}
|
||||
|
||||
while((i-- > 0)&&(isspace(*(--t)))) *t = 0;
|
||||
}
|
||||
|
||||
/* add the cookie to the list now */
|
||||
if ((attr_name != NULL)&&(strlen(attr_name) != 0)) {
|
||||
|
||||
/* handle special attribute names */
|
||||
if (attr_name[0] == '$') {
|
||||
if (prev_attr_name != NULL) {
|
||||
/* cookie keyword, we change the name we use
|
||||
* so they can have a unique name in the cookie table
|
||||
*/
|
||||
attr_name = apr_psprintf(msr->mp, "$%s_%s", prev_attr_name, attr_name + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (attr_value != NULL) {
|
||||
msr_log(msr, 5, "Adding request cookie: name \"%s\", value \"%s\"",
|
||||
log_escape(msr->mp, attr_name), log_escape(msr->mp, attr_value));
|
||||
apr_table_add(cookies, attr_name, attr_value);
|
||||
} else {
|
||||
msr_log(msr, 5, "Adding request cookie: name \"%s\", value empty",
|
||||
log_escape(msr->mp, attr_name));
|
||||
apr_table_add(cookies, attr_name, "");
|
||||
}
|
||||
|
||||
cookie_count++;
|
||||
|
||||
/* only keep the cookie names for later */
|
||||
if (attr_name[0] != '$') prev_attr_name = attr_name;
|
||||
}
|
||||
|
||||
/* at this point the *p is either 0 (in which case we exit), or
|
||||
* right after the current cookie ended - we need to look for
|
||||
* the next cookie
|
||||
*/
|
||||
while( (*p != 0)&&( (*p == ',')||(*p == ';')||(isspace(*p)) ) ) p++;
|
||||
}
|
||||
|
||||
return cookie_count;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int parse_arguments(modsec_rec *msr, const char *s, int argument_separator, const char *origin,
|
||||
apr_table_t *arguments, int *invalid_count)
|
||||
{
|
||||
msc_arg *arg;
|
||||
long inputlength, i, j;
|
||||
char *value = NULL;
|
||||
char *buf;
|
||||
int status;
|
||||
|
||||
if (s == NULL) return -1;
|
||||
inputlength = strlen(s);
|
||||
if (inputlength == 0) return 1;
|
||||
if (inputlength + 1 <= 0) return -1;
|
||||
|
||||
buf = (char *)malloc(inputlength + 1);
|
||||
if (buf == NULL) return -1;
|
||||
|
||||
arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg));
|
||||
arg->origin = origin;
|
||||
|
||||
i = 0;
|
||||
j = 0;
|
||||
status = 0;
|
||||
*invalid_count = 0;
|
||||
while (i < inputlength) {
|
||||
if (status == 0) {
|
||||
/* parameter name */
|
||||
arg->name_origin_offset = i;
|
||||
while ((s[i] != '=') && (s[i] != argument_separator) && (i < inputlength)) {
|
||||
buf[j] = s[i];
|
||||
j++;
|
||||
i++;
|
||||
}
|
||||
buf[j++] = '\0';
|
||||
arg->name_origin_len = i - arg->name_origin_offset;
|
||||
} else {
|
||||
/* parameter value */
|
||||
arg->value_origin_offset = i;
|
||||
while ((s[i] != argument_separator) && (i < inputlength)) {
|
||||
buf[j] = s[i];
|
||||
j++;
|
||||
i++;
|
||||
}
|
||||
buf[j++] = '\0';
|
||||
arg->value_origin_len = i - arg->value_origin_offset;
|
||||
}
|
||||
|
||||
if (status == 0) {
|
||||
arg->name_len = urldecode_nonstrict_inplace_ex(buf, arg->name_origin_len, invalid_count);
|
||||
arg->name = apr_pstrmemdup(msr->mp, buf, arg->name_len);
|
||||
|
||||
if (s[i] == argument_separator) {
|
||||
/* Empty parameter */
|
||||
arg->value_len = 0;
|
||||
arg->value = "";
|
||||
|
||||
apr_table_addn(arguments, arg->name, (void *)arg);
|
||||
msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"",
|
||||
arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len),
|
||||
log_escape_ex(msr->mp, arg->value, arg->value_len));
|
||||
|
||||
arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg));
|
||||
arg->origin = origin;
|
||||
|
||||
status = 0; /* unchanged */
|
||||
j = 0;
|
||||
} else {
|
||||
status = 1;
|
||||
value = &buf[j];
|
||||
}
|
||||
}
|
||||
else {
|
||||
arg->value_len = urldecode_nonstrict_inplace_ex(value, arg->value_origin_len, invalid_count);
|
||||
arg->value = apr_pstrmemdup(msr->mp, value, arg->value_len);
|
||||
|
||||
apr_table_addn(arguments, arg->name, (void *)arg);
|
||||
msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"",
|
||||
arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len),
|
||||
log_escape_ex(msr->mp, arg->value, arg->value_len));
|
||||
|
||||
arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg));
|
||||
arg->origin = origin;
|
||||
|
||||
status = 0;
|
||||
j = 0;
|
||||
}
|
||||
|
||||
i++; /* skip over the separator */
|
||||
}
|
||||
|
||||
/* the last parameter was empty */
|
||||
if (status == 1) {
|
||||
arg->value_len = 0;
|
||||
arg->value = "";
|
||||
|
||||
apr_table_addn(arguments, arg->name, (void *)arg);
|
||||
msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"",
|
||||
arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len),
|
||||
log_escape_ex(msr->mp, arg->value, arg->value_len));
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
25
apache2/msc_parsers.h
Normal file
25
apache2/msc_parsers.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_parsers.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MSC_PARSERS_H_
|
||||
#define _MSC_PARSERS_H_
|
||||
|
||||
#include "modsecurity.h"
|
||||
|
||||
int DSOLOCAL parse_cookies_v0(modsec_rec *msr, char *_cookie_header, apr_table_t *cookies);
|
||||
|
||||
int DSOLOCAL parse_cookies_v1(modsec_rec *msr, char *_cookie_header, apr_table_t *cookies);
|
||||
|
||||
int DSOLOCAL parse_arguments(modsec_rec *msr, const char *s, int argument_separator,
|
||||
const char *origin, apr_table_t *arguments, int *invalid_count);
|
||||
|
||||
#endif
|
93
apache2/msc_pcre.c
Normal file
93
apache2/msc_pcre.c
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_pcre.c,v 1.2 2006/12/28 10:39:13 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "msc_pcre.h"
|
||||
#include "apr_strings.h"
|
||||
|
||||
/**
|
||||
* Releases the resources used by a single regular expression pattern.
|
||||
*/
|
||||
apr_status_t msc_pcre_cleanup(msc_regex_t *regex) {
|
||||
if (regex != NULL) {
|
||||
if (regex->pe != NULL) {
|
||||
free(regex->pe);
|
||||
regex->pe = NULL;
|
||||
}
|
||||
if (regex->re != NULL) {
|
||||
free(regex->re);
|
||||
regex->re = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the provided regular expression pattern. The last two
|
||||
* parameters are optional, but if they are provided and an error
|
||||
* occurs they will contain the error message and the offset in
|
||||
* the pattern where the offending part of the pattern begins.
|
||||
*/
|
||||
void *msc_pregcomp(apr_pool_t *pool, const char *pattern, int options,
|
||||
const char **_errptr, int *_erroffset)
|
||||
{
|
||||
const char *errptr = NULL;
|
||||
int erroffset;
|
||||
msc_regex_t *regex;
|
||||
|
||||
regex = apr_pcalloc(pool, sizeof(msc_regex_t));
|
||||
if (regex == NULL) return NULL;
|
||||
regex->pattern = pattern;
|
||||
|
||||
if ((_errptr == NULL)||(_erroffset == NULL)) {
|
||||
regex->re = pcre_compile(pattern, options, &errptr, &erroffset, NULL);
|
||||
} else {
|
||||
regex->re = pcre_compile(pattern, options, _errptr, _erroffset, NULL);
|
||||
}
|
||||
if (regex->re == NULL) return NULL;
|
||||
|
||||
#ifdef WITH_PCRE_STUDY
|
||||
regex->pe = pcre_study(regex->re, 0, &errptr);
|
||||
#endif
|
||||
|
||||
apr_pool_cleanup_register(pool, (void *)regex,
|
||||
(apr_status_t (*)(void *))msc_pcre_cleanup, apr_pool_cleanup_null);
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes regular expression, capturing subexpressions in the given
|
||||
* vector. Returns PCRE_ERROR_NOMATCH when there is no match, error code < -1
|
||||
* on errors, and a value > 0 when there is a match.
|
||||
*/
|
||||
int msc_regexec_capture(msc_regex_t *regex, const char *s, unsigned int slen,
|
||||
int *ovector, int ovecsize, char **error_msg)
|
||||
{
|
||||
if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */
|
||||
*error_msg = NULL;
|
||||
|
||||
return pcre_exec(regex->re, regex->pe, s, slen, 0, 0, ovector, ovecsize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes regular expression but ignores any of the subexpression
|
||||
* captures. See above for the return codes.
|
||||
*/
|
||||
int msc_regexec(msc_regex_t *regex, const char *s, unsigned int slen,
|
||||
char **error_msg)
|
||||
{
|
||||
if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */
|
||||
*error_msg = NULL;
|
||||
|
||||
return msc_regexec_capture(regex, s, slen, NULL, 0, error_msg);
|
||||
}
|
39
apache2/msc_pcre.h
Normal file
39
apache2/msc_pcre.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_pcre.h,v 1.3 2006/12/28 10:39:13 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MSC_PCRE_H_
|
||||
#define _MSC_PCRE_H_
|
||||
|
||||
typedef struct msc_regex_t msc_regex_t;
|
||||
|
||||
#include "pcre.h"
|
||||
#include "apr_general.h"
|
||||
#include "modsecurity.h"
|
||||
|
||||
struct msc_regex_t {
|
||||
void *re;
|
||||
void *pe;
|
||||
const char *pattern;
|
||||
};
|
||||
|
||||
apr_status_t DSOLOCAL msc_pcre_cleanup(msc_regex_t *regex);
|
||||
|
||||
void DSOLOCAL *msc_pregcomp(apr_pool_t *pool, const char *pattern, int options,
|
||||
const char **_errptr, int *_erroffset);
|
||||
|
||||
int DSOLOCAL msc_regexec_capture(msc_regex_t *regex, const char *s,
|
||||
unsigned int slen, int *ovector, int ovecsize, char **error_msg);
|
||||
|
||||
int DSOLOCAL msc_regexec(msc_regex_t *regex, const char *s, unsigned int slen,
|
||||
char **error_msg);
|
||||
|
||||
#endif
|
636
apache2/msc_reqbody.c
Normal file
636
apache2/msc_reqbody.c
Normal file
@ -0,0 +1,636 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_reqbody.c,v 1.2 2006/12/04 21:54:10 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "modsecurity.h"
|
||||
#include "msc_parsers.h"
|
||||
|
||||
#define CHUNK_CAPACITY 8192
|
||||
|
||||
/**
|
||||
* Prepare to accept the request body (part 2).
|
||||
*/
|
||||
static apr_status_t modsecurity_request_body_start_init(modsec_rec *msr) {
|
||||
if(msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) {
|
||||
/* Prepare to store request body in memory. */
|
||||
|
||||
msr->msc_reqbody_chunks = apr_array_make(msr->msc_reqbody_mp,
|
||||
32, sizeof(msc_data_chunk *));
|
||||
if (msr->msc_reqbody_chunks == NULL) return -1;
|
||||
} else {
|
||||
/* Prepare to store request body on disk. */
|
||||
|
||||
msr->msc_reqbody_filename = apr_psprintf(msr->mp, "%s/%s-%s-request_body-XXXXXX",
|
||||
msr->txcfg->tmp_dir, current_filetime(msr->mp), msr->txid);
|
||||
if (msr->msc_reqbody_filename == NULL) return -1;
|
||||
|
||||
msr->msc_reqbody_fd = msc_mkstemp((char *)msr->msc_reqbody_filename);
|
||||
if (msr->msc_reqbody_fd < 0) {
|
||||
msr_log(msr, 1, "Input filter: Failed to create temporary file: %s",
|
||||
msr->msc_reqbody_filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "Input filter: Created temporary file to store request body: %s",
|
||||
msr->msc_reqbody_filename);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to accept the request body (part 1).
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_start(modsec_rec *msr) {
|
||||
msr->msc_reqbody_length = 0;
|
||||
|
||||
/* Create a separate memory pool that will be used
|
||||
* to allocate structures from (not data, which is allocated
|
||||
* via malloc).
|
||||
*/
|
||||
apr_pool_create(&msr->msc_reqbody_mp, msr->mp);
|
||||
|
||||
/* Initialise request body processors, if any. */
|
||||
|
||||
if (msr->msc_reqbody_processor != NULL) {
|
||||
char *my_error_msg = NULL;
|
||||
|
||||
if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) {
|
||||
if (multipart_init(msr, &my_error_msg) < 0) {
|
||||
msr_log(msr, 1, "Multipart parser init failed: %s", my_error_msg);
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
}
|
||||
}
|
||||
#ifdef WITH_LIBXML2
|
||||
else
|
||||
if (strcmp(msr->msc_reqbody_processor, "XML") == 0) {
|
||||
if (xml_init(msr, &my_error_msg) < 0) {
|
||||
msr_log(msr, 1, "XML parser init failed: %s", my_error_msg);
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) {
|
||||
/* Do nothing, URLENCODED processor does not support streaming yet. */
|
||||
}
|
||||
else {
|
||||
msr_log(msr, 1, "Unknown request body processor: %s", msr->msc_reqbody_processor);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return modsecurity_request_body_start_init(msr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a chunk of request body data to disk.
|
||||
*/
|
||||
static apr_status_t modsecurity_request_body_store_disk(modsec_rec *msr,
|
||||
const char *data, apr_size_t length)
|
||||
{
|
||||
apr_size_t i = write(msr->msc_reqbody_fd, data, length);
|
||||
if (i != length) {
|
||||
msr_log(msr, 1, "Input filter: Failed writing %lu bytes to temporary file (rc %lu).", i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores one chunk of request body data in memory.
|
||||
*/
|
||||
static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr,
|
||||
const char *data, apr_size_t length)
|
||||
{
|
||||
/* Would storing this chunk mean going over the limit? */
|
||||
if ((msr->msc_reqbody_spilltodisk)
|
||||
&& (msr->msc_reqbody_length + length > (apr_size_t)msr->txcfg->reqbody_inmemory_limit))
|
||||
{
|
||||
msc_data_chunk **chunks;
|
||||
unsigned int disklen = 0;
|
||||
int i;
|
||||
|
||||
msr_log(msr, 4, "Input filter: Request too large to store in memory, switching to disk.");
|
||||
|
||||
/* NOTE Must use modsecurity_request_body_store_disk() here
|
||||
* to prevent data to be sent to the streaming
|
||||
* processors again.
|
||||
*/
|
||||
|
||||
/* Initialise disk storage */
|
||||
msr->msc_reqbody_storage = MSC_REQBODY_DISK;
|
||||
if (modsecurity_request_body_start_init(msr) < 0) return -1;
|
||||
|
||||
/* Write the data we keep in memory */
|
||||
chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts;
|
||||
for(i = 0; i < msr->msc_reqbody_chunks->nelts; i++) {
|
||||
disklen += chunks[i]->length;
|
||||
|
||||
if (modsecurity_request_body_store_disk(msr, chunks[i]->data, chunks[i]->length) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(chunks[i]->data);
|
||||
chunks[i]->data = NULL;
|
||||
}
|
||||
|
||||
/* Clear the memory pool as we no longer need the bits. */
|
||||
|
||||
/* IMP1 But since we only used apr_pool_clear memory might
|
||||
* not be released back to the OS straight away?
|
||||
*/
|
||||
msr->msc_reqbody_chunks = NULL;
|
||||
apr_pool_clear(msr->msc_reqbody_mp);
|
||||
|
||||
msr_log(msr, 4, "Input filter: Wrote %lu bytes from memory to disk.", disklen);
|
||||
|
||||
/* Continue with disk storage from now on */
|
||||
return modsecurity_request_body_store_disk(msr, data, length);
|
||||
}
|
||||
|
||||
/* If we're here that means we are not over the
|
||||
* request body in-memory limit yet.
|
||||
*/
|
||||
{
|
||||
unsigned long int bucket_offset, bucket_left;
|
||||
|
||||
bucket_offset = 0;
|
||||
bucket_left = length;
|
||||
|
||||
/* Although we store the request body in chunks we don't
|
||||
* want to use the same chunk sizes as the incoming memory
|
||||
* buffers. They are often of very small sizes and that
|
||||
* would make us waste a lot of memory. That's why we
|
||||
* use our own chunks of CHUNK_CAPACITY sizes.
|
||||
*/
|
||||
|
||||
/* Loop until we empty this bucket into our chunks. */
|
||||
while(bucket_left > 0) {
|
||||
/* Allocate a new chunk if we have to. */
|
||||
if (msr->msc_reqbody_chunk_current == NULL) {
|
||||
msr->msc_reqbody_chunk_current = (msc_data_chunk *)
|
||||
apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk));
|
||||
if (msr->msc_reqbody_chunk_current == NULL) return -1;
|
||||
|
||||
msr->msc_reqbody_chunk_current->data = malloc(CHUNK_CAPACITY);
|
||||
if (msr->msc_reqbody_chunk_current->data == NULL) return -1;
|
||||
|
||||
msr->msc_reqbody_chunk_current->length = 0;
|
||||
msr->msc_reqbody_chunk_current->is_permanent = 1;
|
||||
|
||||
*(const msc_data_chunk **)apr_array_push(msr->msc_reqbody_chunks)
|
||||
= msr->msc_reqbody_chunk_current;
|
||||
}
|
||||
|
||||
if (bucket_left < (CHUNK_CAPACITY - msr->msc_reqbody_chunk_current->length)) {
|
||||
/* There's enough space in the current chunk. */
|
||||
memcpy(msr->msc_reqbody_chunk_current->data +
|
||||
msr->msc_reqbody_chunk_current->length, data + bucket_offset, bucket_left);
|
||||
msr->msc_reqbody_chunk_current->length += bucket_left;
|
||||
bucket_left = 0;
|
||||
} else {
|
||||
/* Fill the existing chunk. */
|
||||
unsigned long int copy_length = CHUNK_CAPACITY -
|
||||
msr->msc_reqbody_chunk_current->length;
|
||||
|
||||
memcpy(msr->msc_reqbody_chunk_current->data +
|
||||
msr->msc_reqbody_chunk_current->length, data + bucket_offset, copy_length);
|
||||
bucket_offset += copy_length;
|
||||
bucket_left -= copy_length;
|
||||
msr->msc_reqbody_chunk_current->length += copy_length;
|
||||
|
||||
/* We're done with this chunk. Setting the pointer
|
||||
* to NULL is going to force a new chunk to be allocated
|
||||
* on the next go.
|
||||
*/
|
||||
msr->msc_reqbody_chunk_current = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
msr->msc_reqbody_length += length;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores one chunk of request body data. Returns -1 on error.
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_store(modsec_rec *msr,
|
||||
const char *data, apr_size_t length)
|
||||
{
|
||||
/* If we have a processor for this request body send
|
||||
* data to it first (but only if it did not report an
|
||||
* error on previous invocations).
|
||||
*/
|
||||
if ((msr->msc_reqbody_processor != NULL)&&(msr->msc_reqbody_error == 0)) {
|
||||
char *my_error_msg = NULL;
|
||||
|
||||
if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) {
|
||||
if (multipart_process_chunk(msr, data, length, &my_error_msg) < 0) {
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
msr_log(msr, 4, "%s", my_error_msg);
|
||||
}
|
||||
}
|
||||
#ifdef WITH_LIBXML2
|
||||
else
|
||||
if (strcmp(msr->msc_reqbody_processor, "XML") == 0) {
|
||||
if (xml_process_chunk(msr, data, length, &my_error_msg) < 0) {
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
msr_log(msr, 4, "%s", my_error_msg);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) {
|
||||
/* Do nothing, URLENCODED processor does not support streaming. */
|
||||
}
|
||||
else {
|
||||
msr_log(msr, 1, "Unknown request body processor: %s", msr->msc_reqbody_processor);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Store data. */
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) {
|
||||
return modsecurity_request_body_store_memory(msr, data, length);
|
||||
}
|
||||
else
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) {
|
||||
return modsecurity_request_body_store_disk(msr, data, length);
|
||||
}
|
||||
|
||||
/* Should never happen. */
|
||||
msr_log(msr, 1, "Internal Error: Unknown value for msc_reqbody_storage: %i",
|
||||
msr->msc_reqbody_storage);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr) {
|
||||
msc_data_chunk **chunks, *one_chunk;
|
||||
char *d;
|
||||
int i, sofar;
|
||||
int invalid_count = 0;
|
||||
|
||||
/* Allocate a buffer large enough to hold the request body. */
|
||||
|
||||
if (msr->msc_reqbody_length + 1 == 0) return -1;
|
||||
msr->msc_reqbody_buffer = malloc(msr->msc_reqbody_length + 1);
|
||||
if (msr->msc_reqbody_buffer == NULL) {
|
||||
msr_log(msr, 1, "Unable to allocate memory to hold request body. Asked for %lu bytes.",
|
||||
msr->msc_reqbody_length + 1);
|
||||
return -1;
|
||||
}
|
||||
msr->msc_reqbody_buffer[msr->msc_reqbody_length] = '\0';
|
||||
|
||||
/* Copy the data we keep in chunks into the new buffer. */
|
||||
|
||||
sofar = 0;
|
||||
d = msr->msc_reqbody_buffer;
|
||||
chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts;
|
||||
for(i = 0; i < msr->msc_reqbody_chunks->nelts; i++) {
|
||||
if (sofar + chunks[i]->length <= msr->msc_reqbody_length) {
|
||||
memcpy(d, chunks[i]->data, chunks[i]->length);
|
||||
d += chunks[i]->length;
|
||||
sofar += chunks[i]->length;
|
||||
} else {
|
||||
msr_log(msr, 1, "Internal error, request body buffer overflow.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now free the memory used by the chunks. */
|
||||
|
||||
chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts;
|
||||
for(i = 0; i < msr->msc_reqbody_chunks->nelts; i++) {
|
||||
free(chunks[i]->data);
|
||||
chunks[i]->data = NULL;
|
||||
}
|
||||
|
||||
/* Create a new array with only one chunk in it. */
|
||||
|
||||
msr->msc_reqbody_chunks = apr_array_make(msr->msc_reqbody_mp, 2, sizeof(msc_data_chunk *));
|
||||
if (msr->msc_reqbody_chunks == NULL) return -1;
|
||||
one_chunk = (msc_data_chunk *)apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk));
|
||||
one_chunk->data = msr->msc_reqbody_buffer;
|
||||
one_chunk->length = msr->msc_reqbody_length;
|
||||
one_chunk->is_permanent = 1;
|
||||
*(const msc_data_chunk **)apr_array_push(msr->msc_reqbody_chunks) = one_chunk;
|
||||
|
||||
/* Parse URL-encoded arguments in the request body. */
|
||||
|
||||
if (parse_arguments(msr, msr->msc_reqbody_buffer, msr->txcfg->argument_separator,
|
||||
"BODY", msr->arguments, &invalid_count) < 0)
|
||||
{
|
||||
msr_log(msr, 1, "Initialisation: Error occurred while parsing BODY arguments.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops receiving the request body.
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_end(modsec_rec *msr) {
|
||||
|
||||
/* Close open file descriptors, if any. */
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) {
|
||||
if (msr->msc_reqbody_fd > 0) {
|
||||
close(msr->msc_reqbody_fd);
|
||||
msr->msc_reqbody_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
msr->msc_reqbody_read = 1;
|
||||
|
||||
if ((msr->msc_reqbody_processor != NULL)&&(msr->msc_reqbody_error == 0)) {
|
||||
char *my_error_msg = NULL;
|
||||
|
||||
if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) {
|
||||
if (multipart_complete(msr, &my_error_msg) < 0) {
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
msr_log(msr, 1, "Multipart error: %s", my_error_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (multipart_get_arguments(msr, "BODY", msr->arguments) < 0) {
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = "Error retrieving arguments.";
|
||||
msr_log(msr, 1, "Multipart error: %s", my_error_msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) {
|
||||
return modsecurity_request_body_end_urlencoded(msr);
|
||||
}
|
||||
#ifdef WITH_LIBXML2
|
||||
else
|
||||
if (strcmp(msr->msc_reqbody_processor, "XML") == 0) {
|
||||
if (xml_complete(msr, &my_error_msg) < 0) {
|
||||
msr->msc_reqbody_error = 1;
|
||||
msr->msc_reqbody_error_msg = my_error_msg;
|
||||
msr_log(msr, 4, "%s", my_error_msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to forward the request body.
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_retrieve_start(modsec_rec *msr) {
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) {
|
||||
msr->msc_reqbody_chunk_position = 0;
|
||||
msr->msc_reqbody_chunk_offset = 0;
|
||||
|
||||
msr->msc_reqbody_disk_chunk = apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk));
|
||||
if (msr->msc_reqbody_disk_chunk == NULL) return -1;
|
||||
msr->msc_reqbody_disk_chunk->is_permanent = 1;
|
||||
}
|
||||
else
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) {
|
||||
msr->msc_reqbody_disk_chunk = apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk));
|
||||
if (msr->msc_reqbody_disk_chunk == NULL) return -1;
|
||||
|
||||
msr->msc_reqbody_disk_chunk->is_permanent = 0;
|
||||
msr->msc_reqbody_disk_chunk->data = apr_palloc(msr->msc_reqbody_mp, CHUNK_CAPACITY);
|
||||
if (msr->msc_reqbody_disk_chunk->data == NULL) return -1;
|
||||
|
||||
msr->msc_reqbody_fd = open(msr->msc_reqbody_filename, O_RDONLY | O_BINARY);
|
||||
if (msr->msc_reqbody_fd < 0) {
|
||||
msr_log(msr, 1, "Input filter: Failed to open temporary file for reading: %s",
|
||||
msr->msc_reqbody_filename);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_retrieve_end(modsec_rec *msr) {
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) {
|
||||
if (msr->msc_reqbody_fd > 0) {
|
||||
close(msr->msc_reqbody_fd);
|
||||
msr->msc_reqbody_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one chunk of request body data. It stores a NULL
|
||||
* in the chunk pointer when there is no data to return. The
|
||||
* return code is 1 if more calls can be made to retrieve more
|
||||
* data, 0 if there is no more data to retrieve, or -1 on error.
|
||||
*
|
||||
* The caller can limit the amount of data returned by providing
|
||||
* a non-negative value in nbytes.
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_retrieve(modsec_rec *msr,
|
||||
msc_data_chunk **chunk, long int nbytes)
|
||||
{
|
||||
msc_data_chunk **chunks;
|
||||
|
||||
if (chunk == NULL) return -1;
|
||||
*chunk = NULL;
|
||||
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) {
|
||||
/* Are there any chunks left? */
|
||||
if (msr->msc_reqbody_chunk_position >= msr->msc_reqbody_chunks->nelts) {
|
||||
/* No more chunks. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We always respond with the same chunk, just different information in it. */
|
||||
*chunk = msr->msc_reqbody_disk_chunk;
|
||||
|
||||
/* Advance to the current chunk and position on the
|
||||
* next byte we need to send.
|
||||
*/
|
||||
chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts;
|
||||
msr->msc_reqbody_disk_chunk->data = chunks[msr->msc_reqbody_chunk_position]->data
|
||||
+ msr->msc_reqbody_chunk_offset;
|
||||
|
||||
if (nbytes < 0) {
|
||||
/* Send what's left in this chunk as there is no limit on the size. */
|
||||
msr->msc_reqbody_disk_chunk->length = chunks[msr->msc_reqbody_chunk_position]->length;
|
||||
msr->msc_reqbody_chunk_position++;
|
||||
msr->msc_reqbody_chunk_offset = 0;
|
||||
} else {
|
||||
/* We have a limit we must obey. */
|
||||
|
||||
if (chunks[msr->msc_reqbody_chunk_position]->length -
|
||||
msr->msc_reqbody_chunk_offset <= (unsigned int)nbytes)
|
||||
{
|
||||
/* If what's left in our chunk is less than the limit
|
||||
* then send it all back.
|
||||
*/
|
||||
msr->msc_reqbody_disk_chunk->length =
|
||||
chunks[msr->msc_reqbody_chunk_position]->length -
|
||||
msr->msc_reqbody_chunk_offset;
|
||||
msr->msc_reqbody_chunk_position++;
|
||||
msr->msc_reqbody_chunk_offset = 0;
|
||||
} else {
|
||||
/* If we have more data in our chunk, send the
|
||||
* maximum bytes we can (nbytes).
|
||||
*/
|
||||
msr->msc_reqbody_disk_chunk->length = nbytes;
|
||||
msr->msc_reqbody_chunk_offset += nbytes;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we've advanced beyond our last chunk then
|
||||
* we have no more data to send.
|
||||
*/
|
||||
if (msr->msc_reqbody_chunk_position >= msr->msc_reqbody_chunks->nelts) {
|
||||
return 0; /* No more chunks. */
|
||||
}
|
||||
|
||||
/* More data available. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) {
|
||||
long int my_nbytes = CHUNK_CAPACITY;
|
||||
int i;
|
||||
|
||||
/* Send CHUNK_CAPACITY bytes at a time unless a lower limit was requested. */
|
||||
if ((nbytes != -1)&&(my_nbytes > nbytes)) {
|
||||
my_nbytes = nbytes;
|
||||
}
|
||||
|
||||
i = read(msr->msc_reqbody_fd, msr->msc_reqbody_disk_chunk->data, my_nbytes);
|
||||
if (i < 0) {
|
||||
msr_log(msr, 1, "Input filter: Error reading from temporary file: %s",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
*chunk = msr->msc_reqbody_disk_chunk;
|
||||
msr->msc_reqbody_disk_chunk->length = i;
|
||||
|
||||
if (i == 0) return 0; /* No more data available. */
|
||||
|
||||
return 1; /* More data available. */
|
||||
}
|
||||
|
||||
msr_log(msr, 1, "Internal error, invalid msc_reqbody_storage value: %i",
|
||||
msr->msc_reqbody_storage);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apr_status_t modsecurity_request_body_clear(modsec_rec *msr) {
|
||||
/* Release memory we used to store request body data. */
|
||||
if (msr->msc_reqbody_chunks != NULL) {
|
||||
msc_data_chunk **chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts;
|
||||
int i;
|
||||
|
||||
for(i = 0; i < msr->msc_reqbody_chunks->nelts; i++) {
|
||||
if (chunks[i]->data != NULL) {
|
||||
free(chunks[i]->data);
|
||||
chunks[i]->data = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) {
|
||||
int keep_body = 0;
|
||||
|
||||
/* Should we keep the body? This normally
|
||||
* happens when a PUT method was used, which
|
||||
* means the body is actually a file.
|
||||
*/
|
||||
if ((msr->upload_remove_files == 0)&&(strcasecmp(msr->request_method, "PUT") == 0)) {
|
||||
if (msr->txcfg->upload_dir != NULL) {
|
||||
keep_body = 1;
|
||||
} else {
|
||||
msr_log(msr, 1, "Input filter: SecUploadDir is undefined, "
|
||||
"unable to store PUT file.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Deal with a request body stored in a file. */
|
||||
|
||||
if (msr->msc_reqbody_filename != NULL) {
|
||||
if (keep_body) {
|
||||
/* Move request body (which is a file) to the storage area. */
|
||||
const char *put_filename = NULL;
|
||||
const char *put_basename = NULL;
|
||||
|
||||
/* Construct the new filename. */
|
||||
put_basename = file_basename(msr->msc_reqbody_mp, msr->msc_reqbody_filename);
|
||||
if (put_basename == NULL) return -1;
|
||||
put_filename = apr_psprintf(msr->msc_reqbody_mp, "%s/%s",
|
||||
msr->txcfg->upload_dir, put_basename);
|
||||
if (put_filename == NULL) return -1;
|
||||
|
||||
if (apr_file_rename(msr->msc_reqbody_filename, put_filename,
|
||||
msr->msc_reqbody_mp) != APR_SUCCESS)
|
||||
{
|
||||
msr_log(msr, 1, "Failed to rename file from \"%s\" to \"%s\".",
|
||||
log_escape(msr->msc_reqbody_mp, msr->msc_reqbody_filename),
|
||||
log_escape(msr->msc_reqbody_mp, put_filename));
|
||||
return -1;
|
||||
} else {
|
||||
msr_log(msr, 4, "Moved file from \"%s\" to \"%s\".",
|
||||
log_escape(msr->msc_reqbody_mp, msr->msc_reqbody_filename),
|
||||
log_escape(msr->msc_reqbody_mp, put_filename));
|
||||
}
|
||||
} else {
|
||||
/* We do not want to keep the request body. */
|
||||
if (apr_file_remove(msr->msc_reqbody_filename,
|
||||
msr->msc_reqbody_mp) != APR_SUCCESS)
|
||||
{
|
||||
msr_log(msr, 1, "Failed to delete temporary file: %s",
|
||||
msr->msc_reqbody_filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "Input filter: Removed temporary file: %s",
|
||||
msr->msc_reqbody_filename);
|
||||
}
|
||||
|
||||
msr->msc_reqbody_filename = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE No need to clear the pool as it has already been destroyed
|
||||
* if (msr->msc_reqbody_mp != NULL) {
|
||||
* apr_pool_clear(msr->msc_reqbody_mp);
|
||||
* }
|
||||
*/
|
||||
|
||||
return 1;
|
||||
}
|
981
apache2/msc_util.c
Normal file
981
apache2/msc_util.c
Normal file
@ -0,0 +1,981 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_util.c,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "msc_util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int parse_boolean(const char *input) {
|
||||
if (input == NULL) return -1;
|
||||
if (strcasecmp(input, "on") == 0) return 1;
|
||||
if (strcasecmp(input, "true") == 0) return 1;
|
||||
if (strcasecmp(input, "1") == 0) return 1;
|
||||
if (strcasecmp(input, "off") == 0) return 0;
|
||||
if (strcasecmp(input, "false") == 0) return 0;
|
||||
if (strcasecmp(input, "0") == 0) return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string that contains a name-value pair in the form "name=value".
|
||||
* IMP1 It does not check for whitespace between tokens.
|
||||
*/
|
||||
int parse_name_eq_value(apr_pool_t *mp, const char *input, char **name, char **value) {
|
||||
char *p = NULL;
|
||||
|
||||
if ((name == NULL)||(value == NULL)) return -1;
|
||||
if (input == NULL) return 0;
|
||||
|
||||
*name = NULL;
|
||||
*value = NULL;
|
||||
p = (char *)input;
|
||||
|
||||
while((*p != '=')&&(*p != '\0')) p++;
|
||||
if (*p == '\0') {
|
||||
*name = (char *)input;
|
||||
return 1;
|
||||
}
|
||||
|
||||
*name = apr_pstrmemdup(mp, input, p - input);
|
||||
if (*name == NULL) return -1;
|
||||
p++;
|
||||
|
||||
*value = apr_pstrdup(mp, p);
|
||||
if (*value == NULL) return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *url_encode(apr_pool_t *mp, char *input, unsigned int input_len) {
|
||||
char *rval, *d;
|
||||
unsigned int i, len;
|
||||
|
||||
len = input_len * 3 + 1;
|
||||
d = rval = apr_palloc(mp, len);
|
||||
if (rval == NULL) return NULL;
|
||||
|
||||
/* ENH Only encode the characters that really need to be encoded. */
|
||||
|
||||
for(i = 0; i < input_len; i++) {
|
||||
unsigned char c = input[i];
|
||||
|
||||
if (c == ' ') {
|
||||
*d++ = '+';
|
||||
} else
|
||||
if ( (c == 42) || ((c >= 48)&&(c <= 57)) || ((c >= 65)&&(c <= 90))
|
||||
|| ((c >= 97)&&(c <= 122))
|
||||
) {
|
||||
*d++ = c;
|
||||
} else {
|
||||
*d++ = '%';
|
||||
c2x(c, (unsigned char *)d);
|
||||
d += 2;
|
||||
}
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an URL-encoded version of the source string to the
|
||||
* destination string, but makes sure that no more than "maxlen"
|
||||
* bytes are added.
|
||||
*/
|
||||
char *strnurlencat(char *destination, char *source, unsigned int maxlen) {
|
||||
char *s = source;
|
||||
char *d = destination;
|
||||
|
||||
/* ENH Only encode the characters that really need to be encoded. */
|
||||
|
||||
/* Advance to the end of destination string. */
|
||||
while(*d != '\0') d++;
|
||||
|
||||
/* Loop while there's bytes in the source string or
|
||||
* until we reach the output limit.
|
||||
*/
|
||||
while((*s != '\0')&&(maxlen > 0)) {
|
||||
unsigned char c = *s;
|
||||
|
||||
if (c == ' ') {
|
||||
*d++ = '+';
|
||||
maxlen--;
|
||||
} else
|
||||
if ( (c == 42) || ((c >= 48)&&(c <= 57)) || ((c >= 65)&&(c <= 90))
|
||||
|| ((c >= 97)&&(c <= 122))
|
||||
) {
|
||||
*d++ = c;
|
||||
maxlen--;
|
||||
} else {
|
||||
if (maxlen >= 3) {
|
||||
*d++ = '%';
|
||||
c2x(c, (unsigned char *)d);
|
||||
d += 2;
|
||||
maxlen -= 3;
|
||||
} else {
|
||||
/* If there's not enough room for the encoded
|
||||
* byte we ignore it.
|
||||
*/
|
||||
maxlen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
s++;
|
||||
}
|
||||
|
||||
*d++ = '\0';
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *file_basename(apr_pool_t *mp, const char *filename) {
|
||||
char *d, *p;
|
||||
|
||||
if (filename == NULL) return NULL;
|
||||
d = apr_pstrdup(mp, filename);
|
||||
if (d == NULL) return NULL;
|
||||
|
||||
p = strrchr(d, '/');
|
||||
if (p != NULL) d = p + 1;
|
||||
p = strrchr(d, '\\');
|
||||
if (p != NULL) d = p + 1;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
#ifdef WIN32
|
||||
char *file_dirname(apr_pool_t *p, const char *filename) {
|
||||
char *b, *c, *d;
|
||||
|
||||
if (filename == NULL) return NULL;
|
||||
b = apr_pstrdup(p, filename);
|
||||
if (b == NULL) return NULL;
|
||||
|
||||
c = strrchr(b, '/');
|
||||
if (c != NULL) {
|
||||
d = strrchr(c, '\\');
|
||||
if (d != NULL) *d = '\0';
|
||||
else *c = '\0';
|
||||
} else {
|
||||
d = strrchr(b, '\\');
|
||||
if (d != NULL) *d = '\0';
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
#else
|
||||
char *file_dirname(apr_pool_t *p, const char *filename) {
|
||||
char *b, *c;
|
||||
|
||||
if (filename == NULL) return NULL;
|
||||
b = apr_pstrdup(p, filename);
|
||||
if (b == NULL) return NULL;
|
||||
|
||||
c = strrchr(b, '/');
|
||||
if (c != NULL) *c = '\0';
|
||||
|
||||
return b;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int hex2bytes_inplace(unsigned char *data, int len) {
|
||||
unsigned char *d = data;
|
||||
int i, count = 0;
|
||||
|
||||
if ((data == NULL)||(len == 0)) return 0;
|
||||
|
||||
for(i = 0; i <= len - 2; i += 2) {
|
||||
*d++ = x2c(&data[i]);
|
||||
count++;
|
||||
}
|
||||
*d = '\0';
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a series of bytes into its hexadecimal
|
||||
* representation.
|
||||
*/
|
||||
char *bytes2hex(apr_pool_t *pool, unsigned char *data, int len) {
|
||||
static unsigned char b2hex[] = "0123456789abcdef";
|
||||
char *hex = NULL;
|
||||
int i, j;
|
||||
|
||||
hex = apr_palloc(pool, (len * 2) + 1);
|
||||
if (hex == NULL) return NULL;
|
||||
|
||||
j = 0;
|
||||
for(i = 0; i < len; i++) {
|
||||
hex[j++] = b2hex[data[i] >> 4];
|
||||
hex[j++] = b2hex[data[i] & 0x0f];
|
||||
}
|
||||
hex[j] = 0;
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int is_token_char(unsigned char c) {
|
||||
/* ENH Is the performance important at all? We could use a table instead. */
|
||||
|
||||
/* CTLs not allowed */
|
||||
if ((c <= 32)||(c >= 127)) return 0;
|
||||
|
||||
switch(c) {
|
||||
case '(' :
|
||||
case ')' :
|
||||
case '<' :
|
||||
case '>' :
|
||||
case '@' :
|
||||
case ',' :
|
||||
case ';' :
|
||||
case ':' :
|
||||
case '\\' :
|
||||
case '"' :
|
||||
case '/' :
|
||||
case '[' :
|
||||
case ']' :
|
||||
case '?' :
|
||||
case '=' :
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int remove_lf_crlf_inplace(char *text) {
|
||||
char *p = text;
|
||||
int count = 0;
|
||||
|
||||
if (text == NULL) return -1;
|
||||
|
||||
while(*p != '\0') {
|
||||
count++;
|
||||
p++;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
if (*(p - 1) == '\n') {
|
||||
*(p - 1) = '\0';
|
||||
if (count > 1) {
|
||||
if (*(p - 2) == '\r') {
|
||||
*(p - 2) = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte given as its hexadecimal representation
|
||||
* into a proper byte. Handles uppercase and lowercase letters
|
||||
* but does not check for overflows.
|
||||
*/
|
||||
unsigned char x2c(unsigned char *what) {
|
||||
register unsigned char digit;
|
||||
|
||||
digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
|
||||
digit *= 16;
|
||||
digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
|
||||
|
||||
return digit;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *guess_tmp_dir(apr_pool_t *p) {
|
||||
char *filename = NULL;
|
||||
|
||||
/* ENH Use apr_temp_dir_get instead. */
|
||||
|
||||
#ifdef WIN32
|
||||
filename = apr_pcalloc(p, 256);
|
||||
if (filename == NULL) return "";
|
||||
if (GetTempPath(255, filename) != 0) return filename;
|
||||
#endif
|
||||
|
||||
filename = getenv("TMPDIR");
|
||||
if (filename != NULL) return filename;
|
||||
|
||||
filename = getenv("TEMP");
|
||||
if (filename != NULL) return filename;
|
||||
|
||||
filename = getenv("TMP");
|
||||
if (filename != NULL) return filename;
|
||||
|
||||
#if defined NETWARE
|
||||
return("sys:/tmp/");
|
||||
#elif defined WIN32
|
||||
return("");
|
||||
#else
|
||||
return("/tmp/");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *current_logtime(apr_pool_t *mp) {
|
||||
apr_time_exp_t t;
|
||||
char tstr[100];
|
||||
apr_size_t len;
|
||||
|
||||
apr_time_exp_lt(&t, apr_time_now());
|
||||
|
||||
apr_strftime(tstr, &len, 80, "%d/%b/%Y:%H:%M:%S ", &t);
|
||||
apr_snprintf(tstr + strlen(tstr), 80 - strlen(tstr), "%c%.2d%.2d",
|
||||
t.tm_gmtoff < 0 ? '-' : '+',
|
||||
t.tm_gmtoff / (60 * 60), t.tm_gmtoff % (60 * 60));
|
||||
return apr_pstrdup(mp, tstr);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *current_filetime(apr_pool_t *mp) {
|
||||
apr_time_exp_t t;
|
||||
char tstr[100];
|
||||
apr_size_t len;
|
||||
|
||||
apr_time_exp_lt(&t, apr_time_now());
|
||||
|
||||
apr_strftime(tstr, &len, 80, "%Y%m%d-%H%M%S", &t);
|
||||
return apr_pstrdup(mp, tstr);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int msc_mkstemp(char *template) {
|
||||
/* ENH Use apr_file_mktemp instead. */
|
||||
|
||||
#if !(defined(WIN32)||defined(NETWARE))
|
||||
return mkstemp(template);
|
||||
#else
|
||||
if (mktemp(template) == NULL) return -1;
|
||||
return open(template, O_WRONLY | O_APPEND | O_CREAT | O_BINARY, CREATEMODE_UNISTD);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input string to lowercase (in-place).
|
||||
*/
|
||||
char *strtolower_inplace(unsigned char *str) {
|
||||
unsigned char *c = str;
|
||||
|
||||
if (str == NULL) return NULL;
|
||||
|
||||
while(*c != 0) {
|
||||
*c = tolower(*c);
|
||||
c++;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single byte into its hexadecimal representation.
|
||||
* Will overwrite two bytes at the destination.
|
||||
*/
|
||||
unsigned char *c2x(unsigned what, unsigned char *where) {
|
||||
static const char c2x_table[] = "0123456789abcdef";
|
||||
|
||||
what = what & 0xff;
|
||||
*where++ = c2x_table[what >> 4];
|
||||
*where++ = c2x_table[what & 0x0f];
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
char *log_escape(apr_pool_t *mp, const char *text) {
|
||||
return _log_escape(mp, text, strlen(text), 1, 0);
|
||||
}
|
||||
|
||||
char *log_escape_nq(apr_pool_t *mp, const char *text) {
|
||||
return _log_escape(mp, text, strlen(text), 0, 0);
|
||||
}
|
||||
|
||||
char *log_escape_ex(apr_pool_t *mp, const char *text, unsigned long int text_length) {
|
||||
return _log_escape(mp, text, text_length, 1, 0);
|
||||
}
|
||||
|
||||
char *log_escape_nq_ex(apr_pool_t *mp, const char *text, unsigned long int text_length) {
|
||||
return _log_escape(mp, text, text_length, 0, 0);
|
||||
}
|
||||
|
||||
char *log_escape_header_name(apr_pool_t *mp, const char *text) {
|
||||
return _log_escape(mp, text, strlen(text), 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input into a form safe for logging.
|
||||
*/
|
||||
char *_log_escape(apr_pool_t *mp, const unsigned char *input, unsigned long int input_len,
|
||||
int escape_quotes, int escape_colon)
|
||||
{
|
||||
unsigned char *d = NULL;
|
||||
char *ret = NULL;
|
||||
unsigned long int i;
|
||||
|
||||
if (input == NULL) return NULL;
|
||||
|
||||
ret = apr_palloc(mp, input_len * 4 + 1);
|
||||
if (ret == NULL) return NULL;
|
||||
d = (unsigned char *)ret;
|
||||
|
||||
i = 0;
|
||||
while(i < input_len) {
|
||||
switch(input[i]) {
|
||||
case ':' :
|
||||
if (escape_colon) {
|
||||
*d++ = '\\';
|
||||
*d++ = ':';
|
||||
} else {
|
||||
*d++ = input[i];
|
||||
}
|
||||
break;
|
||||
case '"' :
|
||||
if (escape_quotes) {
|
||||
*d++ = '\\';
|
||||
*d++ = '"';
|
||||
} else {
|
||||
*d++ = input[i];
|
||||
}
|
||||
break;
|
||||
case '\b' :
|
||||
*d++ = '\\';
|
||||
*d++ = 'b';
|
||||
break;
|
||||
case '\n' :
|
||||
*d++ = '\\';
|
||||
*d++ = 'n';
|
||||
break;
|
||||
case '\r' :
|
||||
*d++ = '\\';
|
||||
*d++ = 'r';
|
||||
break;
|
||||
case '\t' :
|
||||
*d++ = '\\';
|
||||
*d++ = 't';
|
||||
break;
|
||||
case '\v' :
|
||||
*d++ = '\\';
|
||||
*d++ = 'v';
|
||||
break;
|
||||
case '\\' :
|
||||
*d++ = '\\';
|
||||
*d++ = '\\';
|
||||
break;
|
||||
default :
|
||||
if ((input[i] <= 0x1f)||(input[i] >= 0x7f)) {
|
||||
*d++ = '\\';
|
||||
*d++ = 'x';
|
||||
c2x(input[i], d);
|
||||
d += 2;
|
||||
} else {
|
||||
*d++ = input[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
*d = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define VALID_HEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F')))
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int urldecode_uni_nonstrict_inplace_ex(char *input, long int input_len) {
|
||||
unsigned char *d = (unsigned char *)input;
|
||||
long int i, count;
|
||||
|
||||
if (input == NULL) return -1;
|
||||
|
||||
i = count = 0;
|
||||
while (i < input_len) {
|
||||
if (input[i] == '%') {
|
||||
/* Character is a percent sign. */
|
||||
|
||||
if ((i + 1 < input_len)&&( (input[i + 1] == 'u')||(input[i + 1] == 'U') )) {
|
||||
/* IIS-specific %u encoding. */
|
||||
if (i + 5 < input_len) {
|
||||
/* We have at least 4 data bytes. */
|
||||
if ( (VALID_HEX(input[i + 2]))&&(VALID_HEX(input[i + 3]))
|
||||
&&(VALID_HEX(input[i + 4]))&&(VALID_HEX(input[i + 5])) )
|
||||
{
|
||||
/* We make use of the lower byte here, ignoring the higher byte. */
|
||||
*d++ = x2c(&input[i + 4]);
|
||||
count++;
|
||||
i += 6;
|
||||
} else {
|
||||
/* Invalid data. */
|
||||
int j;
|
||||
|
||||
for(j = 0; (j < 6)&&(i < input_len); j++) {
|
||||
*d++ = input[i++];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Not enough bytes available (4 data bytes were needed). */
|
||||
while(i < input_len) {
|
||||
*d++ = input[i++];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Standard URL encoding. */
|
||||
|
||||
/* Are there enough bytes available? */
|
||||
if (i + 2 < input_len) {
|
||||
/* Yes. */
|
||||
|
||||
/* Decode a %xx combo only if it is valid.
|
||||
*/
|
||||
char c1 = input[i + 1];
|
||||
char c2 = input[i + 2];
|
||||
|
||||
/* ENH Use VALID_HEX? */
|
||||
if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) ||
|
||||
((c1 >= 'A')&&(c1 <= 'F')))
|
||||
&& (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) ||
|
||||
((c2 >= 'A')&&(c2 <= 'F'))) )
|
||||
{
|
||||
*d++ = x2c(&input[i + 1]);
|
||||
count++;
|
||||
i += 3;
|
||||
} else {
|
||||
/* Not a valid encoding, copy the raw input bytes. */
|
||||
*d++ = '%';
|
||||
*d++ = c1;
|
||||
*d++ = c2;
|
||||
count += 3;
|
||||
i += 3;
|
||||
}
|
||||
} else {
|
||||
/* Not enough bytes available. */
|
||||
|
||||
*d++ = '%';
|
||||
count++;
|
||||
i++;
|
||||
|
||||
if (i + 1 < input_len) {
|
||||
*d++ = input[i];
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Character is not a percent sign. */
|
||||
if (input[i] == '+') {
|
||||
*d++ = ' ';
|
||||
} else {
|
||||
*d++ = input[i];
|
||||
}
|
||||
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int urldecode_nonstrict_inplace_ex(char *input, long int input_len, int *invalid_count) {
|
||||
unsigned char *d = (unsigned char *)input;
|
||||
long int i, count;
|
||||
|
||||
if (input == NULL) return -1;
|
||||
|
||||
i = count = 0;
|
||||
while (i < input_len) {
|
||||
if (input[i] == '%') {
|
||||
/* Character is a percent sign. */
|
||||
|
||||
/* Are there enough bytes available? */
|
||||
if (i + 2 < input_len) {
|
||||
char c1 = input[i + 1];
|
||||
char c2 = input[i + 2];
|
||||
|
||||
/* ENH Use VALID_HEX? */
|
||||
if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F')))
|
||||
&& (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) )
|
||||
{
|
||||
/* Valid encoding - decode it. */
|
||||
*d++ = x2c(&input[i + 1]);
|
||||
count++;
|
||||
i += 3;
|
||||
} else {
|
||||
/* Invalid encoding, just copy the raw bytes. */
|
||||
*d++ = '%';
|
||||
*d++ = c1;
|
||||
*d++ = c2;
|
||||
count += 3;
|
||||
i += 3;
|
||||
*invalid_count++;
|
||||
}
|
||||
} else {
|
||||
/* Not enough bytes available, copy the raw bytes. */
|
||||
*invalid_count++;
|
||||
|
||||
*d++ = '%';
|
||||
count++;
|
||||
i++;
|
||||
|
||||
if (i + 1 < input_len) {
|
||||
*d++ = input[i];
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Character is not a percent sign. */
|
||||
if (input[i] == '+') {
|
||||
*d++ = ' ';
|
||||
} else {
|
||||
*d++ = input[i];
|
||||
}
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input_len) {
|
||||
unsigned char *d = input;
|
||||
int i, count;
|
||||
|
||||
if ((input == NULL)||(input_len <= 0)) return 0;
|
||||
|
||||
i = count = 0;
|
||||
while((i < input_len)&&(count < input_len)) {
|
||||
int z, copy = 1;
|
||||
|
||||
/* Require an ampersand and at least one character to
|
||||
* start looking into the entity.
|
||||
*/
|
||||
if ((input[i] == '&')&&(i + 1 < input_len)) {
|
||||
int k, j = i + 1;
|
||||
|
||||
if (input[j] == '#') {
|
||||
/* Numerical entity. */
|
||||
copy++;
|
||||
|
||||
if (!(j + 1 < input_len)) goto HTML_ENT_OUT; /* Not enough bytes. */
|
||||
j++;
|
||||
|
||||
if ((input[j] == 'x')||(input[j] == 'X')) {
|
||||
/* Hexadecimal entity. */
|
||||
copy++;
|
||||
|
||||
if (!(j + 1 < input_len)) goto HTML_ENT_OUT; /* Not enough bytes. */
|
||||
j++; /* j is the position of the first digit now. */
|
||||
|
||||
k = j;
|
||||
while((j < input_len)&&(isxdigit(input[j]))) j++;
|
||||
if (j > k) { /* Do we have at least one digit? */
|
||||
/* Decode the entity. */
|
||||
char *x = apr_pstrmemdup(mp, &input[k], j - k);
|
||||
*d++ = (unsigned char)strtol(x, NULL, 16);
|
||||
count++;
|
||||
|
||||
/* Skip over the semicolon if it's there. */
|
||||
if ((j < input_len)&&(input[j] == ';')) i = j + 1;
|
||||
else i = j;
|
||||
|
||||
continue;
|
||||
} else {
|
||||
goto HTML_ENT_OUT;
|
||||
}
|
||||
} else {
|
||||
/* Decimal entity. */
|
||||
k = j;
|
||||
while((j < input_len)&&(isdigit(input[j]))) j++;
|
||||
if (j > k) { /* Do we have at least one digit? */
|
||||
/* Decode the entity. */
|
||||
char *x = apr_pstrmemdup(mp, &input[k], j - k);
|
||||
*d++ = (unsigned char)strtol(x, NULL, 10);
|
||||
count++;
|
||||
|
||||
/* Skip over the semicolon if it's there. */
|
||||
if ((j < input_len)&&(input[j] == ';')) i = j + 1;
|
||||
else i = j;
|
||||
|
||||
continue;
|
||||
} else {
|
||||
goto HTML_ENT_OUT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Text entity. */
|
||||
|
||||
k = j;
|
||||
while((j < input_len)&&(isalnum(input[j]))) j++;
|
||||
if (j > k) { /* Do we have at least one digit? */
|
||||
char *x = apr_pstrmemdup(mp, &input[k], j - k);
|
||||
|
||||
/* Decode the entity. */
|
||||
if (strcasecmp(x, "quot") == 0) *d++ = '"';
|
||||
else
|
||||
if (strcasecmp(x, "amp") == 0) *d++ = '&';
|
||||
else
|
||||
if (strcasecmp(x, "lt") == 0) *d++ = '<';
|
||||
else
|
||||
if (strcasecmp(x, "gt") == 0) *d++ = '>';
|
||||
else
|
||||
if (strcasecmp(x, "nbsp") == 0) *d++ = NBSP;
|
||||
else {
|
||||
/* We do no want to convert this entity, copy the raw data over. */
|
||||
copy = j - k + 1;
|
||||
goto HTML_ENT_OUT;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
/* Skip over the semicolon if it's there. */
|
||||
if ((j < input_len)&&(input[j] == ';')) i = j + 1;
|
||||
else i = j;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HTML_ENT_OUT:
|
||||
|
||||
for(z = 0; ((z < copy) && (count < input_len)); z++) {
|
||||
*d++ = input[i++];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#define ISODIGIT(X) ((X >= '0')&&(X <= '7'))
|
||||
|
||||
int ansi_c_sequences_decode_inplace(unsigned char *input, int input_len) {
|
||||
unsigned char *d = input;
|
||||
int i, count;
|
||||
|
||||
i = count = 0;
|
||||
while(i < input_len) {
|
||||
if ((input[i] == '\\')&&(i + 1 < input_len)) {
|
||||
int c = -1;
|
||||
|
||||
/* ENH Should we handle \c as well?
|
||||
* See http://www.opengroup.org/onlinepubs/009695399/utilities/printf.html
|
||||
*/
|
||||
|
||||
switch(input[i + 1]) {
|
||||
case 'a' :
|
||||
c = '\a';
|
||||
break;
|
||||
case 'b' :
|
||||
c = '\b';
|
||||
break;
|
||||
case 'f' :
|
||||
c = '\f';
|
||||
break;
|
||||
case 'n' :
|
||||
c = '\n';
|
||||
break;
|
||||
case 'r' :
|
||||
c = '\r';
|
||||
break;
|
||||
case 't' :
|
||||
c = '\t';
|
||||
break;
|
||||
case 'v' :
|
||||
c = '\v';
|
||||
break;
|
||||
case '\\' :
|
||||
c = '\\';
|
||||
break;
|
||||
case '?' :
|
||||
c = '?';
|
||||
break;
|
||||
case '\'' :
|
||||
c = '\'';
|
||||
break;
|
||||
case '"' :
|
||||
c = '"';
|
||||
break;
|
||||
}
|
||||
|
||||
if (c != -1) i += 2;
|
||||
|
||||
/* Hexadecimal or octal? */
|
||||
if (c == -1) {
|
||||
if ((input[i + 1] == 'x')||(input[i + 1] == 'X')) {
|
||||
/* Hexadecimal. */
|
||||
if ((i + 3 < input_len)&&(isxdigit(input[i + 2]))&&(isxdigit(input[i + 3]))) {
|
||||
/* Two digits. */
|
||||
c = x2c(&input[i + 2]);
|
||||
i += 4;
|
||||
} else {
|
||||
/* Invalid encoding, do nothing. */
|
||||
}
|
||||
}
|
||||
else
|
||||
if (isdigit(input[i + 1])) { /* Octal. */
|
||||
char buf[10];
|
||||
int j = 0, l = 3;
|
||||
|
||||
/* Up to 4 digits if the first digit is a zero. */
|
||||
if (input[i + 1] == '0') l = 4;
|
||||
|
||||
while((i + 1 + j < input_len)&&(j <= l)) {
|
||||
buf[j] = input[i + 1 + j];
|
||||
j++;
|
||||
if (!ISODIGIT(input[i + 1 + j])) break;
|
||||
}
|
||||
buf[j] = '\0';
|
||||
|
||||
if (j > 0) {
|
||||
c = strtol(buf, NULL, 8);
|
||||
i += 1 + j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1) {
|
||||
/* Didn't recognise encoding, copy raw bytes. */
|
||||
*d++ = input[i + 1];
|
||||
count++;
|
||||
i += 2;
|
||||
} else {
|
||||
/* Converted the encoding. */
|
||||
*d++ = c;
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
/* Input character not a backslash, copy it. */
|
||||
*d++ = input[i++];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int normalise_path_inplace(unsigned char *input, int input_len, int win) {
|
||||
unsigned char *d = input;
|
||||
int i, count;
|
||||
|
||||
i = count = 0;
|
||||
while ((i < input_len)&&(count < input_len)) {
|
||||
char c = input[i];
|
||||
|
||||
/* Convert backslash to forward slash on Windows only. */
|
||||
if ((win)&&(c == '\\')) c = '/';
|
||||
|
||||
if (c == '/') {
|
||||
/* Is there a directory back-reference? Yes, we
|
||||
* require at least 5 prior bytes here. That's on
|
||||
* purpose.
|
||||
*/
|
||||
if ((count >= 5)&&(*(d - 1) == '.')&&(*(d - 2) == '.')&&(*(d - 3) == '/')) {
|
||||
char *cd = d - 4;
|
||||
int ccount = count - 4;
|
||||
|
||||
/* Go back until we reach the beginning or a forward slash. */
|
||||
while ((ccount > 0)&&(*cd != '/')) {
|
||||
ccount--;
|
||||
cd--;
|
||||
}
|
||||
|
||||
if (*cd == '/') {
|
||||
d = cd;
|
||||
count = ccount;
|
||||
}
|
||||
} else
|
||||
/* Is there a directory self-reference? */
|
||||
if ((count >= 2)&&(*(d - 1) == '.')&&(*(d - 2) == '/')) {
|
||||
/* Ignore the last two bytes. */
|
||||
d -= 2;
|
||||
count -= 2;
|
||||
} else
|
||||
/* Or are there just multiple occurences of forward slash? */
|
||||
if ((count >= 1)&&(*(d - 1) == '/')) {
|
||||
/* Ignore the last one byte. */
|
||||
d--;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the byte over. */
|
||||
*d++ = c;
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
return count;
|
||||
}
|
75
apache2/msc_util.h
Normal file
75
apache2/msc_util.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_util.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _UTIL_H_
|
||||
#define _UTIL_H_
|
||||
|
||||
#include "modsecurity.h"
|
||||
|
||||
int DSOLOCAL normalise_path_inplace(unsigned char *input, int len, int win);
|
||||
|
||||
int DSOLOCAL parse_boolean(const char *input);
|
||||
|
||||
int DSOLOCAL parse_name_eq_value(apr_pool_t *mp, const char *input, char **name, char **value);
|
||||
|
||||
char DSOLOCAL *url_encode(apr_pool_t *mp, char *input, unsigned int input_len);
|
||||
|
||||
char DSOLOCAL *strnurlencat(char *destination, char *source, unsigned int maxlen);
|
||||
|
||||
char DSOLOCAL *file_dirname(apr_pool_t *p, const char *filename);
|
||||
|
||||
char DSOLOCAL *file_basename(apr_pool_t *p, const char *filename);
|
||||
|
||||
int DSOLOCAL hex2bytes_inplace(unsigned char *data, int len);
|
||||
|
||||
char DSOLOCAL *bytes2hex(apr_pool_t *pool, unsigned char *data, int len);
|
||||
|
||||
int DSOLOCAL is_token_char(unsigned char c);
|
||||
|
||||
int DSOLOCAL remove_lf_crlf_inplace(char *text);
|
||||
|
||||
unsigned DSOLOCAL char x2c(unsigned char *what);
|
||||
|
||||
char DSOLOCAL *guess_tmp_dir(apr_pool_t *p);
|
||||
|
||||
char DSOLOCAL *current_logtime(apr_pool_t *mp);
|
||||
|
||||
char DSOLOCAL *current_filetime(apr_pool_t *mp);
|
||||
|
||||
int DSOLOCAL msc_mkstemp(char *template);
|
||||
|
||||
char DSOLOCAL *strtolower_inplace(unsigned char *str);
|
||||
|
||||
unsigned char DSOLOCAL *c2x(unsigned what, unsigned char *where);
|
||||
|
||||
char DSOLOCAL *log_escape(apr_pool_t *p, const char *text);
|
||||
|
||||
char DSOLOCAL *log_escape_nq(apr_pool_t *p, const char *text);
|
||||
|
||||
char DSOLOCAL *log_escape_ex(apr_pool_t *p, const char *text, unsigned long int text_length);
|
||||
|
||||
char DSOLOCAL *log_escape_nq_ex(apr_pool_t *p, const char *text, unsigned long int text_length);
|
||||
|
||||
char DSOLOCAL *log_escape_header_name(apr_pool_t *p, const char *text);
|
||||
|
||||
char DSOLOCAL *_log_escape(apr_pool_t *p, const unsigned char *input,
|
||||
unsigned long int input_length, int escape_quotes, int escape_colon);
|
||||
|
||||
int DSOLOCAL urldecode_uni_nonstrict_inplace_ex(char *input, long int input_length);
|
||||
|
||||
int DSOLOCAL urldecode_nonstrict_inplace_ex(char *input, long int input_length, int *invalid_count);
|
||||
|
||||
int DSOLOCAL html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int len);
|
||||
|
||||
int DSOLOCAL ansi_c_sequences_decode_inplace(unsigned char *input, int len);
|
||||
|
||||
#endif
|
137
apache2/msc_xml.c
Normal file
137
apache2/msc_xml.c
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_xml.c,v 1.2 2006/12/04 20:04:09 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifdef WITH_LIBXML2
|
||||
|
||||
#include "msc_xml.h"
|
||||
|
||||
|
||||
/**
|
||||
* Initialise XML parser.
|
||||
*/
|
||||
int xml_init(modsec_rec *msr, char **error_msg) {
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
msr->xml = apr_pcalloc(msr->mp, sizeof(xml_data));
|
||||
if (msr->xml == NULL) return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void xml_receive_sax_error(void *data, const char *msg, ...) {
|
||||
modsec_rec *msr = (modsec_rec *)data;
|
||||
char message[256];
|
||||
|
||||
if (msr == NULL) return;
|
||||
|
||||
apr_snprintf(message, sizeof(message), "%s (line %i pos %i)",
|
||||
log_escape_nq(msr->mp, msr->xml->parsing_ctx->lastError.message),
|
||||
msr->xml->parsing_ctx->lastError.line,
|
||||
msr->xml->parsing_ctx->lastError.int2);
|
||||
|
||||
msr_log(msr, 5, "XML: Parsing error: %s", message);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Feed one chunk of data to the XML parser.
|
||||
*/
|
||||
int xml_process_chunk(modsec_rec *msr, const char *buf, unsigned int size, char **error_msg) {
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* We want to initialise our parsing context here, to
|
||||
* enable us to pass it the first chunk of data so that
|
||||
* it can attempt to auto-detect the encoding.
|
||||
*/
|
||||
if (msr->xml->parsing_ctx == NULL) {
|
||||
|
||||
/* First invocation. */
|
||||
|
||||
msr_log(msr, 4, "XML: Initialising parser.");
|
||||
|
||||
/* NOTE When Sax interface is used libxml will not
|
||||
* create the document object, but we need it.
|
||||
|
||||
msr->xml->sax_handler = (xmlSAXHandler *)apr_pcalloc(msr->mp, sizeof(xmlSAXHandler));
|
||||
if (msr->xml->sax_handler == NULL) return -1;
|
||||
msr->xml->sax_handler->error = xml_receive_sax_error;
|
||||
msr->xml->sax_handler->warning = xml_receive_sax_error;
|
||||
msr->xml->parsing_ctx = xmlCreatePushParserCtxt(msr->xml->sax_handler, msr,
|
||||
buf, size, "body.xml");
|
||||
|
||||
*/
|
||||
|
||||
msr->xml->parsing_ctx = xmlCreatePushParserCtxt(NULL, NULL, buf, size, "body.xml");
|
||||
if (msr->xml->parsing_ctx == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML: Failed to create parsing context.");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
|
||||
/* Not a first invocation. */
|
||||
|
||||
xmlParseChunk(msr->xml->parsing_ctx, buf, size, 0);
|
||||
if (msr->xml->parsing_ctx->wellFormed != 1) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML: Failed parsing document.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise XML parsing.
|
||||
*/
|
||||
int xml_complete(modsec_rec *msr, char **error_msg) {
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* Only if we have a context, meaning we've done some work. */
|
||||
if (msr->xml->parsing_ctx != NULL) {
|
||||
/* This is how we signalise the end of parsing to libxml. */
|
||||
xmlParseChunk(msr->xml->parsing_ctx, NULL, 0, 1);
|
||||
|
||||
/* Preserve the results for our reference. */
|
||||
msr->xml->well_formed = msr->xml->parsing_ctx->wellFormed;
|
||||
msr->xml->doc = msr->xml->parsing_ctx->myDoc;
|
||||
|
||||
/* Clean up everything else. */
|
||||
xmlFreeParserCtxt(msr->xml->parsing_ctx);
|
||||
msr->xml->parsing_ctx = NULL;
|
||||
msr_log(msr, 4, "XML: Parsing complete (well_formed %i).", msr->xml->well_formed);
|
||||
|
||||
if (msr->xml->well_formed != 1) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML: Failed parsing document.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the resources used for XML parsing.
|
||||
*/
|
||||
apr_status_t xml_cleanup(modsec_rec *msr) {
|
||||
if (msr->xml->doc != NULL) {
|
||||
xmlFreeDoc(msr->xml->doc);
|
||||
msr->xml->doc = NULL;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
43
apache2/msc_xml.h
Normal file
43
apache2/msc_xml.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: msc_xml.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MSC_XML_H_
|
||||
#define _MSC_XML_H_
|
||||
|
||||
typedef struct xml_data xml_data;
|
||||
|
||||
#include "modsecurity.h"
|
||||
#include <libxml/xmlschemas.h>
|
||||
#include <libxml/xpath.h>
|
||||
|
||||
/* Structures */
|
||||
|
||||
struct xml_data {
|
||||
xmlSAXHandler *sax_handler;
|
||||
xmlParserCtxtPtr parsing_ctx;
|
||||
xmlDocPtr doc;
|
||||
|
||||
unsigned int well_formed;
|
||||
};
|
||||
|
||||
/* Functions */
|
||||
|
||||
int DSOLOCAL xml_init(modsec_rec *msr, char **error_msg);
|
||||
|
||||
int DSOLOCAL xml_process_chunk(modsec_rec *msr, const char *buf,
|
||||
unsigned int size, char **error_msg);
|
||||
|
||||
int DSOLOCAL xml_complete(modsec_rec *msr, char **error_msg);
|
||||
|
||||
apr_status_t DSOLOCAL xml_cleanup(modsec_rec *msr);
|
||||
|
||||
#endif
|
514
apache2/persist_dbm.c
Normal file
514
apache2/persist_dbm.c
Normal file
@ -0,0 +1,514 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: persist_dbm.c,v 1.3 2006/12/21 19:57:41 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "persist_dbm.h"
|
||||
#include "apr_sdbm.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_table_t *collection_unpack(modsec_rec *msr, char *blob, unsigned int blob_size,
|
||||
int log_vars)
|
||||
{
|
||||
apr_table_t *col = NULL;
|
||||
unsigned int blob_offset;
|
||||
|
||||
col = apr_table_make(msr->mp, 32);
|
||||
if (col == NULL) return NULL;
|
||||
|
||||
/* ENH verify the first 3 bytes (header) */
|
||||
|
||||
blob_offset = 3;
|
||||
while (blob_offset + 1 < blob_size) {
|
||||
msc_string *var = apr_pcalloc(msr->mp, sizeof(msc_string));
|
||||
|
||||
var->name_len = (blob[blob_offset] << 8) + blob[blob_offset + 1];
|
||||
if (var->name_len == 0) break;
|
||||
|
||||
blob_offset += 2;
|
||||
if (blob_offset + var->name_len > blob_size) return NULL;
|
||||
var->name = apr_pstrmemdup(msr->mp, blob + blob_offset, var->name_len - 1);
|
||||
blob_offset += var->name_len;
|
||||
var->name_len--;
|
||||
|
||||
var->value_len = (blob[blob_offset] << 8) + blob[blob_offset + 1];
|
||||
blob_offset += 2;
|
||||
|
||||
if (blob_offset + var->value_len > blob_size) return NULL;
|
||||
var->value = apr_pstrmemdup(msr->mp, blob + blob_offset, var->value_len - 1);
|
||||
blob_offset += var->value_len;
|
||||
var->value_len--;
|
||||
|
||||
if (log_vars) {
|
||||
msr_log(msr, 9, "Read variable: name \"%s\", value \"%s\".",
|
||||
log_escape_ex(msr->mp, var->name, var->name_len),
|
||||
log_escape_ex(msr->mp, var->value, var->value_len));
|
||||
}
|
||||
|
||||
apr_table_addn(col, var->name, (void *)var);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apr_table_t *collection_retrieve(modsec_rec *msr, const char *col_name,
|
||||
const char *col_key, int col_key_len)
|
||||
{
|
||||
char *dbm_filename = NULL;
|
||||
apr_status_t rc;
|
||||
apr_sdbm_datum_t key;
|
||||
apr_sdbm_datum_t *value = NULL;
|
||||
apr_sdbm_t *dbm;
|
||||
apr_table_t *col = NULL;
|
||||
const apr_array_header_t *arr;
|
||||
apr_table_entry_t *te;
|
||||
int i;
|
||||
|
||||
if (msr->txcfg->data_dir == NULL) {
|
||||
msr_log(msr, 1, "Unable to retrieve collection (name \"%s\", key \"%s\"). Use "
|
||||
"SecDataDir to define data directory first.", log_escape(msr->mp, col_name),
|
||||
log_escape(msr->mp, col_key));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", col_name, NULL);
|
||||
|
||||
rc = apr_sdbm_open(&dbm, dbm_filename, APR_READ | APR_SHARELOCK,
|
||||
CREATEMODE, msr->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
key.dptr = (char *)col_key;
|
||||
key.dsize = col_key_len + 1;
|
||||
|
||||
value = (apr_sdbm_datum_t *)apr_pcalloc(msr->mp, sizeof(apr_sdbm_datum_t));
|
||||
rc = apr_sdbm_fetch(dbm, value, key);
|
||||
if (rc != APR_SUCCESS) {
|
||||
apr_sdbm_close(dbm);
|
||||
msr_log(msr, 1, "Failed to read from DBM file \"%s\": %s", log_escape(msr->mp,
|
||||
dbm_filename), get_apr_error(msr->mp, rc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (value->dptr == NULL) { /* Key not found in DBM file. */
|
||||
apr_sdbm_close(dbm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Transform raw data into a table. */
|
||||
col = collection_unpack(msr, value->dptr, value->dsize, 1);
|
||||
if (col == NULL) return NULL;
|
||||
|
||||
/* Remove expired variables. */
|
||||
do {
|
||||
arr = apr_table_elts(col);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
if (strncmp(te[i].key, "__expire_", 9) == 0) {
|
||||
msc_string *var = (msc_string *)te[i].val;
|
||||
int expiry_time = atoi(var->value);
|
||||
|
||||
/* Do not remove the record itself. */
|
||||
if (strcmp(te[i].key, "__expire_KEY") == 0) continue;
|
||||
|
||||
if (expiry_time <= apr_time_sec(msr->request_time)) {
|
||||
char *key_to_expire = apr_pstrdup(msr->mp, te[i].key);
|
||||
msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire + 9);
|
||||
apr_table_unset(col, key_to_expire + 9);
|
||||
msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire);
|
||||
apr_table_unset(col, key_to_expire);
|
||||
msr_log(msr, 4, "Removed expired variable \"%s\".", key_to_expire + 9);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(i != arr->nelts);
|
||||
|
||||
|
||||
/* Update UPDATE_RATE */
|
||||
{
|
||||
msc_string *var;
|
||||
int create_time, counter;
|
||||
|
||||
var = (msc_string *)apr_table_get(col, "CREATE_TIME");
|
||||
if (var == NULL) {
|
||||
/* Error. */
|
||||
} else {
|
||||
create_time = atoi(var->value);
|
||||
var = (msc_string *)apr_table_get(col, "UPDATE_COUNTER");
|
||||
if (var == NULL) {
|
||||
/* Error. */
|
||||
} else {
|
||||
counter = atoi(var->value);
|
||||
var = (msc_string *)apr_table_get(col, "UPDATE_RATE");
|
||||
if (var == NULL) {
|
||||
var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
|
||||
var->name = "UPDATE_RATE";
|
||||
var->name_len = strlen(var->name);
|
||||
apr_table_setn(col, var->name, (void *)var);
|
||||
}
|
||||
var->value = apr_psprintf(msr->mp, "%i",
|
||||
(int)((60 * counter)/(apr_time_sec(apr_time_now()) - create_time)));
|
||||
var->value_len = strlen(var->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apr_sdbm_close(dbm);
|
||||
|
||||
msr_log(msr, 4, "Retrieved collection (name \"%s\", key \"%s\").",
|
||||
log_escape(msr->mp, col_name), log_escape(msr->mp, col_key));
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int collection_store(modsec_rec *msr, apr_table_t *col) {
|
||||
char *dbm_filename = NULL;
|
||||
msc_string *var_name = NULL, *var_key = NULL;
|
||||
unsigned char *blob = NULL;
|
||||
unsigned int blob_size, blob_offset;
|
||||
apr_status_t rc;
|
||||
apr_sdbm_datum_t key;
|
||||
apr_sdbm_datum_t value;
|
||||
apr_sdbm_t *dbm;
|
||||
const apr_array_header_t *arr;
|
||||
apr_table_entry_t *te;
|
||||
int i;
|
||||
|
||||
var_name = (msc_string *)apr_table_get(col, "__name");
|
||||
if (var_name == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var_key = (msc_string *)apr_table_get(col, "__key");
|
||||
if (var_key == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (msr->txcfg->data_dir == NULL) {
|
||||
msr_log(msr, 1, "Unable to store collection (name \"%s\", key \"%s\"). Use "
|
||||
"SecDataDir to define data directory first.",
|
||||
log_escape(msr->mp, var_name->value), log_escape(msr->mp, var_key->value));
|
||||
return -1;
|
||||
}
|
||||
|
||||
dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL);
|
||||
|
||||
/* Remove expired variables. */
|
||||
do {
|
||||
arr = apr_table_elts(col);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
if (strncmp(te[i].key, "__expire_", 9) == 0) {
|
||||
msc_string *var = (msc_string *)te[i].val;
|
||||
int expiry_time = atoi(var->value);
|
||||
|
||||
/* Do not remove the record itself. */
|
||||
if (strcmp(te[i].key, "__expire_KEY") == 0) continue;
|
||||
|
||||
if (expiry_time <= apr_time_sec(msr->request_time)) {
|
||||
char *key_to_expire = apr_pstrdup(msr->mp, te[i].key);
|
||||
msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire + 9);
|
||||
apr_table_unset(col, key_to_expire + 9);
|
||||
msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire);
|
||||
apr_table_unset(col, key_to_expire);
|
||||
msr_log(msr, 4, "Removed expired variable \"%s\".", key_to_expire + 9);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(i != arr->nelts);
|
||||
|
||||
/* Delete the collection if the variable "KEY" does not exist. */
|
||||
if (apr_table_get(col, "KEY") == NULL) {
|
||||
|
||||
rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK,
|
||||
CREATEMODE, msr->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed to access DBM file \"%s\": %s",
|
||||
log_escape(msr->mp, dbm_filename), get_apr_error(msr->mp, rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
key.dptr = var_key->value;
|
||||
key.dsize = var_key->value_len + 1;
|
||||
|
||||
rc = apr_sdbm_delete(dbm, key);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed deleting collection (name \"%s\", "
|
||||
"key \"%s\"): %s", log_escape(msr->mp, var_name->value),
|
||||
log_escape(msr->mp, var_key->value), get_apr_error(msr->mp, rc));
|
||||
apr_sdbm_close(dbm);
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "Deleted collection (name \"%s\", key \"%s\").",
|
||||
log_escape(msr->mp, var_name->value), log_escape(msr->mp, var_key->value));
|
||||
apr_sdbm_close(dbm);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Update the timeout value. */
|
||||
{
|
||||
msc_string *var = (msc_string *)apr_table_get(col, "TIMEOUT");
|
||||
if (var != NULL) {
|
||||
int timeout = atoi(var->value);
|
||||
var = (msc_string *)apr_table_get(col, "__expire_KEY");
|
||||
if (var != NULL) {
|
||||
var->value = apr_psprintf(msr->mp, "%i", (int)(apr_time_sec(apr_time_now()) + timeout));
|
||||
var->value_len = strlen(var->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* LAST_UPDATE_TIME */
|
||||
{
|
||||
msc_string *var = (msc_string *)apr_table_get(col, "LAST_UPDATE_TIME");
|
||||
if (var == NULL) {
|
||||
var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
|
||||
var->name = "LAST_UPDATE_TIME";
|
||||
var->name_len = strlen(var->name);
|
||||
apr_table_setn(col, var->name, (void *)var);
|
||||
}
|
||||
var->value = apr_psprintf(msr->mp, "%i", (int)(apr_time_sec(apr_time_now())));
|
||||
var->value_len = strlen(var->value);
|
||||
}
|
||||
|
||||
/* UPDATE_COUNTER */
|
||||
{
|
||||
msc_string *var = (msc_string *)apr_table_get(col, "UPDATE_COUNTER");
|
||||
int counter = 0;
|
||||
if (var == NULL) {
|
||||
var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
|
||||
var->name = "UPDATE_COUNTER";
|
||||
var->name_len = strlen(var->name);
|
||||
apr_table_setn(col, var->name, (void *)var);
|
||||
} else {
|
||||
counter = atoi(var->value);
|
||||
}
|
||||
var->value = apr_psprintf(msr->mp, "%i", counter + 1);
|
||||
var->value_len = strlen(var->value);
|
||||
}
|
||||
|
||||
/* Calculate the size first. */
|
||||
blob_size = 3 + 2;
|
||||
arr = apr_table_elts(col);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
msc_string *var = (msc_string *)te[i].val;
|
||||
int len;
|
||||
|
||||
len = var->name_len + 1;
|
||||
if (len >= 65536) len = 65536;
|
||||
blob_size += len + 2;
|
||||
|
||||
len = var->value_len + 1;
|
||||
if (len >= 65536) len = 65536;
|
||||
blob_size += len + 2;
|
||||
}
|
||||
|
||||
/* Now generate the binary object. */
|
||||
blob = apr_pcalloc(msr->mp, blob_size);
|
||||
if (blob == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
blob[0] = 0x49;
|
||||
blob[1] = 0x52;
|
||||
blob[2] = 0x01;
|
||||
|
||||
blob_offset = 3;
|
||||
arr = apr_table_elts(col);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
msc_string *var = (msc_string *)te[i].val;
|
||||
int len;
|
||||
|
||||
len = var->name_len + 1;
|
||||
if (len >= 65536) len = 65536;
|
||||
|
||||
blob[blob_offset + 0] = (len & 0xff00) >> 8;
|
||||
blob[blob_offset + 1] = len & 0x00ff;
|
||||
memcpy(blob + blob_offset + 2, var->name, len - 1);
|
||||
blob[blob_offset + 2 + len - 1] = '\0';
|
||||
blob_offset += 2 + len;
|
||||
|
||||
len = var->value_len + 1;
|
||||
if (len >= 65536) len = 65536;
|
||||
|
||||
blob[blob_offset + 0] = (len & 0xff00) >> 8;
|
||||
blob[blob_offset + 1] = len & 0x00ff;
|
||||
memcpy(blob + blob_offset + 2, var->value, len - 1);
|
||||
blob[blob_offset + 2 + len - 1] = '\0';
|
||||
blob_offset += 2 + len;
|
||||
|
||||
msr_log(msr, 9, "Wrote variable: name \"%s\", value \"%s\".",
|
||||
log_escape_ex(msr->mp, var->name, var->name_len),
|
||||
log_escape_ex(msr->mp, var->value, var->value_len));
|
||||
}
|
||||
|
||||
blob[blob_offset] = 0;
|
||||
blob[blob_offset + 1] = 0;
|
||||
|
||||
/* And, finally, store it. */
|
||||
dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL);
|
||||
|
||||
rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK,
|
||||
CREATEMODE, msr->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed to access DBM file \"%s\": %s", log_escape(msr->mp, dbm_filename),
|
||||
get_apr_error(msr->mp, rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
key.dptr = var_key->value;
|
||||
key.dsize = var_key->value_len + 1;
|
||||
|
||||
value.dptr = (char *)blob;
|
||||
value.dsize = blob_size;
|
||||
|
||||
rc = apr_sdbm_store(dbm, key, value, APR_SDBM_REPLACE);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed to write to DBM file \"%s\": %s", dbm_filename,
|
||||
get_apr_error(msr->mp, rc));
|
||||
apr_sdbm_close(dbm);
|
||||
return -1;
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "Persisted collection (name \"%s\", key \"%s\").",
|
||||
log_escape(msr->mp, var_name->value), log_escape(msr->mp, var_key->value));
|
||||
|
||||
apr_sdbm_close(dbm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int collections_remove_stale(modsec_rec *msr, const char *col_name) {
|
||||
char *dbm_filename = NULL;
|
||||
apr_sdbm_datum_t key, value;
|
||||
apr_sdbm_t *dbm;
|
||||
apr_status_t rc;
|
||||
apr_array_header_t *keys_arr;
|
||||
char **keys;
|
||||
int i;
|
||||
unsigned int now = (unsigned int)apr_time_sec(msr->request_time);
|
||||
|
||||
if (msr->txcfg->data_dir == NULL) {
|
||||
/* The user has been warned about this problem enough times already by now.
|
||||
* msr_log(msr, 1, "Unable to access collection file (name \"%s\"). Use SecDataDir to "
|
||||
* "define data directory first.", log_escape(msr->mp, col_name));
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", col_name, NULL);
|
||||
|
||||
rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK,
|
||||
CREATEMODE, msr->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed to access DBM file \"%s\": %s", log_escape(msr->mp, dbm_filename),
|
||||
get_apr_error(msr->mp, rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* First get a list of all keys. */
|
||||
keys_arr = apr_array_make(msr->mp, 256, sizeof(char *));
|
||||
rc = apr_sdbm_lock(dbm, APR_FLOCK_SHARED);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed to lock DBM file \"%s\": %s", log_escape(msr->mp, dbm_filename),
|
||||
get_apr_error(msr->mp, rc));
|
||||
apr_sdbm_close(dbm);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* No one can write to the file while we're
|
||||
* doing this so let's do it as fast as we can.
|
||||
*/
|
||||
rc = apr_sdbm_firstkey(dbm, &key);
|
||||
while(rc == APR_SUCCESS) {
|
||||
char *s = apr_pstrmemdup(msr->mp, key.dptr, key.dsize);
|
||||
*(char **)apr_array_push(keys_arr) = s;
|
||||
rc = apr_sdbm_nextkey(dbm, &key);
|
||||
}
|
||||
apr_sdbm_unlock(dbm);
|
||||
|
||||
msr_log(msr, 9, "Found %i record(s) in file \"%s\".", keys_arr->nelts,
|
||||
log_escape(msr->mp, dbm_filename));
|
||||
|
||||
/* Now retrieve the entires one by one. */
|
||||
keys = (char **)keys_arr->elts;
|
||||
for (i = 0; i < keys_arr->nelts; i++) {
|
||||
key.dptr = keys[i];
|
||||
key.dsize = strlen(key.dptr) + 1;
|
||||
|
||||
rc = apr_sdbm_fetch(dbm, &value, key);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed reading DBM file \"%s\": %s",
|
||||
log_escape(msr->mp, dbm_filename), get_apr_error(msr->mp, rc));
|
||||
apr_sdbm_close(dbm);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (value.dptr != NULL) {
|
||||
apr_table_t *col = NULL;
|
||||
msc_string *var = NULL;
|
||||
|
||||
col = collection_unpack(msr, value.dptr, value.dsize, 0);
|
||||
if (col == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var = (msc_string *)apr_table_get(col, "__expire_KEY");
|
||||
if (var == NULL) {
|
||||
msr_log(msr, 1, "Collection cleanup discovered entry with no "
|
||||
"__expire_KEY (name \"%s\", key \"%s\").",
|
||||
log_escape(msr->mp, col_name), log_escape(msr->mp, key.dptr));
|
||||
} else {
|
||||
unsigned int expiry_time = atoi(var->value);
|
||||
|
||||
msr_log(msr, 9, "Record (name \"%s\", key \"%s\") set to expire in %i seconds.",
|
||||
log_escape(msr->mp, col_name), log_escape(msr->mp, key.dptr),
|
||||
expiry_time - now);
|
||||
|
||||
if (expiry_time <= now) {
|
||||
rc = apr_sdbm_delete(dbm, key);
|
||||
if (rc != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Failed deleting collection (name \"%s\", "
|
||||
"key \"%s\"): %s", log_escape(msr->mp, col_name),
|
||||
log_escape(msr->mp, key.dptr), get_apr_error(msr->mp, rc));
|
||||
return -1;
|
||||
}
|
||||
msr_log(msr, 4, "Removed stale collection (name \"%s\", "
|
||||
"key \"%s\").", log_escape(msr->mp, col_name),
|
||||
log_escape(msr->mp, key.dptr));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Ignore entry not found - it may have been removed in the meantime. */
|
||||
}
|
||||
}
|
||||
|
||||
apr_sdbm_close(dbm);
|
||||
|
||||
return 1;
|
||||
}
|
26
apache2/persist_dbm.h
Normal file
26
apache2/persist_dbm.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: persist_dbm.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _PERSIST_DBM_H_
|
||||
#define _PERSIST_DBM_H_
|
||||
|
||||
#include "apr_general.h"
|
||||
#include "modsecurity.h"
|
||||
|
||||
apr_table_t DSOLOCAL *collection_retrieve(modsec_rec *msr, const char *col_name,
|
||||
const char *col_value, int col_value_length);
|
||||
|
||||
int DSOLOCAL collection_store(modsec_rec *msr, apr_table_t *collection);
|
||||
|
||||
int DSOLOCAL collections_remove_stale(modsec_rec *msr, const char *col_name);
|
||||
|
||||
#endif
|
1479
apache2/re.c
Normal file
1479
apache2/re.c
Normal file
File diff suppressed because it is too large
Load Diff
299
apache2/re.h
Normal file
299
apache2/re.h
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: re.h,v 1.7 2006/12/29 10:31:38 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#ifndef _MSC_RE_H_
|
||||
#define _MSC_RE_H_
|
||||
|
||||
#define ABSOLUTE_VALUE 0
|
||||
#define POSITIVE_VALUE 1
|
||||
#define NEGATIVE_VALUE 2
|
||||
|
||||
typedef struct msre_engine msre_engine;
|
||||
typedef struct msre_ruleset msre_ruleset;
|
||||
typedef struct msre_ruleset_internal msre_ruleset_internal;
|
||||
typedef struct msre_rule msre_rule;
|
||||
typedef struct msre_var_metadata msre_var_metadata;
|
||||
typedef struct msre_var msre_var;
|
||||
typedef struct msre_op_metadata msre_op_metadata;
|
||||
typedef struct msre_tfn_metadata msre_tfn_metadata;
|
||||
typedef struct msre_actionset msre_actionset;
|
||||
typedef struct msre_action_metadata msre_action_metadata;
|
||||
typedef struct msre_action msre_action;
|
||||
|
||||
#include "apr_general.h"
|
||||
#include "apr_tables.h"
|
||||
#include "modsecurity.h"
|
||||
#include "msc_pcre.h"
|
||||
#include "persist_dbm.h"
|
||||
#include "apache2.h"
|
||||
|
||||
|
||||
/* Actions, variables, functions and operator functions */
|
||||
|
||||
int DSOLOCAL expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t *mptmp);
|
||||
|
||||
apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text,
|
||||
apr_array_header_t *arr, char **error_msg);
|
||||
|
||||
apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset,
|
||||
const char *text, char **error_msg);
|
||||
|
||||
msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name);
|
||||
|
||||
msre_action_metadata *msre_resolve_action(msre_engine *engine, const char *name);
|
||||
|
||||
msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *param,
|
||||
modsec_rec *msr, char **error_msg);
|
||||
|
||||
msre_var *msre_create_var_ex(msre_engine *engine, const char *name, const char *param,
|
||||
modsec_rec *msr, char **error_msg);
|
||||
|
||||
msre_action *msre_create_action(msre_engine *engine, const char *name,
|
||||
const char *param, char **error_msg);
|
||||
|
||||
int msre_parse_generic(apr_pool_t *pool, const char *text, apr_table_t *vartable,
|
||||
char **error_msg);
|
||||
|
||||
int DSOLOCAL rule_id_in_range(int ruleid, const char *range);
|
||||
|
||||
|
||||
/* Structures with the corresponding functions */
|
||||
|
||||
struct msre_engine {
|
||||
apr_pool_t *mp;
|
||||
apr_table_t *variables;
|
||||
apr_table_t *operators;
|
||||
apr_table_t *actions;
|
||||
apr_table_t *tfns;
|
||||
};
|
||||
|
||||
msre_engine *msre_engine_create(apr_pool_t *parent_pool);
|
||||
|
||||
void msre_engine_destroy(msre_engine *engine);
|
||||
|
||||
msre_op_metadata *msre_engine_op_resolve(msre_engine *engine, const char *name);
|
||||
|
||||
struct msre_ruleset {
|
||||
apr_pool_t *mp;
|
||||
msre_engine *engine;
|
||||
|
||||
apr_array_header_t *phase_request_headers;
|
||||
apr_array_header_t *phase_request_body;
|
||||
apr_array_header_t *phase_response_headers;
|
||||
apr_array_header_t *phase_response_body;
|
||||
apr_array_header_t *phase_logging;
|
||||
};
|
||||
|
||||
apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr);
|
||||
|
||||
apr_status_t msre_ruleset_process_phase_internal(msre_ruleset *ruleset, modsec_rec *msr);
|
||||
|
||||
msre_ruleset *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp);
|
||||
|
||||
int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase);
|
||||
|
||||
int DSOLOCAL msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re);
|
||||
|
||||
/*
|
||||
int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re,
|
||||
apr_array_header_t *phase_arr);
|
||||
*/
|
||||
|
||||
#define RULE_NO_MATCH 0
|
||||
#define RULE_MATCH 1
|
||||
|
||||
struct msre_rule {
|
||||
apr_array_header_t *targets;
|
||||
const char *op_name;
|
||||
const char *op_param;
|
||||
void *op_param_data;
|
||||
msre_op_metadata *op_metadata;
|
||||
unsigned int op_negated;
|
||||
msre_actionset *actionset;
|
||||
|
||||
msre_ruleset *ruleset;
|
||||
msre_rule *chain_starter;
|
||||
};
|
||||
|
||||
msre_rule *msre_rule_create(msre_ruleset *ruleset, const char *targets,
|
||||
const char *args, const char *actions, char **error_msg);
|
||||
|
||||
void msre_rule_actionset_init(msre_rule *rule);
|
||||
|
||||
apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr);
|
||||
|
||||
#define VAR_SIMPLE 0 /* REQUEST_URI */
|
||||
#define VAR_LIST 1
|
||||
|
||||
#define PHASE_REQUEST_HEADERS 1
|
||||
#define PHASE_REQUEST_BODY 2
|
||||
#define PHASE_RESPONSE_HEADERS 3
|
||||
#define PHASE_RESPONSE_BODY 4
|
||||
#define PHASE_LOGGING 5
|
||||
|
||||
#define FN_OP_PARAM_INIT(X) int (*X)(msre_rule *rule, char **error_msg)
|
||||
#define FN_OP_EXECUTE(X) int (*X)(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg)
|
||||
|
||||
|
||||
struct msre_op_metadata {
|
||||
const char *name;
|
||||
FN_OP_PARAM_INIT (param_init);
|
||||
FN_OP_EXECUTE (execute);
|
||||
};
|
||||
|
||||
#define FN_TFN_EXECUTE(X) int (*X)(apr_pool_t *pool, unsigned char *input, long int input_length, char **rval, long int *rval_length)
|
||||
|
||||
struct msre_tfn_metadata {
|
||||
const char *name;
|
||||
|
||||
/* Functions should populate *rval and return 1 on
|
||||
* success, or return -1 on failure (in which case *rval
|
||||
* should contain the error message. Strict functions
|
||||
* (those that validate in
|
||||
* addition to transforming) can return 0 when input
|
||||
* fails validation. Functions are free to perform
|
||||
* in-place transformation, or to allocate a new buffer
|
||||
* from the provideded temporary (per-rule) memory pool.
|
||||
*
|
||||
* NOTE Strict transformation functions not supported yet.
|
||||
*/
|
||||
FN_TFN_EXECUTE(execute);
|
||||
};
|
||||
|
||||
void DSOLOCAL msre_engine_tfn_register(msre_engine *engine, const char *name,
|
||||
FN_TFN_EXECUTE(execute));
|
||||
|
||||
void msre_engine_op_register(msre_engine *engine, const char *name,
|
||||
FN_OP_PARAM_INIT(fn1), FN_OP_EXECUTE(fn2));
|
||||
|
||||
void DSOLOCAL msre_engine_register_default_tfns(msre_engine *engine);
|
||||
|
||||
void DSOLOCAL msre_engine_register_default_variables(msre_engine *engine);
|
||||
|
||||
void DSOLOCAL msre_engine_register_default_operators(msre_engine *engine);
|
||||
|
||||
void DSOLOCAL msre_engine_register_default_actions(msre_engine *engine);
|
||||
|
||||
msre_tfn_metadata DSOLOCAL *msre_engine_tfn_resolve(msre_engine *engine, const char *name);
|
||||
|
||||
#define VAR_DONT_CACHE 0
|
||||
#define VAR_CACHE 1
|
||||
|
||||
#define FN_VAR_VALIDATE(X) char *(*X)(msre_ruleset *ruleset, msre_var *var)
|
||||
#define FN_VAR_GENERATE(X) int (*X)(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *table, apr_pool_t *mptmp)
|
||||
|
||||
struct msre_var_metadata {
|
||||
const char *name;
|
||||
unsigned int type; /* VAR_TYPE_ constants */
|
||||
unsigned int argc_min;
|
||||
unsigned int argc_max;
|
||||
FN_VAR_VALIDATE (validate);
|
||||
FN_VAR_GENERATE (generate);
|
||||
unsigned int is_cacheable; /* 0 - no, 1 - yes */
|
||||
unsigned int availability; /* when does this variable become available? */
|
||||
};
|
||||
|
||||
struct msre_var {
|
||||
const char *name;
|
||||
const char *value;
|
||||
unsigned int value_len;
|
||||
const char *param;
|
||||
const void *param_data;
|
||||
msre_var_metadata *metadata;
|
||||
msc_regex_t *param_regex;
|
||||
unsigned int is_negated;
|
||||
unsigned int is_counting;
|
||||
};
|
||||
|
||||
|
||||
struct msre_actionset {
|
||||
apr_table_t *actions;
|
||||
|
||||
/* Metadata */
|
||||
const char *id;
|
||||
const char *rev;
|
||||
const char *msg;
|
||||
int severity;
|
||||
int phase;
|
||||
|
||||
/* Flow */
|
||||
int is_chained;
|
||||
int skip_count;
|
||||
|
||||
/* Disruptive */
|
||||
int intercept_action;
|
||||
const char *intercept_uri;
|
||||
int intercept_status;
|
||||
int intercept_pause;
|
||||
|
||||
/* Other */
|
||||
int log;
|
||||
int auditlog;
|
||||
};
|
||||
|
||||
msre_actionset *msre_actionset_create(msre_engine *engine, const char *text,
|
||||
char **error_msg);
|
||||
|
||||
msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent,
|
||||
msre_actionset *child, int inherit_by_default);
|
||||
|
||||
msre_actionset *msre_actionset_create_default(msre_engine *engine);
|
||||
|
||||
void msre_actionset_init(msre_actionset *actionset, msre_rule *rule);
|
||||
|
||||
#define FN_ACTION_VALIDATE(X) char *(*X)(msre_engine *engine, msre_action *action)
|
||||
#define FN_ACTION_INIT(X) apr_status_t (*X)(msre_engine *engine, msre_actionset *actionset, msre_action *action)
|
||||
#define FN_ACTION_EXECUTE(X) apr_status_t (*X)(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, msre_action *action)
|
||||
|
||||
#define ACTION_DISRUPTIVE 1
|
||||
#define ACTION_NON_DISRUPTIVE 2
|
||||
#define ACTION_METADATA 3
|
||||
#define ACTION_FLOW 4
|
||||
|
||||
#define NO_PLUS_MINUS 0
|
||||
#define ALLOW_PLUS_MINUS 1
|
||||
|
||||
#define ACTION_CARDINALITY_ONE 1
|
||||
#define ACTION_CARDINALITY_MANY 2
|
||||
|
||||
struct msre_action_metadata {
|
||||
const char *name;
|
||||
unsigned int type;
|
||||
unsigned int argc_min;
|
||||
unsigned int argc_max;
|
||||
unsigned int allow_param_plusminus;
|
||||
unsigned int cardinality;
|
||||
FN_ACTION_VALIDATE (validate);
|
||||
FN_ACTION_INIT (init);
|
||||
FN_ACTION_EXECUTE (execute);
|
||||
};
|
||||
|
||||
struct msre_action {
|
||||
msre_action_metadata *metadata;
|
||||
const char *param;
|
||||
const void *param_data;
|
||||
unsigned int param_plusminus; /* ABSOLUTE_VALUE, POSITIVE_VALUE, NEGATIVE_VALUE */
|
||||
};
|
||||
|
||||
/* -- MSRE Function Prototypes ---------------------------------------------- */
|
||||
|
||||
msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name);
|
||||
|
||||
int msre_parse_generic(apr_pool_t *pool, const char *text, apr_table_t *vartable,
|
||||
char **error_msg);
|
||||
|
||||
apr_status_t msre_parse_vars(msre_ruleset *ruleset, const char *text,
|
||||
apr_array_header_t *arr, char **error_msg);
|
||||
|
||||
char DSOLOCAL *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset);
|
||||
|
||||
#endif
|
1796
apache2/re_actions.c
Normal file
1796
apache2/re_actions.c
Normal file
File diff suppressed because it is too large
Load Diff
986
apache2/re_operators.c
Normal file
986
apache2/re_operators.c
Normal file
@ -0,0 +1,986 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: re_operators.c,v 1.7 2007/01/23 16:08:15 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "re.h"
|
||||
#include "msc_pcre.h"
|
||||
#include "apr_strmatch.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void msre_engine_op_register(msre_engine *engine, const char *name,
|
||||
FN_OP_PARAM_INIT(fn1), FN_OP_EXECUTE(fn2))
|
||||
{
|
||||
msre_op_metadata *metadata = (msre_op_metadata *)apr_pcalloc(engine->mp,
|
||||
sizeof(msre_op_metadata));
|
||||
if (metadata == NULL) return;
|
||||
|
||||
metadata->name = name;
|
||||
metadata->param_init = fn1;
|
||||
metadata->execute = fn2;
|
||||
apr_table_setn(engine->operators, name, (void *)metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
msre_op_metadata *msre_engine_op_resolve(msre_engine *engine, const char *name) {
|
||||
return (msre_op_metadata *)apr_table_get(engine->operators, name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -- Operators -- */
|
||||
|
||||
/* unconditionalMatch */
|
||||
|
||||
static int msre_op_unconditionalmatch_execute(modsec_rec *msr, msre_rule *rule,
|
||||
msre_var *var, char **error_msg)
|
||||
{
|
||||
*error_msg = "Unconditional match in SecAction.";
|
||||
|
||||
/* Always match. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* rx */
|
||||
|
||||
static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) {
|
||||
const char *errptr = NULL;
|
||||
int erroffset;
|
||||
msc_regex_t *regex;
|
||||
const char *pattern = rule->op_param;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* Compile pattern */
|
||||
regex = msc_pregcomp(rule->ruleset->mp, pattern, 0, &errptr, &erroffset);
|
||||
if (regex == NULL) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (pos %i): %s",
|
||||
erroffset, errptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rule->op_param_data = regex;
|
||||
|
||||
return 1; /* OK */
|
||||
}
|
||||
|
||||
static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
|
||||
msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
|
||||
const char *target;
|
||||
unsigned int target_length;
|
||||
char *my_error_msg = NULL;
|
||||
int ovector[33];
|
||||
int rc;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if (regex == NULL) {
|
||||
*error_msg = "Internal Error: regex data is null.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If the given target is null run against an empty
|
||||
* string. This is a behaviour consistent with previous
|
||||
* releases.
|
||||
*/
|
||||
if (var->value == NULL) {
|
||||
target = "";
|
||||
target_length = 0;
|
||||
} else {
|
||||
target = var->value;
|
||||
target_length = var->value_len;
|
||||
}
|
||||
|
||||
/* IMP1 Can we tell the regex engine not to do any captures if we have no use for them? */
|
||||
rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg);
|
||||
if (rc < -1) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Regex execution failed: %s", my_error_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Handle captured subexpressions. */
|
||||
if (rc > 0) {
|
||||
int capture = 0;
|
||||
const apr_array_header_t *tarr;
|
||||
const apr_table_entry_t *telts;
|
||||
int i;
|
||||
|
||||
/* Are we supposed to store the captured subexpressions? */
|
||||
/* IMP1 Can we use a flag to avoid having to iterate through the list every time. */
|
||||
tarr = apr_table_elts(rule->actionset->actions);
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
for (i = 0; i < tarr->nelts; i++) {
|
||||
msre_action *action = (msre_action *)telts[i].val;
|
||||
if (strcasecmp(action->metadata->name, "capture") == 0) {
|
||||
capture = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (capture) {
|
||||
int k;
|
||||
|
||||
/* Use the available captures. */
|
||||
for(k = 0; k < rc; k++) {
|
||||
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
|
||||
if (s == NULL) return -1;
|
||||
s->name = apr_psprintf(msr->mp, "%i", k);
|
||||
s->value = apr_pstrmemdup(msr->mp,
|
||||
target + ovector[2*k], ovector[2*k + 1] - ovector[2*k]);
|
||||
s->value_len = (ovector[2*k + 1] - ovector[2*k]);
|
||||
if ((s->name == NULL)||(s->value == NULL)) return -1;
|
||||
apr_table_setn(msr->tx_vars, s->name, (void *)s);
|
||||
msr_log(msr, 9, "Adding regex subexpression to TXVARS (%i): %s", k,
|
||||
log_escape_nq(msr->mp, s->value));
|
||||
}
|
||||
|
||||
/* Unset the remaining ones (from previous invocations). */
|
||||
for(k = rc; k <= 9; k++) {
|
||||
char buf[24];
|
||||
apr_snprintf(buf, sizeof(buf), "%i", k);
|
||||
apr_table_unset(msr->tx_vars, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if ( ((rc == PCRE_ERROR_NOMATCH)&&(rule->op_negated == 1))
|
||||
|| ((rc != PCRE_ERROR_NOMATCH)&&(rule->op_negated == 0)) )
|
||||
{
|
||||
*/
|
||||
if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
|
||||
char *pattern_escaped = log_escape(msr->mp, regex->pattern);
|
||||
|
||||
/* This message will be logged. */
|
||||
if (strlen(pattern_escaped) > 252) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Pattern match \"%.252s ...\" at %s.",
|
||||
pattern_escaped, var->name);
|
||||
} else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
|
||||
pattern_escaped, var->name);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* m */
|
||||
|
||||
static int msre_op_m_param_init(msre_rule *rule, char **error_msg) {
|
||||
const apr_strmatch_pattern *compiled_pattern;
|
||||
const char *pattern = rule->op_param;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* Compile pattern */
|
||||
compiled_pattern = apr_strmatch_precompile(rule->ruleset->mp, pattern, 1);
|
||||
if (compiled_pattern == NULL) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern: %s", pattern);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rule->op_param_data = (void *)compiled_pattern;
|
||||
|
||||
return 1; /* OK */
|
||||
}
|
||||
|
||||
static int msre_op_m_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
|
||||
apr_strmatch_pattern *compiled_pattern = (apr_strmatch_pattern *)rule->op_param_data;
|
||||
const char *target;
|
||||
unsigned int target_length;
|
||||
const char *rc;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if (compiled_pattern == NULL) {
|
||||
*error_msg = "Internal Error: strnmatch data is null.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If the given target is null run against an empty
|
||||
* string. This is a behaviour consistent with previous
|
||||
* releases.
|
||||
*/
|
||||
if (var->value == NULL) {
|
||||
target = "";
|
||||
target_length = 0;
|
||||
} else {
|
||||
target = var->value;
|
||||
target_length = var->value_len;
|
||||
}
|
||||
|
||||
rc = apr_strmatch(compiled_pattern, target, target_length);
|
||||
if (rc == NULL) {
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
*error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
|
||||
log_escape(msr->mp, rule->op_param), var->name);
|
||||
|
||||
/* Match. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBXML2
|
||||
|
||||
/* validateDTD */
|
||||
|
||||
static int msre_op_validateDTD_init(msre_rule *rule, char **error_msg) {
|
||||
/* ENH Verify here the file actually exists. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int msre_op_validateDTD_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
xmlValidCtxtPtr cvp;
|
||||
xmlDtdPtr dtd;
|
||||
|
||||
if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML document tree could not be found for "
|
||||
"DTD validation.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dtd = xmlParseDTD(NULL, rule->op_param); /* EHN support relative filenames */
|
||||
if (dtd == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML: Failed to load DTD: %s", rule->op_param);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cvp = xmlNewValidCtxt();
|
||||
if (cvp == NULL) {
|
||||
*error_msg = "XML: Failed to create a validation context.";
|
||||
xmlFreeDtd(dtd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!xmlValidateDtd(cvp, msr->xml->doc, dtd)) {
|
||||
*error_msg = "XML: DTD validation failed.";
|
||||
xmlFreeValidCtxt(cvp);
|
||||
xmlFreeDtd(dtd);
|
||||
return 1; /* No match. */
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "XML: Successfully validated payload against DTD: %s", rule->op_param);
|
||||
|
||||
xmlFreeValidCtxt(cvp);
|
||||
xmlFreeDtd(dtd);
|
||||
|
||||
/* Match. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* validateSchema */
|
||||
|
||||
static int msre_op_validateSchema_init(msre_rule *rule, char **error_msg) {
|
||||
/* ENH Verify here the file actually exists. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
xmlSchemaParserCtxtPtr parserCtx;
|
||||
xmlSchemaValidCtxtPtr validCtx;
|
||||
xmlSchemaPtr schema;
|
||||
int rc;
|
||||
|
||||
if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML document tree could not be found for "
|
||||
"Schema validation.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
parserCtx = xmlSchemaNewParserCtxt(rule->op_param); /* ENH support relative filenames */
|
||||
if (parserCtx == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema from file: %s",
|
||||
rule->op_param);
|
||||
return -1;
|
||||
}
|
||||
|
||||
schema = xmlSchemaParse(parserCtx);
|
||||
if (schema == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema: %s", rule->op_param);
|
||||
xmlSchemaFreeParserCtxt(parserCtx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
validCtx = xmlSchemaNewValidCtxt(schema);
|
||||
if (validCtx == NULL) {
|
||||
*error_msg = "XML: Failed to create validation context.";
|
||||
xmlSchemaFree(schema);
|
||||
xmlSchemaFreeParserCtxt(parserCtx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = xmlSchemaValidateDoc(validCtx, msr->xml->doc);
|
||||
if (rc != 0) {
|
||||
*error_msg = "XML: Schema validation failed.";
|
||||
xmlSchemaFree(schema);
|
||||
xmlSchemaFreeParserCtxt(parserCtx);
|
||||
return 1; /* No match. */
|
||||
}
|
||||
|
||||
msr_log(msr, 4, "XML: Successfully validated payload against Schema: %s", rule->op_param);
|
||||
|
||||
xmlSchemaFree(schema);
|
||||
xmlSchemaFreeValidCtxt(validCtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* rbl */
|
||||
|
||||
static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
|
||||
unsigned int h0, h1, h2, h3;
|
||||
char *name_to_check = NULL;
|
||||
char *target = NULL;
|
||||
apr_sockaddr_t *sa = NULL;
|
||||
apr_status_t rc;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* ENH Add IPv6 support. */
|
||||
|
||||
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
if (target == NULL) return -1;
|
||||
|
||||
/* Construct the host name we want to resolve. */
|
||||
if (sscanf(target, "%d.%d.%d.%d", &h0, &h1, &h2, &h3) == 4) {
|
||||
/* IPv4 address */
|
||||
name_to_check = apr_psprintf(msr->mp, "%i.%i.%i.%i.%s", h3, h2, h1, h0, rule->op_param);
|
||||
} else {
|
||||
/* Assume the input is a domain name. */
|
||||
name_to_check = apr_psprintf(msr->mp, "%s.%s", target, rule->op_param);
|
||||
}
|
||||
|
||||
if (name_to_check == NULL) return -1;
|
||||
|
||||
rc = apr_sockaddr_info_get(&sa, name_to_check,
|
||||
APR_UNSPEC/*msr->r->connection->remote_addr->family*/, 0, 0, msr->mp);
|
||||
if (rc == APR_SUCCESS) {
|
||||
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded.",
|
||||
log_escape_nq(msr->mp, name_to_check));
|
||||
return 1; /* Match. */
|
||||
}
|
||||
|
||||
msr_log(msr, 5, "RBL lookup of %s failed.", log_escape_nq(msr->mp, name_to_check));
|
||||
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* inspectFile */
|
||||
static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
char *script_output = NULL;
|
||||
char const *argv[5];
|
||||
const char *approver_script = rule->op_param;
|
||||
const char *target_file = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file);
|
||||
|
||||
argv[0] = approver_script;
|
||||
argv[1] = target_file;
|
||||
argv[2] = NULL;
|
||||
|
||||
if (apache2_exec(msr, approver_script, (const char **)argv, &script_output) <= 0) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (invocation failed).",
|
||||
log_escape(msr->mp, approver_script));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (script_output == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).",
|
||||
log_escape(msr->mp, approver_script));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (script_output[0] != '1') {
|
||||
*error_msg = apr_psprintf(msr->mp, "File \"%s\" rejected by the approver script \"%s\": %s",
|
||||
log_escape(msr->mp, target_file), log_escape(msr->mp, approver_script),
|
||||
log_escape_nq(msr->mp, script_output));
|
||||
return 1; /* Match. */
|
||||
}
|
||||
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* validateByteRange */
|
||||
|
||||
static int msre_op_validateByteRange_init(msre_rule *rule, char **error_msg) {
|
||||
char *p = NULL, *saveptr = NULL;
|
||||
char *table = NULL, *data = NULL;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if (rule->op_param == NULL) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for validateByteRange.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Initialise. */
|
||||
data = apr_pstrdup(rule->ruleset->mp, rule->op_param);
|
||||
rule->op_param_data = apr_pcalloc(rule->ruleset->mp, 32);
|
||||
if ((data == NULL)||(rule->op_param_data == NULL)) return -1;
|
||||
table = rule->op_param_data;
|
||||
|
||||
/* Extract parameters and update table. */
|
||||
p = apr_strtok(data, ",", &saveptr);
|
||||
while(p != NULL) {
|
||||
char *s = strstr(p, "-");
|
||||
if (s == NULL) {
|
||||
/* Single value. */
|
||||
int x = atoi(p);
|
||||
if ((x < 0)||(x > 255)) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range value: %i", x);
|
||||
return 0;
|
||||
}
|
||||
table[x>>3] = (table[x>>3] | (1 << (x & 0x7)));
|
||||
} else {
|
||||
/* Range. */
|
||||
int start = atoi(p);
|
||||
int end = atoi(s + 1);
|
||||
|
||||
if ((start < 0)||(start > 255)) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range start value: %i",
|
||||
start);
|
||||
return 0;
|
||||
}
|
||||
if ((end < 0)||(end > 255)) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %i", end);
|
||||
return 0;
|
||||
}
|
||||
if (start > end) {
|
||||
*error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %i-%i", start, end);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(start <= end) {
|
||||
table[start >> 3] = (table[start >> 3] | (1 << (start & 0x7)));
|
||||
start++;
|
||||
}
|
||||
}
|
||||
|
||||
p = apr_strtok(NULL, ",", &saveptr);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int msre_op_validateByteRange_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
char *table = rule->op_param_data;
|
||||
unsigned int i, count;
|
||||
|
||||
if (error_msg == NULL) return -1;
|
||||
*error_msg = NULL;
|
||||
|
||||
if (table == NULL) {
|
||||
*error_msg = apr_psprintf(msr->mp, "Internal Error: validateByteRange table not "
|
||||
"initialised.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check every byte of the target to detect characters that are not allowed. */
|
||||
|
||||
count = 0;
|
||||
for(i = 0; i < var->value_len; i++) {
|
||||
int x = ((unsigned char *)var->value)[i];
|
||||
if (!(table[x >> 3] & (1 << (x & 0x7)))) {
|
||||
msr_log(msr, 9, "Value %i outside range: %s", x, rule->op_param);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return 0; /* Valid - no match. */
|
||||
|
||||
*error_msg = apr_psprintf(msr->mp, "Found %i byte(s) outside range: %s.",
|
||||
count, rule->op_param);
|
||||
|
||||
return 1; /* Invalid - match.*/
|
||||
}
|
||||
|
||||
/* validateUrlEncoding */
|
||||
|
||||
static int validate_url_encoding(const char *input, long int input_length) {
|
||||
int i;
|
||||
|
||||
if ((input == NULL)||(input_length < 0)) return -1;
|
||||
|
||||
i = 0;
|
||||
while (i < input_length) {
|
||||
if (input[i] == '%') {
|
||||
if (i + 2 >= input_length) {
|
||||
/* Not enough bytes. */
|
||||
return -3;
|
||||
}
|
||||
else {
|
||||
/* Here we only decode a %xx combination if it is valid,
|
||||
* leaving it as is otherwise.
|
||||
*/
|
||||
char c1 = input[i + 1];
|
||||
char c2 = input[i + 2];
|
||||
|
||||
if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F')))
|
||||
&& (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) )
|
||||
{
|
||||
i += 3;
|
||||
} else {
|
||||
/* Non-hexadecimal characters used in encoding. */
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int msre_op_validateUrlEncoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
int rc = validate_url_encoding(var->value, var->value_len);
|
||||
switch(rc) {
|
||||
case 1 :
|
||||
return 0; /* Encoding is valid, no match. */
|
||||
break;
|
||||
case -2 :
|
||||
*error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Non-hexadecimal "
|
||||
"digits used.");
|
||||
return 1; /* Invalid, match. */
|
||||
break;
|
||||
case -3 :
|
||||
*error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Not enough characters "
|
||||
"at the end of input.");
|
||||
return 1; /* Invalid, match. */
|
||||
break;
|
||||
case -1 :
|
||||
default :
|
||||
*error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %i)", rc);
|
||||
return -1;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* validateUtf8Encoding */
|
||||
|
||||
#define UNICODE_ERROR_CHARACTERS_MISSING -1
|
||||
#define UNICODE_ERROR_INVALID_ENCODING -2
|
||||
#define UNICODE_ERROR_OVERLONG_CHARACTER -3
|
||||
|
||||
static int detect_utf8_character(const char *p_read, unsigned int length) {
|
||||
int unicode_len = 0;
|
||||
unsigned int d = 0;
|
||||
unsigned char c;
|
||||
|
||||
if (p_read == NULL) return 0;
|
||||
c = *p_read;
|
||||
if (c == 0) return 0;
|
||||
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
/* two byte unicode */
|
||||
if (length < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
|
||||
else
|
||||
if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else {
|
||||
unicode_len = 2;
|
||||
d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F);
|
||||
}
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0) {
|
||||
/* three byte unicode */
|
||||
if (length < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
|
||||
else
|
||||
if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else {
|
||||
unicode_len = 3;
|
||||
d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F);
|
||||
}
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0) {
|
||||
/* four byte unicode */
|
||||
if (length < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
|
||||
else
|
||||
if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else {
|
||||
d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F);
|
||||
unicode_len = 4;
|
||||
}
|
||||
}
|
||||
else if ((c & 0xFC) == 0xF8) {
|
||||
/* five byte unicode */
|
||||
if (length < 5) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
|
||||
else
|
||||
if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else {
|
||||
d = ((c & 0x03) << 24) | ((*(p_read + 1) & 0x3F) << 18) | ((*(p_read + 2) & 0x3F) << 12) | ((*(p_read + 3) & 0x3F) << 6) | (*(p_read + 4) & 0x3F);
|
||||
unicode_len = 5;
|
||||
}
|
||||
}
|
||||
else if ((c & 0xFE) == 0xFC) {
|
||||
/* six byte unicode */
|
||||
if (length < 6) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
|
||||
else
|
||||
if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else
|
||||
if (((*(p_read + 5)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
|
||||
else {
|
||||
d = ((c & 0x01) << 30) | ((*(p_read + 1) & 0x3F) << 24) | ((*(p_read + 2) & 0x3F) << 18) | ((*(p_read + 3) & 0x3F) << 12) | ((*(p_read + 4) & 0x3F) << 6) | (*(p_read + 5) & 0x3F);
|
||||
unicode_len = 6;
|
||||
}
|
||||
}
|
||||
|
||||
if ((unicode_len > 1)&&((d & 0x7F) == d)) {
|
||||
unicode_len = UNICODE_ERROR_OVERLONG_CHARACTER;
|
||||
}
|
||||
|
||||
return unicode_len;
|
||||
}
|
||||
|
||||
static int msre_op_validateUtf8Encoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
unsigned int i, bytes_left;
|
||||
|
||||
bytes_left = var->value_len;
|
||||
for(i = 0; i < var->value_len; i++) {
|
||||
int rc = detect_utf8_character(&var->value[i], bytes_left);
|
||||
switch(rc) {
|
||||
case UNICODE_ERROR_CHARACTERS_MISSING :
|
||||
*error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: not enough bytes in "
|
||||
"character.");
|
||||
return 1;
|
||||
break;
|
||||
case UNICODE_ERROR_INVALID_ENCODING :
|
||||
*error_msg = apr_psprintf(msr->mp, "Invalid Unicode encoding: invalid byte value "
|
||||
"in character.");
|
||||
return 1;
|
||||
break;
|
||||
case UNICODE_ERROR_OVERLONG_CHARACTER :
|
||||
*error_msg = apr_psprintf(msr->mp, "Invalid Unicode encoding: overlong "
|
||||
"character detected.");
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_left--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* eq */
|
||||
|
||||
static int msre_op_eq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
int left, right;
|
||||
char *target = NULL;
|
||||
|
||||
if ((var->value == NULL)||(rule->op_param == NULL)) {
|
||||
/* NULL values do not match anything. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
if (target == NULL) return -1;
|
||||
left = atoi(target);
|
||||
right = atoi(rule->op_param);
|
||||
|
||||
if (left != right) {
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Operator EQ match: %i.", right);
|
||||
/* Match. */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* gt */
|
||||
|
||||
static int msre_op_gt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
int left, right;
|
||||
char *target = NULL;
|
||||
|
||||
if ((var->value == NULL)||(rule->op_param == NULL)) {
|
||||
/* NULL values do not match anything. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
if (target == NULL) return -1;
|
||||
left = atoi(target);
|
||||
right = atoi(rule->op_param);
|
||||
|
||||
if (left <= right) {
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Operator GT match: %i.", right);
|
||||
/* Match. */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* lt */
|
||||
|
||||
static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
int left, right;
|
||||
char *target = NULL;
|
||||
|
||||
if ((var->value == NULL)||(rule->op_param == NULL)) {
|
||||
/* NULL values do not match anything. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
if (target == NULL) return -1;
|
||||
left = atoi(target);
|
||||
right = atoi(rule->op_param);
|
||||
|
||||
if (left >= right) {
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Operator LT match: %i.", right);
|
||||
/* Match. */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ge */
|
||||
|
||||
static int msre_op_ge_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
int left, right;
|
||||
char *target = NULL;
|
||||
|
||||
if ((var->value == NULL)||(rule->op_param == NULL)) {
|
||||
/* NULL values do not match anything. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
if (target == NULL) return -1;
|
||||
left = atoi(target);
|
||||
right = atoi(rule->op_param);
|
||||
|
||||
if (left < right) {
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Operator GE match: %i.", right);
|
||||
/* Match. */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* le */
|
||||
|
||||
static int msre_op_le_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
|
||||
char **error_msg)
|
||||
{
|
||||
int left, right;
|
||||
char *target = NULL;
|
||||
|
||||
if ((var->value == NULL)||(rule->op_param == NULL)) {
|
||||
/* NULL values do not match anything. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
|
||||
if (target == NULL) return -1;
|
||||
left = atoi(target);
|
||||
right = atoi(rule->op_param);
|
||||
|
||||
if (left > right) {
|
||||
/* No match. */
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*error_msg = apr_psprintf(msr->mp, "Operator LE match: %i.", right);
|
||||
/* Match. */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void msre_engine_register_default_operators(msre_engine *engine) {
|
||||
/* unconditionalMatch */
|
||||
msre_engine_op_register(engine,
|
||||
"unconditionalMatch",
|
||||
NULL,
|
||||
msre_op_unconditionalmatch_execute
|
||||
);
|
||||
|
||||
/* rx */
|
||||
msre_engine_op_register(engine,
|
||||
"rx",
|
||||
msre_op_rx_param_init,
|
||||
msre_op_rx_execute
|
||||
);
|
||||
|
||||
/* m */
|
||||
msre_engine_op_register(engine,
|
||||
"m",
|
||||
msre_op_m_param_init,
|
||||
msre_op_m_execute
|
||||
);
|
||||
|
||||
#ifdef WITH_LIBXML2
|
||||
|
||||
/* validateDTD */
|
||||
msre_engine_op_register(engine,
|
||||
"validateDTD",
|
||||
msre_op_validateDTD_init,
|
||||
msre_op_validateDTD_execute
|
||||
);
|
||||
|
||||
/* validateSchema */
|
||||
msre_engine_op_register(engine,
|
||||
"validateSchema",
|
||||
msre_op_validateSchema_init,
|
||||
msre_op_validateSchema_execute
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
/* rbl */
|
||||
msre_engine_op_register(engine,
|
||||
"rbl",
|
||||
NULL, /* ENH init function to validate DNS server */
|
||||
msre_op_rbl_execute
|
||||
);
|
||||
|
||||
/* inspectFile */
|
||||
msre_engine_op_register(engine,
|
||||
"inspectFile",
|
||||
NULL,
|
||||
msre_op_inspectFile_execute
|
||||
);
|
||||
|
||||
/* validateByteRange */
|
||||
msre_engine_op_register(engine,
|
||||
"validateByteRange",
|
||||
msre_op_validateByteRange_init,
|
||||
msre_op_validateByteRange_execute
|
||||
);
|
||||
|
||||
/* validateUrlEncoding */
|
||||
msre_engine_op_register(engine,
|
||||
"validateUrlEncoding",
|
||||
NULL,
|
||||
msre_op_validateUrlEncoding_execute
|
||||
);
|
||||
|
||||
/* validateUtf8Encoding */
|
||||
msre_engine_op_register(engine,
|
||||
"validateUtf8Encoding",
|
||||
NULL,
|
||||
msre_op_validateUtf8Encoding_execute
|
||||
);
|
||||
|
||||
/* eq */
|
||||
msre_engine_op_register(engine,
|
||||
"eq",
|
||||
NULL,
|
||||
msre_op_eq_execute
|
||||
);
|
||||
|
||||
/* gt */
|
||||
msre_engine_op_register(engine,
|
||||
"gt",
|
||||
NULL,
|
||||
msre_op_gt_execute
|
||||
);
|
||||
|
||||
/* lt */
|
||||
msre_engine_op_register(engine,
|
||||
"lt",
|
||||
NULL,
|
||||
msre_op_lt_execute
|
||||
);
|
||||
|
||||
/* le */
|
||||
msre_engine_op_register(engine,
|
||||
"le",
|
||||
NULL,
|
||||
msre_op_le_execute
|
||||
);
|
||||
|
||||
/* ge */
|
||||
msre_engine_op_register(engine,
|
||||
"ge",
|
||||
NULL,
|
||||
msre_op_ge_execute
|
||||
);
|
||||
}
|
512
apache2/re_tfns.c
Normal file
512
apache2/re_tfns.c
Normal file
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: re_tfns.c,v 1.3 2006/12/04 12:00:24 ivanr Exp $
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include <ctype.h>
|
||||
|
||||
#include "apr_md5.h"
|
||||
#include "apr_sha1.h"
|
||||
#include "apr_base64.h"
|
||||
|
||||
#include "re.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
/* lowercase */
|
||||
|
||||
static int msre_fn_lowercase_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int i;
|
||||
int changed = 0;
|
||||
|
||||
if (rval == NULL) return -1;
|
||||
*rval = NULL;
|
||||
|
||||
i = 0;
|
||||
while(i < input_len) {
|
||||
int x = input[i];
|
||||
input[i] = tolower(x);
|
||||
if (x != input[i]) changed = 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
*rval = (char *)input;
|
||||
*rval_len = input_len;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* removeNulls */
|
||||
|
||||
static int msre_fn_removeNulls_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int i, j;
|
||||
int changed = 0;
|
||||
|
||||
i = j = 0;
|
||||
while(i < input_len) {
|
||||
if (input[i] == '\0') {
|
||||
changed = 1;
|
||||
} else {
|
||||
input[j] = input[i];
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
*rval = (char *)input;
|
||||
*rval_len = j;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* replaceNulls */
|
||||
|
||||
static int msre_fn_replaceNulls_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int i;
|
||||
int changed = 0;
|
||||
|
||||
if (rval == NULL) return -1;
|
||||
*rval = NULL;
|
||||
|
||||
i = 0;
|
||||
while(i < input_len) {
|
||||
if (input[i] == '\0') {
|
||||
changed = 1;
|
||||
input[i] = ' ';
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
*rval = (char *)input;
|
||||
*rval_len = input_len;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* compressWhitespace */
|
||||
|
||||
static int msre_fn_compressWhitespace_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int i, j, count;
|
||||
int changed = 0;
|
||||
|
||||
i = j = count = 0;
|
||||
while(i < input_len) {
|
||||
if (isspace(input[i])||(input[i] == NBSP)) {
|
||||
changed = 1;
|
||||
count++;
|
||||
} else {
|
||||
if (count) {
|
||||
input[j] = ' ';
|
||||
count = 0;
|
||||
j++;
|
||||
}
|
||||
input[j] = input[i];
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (count) {
|
||||
input[j] = ' ';
|
||||
j++;
|
||||
}
|
||||
|
||||
*rval = (char *)input;
|
||||
*rval_len = j;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* removeWhitespace */
|
||||
|
||||
static int msre_fn_removeWhitespace_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int i, j;
|
||||
int changed = 0;
|
||||
|
||||
i = j = 0;
|
||||
while(i < input_len) {
|
||||
if (isspace(input[i])||(input[i] == NBSP)) {
|
||||
/* do nothing */
|
||||
changed = 1;
|
||||
} else {
|
||||
input[j] = input[i];
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
*rval = (char *)input;
|
||||
*rval_len = j;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* replaceComments */
|
||||
|
||||
static int msre_fn_replaceComments_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int i, j, incomment;
|
||||
int changed = 0;
|
||||
|
||||
i = j = incomment = 0;
|
||||
while(i < input_len) {
|
||||
if (incomment == 0) {
|
||||
if ((input[i] == '/')&&(i + 1 < input_len)&&(input[i + 1] == '*')) {
|
||||
changed = 1;
|
||||
incomment = 1;
|
||||
i += 2;
|
||||
} else {
|
||||
input[j] = input[i];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
} else {
|
||||
if ((input[i] == '*')&&(i + 1 < input_len)&&(input[i + 1] == '/')) {
|
||||
incomment = 0;
|
||||
i += 2;
|
||||
input[j] = ' ';
|
||||
j++;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*rval = (char *)input;
|
||||
*rval_len = j;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* urlDecode */
|
||||
|
||||
static int msre_fn_urlDecode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int length;
|
||||
int invalid_count;
|
||||
|
||||
length = urldecode_nonstrict_inplace_ex(input, input_len, &invalid_count);
|
||||
*rval = (char *)input;
|
||||
*rval_len = length;
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* urlDecodeUni */
|
||||
|
||||
static int msre_fn_urlDecodeUni_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
long int length;
|
||||
|
||||
length = urldecode_uni_nonstrict_inplace_ex(input, input_len);
|
||||
*rval = (char *)input;
|
||||
*rval_len = length;
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* urlEncode */
|
||||
|
||||
static int msre_fn_urlEncode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval = url_encode(mptmp, input, input_len);
|
||||
*rval_len = strlen(*rval);
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* base64Encode */
|
||||
|
||||
static int msre_fn_base64Encode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = apr_base64_encode_len(input_len); /* returns len with NULL byte included */
|
||||
*rval = apr_palloc(mptmp, *rval_len);
|
||||
apr_base64_encode(*rval, input, input_len);
|
||||
(*rval_len)--;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* base64Decode */
|
||||
|
||||
static int msre_fn_base64Decode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = apr_base64_decode_len(input); /* returns len with NULL byte included */
|
||||
*rval = apr_palloc(mptmp, *rval_len);
|
||||
apr_base64_decode(*rval, input);
|
||||
(*rval_len)--;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* md5 */
|
||||
|
||||
static int msre_fn_md5_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
unsigned char digest[APR_MD5_DIGESTSIZE];
|
||||
|
||||
apr_md5(digest, input, input_len);
|
||||
|
||||
*rval_len = APR_MD5_DIGESTSIZE;
|
||||
*rval = apr_pstrmemdup(mptmp, digest, APR_MD5_DIGESTSIZE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* sha1 */
|
||||
|
||||
static int msre_fn_sha1_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
unsigned char digest[APR_SHA1_DIGESTSIZE];
|
||||
apr_sha1_ctx_t context;
|
||||
|
||||
apr_sha1_init(&context);
|
||||
apr_sha1_update(&context, input, input_len);
|
||||
apr_sha1_final(digest, &context);
|
||||
|
||||
*rval_len = APR_SHA1_DIGESTSIZE;
|
||||
*rval = apr_pstrmemdup(mptmp, digest, APR_SHA1_DIGESTSIZE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* hexDecode */
|
||||
|
||||
static int msre_fn_hexDecode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = hex2bytes_inplace(input, input_len);
|
||||
*rval = input;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* hexEncode */
|
||||
|
||||
static int msre_fn_hexEncode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval = bytes2hex(mptmp, input, input_len);
|
||||
*rval_len = strlen(*rval);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* htmlEntityDecode */
|
||||
|
||||
static int msre_fn_htmlEntityDecode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = html_entities_decode_inplace(mptmp, input, input_len);
|
||||
*rval = input;
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* escapeSeqDecode */
|
||||
|
||||
static int msre_fn_escapeSeqDecode_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = ansi_c_sequences_decode_inplace(input, input_len);
|
||||
*rval = input;
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* normalisePath */
|
||||
|
||||
static int msre_fn_normalisePath_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = normalise_path_inplace(input, input_len, 0);
|
||||
*rval = input;
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* normalisePathWin */
|
||||
|
||||
static int msre_fn_normalisePathWin_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = normalise_path_inplace(input, input_len, 1);
|
||||
*rval = input;
|
||||
|
||||
return (*rval_len == input_len ? 0 : 1);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Registers one transformation function with the engine.
|
||||
*/
|
||||
void msre_engine_tfn_register(msre_engine *engine, const char *name,
|
||||
FN_TFN_EXECUTE(execute))
|
||||
{
|
||||
msre_tfn_metadata *metadata = (msre_tfn_metadata *)apr_pcalloc(engine->mp,
|
||||
sizeof(msre_tfn_metadata));
|
||||
if (metadata == NULL) return;
|
||||
|
||||
metadata->name = name;
|
||||
metadata->execute = execute;
|
||||
|
||||
apr_table_setn(engine->tfns, name, (void *)metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns transformation function metadata given a name.
|
||||
*/
|
||||
msre_tfn_metadata *msre_engine_tfn_resolve(msre_engine *engine, const char *name) {
|
||||
return (msre_tfn_metadata *)apr_table_get(engine->tfns, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the default transformation functions.
|
||||
*/
|
||||
void msre_engine_register_default_tfns(msre_engine *engine) {
|
||||
|
||||
/* none */
|
||||
msre_engine_tfn_register(engine,
|
||||
"none",
|
||||
NULL
|
||||
);
|
||||
|
||||
/* base64Decode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"base64Decode",
|
||||
msre_fn_base64Decode_execute
|
||||
);
|
||||
|
||||
/* base64Encode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"base64Encode",
|
||||
msre_fn_base64Encode_execute
|
||||
);
|
||||
|
||||
/* compressWhitespace */
|
||||
msre_engine_tfn_register(engine,
|
||||
"compressWhitespace",
|
||||
msre_fn_compressWhitespace_execute
|
||||
);
|
||||
|
||||
/* escapeSeqDecode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"escapeSeqDecode",
|
||||
msre_fn_escapeSeqDecode_execute
|
||||
);
|
||||
|
||||
/* hexDecode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"hexDecode",
|
||||
msre_fn_hexDecode_execute
|
||||
);
|
||||
|
||||
/* hexEncode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"hexEncode",
|
||||
msre_fn_hexEncode_execute
|
||||
);
|
||||
|
||||
/* htmlEntityDecode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"htmlEntityDecode",
|
||||
msre_fn_htmlEntityDecode_execute
|
||||
);
|
||||
|
||||
/* lowercase */
|
||||
msre_engine_tfn_register(engine,
|
||||
"lowercase",
|
||||
msre_fn_lowercase_execute
|
||||
);
|
||||
|
||||
/* md5 */
|
||||
msre_engine_tfn_register(engine,
|
||||
"md5",
|
||||
msre_fn_md5_execute
|
||||
);
|
||||
|
||||
/* normalisePath */
|
||||
msre_engine_tfn_register(engine,
|
||||
"normalisePath",
|
||||
msre_fn_normalisePath_execute
|
||||
);
|
||||
|
||||
/* normalisePathWin */
|
||||
msre_engine_tfn_register(engine,
|
||||
"normalisePathWin",
|
||||
msre_fn_normalisePathWin_execute
|
||||
);
|
||||
|
||||
/* removeWhitespace */
|
||||
msre_engine_tfn_register(engine,
|
||||
"removeWhitespace",
|
||||
msre_fn_removeWhitespace_execute
|
||||
);
|
||||
|
||||
/* removeNulls */
|
||||
msre_engine_tfn_register(engine,
|
||||
"removeNulls",
|
||||
msre_fn_removeNulls_execute
|
||||
);
|
||||
|
||||
/* replaceNulls */
|
||||
msre_engine_tfn_register(engine,
|
||||
"replaceNulls",
|
||||
msre_fn_replaceNulls_execute
|
||||
);
|
||||
|
||||
/* replaceComments */
|
||||
msre_engine_tfn_register(engine,
|
||||
"replaceComments",
|
||||
msre_fn_replaceComments_execute
|
||||
);
|
||||
|
||||
/* sha1 */
|
||||
msre_engine_tfn_register(engine,
|
||||
"sha1",
|
||||
msre_fn_sha1_execute
|
||||
);
|
||||
|
||||
/* urlDecode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"urlDecode",
|
||||
msre_fn_urlDecode_execute
|
||||
);
|
||||
|
||||
/* urlDecodeUni */
|
||||
msre_engine_tfn_register(engine,
|
||||
"urlDecodeUni",
|
||||
msre_fn_urlDecodeUni_execute
|
||||
);
|
||||
|
||||
/* urlEncode */
|
||||
msre_engine_tfn_register(engine,
|
||||
"urlEncode",
|
||||
msre_fn_urlEncode_execute
|
||||
);
|
||||
}
|
2443
apache2/re_variables.c
Normal file
2443
apache2/re_variables.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
doc/apache_request_cycle-modsecurity.jpg
Normal file
BIN
doc/apache_request_cycle-modsecurity.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
BIN
doc/breach-logo-small.gif
Normal file
BIN
doc/breach-logo-small.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
292
doc/html-chunked.xsl
Normal file
292
doc/html-chunked.xsl
Normal file
@ -0,0 +1,292 @@
|
||||
<?xml version='1.0'?>
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||
|
||||
<xsl:import href="/opt/docbook/xsl/html/chunk.xsl"/>
|
||||
|
||||
<xsl:param name="chunk.first.sections" select="1"/>
|
||||
<xsl:param name="use.id.as.filename" select="1"/>
|
||||
<xsl:param name="html.stylesheet">modsecurity-reference.css</xsl:param>
|
||||
|
||||
<xsl:template name="user.header.navigation">
|
||||
<div style="background:#F5F5F5;width:100%;border-top:1px solid #DDDDDD;border-bottom:1px solid #DDDDDD"><table cellpadding="0" cellspacing="0" width="100%"><tr><td><a href="http://www.modsecurity.org"><img border="0" alt="ModSecurity" height="36" width="120" src="modsecurity.gif" style="margin:4px"/></a></td><td align="right"><a href="http://www.breach.com"><img border="0" width="100" height="36" src="breach-logo-small.gif" style="margin:6px"/></a></td></tr></table></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="user.footer.navigation">
|
||||
<div class="copyright" align="center">Copyright (C) 2004-2006 <a href="http://www.breach.com">Breach Security</a></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="article.titlepage.separator">
|
||||
<hr size="1"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="header.navigation">
|
||||
<xsl:param name="prev" select="/foo"/>
|
||||
<xsl:param name="next" select="/foo"/>
|
||||
<xsl:param name="nav.context"/>
|
||||
|
||||
<xsl:variable name="home" select="/*[1]"/>
|
||||
<xsl:variable name="up" select="parent::*"/>
|
||||
|
||||
<xsl:variable name="row1" select="$navig.showtitles != 0"/>
|
||||
<xsl:variable name="row2" select="count($prev) > 0
|
||||
or (count($up) > 0
|
||||
and generate-id($up) != generate-id($home)
|
||||
and $navig.showtitles != 0)
|
||||
or count($next) > 0"/>
|
||||
|
||||
<xsl:if test="$suppress.navigation = '0' and $suppress.header.navigation = '0'">
|
||||
<div id="navheader">
|
||||
<xsl:if test="$row1 or $row2">
|
||||
<table width="100%" summary="Navigation header">
|
||||
<xsl:if test="$row1">
|
||||
<tr>
|
||||
<th colspan="3" align="center">
|
||||
<xsl:apply-templates select="." mode="object.title.markup"/>
|
||||
</th>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$row2">
|
||||
<tr>
|
||||
<td width="20%" align="left">
|
||||
<xsl:if test="count($prev)>0">
|
||||
<a accesskey="p">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$prev"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'prev'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
</xsl:if>
|
||||
<xsl:text> </xsl:text>
|
||||
</td>
|
||||
<td width="60%" align="center">
|
||||
<xsl:choose>
|
||||
<xsl:when test="count($up) > 0
|
||||
and generate-id($up) != generate-id($home)
|
||||
and $navig.showtitles != 0">
|
||||
<xsl:apply-templates select="$up" mode="object.title.markup"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise> </xsl:otherwise>
|
||||
</xsl:choose>
|
||||
|
||||
<xsl:choose>
|
||||
<xsl:when test="$home != . or $nav.context = 'toc'">
|
||||
<a accesskey="h">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$home"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'home'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
<xsl:if test="$chunk.tocs.and.lots != 0 and $nav.context != 'toc'">
|
||||
<xsl:text> | </xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:when>
|
||||
<xsl:otherwise> </xsl:otherwise>
|
||||
</xsl:choose>
|
||||
|
||||
</td>
|
||||
<td width="20%" align="right">
|
||||
<xsl:text> </xsl:text>
|
||||
<xsl:if test="count($next)>0">
|
||||
<a accesskey="n">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$next"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'next'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
</xsl:if>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
</table>
|
||||
</xsl:if>
|
||||
<xsl:if test="$header.rule != 0">
|
||||
<hr size="1"/>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
|
||||
<xsl:template name="footer.navigation">
|
||||
<xsl:param name="prev" select="/foo"/>
|
||||
<xsl:param name="next" select="/foo"/>
|
||||
<xsl:param name="nav.context"/>
|
||||
|
||||
<xsl:variable name="home" select="/*[1]"/>
|
||||
<xsl:variable name="up" select="parent::*"/>
|
||||
|
||||
<xsl:variable name="row1" select="count($prev) > 0
|
||||
or count($up) > 0
|
||||
or count($next) > 0"/>
|
||||
|
||||
<xsl:variable name="row2" select="($prev and $navig.showtitles != 0)
|
||||
or (generate-id($home) != generate-id(.)
|
||||
or $nav.context = 'toc')
|
||||
or ($chunk.tocs.and.lots != 0
|
||||
and $nav.context != 'toc')
|
||||
or ($next and $navig.showtitles != 0)"/>
|
||||
|
||||
<xsl:if test="$suppress.navigation = '0' and $suppress.footer.navigation = '0'">
|
||||
<div id="navfooter">
|
||||
<xsl:if test="$footer.rule != 0">
|
||||
<hr size="1"/>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$row1 or $row2">
|
||||
<table width="100%" summary="Navigation footer">
|
||||
<xsl:if test="$row1">
|
||||
<tr>
|
||||
<td width="40%" align="left">
|
||||
<xsl:if test="count($prev)>0">
|
||||
<a accesskey="p">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$prev"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'prev'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
</xsl:if>
|
||||
<xsl:text> </xsl:text>
|
||||
</td>
|
||||
<td width="20%" align="center">
|
||||
<xsl:choose>
|
||||
<xsl:when test="count($up)>0
|
||||
and generate-id($up) != generate-id($home)">
|
||||
<a accesskey="u">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$up"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'up'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
</xsl:when>
|
||||
<xsl:otherwise> </xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</td>
|
||||
<td width="40%" align="right">
|
||||
<xsl:text> </xsl:text>
|
||||
<xsl:if test="count($next)>0">
|
||||
<a accesskey="n">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$next"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'next'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
</xsl:if>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$row2">
|
||||
<tr>
|
||||
<td width="40%" align="left" valign="top">
|
||||
<xsl:if test="$navig.showtitles != 0">
|
||||
<xsl:apply-templates select="$prev" mode="object.title.markup"/>
|
||||
</xsl:if>
|
||||
<xsl:text> </xsl:text>
|
||||
</td>
|
||||
<td width="20%" align="center">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$home != . or $nav.context = 'toc'">
|
||||
<a accesskey="h">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:call-template name="href.target">
|
||||
<xsl:with-param name="object" select="$home"/>
|
||||
</xsl:call-template>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="navig.content">
|
||||
<xsl:with-param name="direction" select="'home'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
<xsl:if test="$chunk.tocs.and.lots != 0 and $nav.context != 'toc'">
|
||||
<xsl:text> | </xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:when>
|
||||
<xsl:otherwise> </xsl:otherwise>
|
||||
</xsl:choose>
|
||||
|
||||
<xsl:if test="$chunk.tocs.and.lots != 0 and $nav.context != 'toc'">
|
||||
<a accesskey="t">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:apply-templates select="/*[1]"
|
||||
mode="recursive-chunk-filename">
|
||||
<xsl:with-param name="recursive" select="true()"/>
|
||||
</xsl:apply-templates>
|
||||
<xsl:text>-toc</xsl:text>
|
||||
<xsl:value-of select="$html.ext"/>
|
||||
</xsl:attribute>
|
||||
<xsl:call-template name="gentext">
|
||||
<xsl:with-param name="key" select="'nav-toc'"/>
|
||||
</xsl:call-template>
|
||||
</a>
|
||||
</xsl:if>
|
||||
</td>
|
||||
<td width="40%" align="right" valign="top">
|
||||
<xsl:text> </xsl:text>
|
||||
<xsl:if test="$navig.showtitles != 0">
|
||||
<xsl:apply-templates select="$next" mode="object.title.markup"/>
|
||||
</xsl:if>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
</table>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<!--
|
||||
<xsl:template name="nongraphical.admonition">
|
||||
<xsl:variable name="id">
|
||||
<xsl:call-template name="object.id"/>
|
||||
</xsl:variable>
|
||||
|
||||
<fo:block space-before.minimum="0.8em"
|
||||
space-before.optimum="1em"
|
||||
space-before.maximum="1.2em"
|
||||
start-indent="0.25in"
|
||||
end-indent="0.25in"
|
||||
border-top="0.5pt solid black"
|
||||
border-bottom="0.5pt solid black"
|
||||
padding-top="4pt"
|
||||
padding-bottom="4pt"
|
||||
id="{$id}">
|
||||
<xsl:if test="$admon.textlabel != 0 or title">
|
||||
<fo:block keep-with-next='always'
|
||||
xsl:use-attribute-sets="admonition.title.properties">
|
||||
<xsl:apply-templates select="." mode="object.title.markup"/>
|
||||
</fo:block>
|
||||
</xsl:if>
|
||||
|
||||
<fo:block xsl:use-attribute-sets="admonition.properties">
|
||||
<xsl:apply-templates/>
|
||||
</fo:block>
|
||||
</fo:block>
|
||||
</xsl:template>
|
||||
-->
|
||||
|
||||
</xsl:stylesheet>
|
20
doc/html.xsl
Normal file
20
doc/html.xsl
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version='1.0'?>
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||
|
||||
<xsl:import href="/opt/docbook/xsl/html/onechunk.xsl"/>
|
||||
|
||||
<xsl:template name="user.header.navigation">
|
||||
<div style="background:#F5F5F5;width:100%;border-top:1px solid #DDDDDD;border-bottom:1px solid #DDDDDD"><table cellpadding="0" cellspacing="0" width="100%"><tr><td><a href="http://www.modsecurity.org"><img border="0" alt="ModSecurity" height="36" width="120" src="modsecurity.gif" style="margin:4px"/></a></td><td align="right"><a href="http://www.breach.com"><img border="0" width="100" height="36" src="breach-logo-small.gif" style="margin:6px"/></a></td></tr></table></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="user.footer.navigation">
|
||||
<div class="copyright" align="center">Copyright (C) 2004-2006 <a href="http://www.breach.com">Breach Security</a></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="article.titlepage.separator">
|
||||
<hr size="1"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:param name="html.stylesheet">modsecurity-reference.css</xsl:param>
|
||||
|
||||
</xsl:stylesheet>
|
35
doc/index.html
Normal file
35
doc/index.html
Normal file
@ -0,0 +1,35 @@
|
||||
<html>
|
||||
<head>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>ModSecurity for Apache Reference</title>
|
||||
<link href="modsecurity-reference.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
|
||||
<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
|
||||
<div style="background:#F5F5F5;width:100%;border-top:1px solid #DDDDDD;border-bottom:1px solid #DDDDDD">
|
||||
<table width="100%" cellspacing="0" cellpadding="0"><tr><td><a href="http://www.modsecurity.org"><img style="margin:4px" src="modsecurity.gif" width="120" height="36" alt="ModSecurity" border="0"></a></td><td align="right"><a
|
||||
href="http://www.breach.com"><img style="margin:6px" src="breach-logo-small.gif"
|
||||
height="36" width="100" border="0"></a></td></tr></table>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<hr size="1">
|
||||
|
||||
<h2>ModSecurity for Apache Documentation</h2>
|
||||
|
||||
<p>Version $version</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="html-multipage/index.html">HTML, one page per chapter</a></li>
|
||||
<li><a href="modsecurity2-apache-reference.html">HTML, all in one page</a></li>
|
||||
<li><a href="modsecurity2-apache-reference.pdf">PDF</a>
|
||||
</ul>
|
||||
|
||||
<hr size="1">
|
||||
|
||||
<div align="center" class="copyright">Copyright (C) 2004-2006 <a
|
||||
href="http://www.breach.com">Breach Security</a></div>
|
||||
|
||||
</body>
|
||||
</html>
|
BIN
doc/modsecurity-logo.png
Normal file
BIN
doc/modsecurity-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
102
doc/modsecurity-reference.css
Normal file
102
doc/modsecurity-reference.css
Normal file
@ -0,0 +1,102 @@
|
||||
|
||||
body {
|
||||
font: 13px/20px Arial, Helvetica;
|
||||
background-color: white;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
/* TEXT */
|
||||
|
||||
PRE {
|
||||
font-size: 100%;
|
||||
padding: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #CCCCCC;
|
||||
background-color: #F5F8FA;
|
||||
}
|
||||
|
||||
p, td, tr, li, ol, ul {
|
||||
/*font-family: Trebuchet MS, Verdana, Tahoma, Arial, Helvetica, Geneva;*/
|
||||
/*font-family: Arial, Helvetica;
|
||||
font-size: 14px;
|
||||
line-height: 24px;*/
|
||||
}
|
||||
|
||||
.copyright {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-top: 40px;
|
||||
font: 24px/30px Arial, Helvetica;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font: 20px/30px Arial, Helvetica;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-top: 15px;
|
||||
font: 16px/10px Arial, Helvetica;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
padding-top: 15px;
|
||||
font: 14px/10px Arial, Helvetica;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.header {
|
||||
background-color: #00448B;
|
||||
border-top: 6px solid #002B6E;
|
||||
height: 84px;
|
||||
vertical-align: top;
|
||||
padding-left: 20px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.topNavigation {
|
||||
background-color: #EEEEEE;
|
||||
background: url('g/topnav-background.gif');
|
||||
height: 23px;
|
||||
line-height: 12px;
|
||||
vertical-align: top;
|
||||
padding-left: 12px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.topLink, A.topLink:link, A.topLink:active, A.topLink:visited {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
A.topLink:hover {
|
||||
color: #BB0000;
|
||||
}
|
||||
|
||||
#navheader td {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#navfooter td {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
h3.title {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.note {
|
||||
border-top: 1px solid #CCCCCC;
|
||||
border-bottom: 1px solid #CCCCCC;
|
||||
}
|
BIN
doc/modsecurity.gif
Normal file
BIN
doc/modsecurity.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
4134
doc/modsecurity2-apache-reference.xml
Normal file
4134
doc/modsecurity2-apache-reference.xml
Normal file
File diff suppressed because it is too large
Load Diff
165
doc/pdf.xsl
Normal file
165
doc/pdf.xsl
Normal file
@ -0,0 +1,165 @@
|
||||
<?xml version='1.0'?>
|
||||
<xsl:stylesheet
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:fo="http://www.w3.org/1999/XSL/Format"
|
||||
version="1.0"
|
||||
>
|
||||
|
||||
<xsl:import href="/opt/docbook/xsl/fo/docbook.xsl"/>
|
||||
|
||||
<xsl:param name="paper.type" select="A4"/>
|
||||
|
||||
<!--
|
||||
<xsl:param name="section.autolabel" select="1"/>
|
||||
<xsl:param name="appendix.autolabel" select="1"/>
|
||||
<xsl:param name="section.label.include.component.label" select="1"/>
|
||||
-->
|
||||
|
||||
<xsl:param name="fop.extensions" select="1"/>
|
||||
<xsl:param name="title.margin.left">0pt</xsl:param>
|
||||
<xsl:param name="body.font.master">11</xsl:param>
|
||||
<xsl:param name="line-height">1.5</xsl:param>
|
||||
<xsl:param name="header.column.widths">1 20 1</xsl:param>
|
||||
|
||||
<xsl:attribute-set name="normal.para.spacing">
|
||||
<xsl:attribute name="space-before.optimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.minimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.maximum">0.0em</xsl:attribute>
|
||||
|
||||
<xsl:attribute name="space-after.optimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.minimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.maximum">0.2em</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="section.title.properties">
|
||||
<xsl:attribute name="font-family">
|
||||
<xsl:value-of select="$title.font.family"/>
|
||||
</xsl:attribute>
|
||||
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-column">always</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="space-before.minimum">1.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.optimum">1.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.maximum">1.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.minimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.optimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.maximum">0.0em</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="section.title.level1.properties">
|
||||
<xsl:attribute name="font-size">24pt</xsl:attribute>
|
||||
|
||||
<xsl:attribute name="break-before">page</xsl:attribute>
|
||||
|
||||
<xsl:attribute name="space-before.minimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.optimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.maximum">0.0em</xsl:attribute>
|
||||
|
||||
<!--
|
||||
<xsl:attribute name="space-after.minimum">1.5em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.optimum">1.5em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.maximum">1.5em</xsl:attribute>
|
||||
-->
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="section.title.level2.properties">
|
||||
<xsl:attribute name="font-size">18pt</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="section.title.level3.properties">
|
||||
<xsl:attribute name="font-size">14pt</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="section.title.level4.properties">
|
||||
<xsl:attribute name="font-size">12pt</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="list.item.spacing">
|
||||
<xsl:attribute name="space-before.optimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.minimum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.maximum">0.0em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.optimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.minimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.maximum">0.2em</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="list.block.spacing">
|
||||
<xsl:attribute name="space-before.optimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.minimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-before.maximum">0.2em</xsl:attribute>
|
||||
|
||||
<xsl:attribute name="space-after.optimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.minimum">0.2em</xsl:attribute>
|
||||
<xsl:attribute name="space-after.maximum">0.2em</xsl:attribute>
|
||||
|
||||
<xsl:attribute name="margin-left">2pc</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:param name="shade.verbatim" select="1"/>
|
||||
<xsl:attribute-set name="shade.verbatim.style">
|
||||
<xsl:attribute name="background-color">#f0f0f0</xsl:attribute>
|
||||
<xsl:attribute name="padding">2pt</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="monospace.verbatim.properties">
|
||||
<xsl:attribute name="font-size">90%</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!--
|
||||
<xsl:template name="nongraphical.admonition">
|
||||
<xsl:variable name="id">
|
||||
<xsl:call-template name="object.id"/>
|
||||
</xsl:variable>
|
||||
|
||||
<fo:block id="{$id}"
|
||||
xsl:use-attribute-sets="nongraphical.admonition.properties">
|
||||
<xsl:if test="$admon.textlabel != 0 or title">
|
||||
<fo:block keep-with-next.within-column='always'
|
||||
xsl:use-attribute-sets="admonition.title.properties">
|
||||
<xsl:apply-templates select="." mode="object.title.markup"/>
|
||||
</fo:block>
|
||||
</xsl:if>
|
||||
|
||||
<fo:block xsl:use-attribute-sets="admonition.properties">
|
||||
<xsl:apply-templates/>
|
||||
</fo:block>
|
||||
</fo:block>
|
||||
</xsl:template>
|
||||
-->
|
||||
|
||||
<xsl:template name="nongraphical.admonition">
|
||||
<xsl:variable name="id">
|
||||
<xsl:call-template name="object.id"/>
|
||||
</xsl:variable>
|
||||
|
||||
<fo:block space-before.minimum="0.8em"
|
||||
space-before.optimum="1em"
|
||||
space-before.maximum="1.2em"
|
||||
start-indent="0.25in"
|
||||
end-indent="0.25in"
|
||||
border-top="0.5pt solid black"
|
||||
border-bottom="0.5pt solid black"
|
||||
padding-top="4pt"
|
||||
padding-bottom="4pt"
|
||||
id="{$id}">
|
||||
<xsl:if test="$admon.textlabel != 0 or title">
|
||||
<fo:block keep-with-next='always'
|
||||
xsl:use-attribute-sets="admonition.title.properties">
|
||||
<xsl:apply-templates select="." mode="object.title.markup"/>
|
||||
</fo:block>
|
||||
</xsl:if>
|
||||
|
||||
<fo:block xsl:use-attribute-sets="admonition.properties">
|
||||
<xsl:apply-templates/>
|
||||
</fo:block>
|
||||
</fo:block>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="article.titlepage.before.recto">
|
||||
<fo:block>​
|
||||
<fo:external-graphic width="8cm" src="url(modsecurity-logo.png)"/>
|
||||
</fo:block>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
32
modsecurity.conf-minimal
Normal file
32
modsecurity.conf-minimal
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
# Basic configuration options
|
||||
SecRuleEngine On
|
||||
SecRequestBodyAccess On
|
||||
SecResponseBodyAccess Off
|
||||
|
||||
# Handling of file uploads
|
||||
# TODO Choose a folder private to Apache.
|
||||
# SecUploadDir /opt/apache-frontend/tmp/
|
||||
SecUploadKeepFiles Off
|
||||
|
||||
# Debug log
|
||||
SecDebugLog logs/modsec_debug.log
|
||||
SecDebugLogLevel 0
|
||||
|
||||
# Serial audit log
|
||||
SecAuditEngine RelevantOnly
|
||||
SecAuditLogRelevantStatus ^5
|
||||
SecAuditLogParts ABIFHZ
|
||||
SecAuditLogType Serial
|
||||
SecAuditLog logs/modsec_audit.log
|
||||
|
||||
# Maximum request body size we will
|
||||
# accept for buffering
|
||||
SecRequestBodyLimit 131072
|
||||
|
||||
# Store up to 128 KB in memory
|
||||
SecRequestBodyInMemoryLimit 131072
|
||||
|
||||
# Buffer response bodies of up to
|
||||
# 512 KB in length
|
||||
SecResponseBodyLimit 524288
|
Loading…
x
Reference in New Issue
Block a user