aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2010-03-09 16:37:48 +0000
committerDimitri Sokolyuk <demon@dim13.org>2010-03-09 16:37:48 +0000
commit335bd0d855dc2cd0b2b6c1f666fd7402afb591ca (patch)
tree8535af5c9378ac29bd8a4ef18bd335cd65c7edeb
bogom 1.9.2 import
-rw-r--r--CHANGELOG249
-rw-r--r--COPYING340
-rw-r--r--Makefile30
-rw-r--r--README107
-rw-r--r--README.solaris37
-rw-r--r--bogom.8312
-rw-r--r--bogom.conf-example120
-rw-r--r--conf.c365
-rw-r--r--conf.h49
-rw-r--r--milter.c1385
10 files changed, 2994 insertions, 0 deletions
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..c9f03e5
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,249 @@
+2008-06-25 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: FIX: hostaddr may be NULL if not supported by current version of milter or if the SMTP connection is made via stdin, noticed using milter with postfix - Thanks to Victor Balada Diaz
+
+2008-01-18 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ fix: added an include to avoid a warning while compiling under Linux
+
+=== release 1.9.1
+
+2007-11-24 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ portability: extra flag in some stdio functions to avoid file descriptor limit issue on Solaris 10, thanks to Sergey Shapovalov
+
+2007-10-01 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * conf.c:
+ fix: error reading configuration when the last token was at EOF, thanks to Mikel Ward for the report
+
+2007-02-28 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: Fix: space missing in a log entry.
+
+2007-02-08 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8, conf.c, conf.h, milter.c: copyright year
+
+2007-02-04 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: Error handling while reading bogofilter spamicity response.
+
+2006-10-22 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8, bogom.conf-example, milter.c:
+ smpamicity support for the X-Bogosity header
+
+2006-10-08 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: Avoid multiple X-Bogosity headers.
+
+2006-01-05 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ fix: xxfi_header is called *zero* or more times (check http://www.usebox.net/jjm/bogom/errata/bogom-errata-2006-1.txt)
+
+2005-11-19 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: fix: include changes to support MAXHOSTNAMELEN constant
+
+2005-11-13 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * conf.c: Code cleanning.
+
+2005-11-06 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: sprintf to snprintf
+
+2005-09-23 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8, bogom.conf-example, milter.c:
+ Added quarantine_mdir feature (-q option)
+
+2005-09-12 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: Better logging for forward_spam and subj_tag.
+
+ * bogom.8, bogom.conf-example, milter.c:
+ Added forward_spam feature (-f option)
+
+2005-04-14 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ fix: wrong size on calloc, thanks to Jason M. for his feedback
+
+ * milter.c: fixes for solaris compatibility
+
+2005-04-04 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8, bogom.conf-example, milter.c:
+ new configuration option 'subject_tag'
+
+2005-03-30 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: ifndef for default username, it makes porting easier
+
+2005-02-16 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * conf.c: fix: missing conf file is not an error
+
+2005-02-15 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.conf-example: pidfile added
+
+ * bogom.8, milter.c: option changes: '-p pipe' becomes '-s conn' and
+ '-p pidfile' has been added (plus pidfile conf token)
+
+2005-02-09 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: setup time to timezone before openlog
+
+2005-02-07 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ fix: the order seteuid followed by setuid returns 0 / -1 under NetBSD
+ swaping those commands (setuid and later seteuid) rocks and seems to not
+ matter under OpenBSD - thanks to Heron Gallegos
+
+2005-02-04 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8: seems openbsd doesn't support !prog1,prog2 on syslog.conf
+
+2005-02-02 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * conf.c: fix: type size_t to int
+
+2005-02-01 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ fix: memory leak in conf reading routines, thanks to Dariusz Kulinski
+
+2005-01-31 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8: some fixes
+
+2005-01-29 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: fix: misstyped word
+
+2005-01-28 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8: -d option added and syslog.conf tips
+ little rewrite of some parts
+
+ * milter.c: -d option added and better debug messages
+ fix: -l option missed on getopt
+
+2005-01-27 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.conf-example, bogom.8, milter.c: added body_limit option
+
+ * conf.c: use isspace instead custom is_blank
+
+2005-01-26 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ added mlfi_abort to handle message abort on multi-message connections
+
+ * milter.c: fix: return 1 on error reading conf
+
+2005-01-25 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ re_envrcpt added (thanks to Dariusz Kulinski for his feedback)
+ fix: exit after configuration error
+ and little code clean
+
+ * bogom.8: re_envrcpt added
+
+ * bogom.conf-example: re_envrcpt example added
+
+2005-01-23 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c:
+ fix: smfi_getpriv may return NULL on mlfi_close due re_connection and
+ that should not be logged as error
+
+ * conf.c, conf.h: year on copyright line
+
+2005-01-22 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.conf-example: re_connection added
+
+ * milter.c, bogom.8:
+ -w option removed, re_connection added, year on copyright line
+
+2005-01-10 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c, conf.h, conf.c: Macro fixes and little code clean.
+
+2005-01-09 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * conf.c: Added missing rcsid
+
+2005-01-08 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * conf.c: fix: backslash escaped quotes now works (both \" and \')
+ Quoted string now can use single quotes also
+
+ * milter.c: fix: configuration takes precedence over command line
+
+ * bogom.8: fix: configuration takes precedence over command line
+ fix: backslash escaped quotes now works (both \" and \')
+ Quoted string now can use single quotes also
+
+2005-01-04 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c, bogom.8:
+ -w option deprecated (now configuration file must be used).
+
+2005-01-03 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.conf-example: fix: typo on exclude_string examples.
+
+2005-01-02 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.conf-example, bogom.8, milter.c:
+ Configurable text for the SMTP reject reply.
+
+ * bogom.8: Added -c conf_file option.
+ Configuration file description.
+
+ * milter.c: Added -c conf_file option.
+ Added configuration file support.
+
+ * conf.h, conf.c, bogom.conf-example: New file.
+
+2004-12-31 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * milter.c: fix: whitelist list processing
+
+2004-12-29 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8: fix: D1 macros
+
+ * bogom.8: -w re_whitelist option added
+
+ * milter.c: -w re_whitelist option added
+ Define a white list using regular expressions vs the sender address.
+
+ * milter.c: Removed unneeded includes
+
+2004-12-28 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8: Added sendmail setup information.
+
+ * bogom.8: Better description of logging.
+
+2004-12-27 Juan J. Martínez <reidrac@blackshell.usebox.net>
+
+ * bogom.8: -x exclude_string added
+
+ * milter.c: -x exclude_string added
+ If this string is found in the Subject of a message, it will be
+ automatically accepted.
+
+ * bogom.8, milter.c: New file.
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5b6e7c6
--- /dev/null
+++ b/COPYING
@@ -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) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 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) year 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..924ae31
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+OBJS=bogom.o conf.o
+BIN=bogom
+MAN=bogom.8
+
+# default instalation prefix
+PREFIX=/usr/local
+
+# edit to fit your system configuration
+CPPFLAGS=
+CFLAGS+=-Wall -g
+LDFLAGS=
+LIBS+=-lmilter -lpthread
+
+all: $(BIN)
+
+bogom.o: milter.c conf.h
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c milter.c -o bogom.o
+conf.o: conf.c conf.h
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c conf.c -o conf.o
+
+$(BIN): $(OBJS)
+ $(CC) $(LDFLAGS) -o $(BIN) $(OBJS) $(LIBS)
+
+install: $(BIN) $(MAN)
+ cp -f $(BIN) $(PREFIX)/libexec
+ cp -f $(MAN) $(PREFIX)/man/man8
+
+clean:
+ rm -f $(BIN) $(OBJS)
+
diff --git a/README b/README
new file mode 100644
index 0000000..38ce789
--- /dev/null
+++ b/README
@@ -0,0 +1,107 @@
+bogom - simple sendmail milter to interface bogofilter
+Copyright (C) 2004-2007 Juan J. Martinez <jjm@usebox.net>
+
+
+Required
+--------
+
+This milter requires libmiter API for sendmail 8.13.x or later.
+Installing bogofilter is a good idea also.
+
+Bogom has been developed and tested with the following:
+
+ sendmail 8.13.0 and 8.14.1
+ bogofilter 0.92.8 (with BerkeleyDB 4.2.52)
+
+
+Build and Install
+-----------------
+
+This milter has been developed in OpenBSD, so you may need to tweak the
+Makefile in order to compile it in other systems.
+
+Check README.* files for specific notes.
+
+On most Linux and BSD based systems, simply try:
+
+$ make
+$ su
+# make install
+
+Bogom has been packaged for FreeBSD (mail/milter-bogom) and non officially for
+some Linux distributions.
+
+IMPORTANT: since 1.8.1+ the bogom binary is installed into $(PREFIX)/libexec,
+ so be sure to remove $(PREFIX)/sbin/bogom from any previous
+ installation.
+
+
+Unprivileged user
+-----------------
+
+When root starts bogom, the program drops its privileges to another user. It's
+a good idea you create a new user to run the milter. By default bogofilter user
+is expected.
+
+# mkdir /var/spool/bogofilter
+
+Configure bogofilter with:
+
+bogofilter_dir=/var/spool/bogofilter
+
+... and create your intial words database.
+
+After that, setup the environment with:
+
+# chown -R bogofilter:bogofilter /var/spool/bogofilter
+# chmod 700 /var/spool/bogofilter
+# chmod 600 /var/spool/bogofilter/wordlist.db
+
+
+Custom installation step by step
+--------------------------------
+
+bogom provides some defaults, but don't have to stick into them.
+
+Here follows a 'step by step' quickstart to configure bogom and bogofilter.
+
++ Install bogofilter
++ Install bogom
++ Create the '_bogom' user and group
++ Create the directories and apply permissions:
+
+# mkdir -p /var/run/bogom && chown _bogom:_bogom /var/run/bogom
+# mkdir -p /var/db/bogofilter && chown _bogom:_bogom /var/db/bogofilter
+
++ Copy the example bogofilter.cf file into /etc and edit it:
+
+bogofilter_dir=/var/db/bogofilter
+
++ Setup bogofilter database. About 500 messages of spam/ham will be nice:
+
+# su _bogom -c "bogofilter -s < spam.mbox"
+# su _bogom -c "bogofilter -n < ham.mbox"
+
+(use su with -m option if _bogom user doesn't have shell account)
+
++ Install the example conf file at /etc/bogom.conf and edit it. At least
+you should set up:
+
+user "_bogom"
+pidfile "/var/run/bogom/bogom.pid"
+connection "unix:/var/run/bogom/milter.sock"
+
++ Add to your sendmail mc file:
+
+INPUT_MAIL_FILTER(`bogom',\
+`S=unix:/var/run/bogom/milter.sock, T=S:30s;R:1m')
+
+(rebuild and install the cf file)
+
++ Add bogom to your rc or init.d scripts
++ Restart sendmail
++ Exec bogom or reboot the system
+
+
+* EOF *
+
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..eb90553
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,37 @@
+Solaris notes
+-------------
+
+The information in README applies, so read it too.
+
+Tested with:
+
+ bogom 1.8.2
+ Solaris 10
+ gcc 3.4.3
+ Sendmail 8.13.3+Sun
+
+Build command:
+
+ $ gmake CC=gcc LIBS="-lmilter -lpthread -lsocket -lnsl"
+
+Caveats:
+
+ Man page is in BSD man pages' format and needs mdoc macros
+
+About file descriptor limit:
+
+ This is a problem related to 23-bit applications running on
+ Solaris, that has been fixed on Solaris 10.
+
+ There have been reports of this issue with the milter under
+ high load and several threads running. The milter was built
+ with Sun Strudio 12.
+
+ bogom 1.9.1 and later have a fix, but this hasn't been tested
+ deeply.
+
+ Further information:
+ http://developers.sun.com/solaris/articles/stdio_256.html
+
+* EOF *
+
diff --git a/bogom.8 b/bogom.8
new file mode 100644
index 0000000..5c4b119
--- /dev/null
+++ b/bogom.8
@@ -0,0 +1,312 @@
+.\" $Id$
+.\"
+.\" bogom, simple sendmail milter to interface bogofilter
+.\" Copyright (C) 2004-2007 Juan J. Martinez <jjm*at*usebox*dot*net>
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License Version 2 as
+.\" published by the Free Software Foundation.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+.\" GNU General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with this program; if not, write to the Free Software
+.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.Dd December 25, 2004
+.Dt BOGOM 8
+.Os
+.Sh NAME
+.Nm bogom
+.Nd simple sendmail milter to interface bogofilter
+.Sh SYNOPSIS
+.Nm
+.Op Fl R | Fl D
+.Op Fl t
+.Op Fl v
+.Op Fl S
+.Op Fl u Ar user
+.Op Fl s Ar conn
+.Op Fl b Ar bogo_path
+.Op Fl x Ar exclude_string
+.Op Fl c Ar conf_file
+.Op Fl l Ar body_limit
+.Op Fl p Ar pidfile
+.Op Fl f Ar forward_spam
+.Op Fl q Ar quarantine_mdir
+.Op Fl d
+.Sh DESCRIPTION
+The
+.Nm
+plugin can be used with the milter API of
+.Xr sendmail 8
+to filter mails using
+.Xr bogofilter 1
+bayesian filter.
+.Pp
+.Nm
+is intended to be used with only one words database for the whole system.
+.Pp
+The options are as follows:
+.Bl -tag -width "-x exclude_string"
+.It Fl R
+Reject mail classified as spam
+.It Fl D
+Discard mail classified as spam
+.It Fl t
+Train bogofilter with the mail classified as spam/ham
+.It Fl v
+Verbose logging
+.It Fl S
+Use spamicity header. Read configuration file section for further details.
+.It Fl u Ar user
+User to run the milter.
+Default: bogofilter
+.It Fl s Ar conn
+Path to the pipe to connect sendmail. Default:
+.Pa unix:/var/spool/bogofilter/milter.sock
+.It Fl b Ar bogo_path
+Path to the
+.Xr bogofilter 1
+binary.
+Default:
+.Pa /usr/local/bin/bogofilter
+.It Fl x Ar exclude_string
+If this string is found in the Subject of a message, it will be
+automatically accepted and no filtering operation will be done.
+.It Fl c Ar conf_file
+Path to the configuration file.
+Default:
+.Pa /etc/bogom.conf
+.It Fl l Ar body_limit
+Length limit in bytes to be processed from mail body. The rest of the body
+will be discarded and not analyzed by the filter.
+Default:
+.Pa no limit
+.It Fl p Ar pidfile
+Path to the file to store the pid of the milter. The pidfile is created
+after the milter drops privileges and the user to run the milter
+must have write permission to the specified file. Default:
+.Pa /var/spool/bogofilter/bogom.pid
+.It Fl f Ar forward_spam
+Set a recipient to forward any message classified as spam. Read configuration
+file section for further details.
+.It Fl q Ar quarantine_mdir
+Path to a directory to deliver a copy of any message classified as spam. The
+messages are stored in
+.Xr maildir 5
+format.
+.It Fl d
+Enable debug messages (implies verbose logging)
+.El
+.Pp
+Default policy is to add the
+.Em X-Bogosity
+header (Yes, No, Unsure) and deliver the mail. This can be changed with
+.Cm -R
+or
+.Cm -D
+when
+bogofilter classifies the mail as spam.
+.Pp
+In bogofilter's configuration the
+.Em bogofilter_dir
+token should be set to the directory with system database, usually
+.Pa /var/spool/bogofilter ,
+in bogofilter's configuration, or simply the words database of the
+unprivileged user running the milter can be used.
+.Pp
+.Nm
+uses a temporal file to store each individual message and forks a new
+process to scan it with bogofilter. This temporal file uses
+.Pa /tmp
+directory by default, it's owned by the user running the milter and has
+0600 mode. When a directory is specified in
+.Em quarantine_mdir ,
+the
+.Pa tmp
+subdirectory in that maildir is used as temporal directory.
+.Pp
+The option
+.Cm -t
+registers the mail after classifying it as spam or ham.
+This option can be dangerous because the filter may register errors, so
+you should read carefully bogofilter's manual regarding this point.
+.Sh SENDMAIL SETUP
+Milter support in sendmail binary can be verified with:
+.Pp
+.D1 # sendmail -d0.1 -bv root | grep MILTER
+.D1 Compiled with: DNSMAP LOG MAP_REGEX MATCHGECOS MILTER MIME7TO8 MIME8TO7
+.Pp
+The milter can be added to sendmail's configuration by adding the following
+lines to the mc file:
+.Pp
+.D1 INPUT_MAIL_FILTER(`bogom',
+.D1 `S=unix:/var/spool/bogofilter/milter.sock, T=S:30s;R:1m')
+.Pp
+It assumes the default place for the communication socket.
+.Pp
+The cf file must be rebuilt and sendmail restarted.
+.Sh CONFIGURATION FILE
+Configuration file supports following tokens:
+.Bd -literal
+ # line comment
+
+ policy (pass|reject|discard)
+ default: policy pass
+
+ reject "<text for the SMTP reply>"
+ default: empty
+ (sendmail default is "Command rejected")
+
+ subject_tag "<text to tag the subject>"
+ default: empty
+
+ verbose (0|1)
+ default: verbose 0
+
+ spamicity_header (0|1)
+ default: spamicity_header 0
+
+ bogofilter "<path to bogofilter binary>"
+ default: bogofilter "/usr/local/bin/bogofilter"
+
+ training (0|1)
+ default: training 0
+
+ body_limit <length in bytes>
+ default: no limit
+
+ user "<username to run the milter>"
+ default: user "bogofilter"
+
+ connection "<type>:<location>"
+ default: connection "unix:/var/spool/bogofilter/milter.sock"
+
+ pidfile "<path to milter pidfile>"
+ default: pidfile "/var/spool/bogofilter/bogom.pid"
+
+ exclude_string "<subject exclude string>"
+ default: empty
+
+ forward_spam "<rcpt>"
+ default: empty
+
+ quarantine_mdir "<path to maildir directory>"
+ default: empty
+
+ re_connection "<case insensitive extended re>"
+ default: empty
+
+ re_envfrom "<case insensitive extended re>"
+ default: empty
+
+ re_envrcpt "<case insensitive extended re>"
+ default: empty
+.Ed
+.Pp
+Configuration takes precedence over command line.
+.Pp
+By default
+.Em X-Bogosity
+header will use 'Yes, tests=bogofilter', 'No, tests=bogofilter' and 'Unsure,
+tests=bogofilter'. With
+.Em spamicity_header
+activated, the classification of 'Ham', 'Spam' or 'Unsure' plus the value
+of spamicity will be used to tag the messages.
+.Pp
+.Em subject_tag
+string will be prepend to message subject when it is identified as spam
+and policy is pass.
+.Pp
+.Em body_limit
+specifies the amount of bytes (K suffix for Kilobytes and M for Megabytes)
+of message body that will be passed to bogofilter to be processed.
+This option should help busy servers but is incompatible with
+.Em quarantine_mdir
+feature.
+.Pp
+.Em forward_spam
+recipient will receive a copy of any message classified as spam when policy
+is pass. Notice that the original destination recipients won't be modified
+and general RELAY restrictions will apply.
+.Pp
+If a directory in
+.Em quarantine_mdir
+is specified, any message classified as spam will be delivered there in
+maildir format. Neither reject nor discard policy affect this delivery, but
+.Em body_limit
+option is ignored. When a message is delivered, necessary subdirectories
+are created (tmp and new only). Notice that tmp subdirectory in maildir is
+used as temporal directory for
+.Nm
+process when this option is active.
+.Pp
+The
+.Em re_*
+tokens allow to add items to
+.Em connection ,
+.Em envfrom
+and
+.Em envrcpt
+white lists. Any message with client connection (both host and hostname,
+if available), sender address or destination address matching the case
+insensitive extended regular expression (explained in
+.Xr re_format 7 )
+will be accepted and no filtering operation will be done. Those token can
+be used more than once and all the regular expressions will be checked.
+.Pp
+Quoted strings can use single and double quotes, using backslash to escape both
+characters.
+.
+.Sh LOGGING
+.Nm
+sends messages to
+.Xr syslogd 8
+using
+facility
+daemon and
+levels
+err, notice, info and debug.
+.Pp
+By default only info and, when needed, err levels will be used. With
+.Cm -v
+option, extra information will be addressed with notice level plus the
+log facility provided in bofogilter.
+.Pp
+.Cm -d
+option enables debug level with very verbose logging.
+.Pp
+.Nm
+activity can be logged to a separate file with following lines in
+.Xr syslog.conf 5 :
+.Bd -literal
+!bogom
+*.* /var/log/bogom
+!bogofilter
+*.* /var/log/bogom
+.Ed
+.Sh FILES
+/etc/bogom.conf
+.Sh SEE ALSO
+.Xr sendmail 8 ,
+.Xr bogofilter 1 ,
+.Xr bogoutil 1 ,
+.Xr syslog.conf 5 ,
+.Xr syslogd 8 ,
+.Xr re_format 7 ,
+.Xr maildir 5
+.Pp
+http://www.usebox.net/jjm/bogom/
+.Sh CAVEATS
+By now SIGHUP is ignored.
+.Sh HISTORY
+The first version of
+.Nm
+was written in the end of 2004.
+.Sh AUTHORS
+Juan J. Martinez
+.Aq jjm@usebox.net
diff --git a/bogom.conf-example b/bogom.conf-example
new file mode 100644
index 0000000..354797d
--- /dev/null
+++ b/bogom.conf-example
@@ -0,0 +1,120 @@
+#
+# $Id$
+#
+# Example of bogom configuration
+#
+
+#
+# policy (pass|reject|discard)
+#
+# default: policy pass
+
+#
+# reject "<text for the SMTP reply>"
+#
+# reject "We don't accept junk mail"
+# reject "Spam pattern detected"
+#
+# default: empty
+# (sendmail's default is: "Command rejected")
+
+#
+# subject_tag "<text to tag the subject>"
+#
+# subject_tag "*SPAM*"
+# subject_tag "[spam detected]"
+#
+# default: empty
+
+#
+# verbose (0|1)
+#
+# default: verbose 0
+
+#
+# spamicity_header (0|1)
+#
+# default: spamicity_header 0
+
+#
+# bogofilter "<path to bogofilter binary>"
+#
+# default: bogofilter "/usr/local/bin/bogofilter"
+
+#
+# training (0|1)
+#
+# default: training 0
+
+#
+# body_limit <length in bytes>
+#
+# body_limit 16000
+# body_limit 64k
+# body_limit 1m
+#
+# default: no limit
+
+#
+# user "<username to run the milter>"
+#
+# default: user "bogofilter"
+
+#
+# connection "<type>:<location>"
+#
+# default: connection "unix:/var/spool/bogofilter/milter.sock"
+
+#
+# pidfile "<path to milter pidfile>"
+#
+# default: pidfile "/var/spool/bogofilter/bogom.pid"
+
+#
+# exclude_string "<subject exclude string>"
+#
+# exclude_string "[no-bogofilter]"
+# exclude_string "*no filter*"
+#
+# default: empty
+
+#
+# forward_spam "<rcpt>"
+#
+# forward_spam "spammaster"
+# forward_spam "spam@other.domain.com"
+#
+# default: empty
+
+#
+# quarantine_mdir "<path to maildir directory>"
+#
+# quarantine_mdir "/var/spool/bogofilter/spam.mdir"
+#
+# default: empty
+
+#
+# re_connection "<case insensitive extended re>"
+#
+# re_connection "192\.168\.0\."
+# re_connection "openbsd\.org$"
+#
+# default: empty
+
+#
+# re_envfrom "<case insensitive extended re>"
+#
+# re_envfrom "\.usebox\.net>$"
+# re_envfrom "@usebox\.net>$"
+#
+# default: empty
+
+#
+# re_envrcpt "<case insensitive extended re>"
+#
+# re_envrcpt "spamtrap@usebox\.net>$"
+# re_envrcpt "ilikespam@"
+#
+# default: empty
+
+# EOF
diff --git a/conf.c b/conf.c
new file mode 100644
index 0000000..ab91625
--- /dev/null
+++ b/conf.c
@@ -0,0 +1,365 @@
+/* $Id$ */
+
+/*
+* conf.c, configuration reader and parser
+* Copyright (C) 2004-2007 Juan J. Martinez <jjm*at*usebox*dot*net>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License Version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+*/
+
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+#include<errno.h>
+#include<ctype.h>
+
+#include "libmilter/mfapi.h"
+#include "conf.h"
+
+#define new_string_list(x) do {\
+ x=(struct string_list *) \
+ malloc(sizeof(struct string_list));\
+ x->n=NULL;\
+ } while(0)
+
+static char * pstrncpy(char *, const char *, size_t);
+static int parse_string(char *p);
+static int parse_qstring(char *p);
+static int parse_bool(char *p);
+static char * parse_conf(struct conftoken *conf, char *p);
+
+static const char rcsid[]="$Id$";
+
+/*
+* strncpy alike function that parses scaped quotes
+*/
+static char *
+pstrncpy(char *d, const char *s, size_t l)
+{
+ size_t i, j;
+
+ for(i=0, j=0; i<l && s[i]; i++, j++)
+ {
+ if(s[i]=='\\' && i+1<l)
+ if(s[i+1]=='\'' || s[i+1]=='\"')
+ i++;
+ d[j]=s[i];
+ }
+
+ d[j]=0;
+
+ return d;
+}
+
+/*
+* parses a string and returns its length
+*/
+static int
+parse_string(char *p)
+{
+ char *t;
+
+ for(t=p; *t && !isspace(*t); t++);
+
+ return (int)(t-p);
+}
+
+/*
+* parses a quoted string and returns its length
+* on error:
+* -1 close quote expected
+* 0 empty quotes
+*/
+static int
+parse_qstring(char *p)
+{
+ char *t;
+ char q=*p;
+
+ for(t=++p; *t; t++)
+ {
+ if(*t==q)
+ break;
+
+ if(*t=='\\' && t[1]==q)
+ t++;
+ }
+
+ if(*t!=q)
+ return -1;
+
+ if(t==p)
+ return 0;
+
+ return (int)(t-p);
+}
+
+/*
+* parses a bool and returns 0/1
+* all values ne 0 are true
+*/
+static int
+parse_bool(char *p)
+{
+ if(!p[0] || !isspace(p[1]))
+ return -1;
+
+ if(*p=='0')
+ return 0;
+ else
+ return 1;
+}
+
+/*
+* parses one line each call
+*/
+static char *
+parse_conf(struct conftoken *conf, char *p)
+{
+ int i, len;
+ struct string_list *t;
+
+ if(!p)
+ return NULL;
+
+ if(!p[0] || p[0]=='\n' || p[0]=='#')
+ return NULL;
+
+ while(isspace(*p))
+ p++;
+
+ len=parse_string(p);
+ if(!len)
+ return NULL;
+
+ for(i=0; conf[i].word; i++)
+ if(!strncmp(conf[i].word, p, strlen(conf[i].word)))
+ {
+ p+=len;
+ while(isspace(*p))
+ p++;
+
+ switch(conf[i].required)
+ {
+ case REQ_NONE:
+ /* nothing required */
+ break;
+
+ case REQ_BOOL:
+ len=parse_bool(p);
+ if(len<0)
+ {
+ fprintf(stderr,
+ "bool expected\n");
+ return p;
+ }
+
+ conf[i].bool=len;
+ p++;
+ break;
+
+ case REQ_STRING:
+ len=parse_string(p);
+ if(!len)
+ {
+ fprintf(stderr,
+ "string expected\n");
+ return p;
+ }
+
+ if(conf[i].str)
+ free(conf[i].str);
+ conf[i].str=(char *)malloc(len+1);
+ if(!conf[i].str)
+ {
+ fprintf(stderr, "malloc\n");
+ return p;
+ }
+ strncpy(conf[i].str, p, len);
+ p+=len;
+ break;
+
+ case REQ_QSTRING:
+ if(*p!='\"' && *p!='\'')
+ {
+ fprintf(stderr,
+ "quoted string"
+ " expected\n");
+ return p;
+ }
+
+ len=parse_qstring(p);
+ p++;
+
+ if(len==-1)
+ {
+ fprintf(stderr,
+ "end quote expected\n");
+ return p;
+ }
+
+ if(!len)
+ {
+ fprintf(stderr,
+ "empty quotes\n");
+ return p;
+ }
+
+ if(conf[i].str)
+ free(conf[i].str);
+ conf[i].str=(char *)malloc(len+1);
+ if(!conf[i].str)
+ {
+ fprintf(stderr, "malloc\n");
+ return p;
+ }
+ pstrncpy(conf[i].str, p, len);
+ p+=len+1;
+ break;
+
+ case REQ_LSTQSTRING:
+ if(*p!='\"' && *p!='\'')
+ {
+ fprintf(stderr,
+ "quoted string"
+ " expected\n");
+ return p;
+ }
+
+ len=parse_qstring(p);
+ p++;
+
+ if(len==-1)
+ {
+ fprintf(stderr,
+ "end quote expected\n");
+ return p;
+ }
+
+ if(!len)
+ {
+ fprintf(stderr,
+ "empty quotes\n");
+ return p;
+ }
+
+ if(!conf[i].sl)
+ {
+ new_string_list(conf[i].sl);
+ if(!conf[i].sl)
+ {
+ fprintf(stderr,
+ "malloc");
+ return p;
+ }
+ }
+ else
+ {
+ new_string_list(t);
+ if(!t)
+ {
+ fprintf(stderr,
+ "malloc");
+ return p;
+ }
+ t->n=conf[i].sl;
+ conf[i].sl=t;
+ }
+
+ conf[i].sl->s=(char *)malloc(len+1);
+ if(!conf[i].sl->s)
+ {
+ fprintf(stderr, "malloc\n");
+ return p;
+ }
+ pstrncpy(conf[i].sl->s, p, len);
+ p+=len+1;
+ break;
+ }
+ break;
+ }
+
+ if(conf[i].word)
+ {
+ while(isspace(*p))
+ p++;
+
+ if(!p[0])
+ return NULL;
+
+ fprintf(stderr, "parse error\n");
+ return p;
+ }
+
+ fprintf(stderr, "unknown token\n");
+
+ p[len]=0;
+ return p;
+}
+
+/*
+* reads and parses the configuration file
+*/
+int
+read_conf(const char *filename, struct conftoken *conf)
+{
+ FILE *fd;
+ char buffer[1024];
+ char *ret;
+ int line, i;
+
+ fd=fopen(filename, "r");
+ if(!fd)
+ return 0;
+
+ line=1;
+ while(!feof(fd))
+ {
+ i=0;
+ *buffer=0;
+ do
+ {
+ if(i>1023)
+ {
+ fclose(fd);
+ fprintf(stderr, "conf line %i is too long\n",
+ line);
+ return 1;
+ }
+
+ fscanf(fd, "%c", &buffer[i]);
+
+ } while(buffer[i++]!='\n' && !feof(fd));
+
+ buffer[i]=0;
+
+ ret=parse_conf(conf, buffer);
+ if(ret)
+ {
+ fclose(fd);
+ fprintf(stderr, "conf error at line %i, near: %s\n",
+ line, ret);
+ return 1;
+ }
+
+ line++;
+ }
+
+ fclose(fd);
+
+ return 0;
+}
+
+/* EOF */
diff --git a/conf.h b/conf.h
new file mode 100644
index 0000000..36f8998
--- /dev/null
+++ b/conf.h
@@ -0,0 +1,49 @@
+/* $Id$ */
+
+/*
+* conf.h, configuration reader and parser include
+* Copyright (C) 2004-2007 Juan J. Martinez <jjm*at*usebox*dot*net>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License Version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+*/
+
+#ifndef __BOGOM_CONF__
+#define __BOGOM_CONF__
+
+#define REQ_NONE 0
+#define REQ_BOOL 1
+#define REQ_STRING 2
+#define REQ_QSTRING 3
+#define REQ_LSTQSTRING 4
+
+struct string_list
+{
+ char *s;
+ struct string_list *n;
+};
+
+struct conftoken
+{
+ char *word;
+ int required;
+ char *str;
+ int bool;
+ struct string_list *sl;
+};
+
+int read_conf(const char *filename, struct conftoken *);
+
+#endif
+
diff --git a/milter.c b/milter.c
new file mode 100644
index 0000000..d319164
--- /dev/null
+++ b/milter.c
@@ -0,0 +1,1385 @@
+/* $Id$ */
+
+/*
+* bogom, simple sendmail milter to interface bogofilter
+* Copyright (C) 2004-2007 Juan J. Martinez <jjm*at*usebox*dot*net>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License Version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+*/
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <regex.h>
+#include <time.h>
+#include <netdb.h>
+
+#ifdef __sun__
+#include <fcntl.h>
+#endif
+
+#include "libmilter/mfapi.h"
+#include "conf.h"
+
+/* defaults */
+#ifndef DEF_USER
+#define DEF_USER "bogofilter"
+#endif
+#ifndef DEF_CONN
+#define DEF_CONN "unix:/var/spool/bogofilter/milter.sock"
+#endif
+#ifndef DEF_CONF
+#define DEF_CONF "/etc/bogom.conf"
+#endif
+#ifndef DEF_PIDFILE
+#define DEF_PIDFILE "/var/spool/bogofilter/bogom.pid"
+#endif
+
+struct mlfiPriv
+{
+ FILE *f;
+ char *fullpath;
+ char *filename;
+ char *subject;
+ int eom;
+ size_t bodylen;
+ int old_headers;
+};
+
+sfsistat mlfi_connect(SMFICTX *, char *, _SOCK_ADDR *);
+sfsistat mlfi_envfrom(SMFICTX *, char **);
+sfsistat mlfi_envrcpt(SMFICTX *, char **);
+sfsistat mlfi_header(SMFICTX *, char *, char *);
+sfsistat mlfi_eoh(SMFICTX *);
+sfsistat mlfi_body(SMFICTX *, unsigned char *, size_t);
+sfsistat mlfi_eom(SMFICTX *);
+sfsistat mlfi_abort(SMFICTX *);
+sfsistat mlfi_close(SMFICTX *);
+void mlfi_clean(SMFICTX *);
+void usage(const char *);
+int to_maildir(char *, char *);
+char *hostname_tmp();
+
+#ifdef __sun__
+int daemon(int, int);
+#endif
+
+struct smfiDesc smfilter=
+{
+ "bogom", /* filter name */
+ SMFI_VERSION, /* version code -- do not change */
+ SMFIF_ADDHDRS | SMFIF_CHGHDRS | /* flags -- add and modify headers */
+ SMFIF_ADDRCPT, /* -- add rcpt */
+ mlfi_connect, /* connection info filter */
+ NULL, /* SMTP HELO command filter */
+ mlfi_envfrom, /* envelope sender filter */
+ mlfi_envrcpt, /* envelope recipient filter */
+ mlfi_header, /* header filter */
+ mlfi_eoh, /* end of header */
+ mlfi_body, /* body block filter */
+ mlfi_eom, /* end of message */
+ mlfi_abort, /* message aborted */
+ mlfi_close /* connection cleanup */
+};
+
+struct re_list
+{
+ regex_t p;
+ const char *pat;
+ struct re_list *n;
+};
+
+#define new_re_list(x) do {\
+ x=(struct re_list *) \
+ malloc(sizeof(struct re_list));\
+ x->n=NULL;\
+ } while(0)
+
+static const char rcsid[]="$Id$";
+
+static int mode=SMFIS_CONTINUE;
+static int train=0;
+static int verbose=0;
+static int debug=0;
+static int spamicity=0;
+static size_t bodylimit=0;
+static const char *bogo="/usr/local/bin/bogofilter";
+static const char *exclude=NULL;
+static const char *subj_tag=NULL;
+static const char *forward_spam=NULL;
+static char *quarantine_mdir=NULL;
+
+static char *reject=NULL;
+
+static struct re_list *re_c=NULL; /* re connection */
+static struct re_list *re_f=NULL; /* re envfrom */
+static struct re_list *re_r=NULL; /* re envrcpt */
+
+#ifdef __sun__
+int
+daemon(int nochdir, int noclose)
+{
+ int fd;
+
+ switch(fork())
+ {
+ case 0:
+ break;
+
+ case -1:
+ return -1;
+
+ default:
+ _exit(0);
+ }
+
+ if(setsid()==-1)
+ return -1;
+
+ if(!nochdir && chdir("/"))
+ return -1;
+
+ if(!noclose)
+ {
+ fd=open("/dev/null", O_RDWR, 0);
+ if(fd==-1)
+ return -1;
+
+ dup2(fd, fileno(stdin));
+ dup2(fd, fileno(stdout));
+ dup2(fd, fileno(stderr));
+ }
+
+ return 0;
+}
+#endif
+
+sfsistat
+mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
+{
+ struct mlfiPriv *priv;
+ struct re_list *tre; /* temporal iterator */
+
+ const void *mysaddr=NULL;
+ char host[INET6_ADDRSTRLEN];
+
+ if(hostaddr)
+ {
+ switch(hostaddr->sa_family)
+ {
+ default:
+ syslog(LOG_ERR, "mlfi_connet: unsupported sa_family");
+ break;
+
+ case AF_INET:
+ mysaddr=(const void *)&((struct sockaddr_in *)hostaddr)
+ ->sin_addr.s_addr;
+ break;
+
+ case AF_INET6:
+ mysaddr=(const void *)&((struct sockaddr_in6 *)hostaddr)
+ ->sin6_addr;
+ break;
+ }
+
+ if(!inet_ntop(hostaddr->sa_family, mysaddr, host, sizeof(host)))
+ {
+ syslog(LOG_ERR, "mlfi_connect: inet_ntop failed");
+ strcpy(host, "*");
+ }
+ }
+ else
+ strcpy(host, "*");
+
+ if(debug)
+ syslog(LOG_DEBUG, "connection from %s [ %s ]", hostname, host);
+
+ for(tre=re_c; tre; tre=tre->n)
+ {
+ if(!regexec(&tre->p, hostname, 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (connect): %s",
+ tre->pat);
+
+ return SMFIS_ACCEPT;
+ }
+
+ if(!regexec(&tre->p, host, 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (connect): %s",
+ tre->pat);
+ return SMFIS_ACCEPT;
+ }
+ }
+
+ priv=(struct mlfiPriv *)malloc(sizeof(struct mlfiPriv));
+ if(!priv)
+ {
+ syslog(LOG_ERR, "Unable to get memory: %s",
+ strerror(errno));
+ return SMFIS_TEMPFAIL;
+ }
+
+ priv->fullpath=NULL;
+ priv->filename=NULL;
+ priv->subject=NULL;
+ priv->f=NULL;
+ priv->eom=1;
+ priv->old_headers=0;
+
+ if(smfi_setpriv(ctx, priv)!=MI_SUCCESS)
+ {
+ syslog(LOG_ERR, "on mlfi_connect: smfi_setpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_envfrom(SMFICTX *ctx, char **argv)
+{
+ struct re_list *tre; /* temporal iterator */
+
+ if(debug)
+ syslog(LOG_DEBUG, "envfrom %s", argv[0]);
+
+ for(tre=re_f; tre; tre=tre->n)
+ if(!regexec(&tre->p, argv[0], 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (envfrom): %s",
+ tre->pat);
+ return SMFIS_ACCEPT;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_envrcpt(SMFICTX *ctx, char **argv)
+{
+ struct mlfiPriv *priv;
+ struct re_list *tre; /* temporal iterator */
+ int fd=-1;
+ char *tmp=NULL;
+
+ if(debug)
+ syslog(LOG_DEBUG, "envrcpt %s", argv[0]);
+
+ for(tre=re_r; tre; tre=tre->n)
+ if(!regexec(&tre->p, argv[0], 0, NULL, 0))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "accepted due pattern match (envrcpt): %s",
+ tre->pat);
+ return SMFIS_ACCEPT;
+ }
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_header: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(priv->eom)
+ {
+ /* use tmp/ from quarantine maildir if available */
+ if(quarantine_mdir)
+ {
+ tmp=hostname_tmp();
+ if(!tmp)
+ {
+ syslog(LOG_ERR, "Unable to get memory: %s",
+ strerror(errno));
+ return SMFIS_TEMPFAIL;
+ }
+
+ priv->fullpath=(char *)calloc(strlen(quarantine_mdir)
+ +strlen(tmp)+6, sizeof(char));
+ }
+ else
+ priv->fullpath=strdup("/tmp/bogom-msg.XXXXXXXXXX");
+
+ if(!priv->fullpath)
+ {
+ syslog(LOG_ERR, "Unable to get memory: %s",
+ strerror(errno));
+ if(tmp)
+ free(tmp);
+ return SMFIS_TEMPFAIL;
+ }
+
+ if(quarantine_mdir)
+ {
+ snprintf(priv->fullpath, strlen(quarantine_mdir)+
+ strlen(tmp)+6, "%s/tmp/%s", quarantine_mdir,
+ tmp);
+ priv->filename=priv->fullpath+strlen(quarantine_mdir)
+ +5;
+ free(tmp);
+ }
+
+ fd=mkstemp(priv->fullpath);
+ if(fd==-1)
+ {
+ syslog(LOG_ERR, "Unable to create tmp file in %s: %s",
+ priv->fullpath, strerror(errno));
+
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+#ifdef __sun__
+ priv->f=fdopen(fd, "w+F");
+#else
+ priv->f=fdopen(fd, "w+");
+#endif
+ if(!priv->f)
+ {
+ syslog(LOG_ERR, "Unable to create tmp file in %s: %s",
+ priv->fullpath, strerror(errno));
+
+ if(fd!=-1)
+ close(fd);
+
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+ priv->eom=0;
+ priv->bodylen=0;
+
+ if(debug)
+ syslog(LOG_DEBUG, "message begin...");
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
+{
+ struct mlfiPriv *priv;
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_header: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(exclude && headerv)
+ if(!strcasecmp(headerf, "Subject"))
+ if(strstr(headerv, exclude))
+ {
+ if(verbose)
+ syslog(LOG_INFO,
+ "exclude string found: '%s'",
+ headerv);
+ mlfi_clean(ctx);
+ return SMFIS_ACCEPT;
+ }
+
+ if(debug)
+ syslog(LOG_DEBUG, "header %s [%s]", headerf, headerv);
+
+ if(headerv && !strcasecmp(headerf, "X-Bogosity"))
+ priv->old_headers++;
+
+ if(subj_tag && headerv)
+ if(!strcasecmp(headerf, "Subject"))
+ {
+ if(priv->subject)
+ syslog(LOG_INFO,
+ "Subject header not unique");
+ else
+ {
+ priv->subject=strdup(headerv);
+ if(!priv->subject)
+ syslog(LOG_ERR,
+ "Unable to get memory (subject"
+ " tag): %s",
+ strerror(errno));
+ }
+ }
+
+ if(fprintf(priv->f, "%s: %s\n", headerf, headerv)==EOF)
+ {
+ syslog(LOG_ERR, "failed to write into %s: %s",
+ priv->fullpath, strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_eoh(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+
+ if(debug)
+ syslog(LOG_DEBUG, "headers ok");
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_eoh: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(fprintf(priv->f, "\n")==EOF)
+ {
+ syslog(LOG_ERR, "failed to write into %s: %s",
+ priv->fullpath, strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t bodylen)
+{
+ struct mlfiPriv *priv;
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_body: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ if(bodylimit)
+ {
+ if(bodylimit==priv->bodylen)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "body_limit reached, "
+ " %d bytes discarded", bodylen);
+
+ bodylen=0;
+ }
+ else
+ if(priv->bodylen+bodylen>bodylimit)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "body_limit reached, "
+ " %d bytes discarded",
+ bodylen-(bodylimit-priv->bodylen));
+
+ bodylen=bodylimit-priv->bodylen;
+ }
+ }
+
+ if(bodylen>0)
+ {
+ if(fwrite(bodyp, bodylen, 1, priv->f)!=1)
+ {
+ syslog(LOG_ERR, "failed to write into %s: %s",
+ priv->fullpath, strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_TEMPFAIL;
+ }
+ else
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "%d body bytes written",
+ bodylen);
+
+ priv->bodylen+=bodylen;
+ }
+ }
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_eom(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+ int status, i;
+ char *bogocl, header[64];
+ float spamicity_val;
+ char *tmp_subj;
+ FILE *proc;
+
+ if(debug)
+ syslog(LOG_DEBUG, "...end of message");
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ {
+ syslog(LOG_ERR, "on mlfi_eom: smfi_getpriv");
+ return SMFIS_ACCEPT;
+ }
+
+ fclose(priv->f);
+ priv->f=NULL;
+
+ bogocl=(char *)malloc(strlen(bogo)+strlen(priv->fullpath)+16);
+ if(!bogocl)
+ {
+ syslog(LOG_ERR, "on mlfi_eom: %s", strerror(errno));
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ }
+
+ sprintf(bogocl, "%s -", bogo);
+
+ if(train)
+ strcat(bogocl, "u");
+
+ if(verbose)
+ strcat(bogocl, "l");
+
+ if(spamicity)
+ strcat(bogocl, "TT");
+
+ strcat(bogocl, "B ");
+ strcat(bogocl, priv->fullpath);
+
+#ifdef __sun__
+ proc=popen(bogocl, "rF");
+#else
+ proc=popen(bogocl, "r");
+#endif
+ if(!proc)
+ {
+ syslog(LOG_ERR, "failed to exec bogofilter: %s",
+ strerror(errno));
+ free(bogocl);
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ }
+ free(bogocl);
+
+ if(spamicity)
+ {
+ /* FIXME: spaces in the path will cause trouble */
+ if(fscanf(proc, "%*[^ ] %f\n", &spamicity_val)!=1)
+ {
+ syslog(LOG_ERR, "failed to get bogofilter spamicity "
+ "value");
+ spamicity_val=-1;
+ }
+ else
+ if(debug)
+ syslog(LOG_DEBUG, "spamicity value: %f",
+ spamicity_val);
+ }
+
+ status=pclose(proc);
+
+ if(!WIFEXITED(status))
+ {
+ syslog(LOG_ERR, "bogofilter didn't exit normally");
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ }
+
+ switch(WEXITSTATUS(status))
+ {
+ case 3:
+ case -1:
+ syslog(LOG_ERR, "bogofilter reply: I/O error");
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+ case 0:
+ if(spamicity && spamicity_val!=-1)
+ snprintf(header, 64, "Spam, spamicity=%.6f",
+ spamicity_val);
+ else
+ strcpy(header, "Yes, tests=bogofilter");
+ smfi_insheader(ctx, 0, "X-Bogosity", header);
+
+ priv->old_headers++;
+
+ if(forward_spam)
+ {
+ if(smfi_addrcpt(ctx, (char *)forward_spam)
+ !=MI_SUCCESS)
+ syslog(LOG_ERR, "forward_spam failed:"
+ " '%s'", forward_spam);
+ else
+ if(debug)
+ syslog(LOG_DEBUG,
+ "forward_spam rcpt added: "
+ "'%s'", forward_spam);
+ }
+
+ if(subj_tag && priv->subject)
+ {
+ tmp_subj=(char *)calloc(strlen(subj_tag)+
+ strlen(priv->subject)+2, sizeof(char));
+
+ if(!tmp_subj)
+ syslog(LOG_ERR, "Unable to get memory:"
+ " %s", strerror(errno));
+ else
+ {
+ snprintf(tmp_subj, strlen(subj_tag)+
+ strlen(priv->subject)+2,
+ "%s %s", subj_tag,
+ priv->subject);
+
+ /* truncate if needed and be nice
+ with RFC */
+ if(strlen(tmp_subj)>998)
+ tmp_subj[998]=0;
+
+ if(smfi_chgheader(ctx, "Subject", 1,
+ tmp_subj)!=MI_SUCCESS)
+ syslog(LOG_ERR, "subject_tag"
+ " failed: '%s'",
+ tmp_subj);
+ else
+ if(debug)
+ syslog(LOG_DEBUG,
+ "subject_tag"
+ " added: '%s'",
+ tmp_subj);
+ free(tmp_subj);
+ }
+ }
+
+ if(verbose)
+ {
+ if(mode==SMFIS_CONTINUE)
+ syslog(LOG_NOTICE,
+ "bogofilter reply: spam");
+ else
+ if(mode==SMFIS_REJECT)
+ syslog(LOG_NOTICE,
+ "spam rejected");
+ else
+ syslog(LOG_NOTICE,
+ "spam discarded");
+ }
+
+ if(mode==SMFIS_REJECT && reject)
+ smfi_setreply(ctx, "554", "5.7.1", reject);
+
+ if(quarantine_mdir)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "copying message "
+ "to quarantine_mdir");
+
+ if(chdir(quarantine_mdir)==-1)
+ syslog(LOG_ERR, "failed to chdir to "
+ "quarantine_mdir: %s\n",
+ strerror(errno));
+ else
+ if(to_maildir(priv->fullpath,
+ priv->filename)==-1)
+ syslog(LOG_ERR, "failed to"
+ " copy message to "
+ "quarantine_mdir");
+ }
+
+ mlfi_clean(ctx);
+ return mode;
+ case 1:
+ if(spamicity && spamicity_val!=-1)
+ snprintf(header, 64, "Ham, spamicity=%.6f",
+ spamicity_val);
+ else
+ strcpy(header, "No, tests=bogofilter");
+ smfi_insheader(ctx, 0, "X-Bogosity", header);
+
+ priv->old_headers++;
+
+ if(verbose)
+ syslog(LOG_NOTICE, "bogofilter reply: ham");
+ break;
+ case 2:
+ if(spamicity && spamicity_val!=-1)
+ snprintf(header, 64, "Unsure, spamicity=%.6f",
+ spamicity_val);
+ else
+ strcpy(header, "Unsure, tests=bogofilter");
+ smfi_insheader(ctx, 0, "X-Bogosity", header);
+
+ priv->old_headers++;
+
+ if(verbose)
+ syslog(LOG_NOTICE, "bogofilter reply: unsure");
+ break;
+ default:
+ syslog(LOG_ERR, "bogofilter reply is unknown");
+ break;
+ }
+
+ if(priv->old_headers>1)
+ for(i=2, priv->old_headers++;i<priv->old_headers+1;i++)
+ {
+ smfi_chgheader(ctx, "X-Bogosity", i, NULL);
+ if(debug)
+ syslog(LOG_DEBUG, "previous header removed");
+ }
+
+ mlfi_clean(ctx);
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_abort(SMFICTX *ctx)
+{
+ if(debug)
+ syslog(LOG_DEBUG, "message ABORTED");
+
+ mlfi_clean(ctx);
+
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_close(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+ if(!priv)
+ return SMFIS_CONTINUE;
+
+ if(!priv->eom)
+ mlfi_clean(ctx);
+
+ smfi_setpriv(ctx, NULL);
+ free(priv);
+
+ if(debug)
+ syslog(LOG_DEBUG, "connection closed");
+
+ return SMFIS_CONTINUE;
+}
+
+void
+mlfi_clean(SMFICTX *ctx)
+{
+ struct mlfiPriv *priv;
+
+ if(debug)
+ syslog(LOG_DEBUG, "cleaning message...");
+
+ priv=(struct mlfiPriv *)smfi_getpriv(ctx);
+
+ if(!priv)
+ return;
+
+ if(priv->f)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "closing tmp file");
+ fclose(priv->f);
+ priv->f=NULL;
+ }
+
+ if(priv->fullpath)
+ {
+ if(debug)
+ syslog(LOG_DEBUG, "removing tmp file");
+ unlink(priv->fullpath);
+ free(priv->fullpath);
+ priv->fullpath=NULL;
+ }
+
+ if(priv->subject)
+ {
+ free(priv->subject);
+ priv->subject=NULL;
+ }
+
+ priv->eom=1;
+ priv->old_headers=0;
+
+ if(debug)
+ syslog(LOG_DEBUG, "...cleaning done");
+
+ return;
+}
+
+char *
+hostname_tmp()
+{
+ char *p;
+ char myhostname[MAXHOSTNAMELEN+128];
+ struct timeval tp;
+
+ if(gettimeofday(&tp, NULL)==-1)
+ tp.tv_sec=time(NULL);
+
+ /* time + hostname to make a unique filename NFS friendly */
+ snprintf(myhostname, 117, "bogom_%lu.%lu.", tp.tv_sec, tp.tv_usec);
+
+ if(gethostname(myhostname+strlen(myhostname), MAXHOSTNAMELEN)==-1)
+ {
+ syslog(LOG_NOTICE, "failed to get my hostname");
+ strcpy(myhostname, "unknown_hostname");
+ }
+
+ p=myhostname;
+ while((p=strstr(p, "/")))
+ *p='\057';
+
+ p=myhostname;
+ while((p=strstr(p, ":")))
+ *p='\072';
+
+ strcat(myhostname, ".XXXXXXXXXX");
+
+ return strdup(myhostname);
+}
+
+int
+to_maildir(char *origin, char *filename)
+{
+ char *p;
+ struct stat st;
+
+ /* caller must chdir to quarantine_mdir */
+
+ if(stat("new", &st)==-1 && errno==ENOENT)
+ if(mkdir("new", 0700)==-1)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to "
+ "create new/: %s", strerror(errno));
+ return -1;
+ }
+
+ p=(char *)calloc(strlen(filename)+strlen(quarantine_mdir)+6,
+ sizeof(char));
+ if(!p)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to get memory: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ snprintf(p, strlen(filename)+strlen(quarantine_mdir)+6,
+ "%s/new/%s\n", quarantine_mdir, filename);
+
+ if(link(origin, p)==-1)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to link file: %s",
+ strerror(errno));
+ free(p);
+ return -1;
+ }
+
+ free(p);
+
+ return 0;
+}
+
+void
+usage(const char *argv0)
+{
+ fprintf(stderr, "usage: %s\t[-R | -D] [-t] [-v] [-u user] [-s conn]\n"
+ "\t\t[-b bogo_path ] [-x exclude_string] "
+ "[-c conf_file]\n\t\t[-l body_limit] [-p pidfile] "
+ "[-f forward_spam]\n"
+ "\t\t[-q quarantine_mdir] [-S] [-d]\n", argv0);
+
+ return;
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *user=DEF_USER;
+ const char *conn=DEF_CONN;
+ const char *pipe=NULL;
+ const char *conffile=DEF_CONF;
+ const char *pidfile=DEF_PIDFILE;
+
+ FILE *pidfile_fd;
+ int result;
+
+ struct re_list *tre;
+ struct string_list *tsl, *tsl2;
+
+ /* configuration tokens */
+ struct conftoken conf[]=
+ {
+ { "verbose", REQ_BOOL, NULL, -1, NULL },
+ { "training", REQ_BOOL, NULL, -1, NULL },
+ { "user", REQ_QSTRING, NULL, 0, NULL },
+ { "connection", REQ_QSTRING, NULL, 0, NULL },
+ { "exclude_string", REQ_QSTRING, NULL, 0, NULL },
+ { "re_envfrom", REQ_LSTQSTRING, NULL, 0, NULL },
+ { "bogofilter", REQ_QSTRING, NULL, 0, NULL },
+ { "policy", REQ_STRING, NULL, 0, NULL },
+ { "reject", REQ_QSTRING, NULL, 0, NULL },
+ { "re_connection", REQ_LSTQSTRING, NULL, 0, NULL },
+ { "re_envrcpt", REQ_LSTQSTRING, NULL, 0, NULL },
+ { "body_limit", REQ_STRING, NULL, 0, NULL },
+ { "pidfile", REQ_QSTRING, NULL, 0, NULL },
+ { "subject_tag", REQ_QSTRING, NULL, 0, NULL },
+ { "forward_spam", REQ_QSTRING, NULL, 0, NULL },
+ { "quarantine_mdir", REQ_QSTRING, NULL, 0, NULL },
+ { "spamicity_header", REQ_BOOL, NULL, -1, NULL },
+ { NULL, 0, NULL, 0, NULL }
+ };
+
+ int opt;
+ const char *opts="hu:p:b:RDtvx:w:c:l:ds:f:q:S";
+
+ struct passwd *pw=NULL;
+ struct stat st;
+
+ while((opt=getopt(argc, argv, opts))!=-1)
+ switch(opt)
+ {
+ case 'h':
+ default:
+ usage(argv[0]);
+ exit(1);
+ break;
+
+ case 'u':
+ user=optarg;
+ break;
+
+ case 's':
+ conn=optarg;
+ break;
+
+ case 'b':
+ bogo=optarg;
+ break;
+
+ case 'R':
+ mode=SMFIS_REJECT;
+ break;
+
+ case 'D':
+ mode=SMFIS_DISCARD;
+ break;
+
+ case 't':
+ train=1;
+ break;
+
+ case 'v':
+ verbose=1;
+ break;
+
+ case 'x':
+ exclude=optarg;
+ break;
+
+ case 'c':
+ conffile=optarg;
+ break;
+
+ case 'l':
+ bodylimit=(size_t)atol(optarg);
+ break;
+
+ case 'd':
+ debug=1;
+ verbose=1;
+ break;
+
+ case 'p':
+ pidfile=optarg;
+ break;
+
+ case 'f':
+ forward_spam=optarg;
+ break;
+
+ case 'q':
+ quarantine_mdir=optarg;
+ break;
+
+ case 'S':
+ spamicity=1;
+ break;
+ }
+
+ /* read configuration file */
+ if(!read_conf(conffile, conf))
+ {
+ if(conf[0].bool!=-1)
+ verbose=conf[0].bool;
+
+ if(conf[1].bool!=-1)
+ train=conf[1].bool;
+
+ if(conf[2].str)
+ user=conf[2].str;
+
+ if(conf[3].str)
+ conn=conf[3].str;
+
+ if(conf[4].str)
+ exclude=conf[4].str;
+
+ if(conf[5].sl)
+ {
+ for(tsl=conf[5].sl; tsl; tsl=tsl->n, free(tsl2))
+ {
+ if(!re_f)
+ {
+ new_re_list(re_f);
+ if(!re_f)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ }
+ else
+ {
+ new_re_list(tre);
+ if(!tre)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ tre->n=re_f;
+ re_f=tre;
+ }
+
+ if(regcomp(&(re_f->p), tsl->s, REG_EXTENDED|
+ REG_ICASE|REG_NOSUB))
+ {
+ fprintf(stderr,"Bad pattern: %s\n",
+ tsl->s);
+ return 1;
+ }
+ re_f->pat=tsl->s;
+ tsl2=tsl;
+ }
+ conf[5].sl=NULL;
+ }
+
+ if(conf[6].str)
+ bogo=conf[6].str;
+
+ if(conf[7].str)
+ {
+ if(!strcmp(conf[7].str, "pass"))
+ mode=SMFIS_CONTINUE;
+ else
+ {
+ if(!strcmp(conf[7].str, "reject"))
+ mode=SMFIS_REJECT;
+ else
+ {
+ if(!strcmp(conf[7].str, "discard"))
+ mode=SMFIS_DISCARD;
+ else
+ {
+ fprintf(stderr, "conf error:"
+ " unknown policy\n");
+ return 1;
+ }
+ }
+ }
+ }
+
+ if(conf[8].str)
+ reject=conf[8].str;
+
+ if(conf[9].sl)
+ {
+ for(tsl=conf[9].sl; tsl; tsl=tsl->n, free(tsl2))
+ {
+ if(!re_c)
+ {
+ new_re_list(re_c);
+ if(!re_c)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ }
+ else
+ {
+ new_re_list(tre);
+ if(!tre)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ tre->n=re_c;
+ re_c=tre;
+ }
+
+ if(regcomp(&(re_c->p), tsl->s, REG_EXTENDED|
+ REG_ICASE|REG_NOSUB))
+ {
+ fprintf(stderr,"Bad pattern: %s\n",
+ tsl->s);
+ return 1;
+ }
+ re_c->pat=tsl->s;
+ tsl2=tsl;
+ }
+ conf[9].sl=NULL;
+ }
+
+ if(conf[10].sl)
+ {
+ for(tsl=conf[10].sl; tsl; tsl=tsl->n, free(tsl2))
+ {
+ if(!re_r)
+ {
+ new_re_list(re_r);
+ if(!re_r)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ }
+ else
+ {
+ new_re_list(tre);
+ if(!tre)
+ {
+ fprintf(stderr,
+ "unable to get memory: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ tre->n=re_r;
+ re_r=tre;
+ }
+
+ if(regcomp(&(re_r->p), tsl->s, REG_EXTENDED|
+ REG_ICASE|REG_NOSUB))
+ {
+ fprintf(stderr,"Bad pattern: %s\n",
+ tsl->s);
+ return 1;
+ }
+ re_r->pat=tsl->s;
+ tsl2=tsl;
+ }
+ conf[10].sl=NULL;
+ }
+
+ if(conf[11].str)
+ {
+ bodylimit=atoi(conf[11].str);
+ if(bodylimit<=0)
+ {
+ fprintf(stderr, "Warning: body_length value"
+ "is invalid, ignored\n");
+ bodylimit=0;
+ }
+
+ /* parse units */
+ switch(conf[11].str[strlen(conf[11].str)-1])
+ {
+ default:
+ /* nothing, use bytes */
+ break;
+ case 'k':
+ case 'K':
+ bodylimit*=1024;
+ break;
+ case 'm':
+ case 'M':
+ bodylimit*=1024*1024;
+ break;
+ }
+ }
+
+ if(conf[12].str)
+ pidfile=conf[12].str;
+
+ if(conf[13].str)
+ subj_tag=conf[13].str;
+
+ if(conf[14].str)
+ forward_spam=conf[14].str;
+
+ if(conf[15].str)
+ quarantine_mdir=conf[15].str;
+
+ if(conf[16].bool!=-1)
+ spamicity=conf[16].bool;
+ }
+ else
+ return 1; /* error reading configuration */
+
+ if(access(pidfile, F_OK)!=-1)
+ {
+ fprintf(stderr, "pidfile '%s' exists, delete it if"
+ " the milter is not already running\n", pidfile);
+ return 1;
+ }
+
+ if(!strncmp(conn, "unix:", 5))
+ pipe=conn+5;
+ else
+ if(!strncmp(conn, "local:", 6))
+ pipe=conn+6;
+
+ if(pipe)
+ unlink(pipe);
+
+ /* if we're root, drop privileges */
+ if(!getuid())
+ {
+ /* ugly (and portable) setproctitle */
+ if(argc>1)
+ argv[1]=NULL;
+
+ pw=getpwnam(user);
+ if(!pw)
+ {
+ fprintf(stderr, "getpwnam %s failed: %s\n", user,
+ strerror(errno));
+ return 1;
+ }
+ if(setegid(pw->pw_gid) || setgid(pw->pw_gid))
+ {
+ fprintf(stderr, "setgid failed: %s\n", strerror(errno));
+ return 1;
+ }
+ if(setuid(pw->pw_uid) || seteuid(pw->pw_uid))
+ {
+ fprintf(stderr, "setuid failed: %s\n", strerror(errno));
+ return 1;
+ }
+ }
+
+ if(daemon(0, 0))
+ {
+ fprintf(stderr, "daemon failed: %s\n", strerror(errno));
+ unlink(pidfile);
+ return 1;
+ }
+
+ /* setup time to timezone */
+ tzset();
+
+ openlog("bogom", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ pidfile_fd=fopen(pidfile, "w");
+ if(!pidfile_fd)
+ {
+ syslog(LOG_ERR, "unable to open pidfile '%s': %s\n",
+ pidfile, strerror(errno));
+ return 1;
+ }
+
+ if(fprintf(pidfile_fd, "%li\n", (long)getpid())<=0 ||
+ fclose(pidfile_fd)!=0)
+ {
+ syslog(LOG_ERR, "unable to write into pidfile '%s': %s\n",
+ pidfile, strerror(errno));
+ unlink(pidfile);
+ return 1;
+ }
+
+ if(smfi_setconn((char *)conn)==MI_FAILURE)
+ {
+ syslog(LOG_ERR, "smfi_setconn %s failed\n", conn);
+ return 1;
+ }
+
+ if(smfi_register(smfilter)!=MI_SUCCESS)
+ {
+ syslog(LOG_ERR, "smfi_register failed\n");
+ return 1;
+ }
+
+ syslog(LOG_INFO, "started %s", rcsid);
+
+ if(quarantine_mdir)
+ {
+ if(quarantine_mdir[strlen(quarantine_mdir)]=='/')
+ quarantine_mdir[strlen(quarantine_mdir)]=0;
+
+ if(quarantine_mdir[0]!='/')
+ {
+ syslog(LOG_ERR, "quarantine_mdir path must be"
+ " absolute");
+ return 1;
+ }
+
+ if(chdir(quarantine_mdir)==-1)
+ {
+ syslog(LOG_ERR, "failed to chdir to quarantine_mdir: "
+ "%s", strerror(errno));
+ return 1;
+ }
+
+ if(stat("tmp/", &st)==-1 && errno==ENOENT)
+ {
+ if(mkdir("tmp", 0700)==-1)
+ {
+ syslog(LOG_ERR, "quarantine_mdir failed to "
+ "create tmp/: %s", strerror(errno));
+ return 1;
+ }
+ }
+
+ }
+
+ if(quarantine_mdir && bodylimit)
+ {
+ syslog(LOG_ERR, "body_limit is incompatible with "
+ "quarantine_mdir and will be ignored");
+
+ bodylimit=0;
+ }
+
+ result=smfi_main();
+
+ unlink(pidfile);
+
+ return result;
+}
+
+/* EOF */
+