Import ModSecurity 2.1.0-rc7

This commit is contained in:
ivanr 2007-02-06 12:29:22 +00:00
commit 3f80fdac3b
50 changed files with 22536 additions and 0 deletions

144
CHANGES Normal file
View 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
View 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
View 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
View File

340
apache2/LICENSE Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

588
apache2/apache2_io.c Normal file
View 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
View 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
View 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
View 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;
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

299
apache2/re.h Normal file
View 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

File diff suppressed because it is too large Load Diff

986
apache2/re_operators.c Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

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
View 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) &gt; 0
or (count($up) &gt; 0
and generate-id($up) != generate-id($home)
and $navig.showtitles != 0)
or count($next) &gt; 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>&#160;</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>&#160;</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>&#160;|&#160;</xsl:text>
</xsl:if>
</xsl:when>
<xsl:otherwise>&#160;</xsl:otherwise>
</xsl:choose>
</td>
<td width="20%" align="right">
<xsl:text>&#160;</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) &gt; 0
or count($up) &gt; 0
or count($next) &gt; 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>&#160;</xsl:text>
</td>
<td width="20%" align="center">
<xsl:choose>
<xsl:when test="count($up)&gt;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>&#160;</xsl:otherwise>
</xsl:choose>
</td>
<td width="40%" align="right">
<xsl:text>&#160;</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>&#160;</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>&#160;|&#160;</xsl:text>
</xsl:if>
</xsl:when>
<xsl:otherwise>&#160;</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>&#160;</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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because it is too large Load Diff

165
doc/pdf.xsl Normal file
View 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>&#8203;
<fo:external-graphic width="8cm" src="url(modsecurity-logo.png)"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>

32
modsecurity.conf-minimal Normal file
View 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